diff --git a/C++ProjectTemplate b/C++ProjectTemplate index 8f7c626..9fa0fcb 100755 Binary files a/C++ProjectTemplate and b/C++ProjectTemplate differ diff --git a/sig b/sig index 4b8a12b..b55ded9 100755 --- a/sig +++ b/sig @@ -3,7 +3,7 @@ export AUTO_UPDATE=false source utils/define.sh define PROJECT_NAME "C++ProjectTemplate" -define CUSTOM_PARAMS "-lX11 -lGL -lpthread -lpng -lstdc++fs -lasound" +define CUSTOM_PARAMS "-std=c++17 -lX11 -lGL -lpthread -lpng -lstdc++fs -lpulse -lpulse-simple" define LANGUAGE "C++" -source utils/main.sh \ No newline at end of file +source utils/main.sh diff --git a/soundExtension.h b/soundExtension.h index 4f21671..e482440 100644 --- a/soundExtension.h +++ b/soundExtension.h @@ -1,6 +1,6 @@ /* +-------------------------------------------------------------+ - | OneLoneCoder Sound Wave Engine v0.01 | + | OneLoneCoder Sound Wave Engine v0.02 | | "You wanted noise? Well is this loud enough?" - javidx9 | +-------------------------------------------------------------+ @@ -64,39 +64,73 @@ Homepage: https://www.onelonecoder.com Patreon: https://www.patreon.com/javidx9 - Compiling in Linux - ~~~~~~~~~~~~~~~~~~ - When compiling, you will need to include ALSA to your list of library flags. - Add: -lasound + Thanks + ~~~~~~ + Gorbit99, Dragoneye, Puol - Your command should look something like: + Authors + ~~~~~~~ + slavka, MaGetzUb, cstd, Moros1138 & javidx9 - g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -lasound -std=c++17 + (c)OneLoneCoder 2019, 2020, 2021, 2022 +*/ - Compiling with Emscripten - ~~~~~~~~~~~~~~~~~~~~~~~~~ - When compiling with Emscripten, you will include SDL mixer in your setting flags: - Add: -s USE_SDL_MIXER=2 +/* + 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 +*/ - Your command should look something like: +/* + Using & Installing On Linux + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_SDL_MIXER=2 -s USE_LIBPNG=1 ./YourSource.cpp -o pge.html + GNU Compiler Collection (GCC) + ~~~~~~~~~~~~~~~~~~~~~~~ + 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. + 2) Build with the following command: - Thanks - ~~~~~~ - Slavka - OpenAL and ALSA - MaGetzUb - XAudio - Gorbit99 - Testing, Bug Fixes - Cyberdroid - Testing, Bug Fixes - Dragoneye - Testing - Puol - Testing - - Author - ~~~~~~ - David Barr, aka javidx9, �OneLoneCoder 2019, 2020, 2021, 2022 + 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 @@ -107,6 +141,15 @@ +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 @@ -131,13 +174,14 @@ // 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_ALSA) && !defined(SOUNDWAVE_USING_SDLMIXER) && \ + !defined(SOUNDWAVE_USING_PULSE) \ #if defined(_WIN32) #define SOUNDWAVE_USING_WINMM #endif #if defined(__linux__) - #define SOUNDWAVE_USING_ALSA + #define SOUNDWAVE_USING_PULSE #endif #if defined(__APPLE__) #define SOUNDWAVE_USING_SDLMIXER @@ -167,6 +211,8 @@ namespace olc::sound 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); } @@ -287,6 +333,15 @@ namespace olc::sound } 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; @@ -722,7 +777,11 @@ namespace olc::sound::driver #if defined(SOUNDWAVE_USING_SDLMIXER) +#if defined(__EMSCRIPTEN__) #include +#else +#include +#endif namespace olc::sound::driver { @@ -767,8 +826,12 @@ namespace olc::sound::driver class RingBuffer { public: - RingBuffer(unsigned int bufnum, unsigned int buflen): m_vBuffers(bufnum) + RingBuffer() + { } + + void Resize(unsigned int bufnum = 0, unsigned int buflen = 0) { + m_vBuffers.resize(bufnum); for (auto &vBuffer : m_vBuffers) vBuffer.resize(buflen); } @@ -835,6 +898,33 @@ namespace olc::sound::driver } #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 @@ -868,6 +958,10 @@ namespace olc::sound #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() @@ -938,8 +1032,8 @@ namespace olc::sound WaveInstance wi; wi.bLoop = bLoop; wi.pWave = pWave; - wi.dSpeedModifier = dSpeed; - wi.dDuration = double(pWave->file.samples()) / double(pWave->file.samplerate()) / wi.dSpeedModifier; + 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()); @@ -1006,8 +1100,8 @@ namespace olc::sound } else { - // OR, sample the waveform form the correct channel - fSample += float(wave.pWave->vChannelView[nChannel].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier)); + // OR, sample the waveform from the correct channel + fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier)); } } } @@ -1288,9 +1382,9 @@ namespace olc::sound random_seed += 0xe120fc15; uint64_t tmp; tmp = (uint64_t)random_seed * 0x4a39b70d; - uint32_t m1 = (tmp >> 32) ^ tmp; + uint32_t m1 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF); tmp = (uint64_t)m1 * 0x12fad5c9; - uint32_t m2 = (tmp >> 32) ^ tmp; + uint32_t m2 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF); return m2; } } @@ -1490,15 +1584,15 @@ bool SDLMixer::Open(const std::string& sOutputDevice, const std::string& sInputD { case AUDIO_F32: case AUDIO_S32: - bufferSize = m_pHost->GetBlockSampleCount() * 4; + bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 4; break; case AUDIO_S16: case AUDIO_U16: - bufferSize = m_pHost->GetBlockSampleCount() * 2; + bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 2; break; case AUDIO_S8: case AUDIO_U8: - bufferSize = m_pHost->GetBlockSampleCount() * 1; + bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 1; break; default: std::cerr << "Audio format of device '" << sOutputDevice << "' is not supported" << std::endl; @@ -1557,7 +1651,12 @@ void SDLMixer::FillChunkBuffer(const std::vector& userData) const void SDLMixer::SDLMixerCallback(int channel) { - static std::vector userData(instance->m_pHost->GetBlockSampleCount()); + 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) @@ -1566,7 +1665,7 @@ void SDLMixer::SDLMixerCallback(int channel) instance->GetFullOutputBlock(userData); instance->FillChunkBuffer(userData); - if (Mix_PlayChannel(channel, &instance->audioChunk, 0) == -1) + if (Mix_PlayChannel(0, &instance->audioChunk, 0) == -1) { std::cerr << "Error while playing Chunk" << std::endl; } @@ -1610,7 +1709,7 @@ void SDLMixer::Close() // ALSA Driver Implementation namespace olc::sound::driver { - ALSA::ALSA(WaveEngine* pHost) : Base(pHost), m_rBuffers(pHost->GetBlocks(), pHost->GetBlockSampleCount()) + ALSA::ALSA(WaveEngine* pHost) : Base(pHost) { } ALSA::~ALSA() @@ -1644,7 +1743,7 @@ namespace olc::sound::driver 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() / m_pHost->GetChannels(), 0); + 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 @@ -1658,10 +1757,12 @@ namespace olc::sound::driver bool ALSA::Start() { // Unsure if really needed, helped prevent underrun on my setup - std::vector vSilence(m_pHost->GetBlockSampleCount(), 0.0f); + 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_pHost->GetChannels()); + 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; @@ -1679,7 +1780,8 @@ namespace olc::sound::driver if (m_thDriverLoop.joinable()) m_thDriverLoop.join(); - snd_pcm_drop(m_pPCM); + if (m_pPCM != nullptr) + snd_pcm_drop(m_pPCM); } void ALSA::Close() @@ -1695,7 +1797,7 @@ namespace olc::sound::driver void ALSA::DriverLoop() { - const uint32_t nFrames = m_pHost->GetBlockSampleCount() / m_pHost->GetChannels(); + const uint32_t nFrames = m_pHost->GetBlockSampleCount(); int err; std::vector vFDs; @@ -1774,6 +1876,87 @@ namespace olc::sound::driver } } // 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