/* +-------------------------------------------------------------+ | OneLoneCoder Sound Wave Engine v0.02 | | "You wanted noise? Well is this loud enough?" - javidx9 | +-------------------------------------------------------------+ What is this? ~~~~~~~~~~~~~ olc::SoundWaveEngine is a single file, cross platform audio interface for lightweight applications that just need a bit of easy audio manipulation. It's origins started in the olcNoiseMaker file that accompanied javidx9's "Code-It-Yourself: Synthesizer" series. It was refactored and absorbed into the "olcConsoleGameEngine.h" file, and then refactored again into olcPGEX_Sound.h, that was an extension to the awesome "olcPixelGameEngine.h" file. Alas, it went underused and began to rot, with many myths circulating that "it doesnt work" and "it shouldn't be used". These untruths made javidx9 feel sorry for the poor file, and he decided to breathe some new life into it, in anticipation of new videos! License (OLC-3) ~~~~~~~~~~~~~~~ Copyright 2018 - 2022 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 conditionsand 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 Thanks ~~~~~~ Gorbit99, Dragoneye, Puol Authors ~~~~~~~ slavka, MaGetzUb, cstd, Moros1138 & javidx9 (c)OneLoneCoder 2019, 2020, 2021, 2022 */ /* Using & Installing On Microsoft Windows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Microsoft Visual Studio ~~~~~~~~~~~~~~~~~~~~~~~ 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. 2) That's it! Code::Blocks ~~~~~~~~~~~~ 1) Make sure your compiler toolchain is NOT the default one installed with Code::Blocks. That one is old, out of date, and a general mess. Instead, use MSYS2 to install a recent and decent GCC toolchain, then configure Code::Blocks to use it Guide for installing recent GCC for Windows: https://www.msys2.org/ Guide for configuring code::blocks: https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ 2) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. 3) Add these libraries to "Linker Options": user32 winmm 4) Set this "Compiler Option": -std=c++17 */ /* Using & Installing On Linux ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GNU Compiler Collection (GCC) ~~~~~~~~~~~~~~~~~~~~~~~ 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. 2) Build with the following command: g++ olcSoundWaveEngineExample.cpp -o olcSoundWaveEngineExample -lpulse -lpulse-simple -std=c++17 3) That's it! */ /* Using in multiple-file projects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you intend to use olcSoundWaveEngine across multiple files, it's important to only have one instance of the implementation. This is done using the compiler preprocessor definition: OLC_SOUNDWAVE This is defined typically before the header is included in teh translation unit you wish the implementation to be associated with. To avoid things getting messy I recommend you create a file "olcSoundWaveEngine.cpp" and that file includes ONLY the following code: #define OLC_SOUNDWAVE #include "olcSoundWaveEngine.h" */ /* 0.01: olcPGEX_Sound.h reworked +Changed timekeeping to double, added accuracy fix - Thanks scripticuk +Concept of audio drivers and interface +All internal timing now double precision +All internal sampling now single precsion +Loading form WAV files +LERPed sampling from all buffers +Multi-channel audio support 0.02: +Support multi-channel wave files +Support for 24-bit wave files +Wave files are now sample rate invariant +Linux PulseAudio Updated +Linux ALSA Updated +WinMM Updated +CMake Compatibility =Fix wave format durations preventing playback =Various bug fixes SIG Modifications: - Add pause flag and pause timing flags to handle pausing sound playback. */ #pragma once #ifndef OLC_SOUNDWAVE_H #define OLC_SOUNDWAVE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Compiler/System Sensitivity #if !defined(SOUNDWAVE_USING_WINMM) && !defined(SOUNDWAVE_USING_WASAPI) && \ !defined(SOUNDWAVE_USING_XAUDIO) && !defined(SOUNDWAVE_USING_OPENAL) && \ !defined(SOUNDWAVE_USING_ALSA) && !defined(SOUNDWAVE_USING_SDLMIXER) && \ !defined(SOUNDWAVE_USING_PULSE) \ #if defined(_WIN32) #define SOUNDWAVE_USING_WINMM #endif #if defined(__linux__) #define SOUNDWAVE_USING_PULSE #endif #if defined(__APPLE__) #define SOUNDWAVE_USING_SDLMIXER #endif #if defined(__EMSCRIPTEN__) #define SOUNDWAVE_USING_SDLMIXER #endif #endif namespace olc::sound { namespace wave { // Physically represents a .WAV file, but the data is stored // as normalised floating point values template class File { public: File() = default; File(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples) { m_nChannels = nChannels; m_nSampleSize = nSampleSize; m_nSamples = nSamples; m_nSampleRate = nSampleRate; m_dDuration = double(m_nSamples) / double(m_nSampleRate); m_dDurationInSamples = double(m_nSamples); m_pRawData = std::make_unique(m_nSamples * m_nChannels); } public: T* data() const { return m_pRawData.get(); } size_t samples() const { return m_nSamples; } size_t channels() const { return m_nChannels; } size_t samplesize() const { return m_nSampleSize; } size_t samplerate() const { return m_nSampleRate; } double duration() const { return m_dDuration; } double durationInSamples() const { return m_dDurationInSamples; } bool LoadFile(const std::string& sFilename) { std::ifstream ifs(sFilename, std::ios::binary); if (!ifs.is_open()) return false; struct WaveFormatHeader { uint16_t wFormatTag; /* format type */ uint16_t nChannels; /* number of channels (i.e. mono, stereo...) */ uint32_t nSamplesPerSec; /* sample rate */ uint32_t nAvgBytesPerSec; /* for buffer estimation */ uint16_t nBlockAlign; /* block size of data */ uint16_t wBitsPerSample; /* number of bits per sample of mono data */ }; WaveFormatHeader header{ 0 }; m_pRawData.reset(); char dump[4]; ifs.read(dump, sizeof(uint8_t) * 4); // Read "RIFF" if (strncmp(dump, "RIFF", 4) != 0) return false; ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested ifs.read(dump, sizeof(uint8_t) * 4); // Read "WAVE" if (strncmp(dump, "WAVE", 4) != 0) return false; // Read Wave description chunk ifs.read(dump, sizeof(uint8_t) * 4); // Read "fmt " ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested ifs.read((char*)&header, sizeof(WaveFormatHeader)); // Read Wave Format Structure chunk // Search for audio data chunk int32_t nChunksize = 0; ifs.read(dump, sizeof(uint8_t) * 4); // Read chunk header ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size while (strncmp(dump, "data", 4) != 0) { // Not audio data, so just skip it ifs.seekg(nChunksize, std::ios::cur); ifs.read(dump, sizeof(uint8_t) * 4); // Read next chunk header ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read next chunk size } // Finally got to data, so read it all in and convert to float samples m_nSampleSize = header.wBitsPerSample >> 3; m_nSamples = nChunksize / (header.nChannels * m_nSampleSize); m_nChannels = header.nChannels; m_nSampleRate = header.nSamplesPerSec; m_pRawData = std::make_unique(m_nSamples * m_nChannels); m_dDuration = double(m_nSamples) / double(m_nSampleRate); m_dDurationInSamples = double(m_nSamples); T* pSample = m_pRawData.get(); // Read in audio data and normalise for (long i = 0; i < m_nSamples; i++) { for (int c = 0; c < m_nChannels; c++) { switch (m_nSampleSize) { case 1: { int8_t s = 0; ifs.read((char*)&s, sizeof(int8_t)); *pSample = T(s) / T(std::numeric_limits::max()); } break; case 2: { int16_t s = 0; ifs.read((char*)&s, sizeof(int16_t)); *pSample = T(s) / T(std::numeric_limits::max()); } break; case 3: // 24-bit { int32_t s = 0; ifs.read((char*)&s, 3); if (s & (1 << 23)) s |= 0xFF000000; *pSample = T(s) / T(std::pow(2, 23) - 1); } break; case 4: { int32_t s = 0; ifs.read((char*)&s, sizeof(int32_t)); *pSample = T(s) / T(std::numeric_limits::max()); } break; } pSample++; } } return true; } bool SaveFile(const std::string& sFilename) { return false; } protected: std::unique_ptr m_pRawData; size_t m_nSamples = 0; size_t m_nChannels = 0; size_t m_nSampleRate = 0; size_t m_nSampleSize = 0; double m_dDuration = 0.0; double m_dDurationInSamples = 0.0; }; template class View { public: View() = default; View(const T* pData, const size_t nSamples) { SetData(pData, nSamples); } public: void SetData(T const* pData, const size_t nSamples, const size_t nStride = 1, const size_t nOffset = 0) { m_pData = pData; m_nSamples = nSamples; m_nStride = nStride; m_nOffset = nOffset; } double GetSample(const double dSample) const { double d1 = std::floor(dSample); size_t p1 = static_cast(d1); size_t p2 = p1 + 1; double t = dSample - d1; double a = GetValue(p1); double b = GetValue(p2); return a + t * (b - a); // std::lerp in C++20 } std::pair GetRange(const double dSample1, const double dSample2) const { if (dSample1 < 0 || dSample2 < 0) return { 0,0 }; if (dSample1 > m_nSamples && dSample2 > m_nSamples) return { 0,0 }; double dMin, dMax; double d = GetSample(dSample1); dMin = dMax = d; size_t n1 = static_cast(std::ceil(dSample1)); size_t n2 = static_cast(std::floor(dSample2)); for (size_t n = n1; n < n2; n++) { d = GetValue(n); dMin = std::min(dMin, d); dMax = std::max(dMax, d); } d = GetSample(dSample2); dMin = std::min(dMin, d); dMax = std::max(dMax, d); return { dMin, dMax }; } T GetValue(const size_t nSample) const { if (nSample >= m_nSamples) return 0; else return m_pData[m_nOffset + nSample * m_nStride]; } private: const T* m_pData = nullptr; size_t m_nSamples = 0; size_t m_nStride = 1; size_t m_nOffset = 0; }; } template class Wave_generic { public: Wave_generic() = default; Wave_generic(std::string sWavFile) { LoadAudioWaveform(sWavFile); } Wave_generic(std::istream& sStream) { LoadAudioWaveform(sStream); } Wave_generic(const char* pData, const size_t nBytes) { LoadAudioWaveform(pData, nBytes); } Wave_generic(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples) { vChannelView.clear(); file = wave::File(nChannels, nSampleSize, nSampleRate, nSamples); vChannelView.resize(file.channels()); for (uint32_t c = 0; c < file.channels(); c++) vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c); } bool LoadAudioWaveform(std::string sWavFile) { vChannelView.clear(); if (file.LoadFile(sWavFile)) { // Setup views for each channel vChannelView.resize(file.channels()); for (uint32_t c = 0; c < file.channels(); c++) vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c); return true; } return false; } bool LoadAudioWaveform(std::istream& sStream) { return false; } bool LoadAudioWaveform(const char* pData, const size_t nBytes) { return false; } std::vector> vChannelView; wave::File file; }; typedef Wave_generic Wave; struct WaveInstance { Wave* pWave = nullptr; double dInstanceTime = 0.0; double dDuration = 0.0; double dSpeedModifier = 1.0; bool bFinished = false; bool bLoop = false; bool bFlagForStop = false; bool bPaused = false; double dPauseTime = 0.0; }; typedef std::list::iterator PlayingWave; namespace driver { class Base; } // Container class for Basic Sound Manipulation class WaveEngine { public: WaveEngine(); virtual ~WaveEngine(); // Configure Audio Hardware bool InitialiseAudio(uint32_t nSampleRate = 44100, uint32_t nChannels = 1, uint32_t nBlocks = 8, uint32_t nBlockSamples = 512); // Release Audio Hardware bool DestroyAudio(); // Call to get the names of all the devices capable of audio output - DACs. An entry // from the returned collection can be specified as the device to use in UseOutputDevice() std::vector GetOutputDevices(); // Specify a device for audio output prior to calling InitialiseAudio() void UseOutputDevice(const std::string& sDeviceOut); // Call to get the names of all the devices capable of audio input - ADCs. An entry // from the returned collection can be specified as the device to use in UseInputDevice() std::vector GetInputDevices(); // Specify a device for audio input prior to calling InitialiseAudio() void UseInputDevice(const std::string& sDeviceOut); void SetCallBack_NewSample(std::function func); void SetCallBack_SynthFunction(std::function func); void SetCallBack_FilterFunction(std::function func); public: void SetOutputVolume(const float fVolume); PlayingWave PlayWaveform(Wave* pWave, bool bLoop = false, double dSpeed = 1.0); void StopWaveform(const PlayingWave& w); void StopAll(); private: uint32_t FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples); private: std::unique_ptr m_driver; std::function m_funcNewSample; std::function m_funcUserSynth; std::function m_funcUserFilter; private: uint32_t m_nSampleRate = 44100; uint32_t m_nChannels = 1; uint32_t m_nBlocks = 8; uint32_t m_nBlockSamples = 512; double m_dSamplePerTime = 44100.0; double m_dTimePerSample = 1.0 / 44100; double m_dGlobalTime = 0.0; float m_fOutputVolume = 1.0; std::string m_sInputDevice; std::string m_sOutputDevice; private: std::list m_listWaves; public: uint32_t GetSampleRate() const; uint32_t GetChannels() const; uint32_t GetBlocks() const; uint32_t GetBlockSampleCount() const; double GetTimePerSample() const; // Friends, for access to FillOutputBuffer from Drivers friend class driver::Base; }; namespace driver { // DRIVER DEVELOPERS ONLY!!! // // This interface allows SoundWave to exchange data with OS audio systems. It // is not intended of use by regular users. class Base { public: Base(WaveEngine* pHost); virtual ~Base(); public: // [IMPLEMENT] Opens a connection to the hardware device, returns true if success virtual bool Open(const std::string& sOutputDevice, const std::string& sInputDevice); // [IMPLEMENT] Starts a process that repeatedly requests audio, returns true if success virtual bool Start(); // [IMPLEMENT] Stops a process form requesting audio virtual void Stop(); // [IMPLEMENT] Closes any connections to hardware devices virtual void Close(); virtual std::vector EnumerateOutputDevices(); virtual std::vector EnumerateInputDevices(); protected: // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System. Your // implementation will call this function providing a "DAC" buffer to be filled by // SoundWave from a buffer of floats filled by the user. void ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer); // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System. void GetFullOutputBlock(std::vector& vFloatBuffer); // Handle to SoundWave, to interrogate optons, and get user data WaveEngine* m_pHost = nullptr; }; } namespace synth { class Property { public: double value = 0.0f; public: Property() = default; Property(double f); public: Property& operator =(const double f); }; class Trigger { }; class Module { public: virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) = 0; }; class ModularSynth { public: ModularSynth(); public: bool AddModule(Module* pModule); bool RemoveModule(Module* pModule); bool AddPatch(Property* pInput, Property* pOutput); bool RemovePatch(Property* pInput, Property* pOutput); public: void UpdatePatches(); void Update(uint32_t nChannel, double dTime, double dTimeStep); protected: std::vector m_vModules; std::vector> m_vPatches; }; namespace modules { class Oscillator : public Module { public: enum class Type { Sine, Saw, Square, Triangle, PWM, Wave, Noise, }; public: // Primary frequency of oscillation Property frequency = 0.0f; // Primary amplitude of output Property amplitude = 1.0f; // LFO input if required Property lfo_input = 0.0f; // Primary Output Property output; // Tweakable Parameter Property parameter = 0.0; Type waveform = Type::Sine; Wave* pWave = nullptr; private: double phase_acc = 0.0f; double max_frequency = 20000.0; uint32_t random_seed = 0xB00B1E5; double rndDouble(double min, double max); uint32_t rnd(); public: virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) override; }; } } } #if defined(SOUNDWAVE_USING_WINMM) #define _WIN32_LEAN_AND_MEAN #include #undef min #undef max namespace olc::sound::driver { class WinMM : public Base { public: WinMM(WaveEngine* pHost); ~WinMM(); protected: bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override; bool Start() override; void Stop() override; void Close() override; private: void DriverLoop(); void FreeAudioBlock(); static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2); HWAVEOUT m_hwDevice = nullptr; std::thread m_thDriverLoop; std::atomic m_bDriverLoopActive{ false }; std::unique_ptr[]> m_pvBlockMemory; std::unique_ptr m_pWaveHeaders; std::atomic m_nBlockFree = 0; std::condition_variable m_cvBlockNotZero; std::mutex m_muxBlockNotZero; uint32_t m_nBlockCurrent = 0; }; } #endif // SOUNDWAVE_USING_WINMM #if defined(SOUNDWAVE_USING_SDLMIXER) #if defined(__EMSCRIPTEN__) #include #else #include #endif namespace olc::sound::driver { class SDLMixer final : public Base { public: explicit SDLMixer(WaveEngine* pHost); ~SDLMixer() final; protected: bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) final; bool Start() final; void Stop() final; void Close() final; private: void FillChunkBuffer(const std::vector& userData) const; static void SDLMixerCallback(int channel); private: bool m_keepRunning = false; Uint16 m_haveFormat = AUDIO_F32SYS; std::vector audioBuffer; Mix_Chunk audioChunk; static SDLMixer* instance; }; } #endif // SOUNDWAVE_USING_SDLMIXER #if defined(SOUNDWAVE_USING_ALSA) #include #include #include namespace olc::sound::driver { // Not thread-safe template class RingBuffer { public: RingBuffer() { } void Resize(unsigned int bufnum = 0, unsigned int buflen = 0) { m_vBuffers.resize(bufnum); for (auto& vBuffer : m_vBuffers) vBuffer.resize(buflen); } std::vector& GetFreeBuffer() { assert(!IsFull()); std::vector& result = m_vBuffers[m_nTail]; m_nTail = Next(m_nTail); return result; } std::vector& GetFullBuffer() { assert(!IsEmpty()); std::vector& result = m_vBuffers[m_nHead]; m_nHead = Next(m_nHead); return result; } bool IsEmpty() { return m_nHead == m_nTail; } bool IsFull() { return m_nHead == Next(m_nTail); } private: unsigned int Next(unsigned int current) { return (current + 1) % m_vBuffers.size(); } std::vector> m_vBuffers; unsigned int m_nHead = 0; unsigned int m_nTail = 0; }; class ALSA : public Base { public: ALSA(WaveEngine* pHost); ~ALSA(); protected: bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override; bool Start() override; void Stop() override; void Close() override; private: void DriverLoop(); snd_pcm_t* m_pPCM; RingBuffer m_rBuffers; std::atomic m_bDriverLoopActive{ false }; std::thread m_thDriverLoop; }; } #endif // SOUNDWAVE_USING_ALSA #if defined(SOUNDWAVE_USING_PULSE) #include namespace olc::sound::driver { class PulseAudio : public Base { public: PulseAudio(WaveEngine* pHost); ~PulseAudio(); protected: bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override; bool Start() override; void Stop() override; void Close() override; private: void DriverLoop(); pa_simple* m_pPA; std::atomic m_bDriverLoopActive{ false }; std::thread m_thDriverLoop; }; } #endif // SOUNDWAVE_USING_PULSE #ifdef OLC_SOUNDWAVE #undef OLC_SOUNDWAVE namespace olc::sound { WaveEngine::WaveEngine() { m_sInputDevice = "NONE"; m_sOutputDevice = "DEFAULT"; #if defined(SOUNDWAVE_USING_WINMM) m_driver = std::make_unique(this); #endif #if defined(SOUNDWAVE_USING_WASAPI) m_driver = std::make_unique(this); #endif #if defined(SOUNDWAVE_USING_XAUDIO) m_driver = std::make_unique(this); #endif #if defined(SOUNDWAVE_USING_OPENAL) m_driver = std::make_unique(this); #endif #if defined(SOUNDWAVE_USING_ALSA) m_driver = std::make_unique(this); #endif #if defined(SOUNDWAVE_USING_SDLMIXER) m_driver = std::make_unique(this); #endif #if defined(SOUNDWAVE_USING_PULSE) m_driver = std::make_unique(this); #endif } WaveEngine::~WaveEngine() { DestroyAudio(); } std::vector WaveEngine::GetOutputDevices() { return { "XXX" }; } void WaveEngine::UseOutputDevice(const std::string& sDeviceOut) { m_sOutputDevice = sDeviceOut; } std::vector WaveEngine::GetInputDevices() { return { "XXX" }; } void WaveEngine::UseInputDevice(const std::string& sDeviceIn) { m_sInputDevice = sDeviceIn; } bool WaveEngine::InitialiseAudio(uint32_t nSampleRate, uint32_t nChannels, uint32_t nBlocks, uint32_t nBlockSamples) { m_nSampleRate = nSampleRate; m_nChannels = nChannels; m_nBlocks = nBlocks; m_nBlockSamples = nBlockSamples; m_dSamplePerTime = double(nSampleRate); m_dTimePerSample = 1.0 / double(nSampleRate); m_driver->Open(m_sOutputDevice, m_sInputDevice); m_driver->Start(); return false; } bool WaveEngine::DestroyAudio() { StopAll(); m_driver->Stop(); m_driver->Close(); return false; } void WaveEngine::SetCallBack_NewSample(std::function func) { m_funcNewSample = func; } void WaveEngine::SetCallBack_SynthFunction(std::function func) { m_funcUserSynth = func; } void WaveEngine::SetCallBack_FilterFunction(std::function func) { m_funcUserFilter = func; } PlayingWave WaveEngine::PlayWaveform(Wave* pWave, bool bLoop, double dSpeed) { WaveInstance wi; wi.bLoop = bLoop; wi.pWave = pWave; wi.dSpeedModifier = dSpeed * double(pWave->file.samplerate()) / m_dSamplePerTime; wi.dDuration = pWave->file.duration() / dSpeed; wi.dInstanceTime = m_dGlobalTime; m_listWaves.push_back(wi); return std::prev(m_listWaves.end()); } void WaveEngine::StopWaveform(const PlayingWave& w) { w->bFlagForStop = true; } void WaveEngine::StopAll() { for (auto& wave : m_listWaves) { wave.bFlagForStop = true; } } void WaveEngine::SetOutputVolume(const float fVolume) { m_fOutputVolume = std::clamp(fVolume, 0.0f, 1.0f); } uint32_t WaveEngine::FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples) { for (uint32_t nSample = 0; nSample < nRequiredSamples; nSample++) { double dSampleTime = m_dGlobalTime + nSample * m_dTimePerSample; if (m_funcNewSample) m_funcNewSample(dSampleTime); for (uint32_t nChannel = 0; nChannel < m_nChannels; nChannel++) { // Construct the sample float fSample = 0.0f; // 1) Sample any active waves for (auto& wave : m_listWaves) { // Is wave instance flagged for stopping? if (wave.bFlagForStop) { wave.bFinished = true; } else { // Calculate offset into wave instance double dTimeOffset = dSampleTime - wave.dInstanceTime - wave.dPauseTime; // If offset is larger than wave then... if (dTimeOffset >= wave.dDuration) { if (wave.bLoop) { // ...if looping, reset the wave instance wave.dInstanceTime = dSampleTime; } else { // ...if not looping, flag wave instance as dead wave.bFinished = true; } } else { if (!wave.bPaused) { // OR, sample the waveform from the correct channel fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier)); } else { wave.dPauseTime += m_dTimePerSample; } } } } // Remove waveform instances that have finished m_listWaves.remove_if([](const WaveInstance& wi) {return wi.bFinished; }); // 2) If user is synthesizing, request sample if (m_funcUserSynth) fSample += m_funcUserSynth(nChannel, dSampleTime); // 3) Apply global filters // 4) If user is filtering, allow manipulation of output if (m_funcUserFilter) fSample = m_funcUserFilter(nChannel, dSampleTime, fSample); // Place sample in buffer vBuffer[nBufferOffset + nSample * m_nChannels + nChannel] = fSample * m_fOutputVolume; } } // UPdate global time, accounting for error (thanks scripticuk) m_dGlobalTime += nRequiredSamples * m_dTimePerSample; return nRequiredSamples; } uint32_t WaveEngine::GetSampleRate() const { return m_nSampleRate; } uint32_t WaveEngine::GetChannels() const { return m_nChannels; } uint32_t WaveEngine::GetBlocks() const { return m_nBlocks; } uint32_t WaveEngine::GetBlockSampleCount() const { return m_nBlockSamples; } double WaveEngine::GetTimePerSample() const { return m_dTimePerSample; } namespace driver { Base::Base(olc::sound::WaveEngine* pHost) : m_pHost(pHost) {} Base::~Base() {} bool Base::Open(const std::string& sOutputDevice, const std::string& sInputDevice) { return false; } bool Base::Start() { return false; } void Base::Stop() { } void Base::Close() { } std::vector Base::EnumerateOutputDevices() { return { "DEFAULT" }; } std::vector Base::EnumerateInputDevices() { return { "NONE" }; } void Base::ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer) { constexpr float fMaxSample = float(std::numeric_limits::max()); constexpr float fMinSample = float(std::numeric_limits::min()); // So... why not use vFloatBuffer.size()? Well with this implementation // we can, but i suspect there may be some platforms that request a // specific number of samples per "loop" rather than this block architecture uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount(); uint32_t nSampleOffset = 0; while (nSamplesToProcess > 0) { uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess); // Vector is in float32 format, so convert to hardware required format for (uint32_t n = 0; n < nSamplesGathered; n++) { for (uint32_t c = 0; c < m_pHost->GetChannels(); c++) { size_t nSampleID = nSampleOffset + (n * m_pHost->GetChannels() + c); vDACBuffer[nSampleID] = short(std::clamp(vFloatBuffer[nSampleID] * fMaxSample, fMinSample, fMaxSample)); } } nSampleOffset += nSamplesGathered; nSamplesToProcess -= nSamplesGathered; } } void Base::GetFullOutputBlock(std::vector& vFloatBuffer) { uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount(); uint32_t nSampleOffset = 0; while (nSamplesToProcess > 0) { uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess); nSampleOffset += nSamplesGathered; nSamplesToProcess -= nSamplesGathered; } } } namespace synth { Property::Property(double f) { value = std::clamp(f, -1.0, 1.0); } Property& Property::operator =(const double f) { value = std::clamp(f, -1.0, 1.0); return *this; } ModularSynth::ModularSynth() { } bool ModularSynth::AddModule(Module* pModule) { // Check if module already added if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules)) { m_vModules.push_back(pModule); return true; } return false; } bool ModularSynth::RemoveModule(Module* pModule) { if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules)) { m_vModules.erase(std::remove(m_vModules.begin(), m_vModules.end(), pModule), m_vModules.end()); return true; } return false; } bool ModularSynth::AddPatch(Property* pInput, Property* pOutput) { // Does patch exist? std::pair newPatch = std::pair(pInput, pOutput); if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches)) { // Patch doesnt exist, now check if either are null if (pInput != nullptr && pOutput != nullptr) { m_vPatches.push_back(newPatch); return true; } } return false; } bool ModularSynth::RemovePatch(Property* pInput, Property* pOutput) { std::pair newPatch = std::pair(pInput, pOutput); if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches)) { m_vPatches.erase(std::remove(m_vPatches.begin(), m_vPatches.end(), newPatch), m_vPatches.end()); return true; } return false; } void ModularSynth::UpdatePatches() { // Update patches for (auto& patch : m_vPatches) { patch.second->value = patch.first->value; } } void ModularSynth::Update(uint32_t nChannel, double dTime, double dTimeStep) { // Now update synth for (auto& pModule : m_vModules) { pModule->Update(nChannel, dTime, dTimeStep); } } namespace modules { void Oscillator::Update(uint32_t nChannel, double dTime, double dTimeStep) { // We use phase accumulation to combat change in parameter glitches double w = frequency.value * max_frequency * dTimeStep; phase_acc += w + lfo_input.value * frequency.value; if (phase_acc >= 2.0) phase_acc -= 2.0; switch (waveform) { case Type::Sine: output = amplitude.value * sin(phase_acc * 2.0 * 3.14159); break; case Type::Saw: output = amplitude.value * (phase_acc - 1.0) * 2.0; break; case Type::Square: output = amplitude.value * (phase_acc >= 1.0) ? 1.0 : -1.0; break; case Type::Triangle: output = amplitude.value * (phase_acc < 1.0) ? (phase_acc * 0.5) : (1.0 - phase_acc * 0.5); break; case Type::PWM: output = amplitude.value * (phase_acc >= (parameter.value + 1.0)) ? 1.0 : -1.0; break; case Type::Wave: if (pWave != nullptr) output = amplitude.value * pWave->vChannelView[nChannel].GetSample(phase_acc * 0.5 * pWave->file.durationInSamples()); break; case Type::Noise: output = amplitude.value * rndDouble(-1.0, 1.0); break; } } double Oscillator::rndDouble(double min, double max) { return ((double)rnd() / (double)(0x7FFFFFFF)) * (max - min) + min; } uint32_t Oscillator::rnd() { random_seed += 0xe120fc15; uint64_t tmp; tmp = (uint64_t)random_seed * 0x4a39b70d; uint32_t m1 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF); tmp = (uint64_t)m1 * 0x12fad5c9; uint32_t m2 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF); return m2; } } } } #if defined(SOUNDWAVE_USING_WINMM) // WinMM Driver Implementation namespace olc::sound::driver { #pragma comment(lib, "winmm.lib") WinMM::WinMM(WaveEngine* pHost) : Base(pHost) { } WinMM::~WinMM() { Stop(); Close(); } bool WinMM::Open(const std::string& sOutputDevice, const std::string& sInputDevice) { // Device is available WAVEFORMATEX waveFormat; waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nSamplesPerSec = m_pHost->GetSampleRate(); waveFormat.wBitsPerSample = sizeof(short) * 8; waveFormat.nChannels = m_pHost->GetChannels(); waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; waveFormat.cbSize = 0; // Open Device if valid if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)WinMM::waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) return false; // Allocate array of wave header objects, one per block m_pWaveHeaders = std::make_unique(m_pHost->GetBlocks()); // Allocate block memory - I dont like vector of vectors, so going with this mess instead // My std::vector's content will change, but their size never will - they are basically array now m_pvBlockMemory = std::make_unique[]>(m_pHost->GetBlocks()); for (size_t i = 0; i < m_pHost->GetBlocks(); i++) m_pvBlockMemory[i].resize(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0); // Link headers to block memory - clever, so we only move headers about // rather than memory... for (unsigned int n = 0; n < m_pHost->GetBlocks(); n++) { m_pWaveHeaders[n].dwBufferLength = DWORD(m_pvBlockMemory[0].size() * sizeof(short)); m_pWaveHeaders[n].lpData = (LPSTR)(m_pvBlockMemory[n].data()); } // To begin with, all blocks are free m_nBlockFree = m_pHost->GetBlocks(); return true; } bool WinMM::Start() { // Prepare driver thread for activity m_bDriverLoopActive = true; // and get it going! m_thDriverLoop = std::thread(&WinMM::DriverLoop, this); return true; } void WinMM::Stop() { // Signal the driver loop to exit m_bDriverLoopActive = false; // Wait for driver thread to exit gracefully if (m_thDriverLoop.joinable()) m_thDriverLoop.join(); } void WinMM::Close() { waveOutClose(m_hwDevice); } // Static Callback wrapper - specific instance is specified void CALLBACK WinMM::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2) { // All sorts of messages may be pinged here, but we're only interested // in audio block is finished... if (uMsg != WOM_DONE) return; // ...which has happened so allow driver object to free resource WinMM* driver = (WinMM*)dwInstance; driver->FreeAudioBlock(); } void WinMM::FreeAudioBlock() { // Audio subsystem is done with the block it was using, thus // making it available again m_nBlockFree++; // Signal to driver loop that a block is now available. It // could have been suspended waiting for one std::unique_lock lm(m_muxBlockNotZero); m_cvBlockNotZero.notify_one(); } void WinMM::DriverLoop() { // We will be using this vector to transfer to the host for filling, with // user sound data (float32, -1.0 --> +1.0) std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f); // While the system is active, start requesting audio data while (m_bDriverLoopActive) { // Are there any blocks available to fill? ... if (m_nBlockFree == 0) { // ...no, So wait until one is available std::unique_lock lm(m_muxBlockNotZero); while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly { // This thread will suspend until this CV is signalled // from FreeAudioBlock. m_cvBlockNotZero.wait(lm); } } // ...yes, so use next one, by indicating one fewer // block is available m_nBlockFree--; // Prepare block for processing, by oddly, marking it as unprepared :P if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) { waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); } // Give the userland the opportunity to fill the buffer. Note that the driver // doesnt give a hoot about timing. Thats up to the SoundWave interface to // maintain // Userland will populate a float buffer, that gets cleanly converted to // a buffer of shorts for DAC ProcessOutputBlock(vFloatBuffer, m_pvBlockMemory[m_nBlockCurrent]); // 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_pHost->GetBlocks(); } } } // WinMM Driver Implementation #endif #if defined(SOUNDWAVE_USING_SDLMIXER) namespace olc::sound::driver { SDLMixer* SDLMixer::instance = nullptr; SDLMixer::SDLMixer(olc::sound::WaveEngine* pHost) : Base(pHost) { instance = this; } SDLMixer::~SDLMixer() { Stop(); Close(); } bool SDLMixer::Open(const std::string& sOutputDevice, const std::string& sInputDevice) { auto errc = Mix_OpenAudioDevice(static_cast(m_pHost->GetSampleRate()), AUDIO_F32, static_cast(m_pHost->GetChannels()), static_cast(m_pHost->GetBlockSampleCount()), sOutputDevice == "DEFAULT" ? nullptr : sOutputDevice.c_str(), SDL_AUDIO_ALLOW_FORMAT_CHANGE); // Query the actual format of the audio device, as we have allowed it to be changed. if (errc || !Mix_QuerySpec(nullptr, &m_haveFormat, nullptr)) { std::cerr << "Failed to open audio device '" << sOutputDevice << "'" << std::endl; return false; } // Compute the Mix_Chunk buffer's size according to the format of the audio device Uint32 bufferSize = 0; switch (m_haveFormat) { case AUDIO_F32: case AUDIO_S32: bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 4; break; case AUDIO_S16: case AUDIO_U16: bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 2; break; case AUDIO_S8: case AUDIO_U8: bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 1; break; default: std::cerr << "Audio format of device '" << sOutputDevice << "' is not supported" << std::endl; return false; } // Allocate the buffer once. The size will never change after this audioBuffer.resize(bufferSize); audioChunk = { 0, // 0, as the chunk does not own the array audioBuffer.data(), // Pointer to data array bufferSize, // Size in bytes 128 // Volume; max by default as it's not controlled by the driver. }; return true; } template void ConvertFloatTo(const std::vector& fromArr, Int* toArr) { static auto minVal = static_cast(std::numeric_limits::min()); static auto maxVal = static_cast(std::numeric_limits::max()); for (size_t i = 0; i != fromArr.size(); ++i) { toArr[i] = static_cast(std::clamp(fromArr[i] * maxVal, minVal, maxVal)); } } void SDLMixer::FillChunkBuffer(const std::vector& userData) const { // Since the audio device might have changed the format we need to provide, // we convert the wave data from the user to that format. switch (m_haveFormat) { case AUDIO_F32: memcpy(audioChunk.abuf, userData.data(), audioChunk.alen); break; case AUDIO_S32: ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); break; case AUDIO_S16: ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); break; case AUDIO_U16: ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); break; case AUDIO_S8: ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); break; case AUDIO_U8: ConvertFloatTo(userData, audioChunk.abuf); break; } } void SDLMixer::SDLMixerCallback(int channel) { static std::vector userData(instance->m_pHost->GetBlockSampleCount() * instance->m_pHost->GetChannels()); if (channel != 0) { std::cerr << "Unexpected channel number" << std::endl; } // Don't add another chunk if we should not keep running if (!instance->m_keepRunning) return; instance->GetFullOutputBlock(userData); instance->FillChunkBuffer(userData); if (Mix_PlayChannel(0, &instance->audioChunk, 0) == -1) { std::cerr << "Error while playing Chunk" << std::endl; } } bool SDLMixer::Start() { m_keepRunning = true; // Kickoff the audio driver SDLMixerCallback(0); // SDLMixer handles all other calls to reinsert user data Mix_ChannelFinished(SDLMixerCallback); return true; } void SDLMixer::Stop() { m_keepRunning = false; // Stop might be called multiple times, so we check whether the device is already closed if (Mix_QuerySpec(nullptr, nullptr, nullptr)) { for (int i = 0; i != m_pHost->GetChannels(); ++i) { if (Mix_Playing(i)) Mix_HaltChannel(i); } } } void SDLMixer::Close() { Mix_CloseAudio(); } } #endif // SOUNDWAVE_USING_SDLMIXER #if defined(SOUNDWAVE_USING_ALSA) // ALSA Driver Implementation namespace olc::sound::driver { ALSA::ALSA(WaveEngine* pHost) : Base(pHost) { } ALSA::~ALSA() { Stop(); Close(); } bool ALSA::Open(const std::string& sOutputDevice, const std::string& sInputDevice) { // Open PCM stream int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); // Clear global cache. // This won't affect users who don't want to create multiple instances of this driver, // but it will prevent valgrind from whining about "possibly lost" memory. // If the user's ALSA setup uses a PulseAudio plugin, then valgrind will still compain // about some "still reachable" data used by that plugin. TODO? snd_config_update_free_global(); if (rc < 0) return false; // Prepare the parameter structure and set default parameters snd_pcm_hw_params_t* params; snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(m_pPCM, params); // Set other parameters snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_FLOAT); snd_pcm_hw_params_set_rate(m_pPCM, params, m_pHost->GetSampleRate(), 0); snd_pcm_hw_params_set_channels(m_pPCM, params, m_pHost->GetChannels()); snd_pcm_hw_params_set_period_size(m_pPCM, params, m_pHost->GetBlockSampleCount(), 0); snd_pcm_hw_params_set_periods(m_pPCM, params, m_pHost->GetBlocks(), 0); // Save these parameters rc = snd_pcm_hw_params(m_pPCM, params); if (rc < 0) return false; return true; } bool ALSA::Start() { // Unsure if really needed, helped prevent underrun on my setup std::vector vSilence(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f); snd_pcm_start(m_pPCM); for (unsigned int i = 0; i < m_pHost->GetBlocks(); i++) snd_pcm_writei(m_pPCM, vSilence.data(), m_pHost->GetBlockSampleCount()); m_rBuffers.Resize(m_pHost->GetBlocks(), m_pHost->GetBlockSampleCount() * m_pHost->GetChannels()); snd_pcm_start(m_pPCM); m_bDriverLoopActive = true; m_thDriverLoop = std::thread(&ALSA::DriverLoop, this); return true; } void ALSA::Stop() { // Signal the driver loop to exit m_bDriverLoopActive = false; // Wait for driver thread to exit gracefully if (m_thDriverLoop.joinable()) m_thDriverLoop.join(); if (m_pPCM != nullptr) snd_pcm_drop(m_pPCM); } void ALSA::Close() { if (m_pPCM != nullptr) { snd_pcm_close(m_pPCM); m_pPCM = nullptr; } // Clear the global cache again for good measure snd_config_update_free_global(); } void ALSA::DriverLoop() { const uint32_t nFrames = m_pHost->GetBlockSampleCount(); int err; std::vector vFDs; int nFDs = snd_pcm_poll_descriptors_count(m_pPCM); if (nFDs < 0) { std::cerr << "snd_pcm_poll_descriptors_count returned " << nFDs << "\n"; std::cerr << "disabling polling\n"; nFDs = 0; } else { vFDs.resize(nFDs); err = snd_pcm_poll_descriptors(m_pPCM, vFDs.data(), vFDs.size()); if (err < 0) { std::cerr << "snd_pcm_poll_descriptors returned " << err << "\n"; std::cerr << "disabling polling\n"; vFDs = {}; } } // While the system is active, start requesting audio data while (m_bDriverLoopActive) { if (!m_rBuffers.IsFull()) { // Grab some audio data auto& vFreeBuffer = m_rBuffers.GetFreeBuffer(); GetFullOutputBlock(vFreeBuffer); } // Wait a bit if our buffer is full auto avail = snd_pcm_avail_update(m_pPCM); while (m_rBuffers.IsFull() && avail < nFrames) { if (vFDs.size() == 0) break; err = poll(vFDs.data(), vFDs.size(), -1); if (err < 0) std::cerr << "poll returned " << err << "\n"; unsigned short revents; err = snd_pcm_poll_descriptors_revents(m_pPCM, vFDs.data(), vFDs.size(), &revents); if (err < 0) std::cerr << "snd_pcm_poll_descriptors_revents returned " << err << "\n"; if (revents & POLLERR) std::cerr << "POLLERR\n"; avail = snd_pcm_avail_update(m_pPCM); } // Write whatever we can while (!m_rBuffers.IsEmpty() && avail >= nFrames) { auto vFullBuffer = m_rBuffers.GetFullBuffer(); uint32_t nWritten = 0; while (nWritten < nFrames) { auto err = snd_pcm_writei(m_pPCM, vFullBuffer.data() + nWritten, nFrames - nWritten); if (err > 0) nWritten += err; else { std::cerr << "snd_pcm_writei returned " << err << "\n"; break; } } avail = snd_pcm_avail_update(m_pPCM); } } } } // ALSA Driver Implementation #endif #if defined(SOUNDWAVE_USING_PULSE) // PULSE Driver Implementation #include #include namespace olc::sound::driver { PulseAudio::PulseAudio(WaveEngine* pHost) : Base(pHost) { } PulseAudio::~PulseAudio() { Stop(); Close(); } bool PulseAudio::Open(const std::string& sOutputDevice, const std::string& sInputDevice) { pa_sample_spec ss{ PA_SAMPLE_FLOAT32, m_pHost->GetSampleRate(), (uint8_t)m_pHost->GetChannels() }; m_pPA = pa_simple_new(NULL, "olcSoundWaveEngine", PA_STREAM_PLAYBACK, NULL, "Output Stream", &ss, NULL, NULL, NULL); if (m_pPA == NULL) return false; return true; } bool PulseAudio::Start() { m_bDriverLoopActive = true; m_thDriverLoop = std::thread(&PulseAudio::DriverLoop, this); return true; } void PulseAudio::Stop() { // Signal the driver loop to exit m_bDriverLoopActive = false; // Wait for driver thread to exit gracefully if (m_thDriverLoop.joinable()) m_thDriverLoop.join(); } void PulseAudio::Close() { if (m_pPA != nullptr) { pa_simple_free(m_pPA); m_pPA = nullptr; } } void PulseAudio::DriverLoop() { // We will be using this vector to transfer to the host for filling, with // user sound data (float32, -1.0 --> +1.0) std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f); // While the system is active, start requesting audio data while (m_bDriverLoopActive) { // Grab audio data from user GetFullOutputBlock(vFloatBuffer); // Fill PulseAudio data buffer int error; if (pa_simple_write(m_pPA, vFloatBuffer.data(), vFloatBuffer.size() * sizeof(float), &error) < 0) { std::cerr << "Failed to feed data to PulseAudio: " << pa_strerror(error) << "\n"; } } } } // PulseAudio Driver Implementation #endif #endif // OLC_SOUNDWAVE IMPLEMENTATION #endif // OLC_SOUNDWAVE_H