#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;
}