Skip to content

Instantly share code, notes, and snippets.

@bit-hack
Created July 27, 2017 09:41
Show Gist options
  • Save bit-hack/1081292b55c1a414f0c2c5451ed92395 to your computer and use it in GitHub Desktop.
Save bit-hack/1081292b55c1a414f0c2c5451ed92395 to your computer and use it in GitHub Desktop.
Threaded winmm audio playback
#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