385 lines
8.6 KiB
C
385 lines
8.6 KiB
C
|
#include "stdafx.h"
|
||
|
#define _BGM_DEFINE_
|
||
|
#include "BGM.h"
|
||
|
#include "resource.h"
|
||
|
#include <xmp.h>
|
||
|
#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;
|
||
|
}
|