App-MHFS

 view release on metacpan or  search on metacpan

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN


Note that due to the nature of multi-threading the times may not be 100% exact. If this is an
issue, consider scheduling state changes from within a processing callback. An idea might be to
have some kind of passthrough trigger node that is used specifically for tracking time and handling
events.



7.2. Thread Safety and Locking
------------------------------
When processing audio, it's ideal not to have any kind of locking in the audio thread. Since it's
expected that `ma_node_graph_read_pcm_frames()` would be run on the audio thread, it does so
without the use of any locks. This section discusses the implementation used by miniaudio and goes
over some of the compromises employed by miniaudio to achieve this goal. Note that the current
implementation may not be ideal - feedback and critiques are most welcome.

The node graph API is not *entirely* lock-free. Only `ma_node_graph_read_pcm_frames()` is expected
to be lock-free. Attachment, detachment and uninitialization of nodes use locks to simplify the
implementation, but are crafted in a way such that such locking is not required when reading audio
data from the graph. Locking in these areas are achieved by means of spinlocks.

The main complication with keeping `ma_node_graph_read_pcm_frames()` lock-free stems from the fact
that a node can be uninitialized, and it's memory potentially freed, while in the middle of being
processed on the audio thread. There are times when the audio thread will be referencing a node,
which means the uninitialization process of a node needs to make sure it delays returning until the
audio thread is finished so that control is not handed back to the caller thereby giving them a
chance to free the node's memory.

When the audio thread is processing a node, it does so by reading from each of the output buses of
the node. In order for a node to process data for one of it's output buses, it needs to read from
each of it's input buses, and so on an so forth. It follows that once all output buses of a node
are detached, the node as a whole will be disconnected and no further processing will occur unless
it's output buses are reattached, which won't be happening when the node is being uninitialized.
By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can
simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By
doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output
nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean
up.

With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as
it takes to process the output bus being detached. This will happen if it's called at just the
wrong moment where the audio thread has just iterated it and has just started processing. The
caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which
includes the cost of recursively processing it's inputs. This is the biggest compromise made with
the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes
earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching
higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass
detachments, detach starting from the lowest level nodes and work your way towards the final
endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not
running, detachment will be fast and detachment in any order will be the same. The reason nodes
need to wait for their input attachments to complete is due to the potential for desyncs between
data sources. If the node was to terminate processing mid way through processing it's inputs,
there's a chance that some of the underlying data sources will have been read, but then others not.
That will then result in a potential desynchronization when detaching and reattaching higher-level
nodes. A possible solution to this is to have an option when detaching to terminate processing
before processing all input attachments which should be fairly simple.

Another compromise, albeit less significant, is locking when attaching and detaching nodes. This
locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present
for each input bus and output bus. When an output bus is connected to an input bus, both the output
bus and input bus is locked. This locking is specifically for attaching and detaching across
different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and
unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when
considering that iterating over attachments must not break as a result of attaching or detaching a
node while iteration is occuring.

Attaching and detaching are both quite simple. When an output bus of a node is attached to an input
bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where
each item in the list is and output bus. We have some intentional (and convenient) restrictions on
what can done with the linked list in order to simplify the implementation. First of all, whenever
something needs to iterate over the list, it must do so in a forward direction. Backwards iteration
is not supported. Also, items can only be added to the start of the list.

The linked list is a doubly-linked list where each item in the list (an output bus) holds a pointer
to the next item in the list, and another to the previous item. A pointer to the previous item is
only required for fast detachment of the node - it is never used in iteration. This is an
important property because it means from the perspective of iteration, attaching and detaching of
an item can be done with a single atomic assignment. This is exploited by both the attachment and
detachment process. When attaching the node, the first thing that is done is the setting of the
local "next" and "previous" pointers of the node. After that, the item is "attached" to the list
by simply performing an atomic exchange with the head pointer. After that, the node is "attached"
to the list from the perspective of iteration. Even though the "previous" pointer of the next item
hasn't yet been set, from the perspective of iteration it's been attached because iteration will
only be happening in a forward direction which means the "previous" pointer won't actually ever get
used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and
`ma_node_detach_output_bus()` for the implementation of this mechanism.



8. Decoding
===========
The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from
devices and can be used independently. The following formats are supported:

    +---------+------------------+----------+
    | Format  | Decoding Backend | Built-In |
    +---------+------------------+----------+
    | WAV     | dr_wav           | Yes      |
    | MP3     | dr_mp3           | Yes      |
    | FLAC    | dr_flac          | Yes      |
    | Vorbis  | stb_vorbis       | No       |
    +---------+------------------+----------+

Vorbis is supported via stb_vorbis which can be enabled by including the header section before the
implementation of miniaudio, like the following:

    ```c
    #define STB_VORBIS_HEADER_ONLY
    #include "extras/stb_vorbis.c"    // Enables Vorbis decoding.

    #define MINIAUDIO_IMPLEMENTATION
    #include "miniaudio.h"

    // The stb_vorbis implementation must come after the implementation of miniaudio.
    #undef STB_VORBIS_HEADER_ONLY
    #include "extras/stb_vorbis.c"
    ```

A copy of stb_vorbis is included in the "extras" folder in the miniaudio repository (https://github.com/mackron/miniaudio).

Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN

/*
Free's an aligned malloc'd buffer.
*/
MA_API void ma_aligned_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks);

/*
Retrieves a friendly name for a format.
*/
MA_API const char* ma_get_format_name(ma_format format);

/*
Blends two frames in floating point format.
*/
MA_API void ma_blend_f32(float* pOut, float* pInA, float* pInB, float factor, ma_uint32 channels);

/*
Retrieves the size of a sample in bytes for the given format.

This API is efficient and is implemented using a lookup table.

Thread Safety: SAFE
  This API is pure.
*/
MA_API ma_uint32 ma_get_bytes_per_sample(ma_format format);
static MA_INLINE ma_uint32 ma_get_bytes_per_frame(ma_format format, ma_uint32 channels) { return ma_get_bytes_per_sample(format) * channels; }

/*
Converts a log level to a string.
*/
MA_API const char* ma_log_level_to_string(ma_uint32 logLevel);




/************************************************************************************************************************************************************

Synchronization

************************************************************************************************************************************************************/
/*
Locks a spinlock.
*/
MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock);

/*
Locks a spinlock, but does not yield() when looping.
*/
MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock);

/*
Unlocks a spinlock.
*/
MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock);


#ifndef MA_NO_THREADING

/*
Creates a mutex.

A mutex must be created from a valid context. A mutex is initially unlocked.
*/
MA_API ma_result ma_mutex_init(ma_mutex* pMutex);

/*
Deletes a mutex.
*/
MA_API void ma_mutex_uninit(ma_mutex* pMutex);

/*
Locks a mutex with an infinite timeout.
*/
MA_API void ma_mutex_lock(ma_mutex* pMutex);

/*
Unlocks a mutex.
*/
MA_API void ma_mutex_unlock(ma_mutex* pMutex);


/*
Initializes an auto-reset event.
*/
MA_API ma_result ma_event_init(ma_event* pEvent);

/*
Uninitializes an auto-reset event.
*/
MA_API void ma_event_uninit(ma_event* pEvent);

/*
Waits for the specified auto-reset event to become signalled.
*/
MA_API ma_result ma_event_wait(ma_event* pEvent);

/*
Signals the specified auto-reset event.
*/
MA_API ma_result ma_event_signal(ma_event* pEvent);
#endif  /* MA_NO_THREADING */


/*
Fence
=====
This locks while the counter is larger than 0. Counter can be incremented and decremented by any
thread, but care needs to be taken when waiting. It is possible for one thread to acquire the
fence just as another thread returns from ma_fence_wait().

The idea behind a fence is to allow you to wait for a group of operations to complete. When an
operation starts, the counter is incremented which locks the fence. When the operation completes,
the fence will be released which decrements the counter. ma_fence_wait() will block until the
counter hits zero.

If threading is disabled, ma_fence_wait() will spin on the counter.
*/
typedef struct
{
#ifndef MA_NO_THREADING
    ma_event e;
#endif

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN

            {
                c89atomic_uint8 result = 0;
                __asm {
                    mov ecx, dst
                    mov al,  expected
                    mov dl,  desired
                    lock cmpxchg [ecx], dl
                    mov result, al
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_16)
            static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_compare_and_swap_16(volatile c89atomic_uint16* dst, c89atomic_uint16 expected, c89atomic_uint16 desired)
            {
                c89atomic_uint16 result = 0;
                __asm {
                    mov ecx, dst
                    mov ax,  expected
                    mov dx,  desired
                    lock cmpxchg [ecx], dx
                    mov result, ax
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_32)
            static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_compare_and_swap_32(volatile c89atomic_uint32* dst, c89atomic_uint32 expected, c89atomic_uint32 desired)
            {
                c89atomic_uint32 result = 0;
                __asm {
                    mov ecx, dst
                    mov eax, expected
                    mov edx, desired
                    lock cmpxchg [ecx], edx
                    mov result, eax
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_64)
            static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_compare_and_swap_64(volatile c89atomic_uint64* dst, c89atomic_uint64 expected, c89atomic_uint64 desired)
            {
                c89atomic_uint32 resultEAX = 0;
                c89atomic_uint32 resultEDX = 0;
                __asm {
                    mov esi, dst
                    mov eax, dword ptr expected
                    mov edx, dword ptr expected + 4
                    mov ebx, dword ptr desired
                    mov ecx, dword ptr desired + 4
                    lock cmpxchg8b qword ptr [esi]
                    mov resultEAX, eax
                    mov resultEDX, edx
                }
                return ((c89atomic_uint64)resultEDX << 32) | resultEAX;
            }
        #endif
    #else
        #if defined(C89ATOMIC_HAS_8)
            #define c89atomic_compare_and_swap_8( dst, expected, desired) (c89atomic_uint8 )_InterlockedCompareExchange8((volatile char*)dst, (char)desired, (char)expected)
        #endif
        #if defined(C89ATOMIC_HAS_16)
            #define c89atomic_compare_and_swap_16(dst, expected, desired) (c89atomic_uint16)_InterlockedCompareExchange16((volatile short*)dst, (short)desired, (short)expected)
        #endif
        #if defined(C89ATOMIC_HAS_32)
            #define c89atomic_compare_and_swap_32(dst, expected, desired) (c89atomic_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)desired, (long)expected)
        #endif
        #if defined(C89ATOMIC_HAS_64)
            #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile c89atomic_int64*)dst, (c89atomic_int64)desired, (c89atomic_int64)expected)
        #endif
    #endif
    #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY)
        #if defined(C89ATOMIC_HAS_8)
            static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order)
            {
                c89atomic_uint8 result = 0;
                (void)order;
                __asm {
                    mov ecx, dst
                    mov al,  src
                    lock xchg [ecx], al
                    mov result, al
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_16)
            static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order)
            {
                c89atomic_uint16 result = 0;
                (void)order;
                __asm {
                    mov ecx, dst
                    mov ax,  src
                    lock xchg [ecx], ax
                    mov result, ax
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_32)
            static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order)
            {
                c89atomic_uint32 result = 0;
                (void)order;
                __asm {
                    mov ecx, dst
                    mov eax, src
                    lock xchg [ecx], eax
                    mov result, eax
                }
                return result;
            }
        #endif
    #else
        #if defined(C89ATOMIC_HAS_8)
            static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint8)_InterlockedExchange8((volatile char*)dst, (char)src);
            }
        #endif
        #if defined(C89ATOMIC_HAS_16)
            static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint16)_InterlockedExchange16((volatile short*)dst, (short)src);
            }
        #endif
        #if defined(C89ATOMIC_HAS_32)
            static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint32)_InterlockedExchange((volatile long*)dst, (long)src);
            }
        #endif
        #if defined(C89ATOMIC_HAS_64) && defined(C89ATOMIC_64BIT)
            static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint64)_InterlockedExchange64((volatile long long*)dst, (long long)src);
            }
        #else
        #endif
    #endif
    #if defined(C89ATOMIC_HAS_64) && !defined(C89ATOMIC_64BIT)
        static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order)
        {
            c89atomic_uint64 oldValue;
            do {
                oldValue = *dst;
            } while (c89atomic_compare_and_swap_64(dst, oldValue, src) != oldValue);
            (void)order;
            return oldValue;
        }
    #endif
    #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY)
        #if defined(C89ATOMIC_HAS_8)
            static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order)
            {
                c89atomic_uint8 result = 0;
                (void)order;
                __asm {
                    mov ecx, dst
                    mov al,  src
                    lock xadd [ecx], al
                    mov result, al
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_16)
            static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order)
            {
                c89atomic_uint16 result = 0;
                (void)order;
                __asm {
                    mov ecx, dst
                    mov ax,  src
                    lock xadd [ecx], ax
                    mov result, ax
                }
                return result;
            }
        #endif
        #if defined(C89ATOMIC_HAS_32)
            static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order)
            {
                c89atomic_uint32 result = 0;
                (void)order;
                __asm {
                    mov ecx, dst
                    mov eax, src
                    lock xadd [ecx], eax
                    mov result, eax
                }
                return result;
            }
        #endif
    #else
        #if defined(C89ATOMIC_HAS_8)
            static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint8)_InterlockedExchangeAdd8((volatile char*)dst, (char)src);
            }
        #endif
        #if defined(C89ATOMIC_HAS_16)
            static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint16)_InterlockedExchangeAdd16((volatile short*)dst, (short)src);
            }
        #endif
        #if defined(C89ATOMIC_HAS_32)
            static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint32)_InterlockedExchangeAdd((volatile long*)dst, (long)src);
            }
        #endif
        #if defined(C89ATOMIC_HAS_64) && defined(C89ATOMIC_64BIT)
            static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order)
            {
                (void)order;
                return (c89atomic_uint64)_InterlockedExchangeAdd64((volatile long long*)dst, (long long)src);
            }
        #else
        #endif
    #endif
    #if defined(C89ATOMIC_HAS_64) && !defined(C89ATOMIC_64BIT)
        static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order)
        {
            c89atomic_uint64 oldValue;
            c89atomic_uint64 newValue;
            do {
                oldValue = *dst;
                newValue = oldValue + src;
            } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue);
            (void)order;
            return oldValue;
        }
    #endif
    #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY)
        static C89ATOMIC_INLINE void __stdcall c89atomic_thread_fence(c89atomic_memory_order order)
        {
            (void)order;
            __asm {
                lock add [esp], 0
            }
        }
    #else
        #if defined(C89ATOMIC_X64)
            #define c89atomic_thread_fence(order)   __faststorefence(), (void)order
        #else
            static C89ATOMIC_INLINE void c89atomic_thread_fence(c89atomic_memory_order order)
            {
                volatile c89atomic_uint32 barrier = 0;
                c89atomic_fetch_add_explicit_32(&barrier, 0, order);
            }
        #endif
    #endif
    #define c89atomic_compiler_fence()      c89atomic_thread_fence(c89atomic_memory_order_seq_cst)
    #define c89atomic_signal_fence(order)   c89atomic_thread_fence(order)
    #if defined(C89ATOMIC_HAS_8)
        static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_load_explicit_8(volatile const c89atomic_uint8* ptr, c89atomic_memory_order order)
        {
            (void)order;
            return c89atomic_compare_and_swap_8((volatile c89atomic_uint8*)ptr, 0, 0);
        }
    #endif
    #if defined(C89ATOMIC_HAS_16)
        static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_load_explicit_16(volatile const c89atomic_uint16* ptr, c89atomic_memory_order order)
        {
            (void)order;
            return c89atomic_compare_and_swap_16((volatile c89atomic_uint16*)ptr, 0, 0);
        }
    #endif
    #if defined(C89ATOMIC_HAS_32)
        static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_load_explicit_32(volatile const c89atomic_uint32* ptr, c89atomic_memory_order order)
        {
            (void)order;
            return c89atomic_compare_and_swap_32((volatile c89atomic_uint32*)ptr, 0, 0);
        }
    #endif
    #if defined(C89ATOMIC_HAS_64)

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN

        }

        resultMM = ((MA_PFN_waveOutOpen)pDevice->pContext->winmm.waveOutOpen)((LPHWAVEOUT)&pDevice->winmm.hDevicePlayback, winMMDeviceIDPlayback, &wf, (DWORD_PTR)pDevice->winmm.hEventPlayback, (DWORD_PTR)pDevice, CALLBACK_EVENT | WAVE_ALLOWSYNC);
        if (resultMM != MMSYSERR_NOERROR) {
            errorMsg = "[WinMM] Failed to open playback device.", errorCode = MA_FAILED_TO_OPEN_BACKEND_DEVICE;
            goto on_error;
        }

        pDescriptorPlayback->format             = ma_format_from_WAVEFORMATEX(&wf);
        pDescriptorPlayback->channels           = wf.nChannels;
        pDescriptorPlayback->sampleRate         = wf.nSamplesPerSec;
        ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels);
        pDescriptorPlayback->periodCount        = pDescriptorPlayback->periodCount;
        pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile);
    }

    /*
    The heap allocated data is allocated like so:

    [Capture WAVEHDRs][Playback WAVEHDRs][Capture Intermediary Buffer][Playback Intermediary Buffer]
    */
    heapSize = 0;
    if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) {
        heapSize += sizeof(WAVEHDR)*pDescriptorCapture->periodCount + (pDescriptorCapture->periodSizeInFrames * pDescriptorCapture->periodCount * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels));
    }
    if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) {
        heapSize += sizeof(WAVEHDR)*pDescriptorPlayback->periodCount + (pDescriptorPlayback->periodSizeInFrames * pDescriptorPlayback->periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels));
    }

    pDevice->winmm._pHeapData = (ma_uint8*)ma_calloc(heapSize, &pDevice->pContext->allocationCallbacks);
    if (pDevice->winmm._pHeapData == NULL) {
        errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY;
        goto on_error;
    }

    MA_ZERO_MEMORY(pDevice->winmm._pHeapData, heapSize);

    if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) {
        ma_uint32 iPeriod;

        if (pConfig->deviceType == ma_device_type_capture) {
            pDevice->winmm.pWAVEHDRCapture            = pDevice->winmm._pHeapData;
            pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount));
        } else {
            pDevice->winmm.pWAVEHDRCapture            = pDevice->winmm._pHeapData;
            pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount));
        }

        /* Prepare headers. */
        for (iPeriod = 0; iPeriod < pDescriptorCapture->periodCount; ++iPeriod) {
            ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->format, pDescriptorCapture->channels);

            ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].lpData         = (LPSTR)(pDevice->winmm.pIntermediaryBufferCapture + (periodSizeInBytes*iPeriod));
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwBufferLength = periodSizeInBytes;
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwFlags        = 0L;
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwLoops        = 0L;
            ((MA_PFN_waveInPrepareHeader)pDevice->pContext->winmm.waveInPrepareHeader)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR));

            /*
            The user data of the WAVEHDR structure is a single flag the controls whether or not it is ready for writing. Consider it to be named "isLocked". A value of 0 means
            it's unlocked and available for writing. A value of 1 means it's locked.
            */
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwUser = 0;
        }
    }

    if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) {
        ma_uint32 iPeriod;

        if (pConfig->deviceType == ma_device_type_playback) {
            pDevice->winmm.pWAVEHDRPlayback            = pDevice->winmm._pHeapData;
            pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*pDescriptorPlayback->periodCount);
        } else {
            pDevice->winmm.pWAVEHDRPlayback            = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount));
            pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)) + (pDescriptorCapture->periodSizeInFrames*pDescriptorCapture->periodCount*ma_g...
        }

        /* Prepare headers. */
        for (iPeriod = 0; iPeriod < pDescriptorPlayback->periodCount; ++iPeriod) {
            ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->format, pDescriptorPlayback->channels);

            ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].lpData         = (LPSTR)(pDevice->winmm.pIntermediaryBufferPlayback + (periodSizeInBytes*iPeriod));
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwBufferLength = periodSizeInBytes;
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwFlags        = 0L;
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwLoops        = 0L;
            ((MA_PFN_waveOutPrepareHeader)pDevice->pContext->winmm.waveOutPrepareHeader)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(WAVEHDR));

            /*
            The user data of the WAVEHDR structure is a single flag the controls whether or not it is ready for writing. Consider it to be named "isLocked". A value of 0 means
            it's unlocked and available for writing. A value of 1 means it's locked.
            */
            ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwUser = 0;
        }
    }

    return MA_SUCCESS;

on_error:
    if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
        if (pDevice->winmm.pWAVEHDRCapture != NULL) {
            ma_uint32 iPeriod;
            for (iPeriod = 0; iPeriod < pDescriptorCapture->periodCount; ++iPeriod) {
                ((MA_PFN_waveInUnprepareHeader)pDevice->pContext->winmm.waveInUnprepareHeader)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR));
            }
        }

        ((MA_PFN_waveInClose)pDevice->pContext->winmm.waveInClose)((HWAVEIN)pDevice->winmm.hDeviceCapture);
    }

    if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
        if (pDevice->winmm.pWAVEHDRCapture != NULL) {
            ma_uint32 iPeriod;
            for (iPeriod = 0; iPeriod < pDescriptorPlayback->periodCount; ++iPeriod) {
                ((MA_PFN_waveOutUnprepareHeader)pDevice->pContext->winmm.waveOutUnprepareHeader)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(WAVEHDR));
            }
        }

        ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback);
    }

    ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks);

    if (errorMsg != NULL && errorMsg[0] != '\0') {
        ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "%s", errorMsg);
    }

    return errorCode;
}

static ma_result ma_device_start__winmm(ma_device* pDevice)
{
    MA_ASSERT(pDevice != NULL);

    if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
        MMRESULT resultMM;
        WAVEHDR* pWAVEHDR;
        ma_uint32 iPeriod;

        pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRCapture;

        /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */
        ResetEvent((HANDLE)pDevice->winmm.hEventCapture);

        /* To start the device we attach all of the buffers and then start it. As the buffers are filled with data we will get notifications. */
        for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) {
            resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR));
            if (resultMM != MMSYSERR_NOERROR) {
                ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture.");
                return ma_result_from_MMRESULT(resultMM);
            }

            /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */
            pWAVEHDR[iPeriod].dwUser = 1;   /* 1 = locked. */
        }

        /* Capture devices need to be explicitly started, unlike playback devices. */
        resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDeviceCapture);
        if (resultMM != MMSYSERR_NOERROR) {
            ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device.");
            return ma_result_from_MMRESULT(resultMM);
        }
    }

    if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
        /* Don't need to do anything for playback. It'll be started automatically in ma_device_start__winmm(). */
    }

    return MA_SUCCESS;
}

static ma_result ma_device_stop__winmm(ma_device* pDevice)
{
    MMRESULT resultMM;

    MA_ASSERT(pDevice != NULL);

    if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
        if (pDevice->winmm.hDeviceCapture == NULL) {
            return MA_INVALID_ARGS;
        }

        resultMM = ((MA_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((HWAVEIN)pDevice->winmm.hDeviceCapture);
        if (resultMM != MMSYSERR_NOERROR) {
            ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset capture device.");
        }
    }

    if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
        ma_uint32 iPeriod;
        WAVEHDR* pWAVEHDR;

        if (pDevice->winmm.hDevicePlayback == NULL) {
            return MA_INVALID_ARGS;
        }

        /* We need to drain the device. To do this we just loop over each header and if it's locked just wait for the event. */
        pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback;
        for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; iPeriod += 1) {
            if (pWAVEHDR[iPeriod].dwUser == 1) { /* 1 = locked. */
                if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventPlayback, INFINITE) != WAIT_OBJECT_0) {
                    break;  /* An error occurred so just abandon ship and stop the device without draining. */
                }

                pWAVEHDR[iPeriod].dwUser = 0;
            }
        }

        resultMM = ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevicePlayback);
        if (resultMM != MMSYSERR_NOERROR) {
            ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset playback device.");
        }
    }

    return MA_SUCCESS;
}

static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten)
{
    ma_result result = MA_SUCCESS;
    MMRESULT resultMM;
    ma_uint32 totalFramesWritten;
    WAVEHDR* pWAVEHDR;

    MA_ASSERT(pDevice != NULL);
    MA_ASSERT(pPCMFrames != NULL);

    if (pFramesWritten != NULL) {
        *pFramesWritten = 0;
    }

    pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback;

    /* Keep processing as much data as possible. */
    totalFramesWritten = 0;
    while (totalFramesWritten < frameCount) {
        /* If the current header has some space available we need to write part of it. */
        if (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser == 0) { /* 0 = unlocked. */
            /*
            This header has room in it. We copy as much of it as we can. If we end up fully consuming the buffer we need to
            write it out and move on to the next iteration.
            */
            ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
            ma_uint32 framesRemainingInHeader = (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwBufferLength/bpf) - pDevice->winmm.headerFramesConsumedPlayback;

            ma_uint32 framesToCopy = ma_min(framesRemainingInHeader, (frameCount - totalFramesWritten));
            const void* pSrc = ma_offset_ptr(pPCMFrames, totalFramesWritten*bpf);
            void* pDst = ma_offset_ptr(pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].lpData, pDevice->winmm.headerFramesConsumedPlayback*bpf);
            MA_COPY_MEMORY(pDst, pSrc, framesToCopy*bpf);

            pDevice->winmm.headerFramesConsumedPlayback += framesToCopy;
            totalFramesWritten += framesToCopy;

            /* If we've consumed the buffer entirely we need to write it out to the device. */
            if (pDevice->winmm.headerFramesConsumedPlayback == (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwBufferLength/bpf)) {
                pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser = 1;            /* 1 = locked. */
                pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwFlags &= ~WHDR_DONE; /* <-- Need to make sure the WHDR_DONE flag is unset. */

                /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */
                ResetEvent((HANDLE)pDevice->winmm.hEventPlayback);

                /* The device will be started here. */
                resultMM = ((MA_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &pWAVEHDR[pDevice->winmm.iNextHeaderPlayback], sizeof(WAVEHDR));
                if (resultMM != MMSYSERR_NOERROR) {
                    result = ma_result_from_MMRESULT(resultMM);
                    ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed.");
                    break;
                }

                /* Make sure we move to the next header. */
                pDevice->winmm.iNextHeaderPlayback = (pDevice->winmm.iNextHeaderPlayback + 1) % pDevice->playback.internalPeriods;
                pDevice->winmm.headerFramesConsumedPlayback = 0;
            }

            /* If at this point we have consumed the entire input buffer we can return. */
            MA_ASSERT(totalFramesWritten <= frameCount);
            if (totalFramesWritten == frameCount) {
                break;
            }

            /* Getting here means there's more to process. */
            continue;
        }

        /* Getting here means there isn't enough room in the buffer and we need to wait for one to become available. */
        if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventPlayback, INFINITE) != WAIT_OBJECT_0) {
            result = MA_ERROR;
            break;
        }

        /* Something happened. If the next buffer has been marked as done we need to reset a bit of state. */
        if ((pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwFlags & WHDR_DONE) != 0) {
            pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser = 0;    /* 0 = unlocked (make it available for writing). */
            pDevice->winmm.headerFramesConsumedPlayback = 0;
        }

        /* If the device has been stopped we need to break. */
        if (ma_device_get_state(pDevice) != ma_device_state_started) {
            break;
        }
    }

    if (pFramesWritten != NULL) {
        *pFramesWritten = totalFramesWritten;
    }

    return result;
}

static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead)
{
    ma_result result = MA_SUCCESS;
    MMRESULT resultMM;
    ma_uint32 totalFramesRead;
    WAVEHDR* pWAVEHDR;

    MA_ASSERT(pDevice != NULL);
    MA_ASSERT(pPCMFrames != NULL);

    if (pFramesRead != NULL) {
        *pFramesRead = 0;
    }

    pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRCapture;

    /* Keep processing as much data as possible. */
    totalFramesRead = 0;
    while (totalFramesRead < frameCount) {
        /* If the current header has some space available we need to write part of it. */
        if (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser == 0) { /* 0 = unlocked. */
            /* The buffer is available for reading. If we fully consume it we need to add it back to the buffer. */
            ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels);
            ma_uint32 framesRemainingInHeader = (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwBufferLength/bpf) - pDevice->winmm.headerFramesConsumedCapture;

            ma_uint32 framesToCopy = ma_min(framesRemainingInHeader, (frameCount - totalFramesRead));
            const void* pSrc = ma_offset_ptr(pWAVEHDR[pDevice->winmm.iNextHeaderCapture].lpData, pDevice->winmm.headerFramesConsumedCapture*bpf);
            void* pDst = ma_offset_ptr(pPCMFrames, totalFramesRead*bpf);
            MA_COPY_MEMORY(pDst, pSrc, framesToCopy*bpf);

            pDevice->winmm.headerFramesConsumedCapture += framesToCopy;
            totalFramesRead += framesToCopy;

            /* If we've consumed the buffer entirely we need to add it back to the device. */
            if (pDevice->winmm.headerFramesConsumedCapture == (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwBufferLength/bpf)) {
                pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser = 1;            /* 1 = locked. */
                pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwFlags &= ~WHDR_DONE; /* <-- Need to make sure the WHDR_DONE flag is unset. */

                /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */
                ResetEvent((HANDLE)pDevice->winmm.hEventCapture);

                /* The device will be started here. */
                resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[pDevice->winmm.iNextHeaderCapture], sizeof(WAVEHDR));
                if (resultMM != MMSYSERR_NOERROR) {
                    result = ma_result_from_MMRESULT(resultMM);
                    ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed.");
                    break;
                }

                /* Make sure we move to the next header. */
                pDevice->winmm.iNextHeaderCapture = (pDevice->winmm.iNextHeaderCapture + 1) % pDevice->capture.internalPeriods;
                pDevice->winmm.headerFramesConsumedCapture = 0;
            }

            /* If at this point we have filled the entire input buffer we can return. */
            MA_ASSERT(totalFramesRead <= frameCount);
            if (totalFramesRead == frameCount) {
                break;
            }

            /* Getting here means there's more to process. */
            continue;
        }

        /* Getting here means there isn't enough any data left to send to the client which means we need to wait for more. */
        if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventCapture, INFINITE) != WAIT_OBJECT_0) {
            result = MA_ERROR;
            break;
        }

        /* Something happened. If the next buffer has been marked as done we need to reset a bit of state. */
        if ((pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwFlags & WHDR_DONE) != 0) {
            pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser = 0;    /* 0 = unlocked (make it available for reading). */
            pDevice->winmm.headerFramesConsumedCapture = 0;
        }

        /* If the device has been stopped we need to break. */
        if (ma_device_get_state(pDevice) != ma_device_state_started) {
            break;
        }
    }

    if (pFramesRead != NULL) {
        *pFramesRead = totalFramesRead;
    }

    return result;
}

static ma_result ma_context_uninit__winmm(ma_context* pContext)
{
    MA_ASSERT(pContext != NULL);
    MA_ASSERT(pContext->backend == ma_backend_winmm);

    ma_dlclose(pContext, pContext->winmm.hWinMM);
    return MA_SUCCESS;
}

static ma_result ma_context_init__winmm(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks)
{
    MA_ASSERT(pContext != NULL);

    (void)pConfig;

    pContext->winmm.hWinMM = ma_dlopen(pContext, "winmm.dll");
    if (pContext->winmm.hWinMM == NULL) {
        return MA_NO_BACKEND;
    }

    pContext->winmm.waveOutGetNumDevs      = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutGetNumDevs");
    pContext->winmm.waveOutGetDevCapsA     = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutGetDevCapsA");
    pContext->winmm.waveOutOpen            = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutOpen");
    pContext->winmm.waveOutClose           = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutClose");
    pContext->winmm.waveOutPrepareHeader   = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutPrepareHeader");
    pContext->winmm.waveOutUnprepareHeader = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutUnprepareHeader");
    pContext->winmm.waveOutWrite           = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutWrite");
    pContext->winmm.waveOutReset           = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveOutReset");
    pContext->winmm.waveInGetNumDevs       = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInGetNumDevs");
    pContext->winmm.waveInGetDevCapsA      = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInGetDevCapsA");
    pContext->winmm.waveInOpen             = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInOpen");
    pContext->winmm.waveInClose            = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInClose");
    pContext->winmm.waveInPrepareHeader    = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInPrepareHeader");
    pContext->winmm.waveInUnprepareHeader  = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInUnprepareHeader");
    pContext->winmm.waveInAddBuffer        = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInAddBuffer");
    pContext->winmm.waveInStart            = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInStart");
    pContext->winmm.waveInReset            = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInReset");

    pCallbacks->onContextInit             = ma_context_init__winmm;
    pCallbacks->onContextUninit           = ma_context_uninit__winmm;
    pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__winmm;
    pCallbacks->onContextGetDeviceInfo    = ma_context_get_device_info__winmm;
    pCallbacks->onDeviceInit              = ma_device_init__winmm;
    pCallbacks->onDeviceUninit            = ma_device_uninit__winmm;



( run in 0.793 second using v1.01-cache-2.11-cpan-800906f7e73 )