diff options
Diffstat (limited to 'drivers/gaudio/Win32/gaudio_play_lld.c')
-rw-r--r-- | drivers/gaudio/Win32/gaudio_play_lld.c | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/drivers/gaudio/Win32/gaudio_play_lld.c b/drivers/gaudio/Win32/gaudio_play_lld.c new file mode 100644 index 00000000..6b4f2dab --- /dev/null +++ b/drivers/gaudio/Win32/gaudio_play_lld.c @@ -0,0 +1,185 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.org/license.html + */ + +/** + * @file drivers/gaudio/Win32/gaudio_play_lld.c + * @brief GAUDIO - Play Driver file for Win32. + */ + +#include "gfx.h" + +#if GFX_USE_GAUDIO && GAUDIO_NEED_PLAY + +/* Include the driver defines */ +#include "src/gaudio/driver_play.h" + +#undef Red +#undef Green +#undef Blue +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <stdio.h> +#include <mmsystem.h> + +#define MAX_WAVE_HEADERS 2 // Larger numbers enable more buffering which is good for ensuring + // there are no skips due to data not being available, however larger + // numbers of buffers also create higher latency. + +static HWAVEOUT ah = 0; +static volatile int nQueuedBuffers; +static bool_t isRunning; +static WAVEHDR WaveHdrs[MAX_WAVE_HEADERS]; +static HANDLE waveThread; +static DWORD threadID; + +/**************************** waveProc() ******************************* + * We don't use CALLBACK_FUNCTION because it is restricted to calling only + * a few particular Windows functions, namely some of the time functions, + * and a few of the Low Level MIDI API. If you violate this rule, your app can + * hang inside of the callback). One of the Windows API that a callback can't + * call is waveOutUnPrepareBuffer() which is what we need to use whenever we receive a + * MM_WOM_DONE. My callback would need to defer that job to another thread + * anyway, so instead just use CALLBACK_THREAD here instead. + *************************************************************************/ + +static bool_t senddata(WAVEHDR *pwh) { + GAudioData *paud; + + // Get the next data block to send + gfxSystemLock(); + paud = gaudioPlayGetDataBlockI(); + if (!paud && !nQueuedBuffers) + gaudioPlayDoneI(); + gfxSystemUnlock(); + if (!paud) + return FALSE; + + // Prepare the wave header for Windows + pwh->dwUser = (DWORD_PTR)paud; + pwh->lpData = (LPSTR)(paud+1); // The data is on the end of the structure + pwh->dwBufferLength = paud->len; + pwh->dwFlags = 0; + pwh->dwLoops = 0; + if (waveOutPrepareHeader(ah, pwh, sizeof(WAVEHDR))) { + fprintf(stderr, "GAUDIO: Failed to prepare a play buffer"); + exit(-1); + } + + // Send it to windows + if (waveOutWrite(ah, pwh, sizeof(WAVEHDR))) { + fprintf(stderr, "GAUDIO: Failed to write the play buffer"); + exit(-1); + } + + nQueuedBuffers++; + return TRUE; +} + +static DWORD WINAPI waveProc(LPVOID arg) { + MSG msg; + WAVEHDR *pwh; + (void) arg; + + while (GetMessage(&msg, 0, 0, 0)) { + switch (msg.message) { + case MM_WOM_DONE: + pwh = (WAVEHDR *)msg.lParam; + + // Windows - Let go! + waveOutUnprepareHeader(ah, pwh, sizeof(WAVEHDR)); + + // Give the buffer back to the Audio Free List + gfxSystemLock(); + gaudioPlayReleaseDataBlockI((GAudioData *)pwh->dwUser); + gfxSystemUnlock(); + pwh->lpData = 0; + nQueuedBuffers--; + + // Are we stopping? + if (!isRunning) { + // Have we finished yet? + if (!nQueuedBuffers) { + gfxSystemLock(); + gaudioPlayDoneI(); + gfxSystemUnlock(); + } + break; + } + + // Try and get a new block + senddata(pwh); + break; + } + } + return 0; +} + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +bool_t gaudio_play_lld_init(uint16_t channel, uint32_t frequency, ArrayDataFormat format) { + WAVEFORMATEX wfx; + + if (format != ARRAY_DATA_8BITUNSIGNED && format != ARRAY_DATA_16BITSIGNED) + return FALSE; + + if (!waveThread) { + if (!(waveThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)waveProc, 0, 0, &threadID))) { + fprintf(stderr, "GAUDIO: Can't create WAVE play-back thread\n"); + exit(-1); + } + CloseHandle(waveThread); + } + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = channel == GAUDIO_PLAY_STEREO ? 2 : 1; + wfx.nSamplesPerSec = frequency; + wfx.nBlockAlign = wfx.nChannels * (format == ARRAY_DATA_8BITUNSIGNED ? 1 : 2); + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.wBitsPerSample = (format == ARRAY_DATA_8BITUNSIGNED ? 8 : 16); + wfx.cbSize = 0; + + if (ah) { + waveOutClose(ah); + ah = 0; + } + if (waveOutOpen(&ah, WAVE_MAPPER, &wfx, (DWORD_PTR)threadID, 0, CALLBACK_THREAD)) { + fprintf(stderr, "GAUDIO: Can't open WAVE play-back device\n"); + exit(-1); + } + + return TRUE; +} + +bool_t gaudio_play_lld_set_volume(uint8_t vol) { + if (!ah) + return FALSE; + return waveOutSetVolume(ah, (((uint16_t)vol)<<8)|vol) != 0; +} + +void gaudio_play_lld_start(void) { + WAVEHDR *pwh; + + isRunning = TRUE; + while (nQueuedBuffers < MAX_WAVE_HEADERS) { + // Find the empty one - there will always be at least one. + for(pwh = WaveHdrs; pwh->lpData; pwh++); + + // Grab the next audio block from the Audio Out Queue + if (!senddata(pwh)) + break; + } +} + +void gaudio_play_lld_stop(void) { + isRunning = FALSE; + waveOutReset(ah); + while(nQueuedBuffers) Sleep(1); +} + +#endif /* GFX_USE_GAUDIO && GAUDIO_NEED_PLAY */ |