diff --git a/olcSoundWaveEngine.h b/olcSoundWaveEngine.h index e482440..c914f28 100644 --- a/olcSoundWaveEngine.h +++ b/olcSoundWaveEngine.h @@ -1,1963 +1,1820 @@ -/* - +-------------------------------------------------------------+ - | 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 -*/ - -#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; - }; - - 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; - - // 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 - { - // OR, sample the waveform from the correct channel - fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier)); - } - } - } - - // 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 - +#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; + }; + + 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); + void SetCallBack_OnWaveDestroy(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; + std::function m_funcOnWaveFinished; + + + 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; + } + + void WaveEngine::SetCallBack_OnWaveDestroy(std::function func) { + m_funcOnWaveFinished = 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; + + // 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 + { + // OR, sample the waveform from the correct channel + fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier)); + } + } + + + if (wave.bFinished && m_funcOnWaveFinished) + { + m_funcOnWaveFinished( + std::find_if(m_listWaves.begin(), m_listWaves.end(), [&wave](const auto& w) { return &w == &wave; }) + ); + } + } + + // 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 +