MME Buddy
In Windows 3.1 (which the MME API was originally implemented on), it was possible to communicate directly with the hardware. For example, there was sndblst.drv, which takes requests from the MME API and directly controls a Sound Blaster card.
With the release of Windows NT, which of course prohibits user-mode applications from directly communicating with the hardware, it was necessary to split sound card drivers into two parts - one part runs in user-mode, the other in kernel-mode.
This explains why, for a Sound Blaster sound card, in Windows NT you have two files instead of one:
- sndblst.dll
- sndblst.sys
There is also a component named mmdrv.dll which is a generic user-mode sound driver support library. It does the same job as sndblst.dll except is not specific to Sound Blaster sound cards.
I'm not 100% certain of the details behind the existence of mmdrv.dll, as in NT it's referred to as "Low-level audio" and, if I recall correctly, this also existed on Windows 3.1
The differences I have found so far is that: - mmdrv.dll operates on devices such as \\.\WaveOut0 whilst sndblst.dll on \\.\SBWaveOut0 - sndblst.dll refers to strings in its resources to provide the product name
More recent versions of Windows include a component called wdmaud.drv which just provides access to the sound devices available via Kernel Streaming (ie, following the WDM audio driver model).
All of these user-mode components do pretty much the same job, so it makes sense to extract the common functionality and place it in a library - currently called MME Buddy. Indeed, this is the way in which it used to be done with the Windows NT4 DDK. However, we're going to need to support plug and play etc. and the way in which MME Buddy is being designed allows flexibility as far as functionality is concerned.
Contents
Implementation Status
Wave Output
Message | Corresponding MME API | Purpose | Supported? | Notes |
---|---|---|---|---|
WODM_BREAKLOOP | waveOutBreakLoop | Stop looping | Yes | |
WODM_CLOSE | waveOutClose | Close a device | Yes | |
WODM_GETDEVCAPS | waveOutGetDevCaps | Retrieve information about a device | Yes | |
WODM_GETNUMDEVS | waveOutGetNumDevs | Obtain the number of devices | Yes | |
WODM_GETPITCH | waveOutGetPitch | Get the current playback pitch | No | Never used this |
WODM_GETPLAYBACKRATE | waveOutGetPlaybackRate | Get the current playback rate | No | Never used this |
WODM_GETPOS | waveOutGetPosition | Obtain the current playback position in various time formats | No | |
WODM_GETVOLUME | waveOutGetVolume | Get the current playback volume | No | Never used this |
WODM_OPEN | waveOutOpen | Open a device | Yes | |
WODM_PAUSE | waveOutPause | Pause playback | Yes | |
WODM_PREFERRED | ? | ? | No | |
WODM_PREPARE | waveOutPrepareHeader | Prepare a header describing a wave buffer for playback | No | Relying on WinMM for this functionality at present. |
WODM_RESET | waveOutReset | Stop playback of audio | Partially | Currently this is supported by setting the current audio buffer to NULL. |
WODM_RESTART | waveOutRestart | Continue playback of audio | Yes | |
WODM_SETPITCH | waveOutSetPitch | Set the playback pitch | No | Never used this |
WODM_SETPLAYBACKRATE | waveOutSetPlaybackRate | Set the playback rate | No | Never used this |
WODM_SETVOLUME | waveOutSetVolume | Set the playback volume | No | Never used this |
WODM_UNPREPARE | waveOutUnprepareHeader | Clean-up a wave audio header | No | Relying on WinMM for this functionality at present |
WODM_WRITE | waveOutWrite | Submit a header describing a wave audio buffer | Yes | |
DRVM_EXIT | n/a | ? | No | |
DRVM_INIT | n/a | ? | No |
Wave Input
Message | Corresponding MME API | Purpose | Supported? | Notes |
---|---|---|---|---|
WIDM_ADDBUFFER | waveInAddBuffer | Submit a header describing a buffer to record wave audio into | No | |
WIDM_CLOSE | waveInClose | Close a device | No | |
WIDM_GETDEVCAPS | waveInGetDevCaps | Retrieve information about a device | No | |
WIDM_GETNUMDEVS | waveInGetNumDevs | Obtain the number of devices | No | |
WIDM_GETPOS | waveInGetPosition | Obtain the current recording position in various time formats | No | |
WIDM_OPEN | waveInOpen | Open a device | No | |
WIDM_PREFERRED | ? | ? | No | |
WIDM_PREPARE | waveInPrepareHeader | Prepare a header describing a wave buffer for recording | No | Relying on WinMM for this functionality at present. |
WIDM_RESET | waveInReset | No | ||
WIDM_START | waveInStart | Begin recording | No | |
WIDM_STOP | waveInStop | Stop recording | No | |
WODM_UNPREPARE | waveInUnprepareHeader | Clean-up a wave audio header | No | Relying on WinMM for this functionality at present |
DRVM_EXIT | n/a | ? | No | |
DRVM_INIT | n/a | ? | No |
MIDI Output
Message | Corresponding MME API | Purpose | Supported? | Notes |
---|---|---|---|---|
MODM_CLOSE | midiOutClose | Close a device | No | |
MODM_GETDEVCAPS | midiOutGetDevCaps | Retrieve information about a device | No | |
MODM_GETNUMDEVS | midiOutGetNumDevs | Obtain the number of devices | No | |
MODM_OPEN | midiOutOpen | Open a device | No | |
DRVM_EXIT | n/a | ? | No | |
DRVM_INIT | n/a | ? | No |
This list is incomplete
MIDI Input
Message | Corresponding MME API | Purpose | Supported? | Notes |
---|---|---|---|---|
MIDM_CLOSE | midiInClose | Close a device | No | |
MIDM_GETDEVCAPS | midiInGetDevCaps | Retrieve information about a device | No | |
MIDM_GETNUMDEVS | midiInGetNumDevs | Obtain the number of devices | No | |
MIDM_OPEN | midiInOpen | Open a device | No | |
DRVM_EXIT | n/a | ? | No | |
DRVM_INIT | n/a | ? | No |
This list is incomplete
Mixer
Message | Corresponding MME API | Purpose | Supported? | Notes |
---|---|---|---|---|
MXDM_CLOSE | mixerClose | Close a device | No | |
MXDM_GETCONTROLDETAILS | mixerGetControlDetails | get details of a control | No | |
MXDM_GETDEVCAPS | mixerGetDevCaps | Retrieve information about a device | Yes | |
MXDM_GETLINECONTROLS | mixerGetLineControls | gets information about a control | No | |
MXDM_GETLINEINFO | mixerGetLineInfo | gets information about a source / destination line | Yes | |
MXDM_GETNUMDEVS | mixerGetNumDevs | Obtain the number of devices | Yes | |
MXDM_OPEN | mixerOpen | Open a device | Yes | |
MXDM_SETCONTROLDETAILS | mixerSetControlDetails | sets details of a control | No | |
DRVM_EXIT | n/a | ? | No | |
DRVM_INIT | n/a | ? | No | |
DRVM_MAPPER | n/a | ? | No |
Auxiliary
Message | Corresponding MME API | Purpose | Supported? | Notes |
---|---|---|---|---|
AUXDM_GETDEVCAPS | auxGetDevCaps | Retrieve information about a device | No | |
AUXDM_GETNUMDEVS | auxGetNumDevs | Obtain the number of devices | No | |
AUXDM_GETVOLUME | auxGetVolume | Retrieve the current volume | No | |
AUXDM_SETVOLUME | auxSetVolume | Set the volume | No | |
DRVM_EXIT | n/a | ? | No | |
DRVM_INIT | n/a | ? | No |
Other Features / Bugs / To Do
- Setting WinMM data on opening a device should be dealt with in a subroutine.
- Queued buffers never get removed, even after completion.
- WODM_RESET could be implemented in a cleaner fashion.
- NT4's own MME components dont fire WOM_DONE until closure if breaking out of a loop, under some circumstances. I think, when the loop ends or is broken, as the end loop header is completed, we should send WOM_DONE for each header starting from the beginning of the loop.
- Undocumented DRV_QUERYDRVENTRY, DRV_QUERYDEVNODE, DRV_QUERYNAME, DRV_QUERYDRIVERIDS, DRV_QUERYMAPPABLE, DRVM_MAPPER_PREFERRED_GET, DRV_QUERYDEVICEINTERFACE, DRV_QUERYDEVICEINTERFACESIZE, DRV_QUERYDSOUNDIFACE, DRV_QUERYDSOUNDDESC
- How should writing the same WAVEHDR repeatedly be dealt with?
Development Notes
MSDN warns against, for example, calling waveOutWrite from inside a WOM_DONE callback, as it can cause a deadlock. My testing however shows that, at least on Windows XP, this does not hold true. Due to the current threading mechanism I have implemented, this situation will deadlock.
This is because WOM_DONE gets called within the same thread as the streaming code, and waveOutWrite waits to synchronise with the stream before it submits buffers. The streaming thread will never be "ready" so long as it is running a WOM_DONE routine, hence it never finishes.
This can be tackled in one of two ways: - Build a queue of new buffers outside of the streaming thread, set an event when there are buffers in this queue and protect it with a mutex. - Or, spawn a second thread responsible solely for dispatching "done" callbacks.
Mutex-protected buffer queue
The former of these could use the buffer queue already in the SoundDeviceInstance structure, which does however mean guarding it at every possible point at which it's used. If all validation and initialisation for a WAVEHDR is done prior to actually enqueueing the buffer, there'd be no reason to hang around for an MMRESULT unless waiting for the outcome of the streaming operation...
"Done" buffer callback dispatcher
The way this would work would be as follows: - The driver returns a buffer - MME Buddy's completion routine calculates how much data has been written for the current header - If a buffer has been completed, and it's not part of a loop, wait for a callback-thread-ready event - When we resume, fill a structure with the WAVEHDR and other relevant details (this is a static global structure) - Set another event (callback-thread-work?) - Wait on another event (callback-thread-done?)
In the meantime: - The callback thread sets callback-thread-ready event - It then waits for callback-thread-work event - The structure filled by the other thread gets copied - Set the callback-thread-done event - Do the callback
This basically means that the streaming thread doesn't have to wait whilst WOM_DONE gets processed - it can simply pass the details of the callback to be done and have it dealt with asynchronously.
The only thing to be careful of with this is that the waveOutWrite code for example is not callable in a thread-safe fashion!