Created
July 27, 2017 09:41
-
-
Save bit-hack/1081292b55c1a414f0c2c5451ed92395 to your computer and use it in GitHub Desktop.
Threaded winmm audio playback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <array> | |
#include <cassert> | |
#include <cmath> | |
#include <cstdint> | |
#include <Windows.h> | |
#define MMOK(EXP) ((EXP) == MMSYSERR_NOERROR) | |
struct WaveInfo { | |
uint32_t sampleRate; | |
uint32_t bitDepth; | |
uint32_t channels; | |
uint32_t bufferSize; | |
void (*waveRender)(void* buffer, size_t bufferSize, void* user); | |
void* user; | |
}; | |
struct WaveData { | |
// user supplied info | |
WaveInfo info; | |
// internal wave info | |
std::array<WAVEHDR, 4> wavehdr; | |
HWAVEOUT hwo; | |
LONG volatile alive; | |
HANDLE waveEvent; | |
HANDLE waveThread; | |
}; | |
bool wavePrepare(WaveData& data) | |
{ | |
const size_t numSamples = data.info.bufferSize * data.info.channels; | |
const size_t numBytes = numSamples * data.info.bitDepth / 8; | |
for (WAVEHDR& hdr : data.wavehdr) { | |
// allocate the wave header object | |
memset(&hdr, 0, sizeof(hdr)); | |
hdr.lpData = (LPSTR) new int16_t[numBytes]; | |
hdr.dwBufferLength = numBytes; | |
memset(hdr.lpData, 0, numBytes); | |
// prepare the header for the device | |
if (!MMOK(waveOutPrepareHeader(data.hwo, &hdr, sizeof(hdr)))) { | |
return false; | |
} | |
// write the buffer to the device | |
if (!MMOK(waveOutWrite(data.hwo, &hdr, sizeof(hdr)))) { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool waveClose(WaveData& data) | |
{ | |
// kill the wave thread | |
if (TerminateThread(data.waveThread, 0) == FALSE) { | |
return false; | |
} | |
// close the wave audio object | |
if (!MMOK(waveOutClose(data.hwo))) { | |
return false; | |
} | |
return true; | |
} | |
DWORD WINAPI waveThreadProc(LPVOID param) | |
{ | |
assert(param); | |
WaveData& data = *(WaveData*)param; | |
while (data.alive) { | |
// wait for a wave event | |
const DWORD ret = WaitForSingleObject(data.waveEvent, INFINITE); | |
// poll waveheaders for a free block | |
for (WAVEHDR& hdr : data.wavehdr) { | |
if ((hdr.dwFlags & WHDR_DONE) == 0) { | |
// buffer is not free for use | |
continue; | |
} | |
if (!MMOK(waveOutUnprepareHeader(data.hwo, &hdr, sizeof(hdr)))) { | |
return 1; | |
} | |
// call user function to fill with new data | |
if (data.info.waveRender) { | |
data.info.waveRender(hdr.lpData, hdr.dwBufferLength, data.info.user); | |
} | |
if (!MMOK(waveOutPrepareHeader(data.hwo, &hdr, sizeof(hdr)))) { | |
return 1; | |
} | |
if (!MMOK(waveOutWrite(data.hwo, &hdr, sizeof(WAVEHDR)))) { | |
return 1; | |
} | |
} | |
} | |
return 0; | |
} | |
bool waveOpen(WaveData& wave, const WaveInfo& info) | |
{ | |
memset(&wave, 0, sizeof(wave)); | |
InterlockedExchange(&wave.alive, 1); | |
// copy wave info structure to internal data for reference | |
wave.info = info; | |
// create waitable wave event | |
wave.waveEvent = CreateEventA(NULL, FALSE, FALSE, NULL); | |
if (wave.waveEvent == NULL) { | |
return false; | |
} | |
// prepare output wave format | |
WAVEFORMATEX waveformat; | |
memset(&waveformat, 0, sizeof(waveformat)); | |
waveformat.cbSize = 0; | |
waveformat.wFormatTag = WAVE_FORMAT_PCM; | |
waveformat.nChannels = info.channels; | |
waveformat.nSamplesPerSec = info.sampleRate; | |
waveformat.wBitsPerSample = info.bitDepth; | |
waveformat.nBlockAlign = (info.channels * waveformat.wBitsPerSample) / 8; | |
waveformat.nAvgBytesPerSec = info.sampleRate * waveformat.nBlockAlign; | |
// create wave output | |
memset(&wave.hwo, 0, sizeof(wave.hwo)); | |
if (!MMOK(waveOutOpen( | |
&wave.hwo, | |
WAVE_MAPPER, | |
&waveformat, | |
(DWORD_PTR)wave.waveEvent, | |
NULL, | |
CALLBACK_EVENT))) { | |
return false; | |
} | |
// create the wave thread | |
wave.waveThread = CreateThread( | |
NULL, 0, waveThreadProc, &wave, CREATE_SUSPENDED, 0); | |
if (wave.waveThread == NULL) { | |
return false; | |
} | |
// prepare waveout for playback | |
return wavePrepare(wave); | |
} | |
bool waveStart(WaveData& wave) | |
{ | |
ResumeThread(wave.waveThread); | |
return true; | |
} | |
// ---- ---- ---- ---- ---- ---- ---- ----- ---- ---- ---- ---- ---- ---- ---- | |
// TEST CODE | |
// ---- ---- ---- ---- ---- ---- ---- ----- ---- ---- ---- ---- ---- ---- ---- | |
float rate = ((2.f * 3.14f) / float(44100)) * 1000.f; | |
float wval = 0.f; | |
void testWaveRender(void* bytes, size_t size, void* user) | |
{ | |
int16_t* ptr = (int16_t*)bytes; | |
const int16_t* end = (int16_t*)((uint8_t*)ptr + size); | |
for (; ptr < end; ptr += 2) { | |
const int16_t v = int16_t(sinf(wval) * float(0x1fff)); | |
ptr[0] = v; | |
ptr[1] = v; | |
wval += rate; | |
} | |
} | |
int main() | |
{ | |
const WaveInfo info = { | |
44100, 16, 2, 1024, testWaveRender, NULL | |
}; | |
// open wave playback | |
WaveData wavedata; | |
if (!waveOpen(wavedata, info)) { | |
return 1; | |
} | |
// start the wave polling loop | |
if (!waveStart(wavedata)) { | |
return 1; | |
} | |
for (;;) { | |
getchar(); | |
waveClose(wavedata); | |
break; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment