diff --git a/OneLoneCoder_PGE_SoundTest.cpp b/OneLoneCoder_PGE_SoundTest.cpp new file mode 100644 index 0000000..106b94d --- /dev/null +++ b/OneLoneCoder_PGE_SoundTest.cpp @@ -0,0 +1,269 @@ +/* + Simple example code for olcPGEX_Sound.h - Mind your speakers! + + You will need SampleA.wav, SampleB.wav and SampleC.wav for this demo. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#include "olcPGEX_Sound.h" + +#include + +class SoundTest : public olc::PixelGameEngine +{ +public: + SoundTest() + { + sAppName = "Sound Test"; + } + +private: + int sndSampleA; + int sndSampleB; + int sndSampleC; + bool bToggle = false; + static bool bSynthPlaying; + static float fSynthFrequency; + static float fFilterVolume; + + const olc::Key keys[12] = { olc::Key::Z, olc::Key::S, olc::Key::X, olc::Key::D, olc::Key::C, + olc::Key::V, olc::Key::G, olc::Key::B, olc::Key::H, olc::Key::N, olc::Key::J, olc::Key::M}; + + static float fPreviousSamples[128]; + static int nSamplePos; + + +private: + + // This is an optional function that allows the user to generate or synthesize sounds + // in a custom way, it is fed into the output mixer bu the extension + static float MyCustomSynthFunction(int nChannel, float fGlobalTime, float fTimeStep) + { + // Just generate a sine wave of the appropriate frequency + if (bSynthPlaying) + return sin(fSynthFrequency * 2.0f * 3.14159f * fGlobalTime); + else + return 0.0f; + } + + // This is an optional function that allows the user to filter the output from + // the internal mixer of the extension. Here you could add effects or just + // control volume. I also like to use it to extract information about + // the currently playing output waveform + static float MyCustomFilterFunction(int nChannel, float fGlobalTime, float fSample) + { + // Fundamentally just control volume + float fOutput = fSample * fFilterVolume; + + // But also add sample to list of previous samples for visualisation + fPreviousSamples[nSamplePos] = fOutput; + nSamplePos++; + nSamplePos %= 128; + + return fOutput; + } + + + bool OnUserCreate() + { + olc::SOUND::InitialiseAudio(); + sndSampleA = olc::SOUND::LoadAudioSample("SampleA.wav"); + sndSampleB = olc::SOUND::LoadAudioSample("SampleB.wav"); + sndSampleC = olc::SOUND::LoadAudioSample("SampleC.wav"); + + // Give the sound engine a hook to a custom generation function + olc::SOUND::SetUserSynthFunction(MyCustomSynthFunction); + + // Give the sound engine a hook to a custom filtering function + olc::SOUND::SetUserFilterFunction(MyCustomFilterFunction); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) + { + //olc::SOUND::PlaySample(sndTest); + + auto PointInRect = [&](int x, int y, int rx, int ry, int rw, int rh) + { + return x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh); + }; + + int nMouseX = GetMouseX(); + int nMouseY = GetMouseY(); + + if(GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 16, 128, 24)) + olc::SOUND::PlaySample(sndSampleA); // Plays the sample once + + if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 48, 128, 24)) + olc::SOUND::PlaySample(sndSampleB); + + if (GetMouse(0).bPressed && PointInRect(nMouseX, nMouseY, 16, 80, 128, 24)) + { + bToggle = !bToggle; + if (bToggle) + { + olc::SOUND::PlaySample(sndSampleC, true); // Plays the sample in looping mode + } + else + { + olc::SOUND::StopSample(sndSampleC); + } + } + + if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 16, 90, 24)) + fFilterVolume += 2.0f * fElapsedTime; + + if (GetMouse(0).bHeld && PointInRect(nMouseX, nMouseY, 160, 48, 90, 24)) + fFilterVolume -= 2.0f * fElapsedTime; + + if (fFilterVolume < 0.0f) fFilterVolume = 0.0f; + if (fFilterVolume > 1.0f) fFilterVolume = 1.0f; + + // Detect keyboard - very simple synthesizer + if (IsFocused()) + { + bool bKeyIsPressed = false; + float fFrequency = 0.0f; + for (int i = 0; i < 12; i++) + { + if (GetKey(keys[i]).bHeld) + { + bKeyIsPressed = true; + float fOctaveBaseFrequency = 220.0f; + float f12thRootOf2 = pow(2.0f, 1.0f / 12.0f); + fFrequency = fOctaveBaseFrequency * powf(f12thRootOf2, (float)i); + } + } + + fSynthFrequency = fFrequency; + bSynthPlaying = bKeyIsPressed; + } + + + // Draw Buttons + Clear(olc::BLUE); + + DrawRect(16, 16, 128, 24); + DrawString(20, 20, "Play Sample A"); + + DrawRect(16, 48, 128, 24); + DrawString(20, 52, "Play Sample B"); + + DrawRect(16, 80, 128, 24); + DrawString(20, 84, (bToggle ? "Stop Sample C" : "Loop Sample C")); + + + DrawRect(160, 16, 90, 24); + DrawString(164, 20, "Volume +"); + + DrawRect(160, 48, 90, 24); + DrawString(164, 52, "Volume -"); + + + DrawString(164, 80, "Volume: " + std::to_string((int)(fFilterVolume * 10.0f))); + + // Draw Keyboard + + // White Keys + for (int i = 0; i < 7; i++) + { + FillRect(i * 16 + 8, 160, 16, 64); + DrawRect(i * 16 + 8, 160, 16, 64, olc::BLACK); + DrawString(i * 16 + 12, 212, std::string(1, "ZXCVBNM"[i]), olc::BLACK); + } + + // Black Keys + for (int i = 0; i < 6; i++) + { + if (i != 2) + { + FillRect(i * 16 + 18, 160, 12, 32, olc::BLACK); + DrawString(i * 16 + 20, 180, std::string(1, "SDFGHJ"[i]), olc::WHITE); + } + } + + // Draw visualisation + int nStartPos = (nSamplePos + 127) % 128; + + for (int i = 127; i >= 0; i--) + { + float fSample = fPreviousSamples[(nSamplePos + i) % 128]; + DrawLine(124 + i, 210, 124 + i, 210 + (int)(fSample * 20.0f), olc::RED); + } + + + return true; + } + + + // Note we must shut down the sound system too!! + bool OnUserDestroy() + { + olc::SOUND::DestroyAudio(); + return true; + } +}; + +bool SoundTest::bSynthPlaying = false; +float SoundTest::fSynthFrequency = 0.0f; +float SoundTest::fFilterVolume = 1.0f; +int SoundTest::nSamplePos = 0; +float SoundTest::fPreviousSamples[128]; + +int main() +{ + SoundTest demo; + if(demo.Construct(256, 240, 4, 4)) + demo.Start(); + + return 0; +} \ No newline at end of file diff --git a/SampleA.wav b/SampleA.wav new file mode 100644 index 0000000..9a53e04 Binary files /dev/null and b/SampleA.wav differ diff --git a/SampleB.wav b/SampleB.wav new file mode 100644 index 0000000..aac72c7 Binary files /dev/null and b/SampleB.wav differ diff --git a/SampleC.wav b/SampleC.wav new file mode 100644 index 0000000..111a57b Binary files /dev/null and b/SampleC.wav differ diff --git a/olcPGEX_Sound.h b/olcPGEX_Sound.h new file mode 100644 index 0000000..059696d --- /dev/null +++ b/olcPGEX_Sound.h @@ -0,0 +1,513 @@ +/* + olcPGEX_Sound.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Sound - v0.2 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + sound generation and wave playing routines. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018 +*/ + + +#ifndef OLC_PGEX_SOUND +#define OLC_PGEX_SOUND + +#include + +#include +#undef min +#undef max + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class SOUND : public olc::PGEX + { + // A representation of an affine transform, used to rotate, scale, offset & shear space + public: + class AudioSample + { + public: + AudioSample(); + AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr); + + public: + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + bool bFlagForStop = false; + }; + + static std::list listActiveSamples; + + public: + static bool InitialiseAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); + static bool DestroyAudio(); + static void SetUserSynthFunction(std::function func); + static void SetUserFilterFunction(std::function func); + + public: + static unsigned int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); + static void PlaySample(int id, bool bLoop = false); + static void StopSample(int id); + static void StopAll(); + static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); + +#ifdef WIN32 + private: + static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); + static void AudioThread(); + static unsigned int m_nSampleRate; + static unsigned int m_nChannels; + static unsigned int m_nBlockCount; + static unsigned int m_nBlockSamples; + static unsigned int m_nBlockCurrent; + static short* m_pBlockMemory; + static WAVEHDR *m_pWaveHeaders; + static HWAVEOUT m_hwDevice; + static std::thread m_AudioThread; + static std::atomic m_bAudioThreadActive; + static std::atomic m_nBlockFree; + static std::condition_variable m_cvBlockNotZero; + static std::mutex m_muxBlockNotZero; + static std::atomic m_fGlobalTime; + static std::function funcUserSynth; + static std::function funcUserFilter; +#endif + + }; +} + + +#ifdef WIN32 +#pragma comment(lib, "winmm.lib") +namespace olc +{ + SOUND::AudioSample::AudioSample() + { + + + + } + + SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack) + { + LoadFromFile(sWavFile, pack); + } + + olc::rcode SOUND::AudioSample::LoadFromFile(std::string sWavFile, olc::ResourcePack *pack) + { + auto ReadWave = [&](std::istream &is) + { + char dump[4]; + is.read(dump, sizeof(char) * 4); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return olc::FAIL; + is.read(dump, sizeof(char) * 4); // Not Interested + is.read(dump, sizeof(char) * 4); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return olc::FAIL; + + // Read Wave description chunk + is.read(dump, sizeof(char) * 4); // Read "fmt " + unsigned int nHeaderSize = 0; + is.read((char*)&nHeaderSize, sizeof(unsigned int)); // Not Interested + is.read((char*)&wavHeader, nHeaderSize);// sizeof(WAVEFORMATEX)); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcPGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + return olc::FAIL; + + // Search for audio data chunk + long nChunksize = 0; + is.read(dump, sizeof(char) * 4); // Read chunk header + is.read((char*)&nChunksize, sizeof(long)); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + //std::fseek(f, nChunksize, SEEK_CUR); + is.seekg(nChunksize, std::istream::cur); + is.read(dump, sizeof(char) * 4); + is.read((char*)&nChunksize, sizeof(long)); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + if (!is.eof()) + { + is.read((char*)&s, sizeof(short)); + + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + } + + // All done, flag sound as valid + bSampleValid = true; + return olc::OK; + }; + + if (pack != nullptr) + { + std::istream is(&(pack->GetStreamBuffer(sWavFile))); + return ReadWave(is); + } + else + { + // Read from file + std::ifstream ifs(sWavFile, std::ifstream::binary); + if (ifs.is_open()) + { + return ReadWave(ifs); + } + else + return olc::FAIL; + } + } + + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + listActiveSamples.clear(); + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)SOUND::waveOutProc, (DWORD_PTR)0, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + m_AudioThread.join(); + return false; + } + + // Handler for soundcard request for more data + void CALLBACK SOUND::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) + { + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + } + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } + } + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + + void SOUND::SetUserSynthFunction(std::function func) + { + funcUserSynth = func; + } + + void SOUND::SetUserFilterFunction(std::function func) + { + funcUserFilter = func; + } + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) + { + + olc::SOUND::AudioSample a(sWavFile, pack); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void SOUND::PlaySample(int id, bool bLoop) + { + olc::SOUND::sCurrentlyPlayingSample a; + a.nAudioSampleID = id; + a.nSamplePosition = 0; + a.bFinished = false; + a.bFlagForStop = false; + a.bLoop = bLoop; + SOUND::listActiveSamples.push_back(a); + } + + void SOUND::StopSample(int id) + { + // Find first occurence of sample id + auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); + if(s != listActiveSamples.end()) + s->bFlagForStop = true; + } + + void SOUND::StopAll() + { + for (auto &s : listActiveSamples) + { + s.bFlagForStop = true; + } + } + + float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + if (m_bAudioThreadActive) + { + if (s.bFlagForStop) + { + s.bLoop = false; + s.bFinished = true; + } + else + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + { + if (s.bLoop) + { + s.nSamplePosition = 0; + } + else + s.bFinished = true; // Else sound has completed + } + } + } + else + return 0.0f; + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + if(funcUserSynth != nullptr) + fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + if (funcUserFilter != nullptr) + return funcUserFilter(nChannel, fGlobalTime, fMixerSample); + else + return fMixerSample; + } + + + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockCount = 0; + unsigned int SOUND::m_nBlockSamples = 0; + unsigned int SOUND::m_nBlockCurrent = 0; + short* SOUND::m_pBlockMemory = nullptr; + WAVEHDR *SOUND::m_pWaveHeaders = nullptr; + HWAVEOUT SOUND::m_hwDevice; + std::thread SOUND::m_AudioThread; + std::atomic SOUND::m_bAudioThreadActive = false; + std::atomic SOUND::m_nBlockFree = 0; + std::condition_variable SOUND::m_cvBlockNotZero; + std::mutex SOUND::m_muxBlockNotZero; + std::atomic SOUND::m_fGlobalTime = 0.0f; + std::list SOUND::listActiveSamples; + std::function SOUND::funcUserSynth = nullptr; + std::function SOUND::funcUserFilter = nullptr; +} +#endif + +// Currently no Linux implementation so just go blank :( + +#endif \ No newline at end of file