From c4bed5aebbcabd19c726f969fae1d07c9e9679ca Mon Sep 17 00:00:00 2001 From: ferris Date: Fri, 17 May 2013 23:52:27 +0200 Subject: [PATCH] Audio works --- Chip8/Apu.cpp | 23 ++++++ Chip8/Apu.h | 5 ++ DirectSoundAudioDriver.cpp | 165 +++++++++++++++++++++++++++++++++++-- DirectSoundAudioDriver.h | 26 +++++- IAudioDriver.h | 5 ++ Main.cpp | 17 ++-- 6 files changed, 229 insertions(+), 12 deletions(-) diff --git a/Chip8/Apu.cpp b/Chip8/Apu.cpp index d91d9b8..37409ba 100644 --- a/Chip8/Apu.cpp +++ b/Chip8/Apu.cpp @@ -2,7 +2,10 @@ Apu::Apu() { + audioDriver = nullptr; mutex = Mutex::Create(); + beeping = false; + beepPhase = 0.0; } Apu::~Apu() @@ -12,6 +15,10 @@ Apu::~Apu() void Apu::SetAudioDriver(IAudioDriver *audioDriver) { + auto oldAudioDriver = this->audioDriver; + if (oldAudioDriver) oldAudioDriver->SetRenderCallback(nullptr, nullptr); + this->audioDriver = audioDriver; + audioDriver->SetRenderCallback(renderCallbackRouter, this); } void Apu::SetBeeping(bool beeping) @@ -20,3 +27,19 @@ void Apu::SetBeeping(bool beeping) this->beeping = beeping; mutex->Unlock(); } + +void Apu::renderCallbackRouter(float *leftBuffer, float *rightBuffer, int numSamples, void *userData) +{ + if (userData) ((Apu *)userData)->renderCallback(leftBuffer, rightBuffer, numSamples); +} + +void Apu::renderCallback(float *leftBuffer, float *rightBuffer, int numSamples) +{ + mutex->Lock(); + for (int i = 0; i < numSamples; i++) + { + leftBuffer[i] = rightBuffer[i] = beeping ? (sin(beepPhase) > 0.0 ? 1.0f : -1.0f) : 0.0f; + beepPhase += .04; + } + mutex->Unlock(); +} diff --git a/Chip8/Apu.h b/Chip8/Apu.h index 47fe503..8a769f4 100644 --- a/Chip8/Apu.h +++ b/Chip8/Apu.h @@ -16,9 +16,14 @@ class Apu void SetBeeping(bool beeping); private: + static void renderCallbackRouter(float *leftBuffer, float *rightBuffer, int numSamples, void *userData); + + void renderCallback(float *leftBuffer, float *rightBuffer, int numSamples); + IAudioDriver *audioDriver; Mutex *mutex; bool beeping; + double beepPhase; }; #endif diff --git a/DirectSoundAudioDriver.cpp b/DirectSoundAudioDriver.cpp index 64920d7..98ee879 100644 --- a/DirectSoundAudioDriver.cpp +++ b/DirectSoundAudioDriver.cpp @@ -1,27 +1,182 @@ #include "DirectSoundAudioDriver.h" -DirectSoundAudioDriver::DirectSoundAudioDriver(RenderCallback renderCallback, int latencyMs, int sampleRate) +DirectSoundAudioDriver::DirectSoundAudioDriver(int latencyMs, int sampleRate, RenderCallback renderCallback, void *userData) { - if (!renderCallback) throw FSL_EXCEPTION("Render callback cannot be null"); - this->renderCallback = renderCallback; this->latencyMs = latencyMs; this->sampleRate = sampleRate; + enabled = true; + this->renderCallback = renderCallback; + this->userData = userData; + + DirectSoundCreate8(0, &device, 0); + device->SetCooperativeLevel(GetForegroundWindow(), DSSCL_NORMAL); + + createBuffers(); + + mutex = Mutex::Create(); + mutex->Lock(); + thread = CreateThread(0, 0, threadProc, (LPVOID)this, 0, 0); + SetThreadPriority(thread, THREAD_PRIORITY_HIGHEST); + mutex->Unlock(); } DirectSoundAudioDriver::~DirectSoundAudioDriver() { + mutex->Lock(); + TerminateThread(thread, 0); + mutex->Unlock(); + delete mutex; + + destroyBuffers(); + + device->Release(); +} + +void DirectSoundAudioDriver::SetRenderCallback(RenderCallback renderCallback, void *userData) +{ + mutex->Lock(); + this->renderCallback = renderCallback; + this->userData = userData; + mutex->Unlock(); +} + +void DirectSoundAudioDriver::SetEnabled(bool enabled) +{ + mutex->Lock(); + this->enabled = enabled; + mutex->Unlock(); +} + +bool DirectSoundAudioDriver::GetEnabled() const +{ + mutex->Lock(); + auto ret = enabled; + mutex->Unlock(); + return ret; } void DirectSoundAudioDriver::SetLatencyMs(int latencyMs) { + mutex->Lock(); + this->latencyMs = latencyMs; + recreateBuffers(); + mutex->Unlock(); } int DirectSoundAudioDriver::GetLatencyMs() const { - return latencyMs; + mutex->Lock(); + auto ret = latencyMs; + mutex->Unlock(); + return ret; } int DirectSoundAudioDriver::GetSampleRate() const { - return sampleRate; + mutex->Lock(); + auto ret = sampleRate; + mutex->Unlock(); + return ret; +} + +DWORD WINAPI DirectSoundAudioDriver::threadProc(LPVOID lpParameter) +{ + auto driver = (DirectSoundAudioDriver *)lpParameter; + + while (true) + { + driver->mutex->Lock(); + + int playCursorPos; + driver->buffer->GetCurrentPosition((LPDWORD)&playCursorPos, 0); + int bytesToRender = playCursorPos - driver->oldPlayCursorPos; + if (bytesToRender) + { + if (bytesToRender < 0) bytesToRender += driver->bufferSizeBytes; + short *p1, *p2; + int b1, b2; + driver->buffer->Lock(driver->oldPlayCursorPos, bytesToRender, (LPVOID *)&p1, (LPDWORD)&b1, (LPVOID *)&p2, (LPDWORD)&b2, 0); + driver->renderSamples(p1, b1 / sizeof(short)); + if (b2) driver->renderSamples(p2, b2 / sizeof(short)); + driver->buffer->Unlock(p1, b1, p2, b2); + driver->oldPlayCursorPos = playCursorPos; + } + + driver->mutex->Unlock(); + + Sleep(3); + } +} + +void DirectSoundAudioDriver::createBuffers() +{ + int channels = 2; + int bitsPerSample = 16; + int bytesPerSample = bitsPerSample / 8; + int blockAlign = channels * bytesPerSample; + int bytesPerSec = sampleRate * blockAlign; + + int floatBuffersSize = sampleRate * channels * latencyMs / 1000; + leftBuffer = new float[floatBuffersSize]; + rightBuffer = new float[floatBuffersSize]; + + bufferSizeBytes = floatBuffersSize * bytesPerSample; + WAVEFORMATEX bufferFormat = + { + WAVE_FORMAT_PCM, + channels, + sampleRate, + bytesPerSec, + blockAlign, + bitsPerSample, + 0 + }; + DSBUFFERDESC bufferDesc = + { + sizeof(DSBUFFERDESC), + DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2, + bufferSizeBytes, + 0, + &bufferFormat, + GUID_NULL + }; + device->CreateSoundBuffer(&bufferDesc, &buffer, 0); + + oldPlayCursorPos = 0; + buffer->Play(0, 0, DSBPLAY_LOOPING); +} + +void DirectSoundAudioDriver::destroyBuffers() +{ + delete leftBuffer; + delete rightBuffer; + + buffer->Stop(); + buffer->Release(); +} + +void DirectSoundAudioDriver::recreateBuffers() +{ + destroyBuffers(); + createBuffers(); +} + +void DirectSoundAudioDriver::renderSamples(short *buffer, int numSamples) +{ + if (renderCallback && enabled) + { + int numFloatSamples = numSamples / 2; + renderCallback(leftBuffer, rightBuffer, numFloatSamples, userData); + for (int i = 0; i < numSamples; i++) + { + int sample = (int)((i & 1 ? rightBuffer : leftBuffer)[i / 2] * 32767.0f); + if (sample < -32768) sample = -32768; + if (sample > 32767) sample = 32767; + buffer[i] = (short)sample; + } + } + else + { + memset(buffer, 0, sizeof(short) * numSamples); + } } diff --git a/DirectSoundAudioDriver.h b/DirectSoundAudioDriver.h index ddbd4c5..996bab8 100644 --- a/DirectSoundAudioDriver.h +++ b/DirectSoundAudioDriver.h @@ -10,19 +10,41 @@ class DirectSoundAudioDriver : public IAudioDriver { public: - DirectSoundAudioDriver(RenderCallback renderCallback, int latencyMs = 100, int sampleRate = 44100); + DirectSoundAudioDriver(int latencyMs = 60, int sampleRate = 44100, RenderCallback renderCallback = nullptr, void *userData = nullptr); virtual ~DirectSoundAudioDriver(); + virtual void SetRenderCallback(RenderCallback renderCallback, void *userData); + + virtual void SetEnabled(bool enabled); + virtual bool GetEnabled() const; + virtual void SetLatencyMs(int latencyMs); virtual int GetLatencyMs() const; virtual int GetSampleRate() const; private: - RenderCallback renderCallback; + static DWORD WINAPI threadProc(LPVOID lpParameter); + + void createBuffers(); + void destroyBuffers(); + void recreateBuffers(); + + void renderSamples(short *buffer, int numSamples); + int latencyMs; int sampleRate; + bool enabled; + RenderCallback renderCallback; + void *userData; + + LPDIRECTSOUND8 device; + int bufferSizeBytes; + float *leftBuffer, *rightBuffer; + LPDIRECTSOUNDBUFFER buffer; + int oldPlayCursorPos; + Mutex *mutex; HANDLE thread; }; diff --git a/IAudioDriver.h b/IAudioDriver.h index 44e1575..709cfa6 100644 --- a/IAudioDriver.h +++ b/IAudioDriver.h @@ -8,6 +8,11 @@ class IAudioDriver virtual ~IAudioDriver(); + virtual void SetRenderCallback(RenderCallback renderCallback, void *userData) = 0; + + virtual void SetEnabled(bool enabled) = 0; + virtual bool GetEnabled() const = 0; + virtual void SetLatencyMs(int latencyMs) = 0; virtual int GetLatencyMs() const = 0; diff --git a/Main.cpp b/Main.cpp index 45a6461..1311151 100644 --- a/Main.cpp +++ b/Main.cpp @@ -1,6 +1,7 @@ #include "Common.h" #include "Chip8/Chip8.h" #include "GLVideoDriver.h" +#include "DirectSoundAudioDriver.h" int Main(const List& arguments) { @@ -57,20 +58,24 @@ int Main(const List& arguments) systemMenu->AddChild(systemReset); systemMenu->AddSeparator(); + auto audioDriver = new DirectSoundAudioDriver(); + chip8.SetAudioDriver(audioDriver); + auto systemAudioMenu = Menu::Create("Audio"); auto systemAudioEnabled = MenuItem::Create("Enabled"); - systemAudioEnabled->SetChecked(true); + systemAudioEnabled->SetChecked(audioDriver->GetEnabled()); systemAudioEnabled->SetToggleEnabled(true); + systemAudioEnabled->CheckedChanged += [&] { audioDriver->SetEnabled(systemAudioEnabled->GetChecked()); }; systemAudioMenu->AddChild(systemAudioEnabled); auto systemAudioLatencyMenu = Menu::Create("Latency"); - const int numLatencies = 5; - const int latencies[] = { 50, 100, 250, 500, 1000 }; - int latencyIndex = 0; + const int numLatencies = 8; + const int latencies[] = { 20, 40, 60, 80, 100, 200, 500, 1000 }; + int latencyIndex = 2; List systemAudioLatencyItems; auto reflectLatencyIndex = [&] { auto latency = latencies[latencyIndex]; - // TODO: make functional + audioDriver->SetLatencyMs(latency); for (int i = 0; i < systemAudioLatencyItems.Count(); i++) systemAudioLatencyItems[i]->SetChecked(i == latencyIndex); }; for (int i = 0; i < numLatencies; i++) @@ -189,8 +194,10 @@ int Main(const List& arguments) delete helpMenu; delete helpAbout; + delete viewport; delete videoDriver; + delete audioDriver; } catch (const Exception& e) {