#include "stdafx.h" #define _BGM_DEFINE_ #include "BGM.h" #include "resource.h" #include #include "Debug.h" static DWORD WINAPI MusicThreadProc(LPVOID); struct _BgmState { CRITICAL_SECTION cs; // to sync shutdown LPWAVEFORMATEX pwfx; // wave-out data HANDLE hThread; // audio thread HANDLE hDone; // Are we done? HANDLE hPause; // Play/Pause BOOL bPaused; // Are we paused? }; int TogglePlayPause(BgmState state) { BOOL bRet; if (!state) { SetLastError(ERROR_INVALID_PARAMETER); return -1; } bRet = state->bPaused; state->bPaused = !state->bPaused; if (!SetEvent(state->hPause)) { state->bPaused = bRet; return -1; } return state->bPaused; } BOOL PrepareMusic(HINSTANCE hInstance, BgmState *state) { HANDLE hHeap = GetProcessHeap(); LPWAVEFORMATEX pwfx = NULL; int bIdx; DWORD dwIgnore; BOOL bFound = FALSE; struct _BgmState *sTmp = NULL; const DWORD dwTestFormats[] = { 44100, 22050, 11025, 8000, }; MusicStartFailReason = "Couldn't verify parameters"; if (!hInstance || !state) return FALSE; // TODO: do we even bother? we'll error out later MusicStartFailReason = "Couldn't get number of waveOut devices"; if (!waveOutGetNumDevs()) return FALSE; MusicStartFailReason = "Couldn't allocate memory"; sTmp = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(struct _BgmState)); if (!state) return FALSE; pwfx = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WAVEFORMATEX)); if (!pwfx) return FALSE; pwfx->wFormatTag = WAVE_FORMAT_PCM; pwfx->nChannels = 2; pwfx->wBitsPerSample = 16; for (bIdx = 0; bIdx < (sizeof(dwTestFormats) / sizeof(DWORD)); bIdx++) { MMRESULT mRes; pwfx->nSamplesPerSec = dwTestFormats[bIdx]; pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nChannels; pwfx->nBlockAlign = 2; // (1 channel * 16 bits) / (1 byte / 8 bits) pwfx->cbSize = 0; mRes = waveOutOpen(NULL, WAVE_MAPPER, pwfx, 0, 0, WAVE_FORMAT_QUERY); if (mRes == MMSYSERR_NOERROR) { bFound = TRUE; break; } } MusicStartFailReason = "Couldn't find a working sound device"; // did we find a working sound device? if (!bFound) { HeapFree(hHeap, 0, pwfx); HeapFree(hHeap, 0, sTmp); return FALSE; } sTmp->pwfx = pwfx; MusicStartFailReason = "Couldn't create an event?"; // build the sync event used for ending the thread sTmp->hDone = CreateEvent(NULL, FALSE, FALSE, NULL); if (!sTmp->hDone) { HeapFree(hHeap, 0, pwfx); HeapFree(hHeap, 0, sTmp); return FALSE; } MusicStartFailReason = "Couldn't create another event???"; sTmp->hPause = CreateEvent(NULL, FALSE, FALSE, NULL); if (!sTmp->hDone) { HeapFree(hHeap, 0, pwfx); HeapFree(hHeap, 0, sTmp); return FALSE; } MusicStartFailReason = "Couldn't create the music thread????????"; sTmp->hThread = CreateThread(NULL, 0, MusicThreadProc, sTmp, 0, &dwIgnore); if (!sTmp->hThread) { HeapFree(hHeap, 0, pwfx); HeapFree(hHeap, 0, sTmp); return FALSE; } *state = sTmp; MusicStartFailReason = "Everything's fine actually ??????????????"; return TRUE; } struct WaveBuf { WAVEHDR *hdr; struct WaveBuf *prev; struct WaveBuf *next; }; static DWORD WINAPI MusicThreadProc(LPVOID lpParam) { BgmState state = (BgmState)lpParam; xmp_context xCtx = NULL; HWAVEOUT hwOut = NULL; // audio out dev LPVOID pModData = NULL; // module bytes DWORD dwErr = ERROR_SUCCESS, // return code dwModSize; // rsrc byte len MMRESULT mRes; // waveOut* error int iRes; // xmplib error HANDLE hMmEvent = NULL; // Multi-buffer drifting #define BUF_COUNT 3 LPBYTE lpBufs[BUF_COUNT] = {0}; WAVEHDR wHdrs[BUF_COUNT] = {0}; const DWORD dwBufSize = XMP_MAX_FRAMESIZE; if (state == NULL) return ERROR_INVALID_PARAMETER; hMmEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!hMmEvent) return GetLastError(); { // Generate the buffers int i; for (i = 0; i < BUF_COUNT; i++) { lpBufs[i] = HeapAlloc(GetProcessHeap(), 0, dwBufSize); if (lpBufs[i] == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; goto threadDone; } wHdrs[i].lpData = lpBufs[i]; wHdrs[i].dwBufferLength = dwBufSize; } } mRes = waveOutOpen(&hwOut, WAVE_MAPPER, state->pwfx, (DWORD_PTR)hMmEvent, 0, CALLBACK_EVENT); if (mRes) return ERROR_INVALID_HANDLE; xCtx = xmp_create_context(); if (!xCtx) { dwErr = ERROR_BROKEN_PIPE; goto threadDone; } { // Find and load the resource. We supposedly don't // need to free our handles on Win32, so handle it in // its own block, I guess? HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_BGM), _T("MUSIC")); HGLOBAL hModFile = NULL; if (hRsrc) { hModFile = LoadResource(NULL, hRsrc); } if (!hModFile) { dwErr = GetLastError(); goto threadDone; } dwModSize = SizeofResource(NULL, hRsrc); if (!dwModSize) { dwErr = GetLastError(); goto threadDone; } pModData = LockResource(hModFile); if (!pModData) { dwErr = GetLastError(); goto threadDone; } } if ((iRes = xmp_load_module_from_memory(xCtx, pModData, dwModSize)) != 0) { dwErr = ERROR_INVALID_DATA; goto threadDone; } DebugPrintf("Loading mod at %d Hz\n", state->pwfx->nSamplesPerSec); xmp_start_player(xCtx, state->pwfx->nSamplesPerSec, 0); // Player frame loop! { int iErrCount = 0, // max 5 consecutive errs before // we give up iCurBuf = 0; // Current buffer BOOL bDone = FALSE; const HANDLE hEvents[] = { hMmEvent, state->hDone, state->hPause, }; #if _DEBUG struct xmp_module_info xmi = {0}; xmp_get_module_info(xCtx, &xmi); DebugPrintf("\n== Mod info ==\n" _T("Speed: %d\n") _T("BPM: %d\n") _T("Fmt: %s\n") _T("== End Mod info ==\n\n"), xmi.mod->spd, xmi.mod->bpm, xmi.mod->type); #endif /* _DEBUG */ while (iErrCount < 5 && !bDone) { struct xmp_frame_info xfi = {0}; // Reset the WAVEHDR wHdrs[iCurBuf].dwFlags = 0; wHdrs[iCurBuf].lpData = lpBufs[iCurBuf]; // Pull the current audio frame if (xmp_play_frame(xCtx)) { dwErr = ERROR_READ_FAULT; break; } xmp_get_frame_info(xCtx, &xfi); memcpy(wHdrs[iCurBuf].lpData, xfi.buffer, xfi.buffer_size); wHdrs[iCurBuf].dwBufferLength = xfi.buffer_size; waveOutPrepareHeader(hwOut, &wHdrs[iCurBuf], sizeof(WAVEHDR)); // Write it to the audio device if (waveOutWrite(hwOut, &wHdrs[iCurBuf], sizeof(WAVEHDR))) { dwErr = ERROR_WRITE_FAULT; iErrCount++; } else { iErrCount = dwErr = 0; // Set up the next buffer, or wait for it to complete iCurBuf = (iCurBuf + 1) % BUF_COUNT; while (((wHdrs[iCurBuf].dwFlags & WHDR_PREPARED) == WHDR_PREPARED) && ((wHdrs[iCurBuf].dwFlags & WHDR_DONE) != WHDR_DONE)) { // Use this opportunity to listen for every event DWORD dwRes = WaitForMultipleObjects( sizeof(hEvents) / sizeof(HANDLE), hEvents, FALSE, 1000); if (dwRes == WAIT_FAILED) { HRESULT hrErr = GetLastError(); DebugPrintf("Agh, got error %08x\n", hrErr); iErrCount++; } else if (dwRes == WAIT_OBJECT_0) { // TODO: should auto-reset, right? ResetEvent(hMmEvent); } else if (dwRes == WAIT_OBJECT_0 + 1) { // state->hDone set, clean up after we're done bDone = TRUE; } else if (dwRes == WAIT_OBJECT_0 + 2) { // state->hPause set, pause or unpause if (state->bPaused) { waveOutPause(hwOut); } else { waveOutRestart(hwOut); } } } waveOutUnprepareHeader(hwOut, &wHdrs[iCurBuf], sizeof(WAVEHDR)); } } } threadDone: if (xCtx) { xmp_end_player(xCtx); xmp_release_module(xCtx); xmp_free_context(xCtx); } if (hwOut) { waveOutReset(hwOut); waveOutClose(hwOut); } { int i; for (i = 0; i < BUF_COUNT; i++) { if (lpBufs[i]) { if (wHdrs[i].dwFlags & WHDR_PREPARED) { waveOutUnprepareHeader(hwOut, &wHdrs[i], sizeof(WAVEHDR)); } HeapFree(GetProcessHeap(), 0, lpBufs[i]); } } } ExitThread(dwErr); return dwErr; } void EndMusic(BgmState state) { if (!state) return; SetEvent(state->hDone); // Wait for thread to finish, to avoid an audio driver crash WaitForSingleObject(state->hThread, INFINITE); // Clean up HeapFree(GetProcessHeap(), 0, state->pwfx); CloseHandle(state->hDone); CloseHandle(state->hPause); HeapFree(GetProcessHeap(), 0, state); return; }