diff --git a/olcPGEX_Sound.h b/olcPGEX_Sound.h index c6f3b44..0b4dcf3 100644 --- a/olcPGEX_Sound.h +++ b/olcPGEX_Sound.h @@ -3,7 +3,7 @@ +-------------------------------------------------------------+ | OneLoneCoder Pixel Game Engine Extension | - | Sound - v0.2 | + | Sound - v0.3 | +-------------------------------------------------------------+ What is this? @@ -11,10 +11,18 @@ This is an extension to the olcPixelGameEngine, which provides sound generation and wave playing routines. + Special Thanks: + ~~~~~~~~~~~~~~~ + Slavka - For entire non-windows system back end! + Gorbit99 - Testing, Bug Fixes + Cyberdroid - Testing, Bug Fixes + Dragoneye - Testing + Puol - Testing + License (OLC-3) ~~~~~~~~~~~~~~~ - Copyright 2018 OneLoneCoder.com + Copyright 2018 - 2019 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -56,28 +64,59 @@ Author ~~~~~~ - David Barr, aka javidx9, ŠOneLoneCoder 2018 + David Barr, aka javidx9, ŠOneLoneCoder 2019 */ -#ifndef OLC_PGEX_SOUND -#define OLC_PGEX_SOUND +#ifndef OLC_PGEX_SOUND_H +#define OLC_PGEX_SOUND_H #include +#include +#include #include #undef min #undef max +// Choose a default sound backend +#if !defined(USE_ALSA) && !defined(USE_OPENAL) && !defined(USE_WINDOWS) +#ifdef __linux__ +#define USE_ALSA +#endif + +#ifdef __EMSCRIPTEN__ +#define USE_OPENAL +#endif + +#ifdef _WIN32 +#define USE_WINDOWS +#endif + +#endif + +#ifdef USE_ALSA +#define ALSA_PCM_NEW_HW_PARAMS_API +#include +#endif + +#ifdef USE_OPENAL +#include +#include +#include +#endif + +#pragma pack(push, 1) typedef struct { - unsigned short wFormatTag; - unsigned short nChannels; - unsigned long nSamplesPerSec; - unsigned long nAvgBytesPerSec; - unsigned short nBlockAlign; - unsigned short wBitsPerSample; - unsigned short cbSize; + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; } OLC_WAVEFORMATEX; +#pragma pack(pop) namespace olc { @@ -87,18 +126,18 @@ namespace olc // A representation of an affine transform, used to rotate, scale, offset & shear space public: class AudioSample - { + { public: AudioSample(); AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr); - + public: OLC_WAVEFORMATEX wavHeader; float *fSample = nullptr; long nSamples = 0; int nChannels = 0; - bool bSampleValid = false; + bool bSampleValid = false; }; struct sCurrentlyPlayingSample @@ -119,7 +158,7 @@ namespace olc static void SetUserFilterFunction(std::function func); public: - static unsigned int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); + static int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); static void PlaySample(int id, bool bLoop = false); static void StopSample(int id); static void StopAll(); @@ -127,8 +166,8 @@ namespace olc private: -#ifdef WIN32 // Windows specific sound management - static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); +#ifdef USE_WINDOWS // Windows specific sound management + static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); static unsigned int m_nSampleRate; static unsigned int m_nChannels; static unsigned int m_nBlockCount; @@ -136,26 +175,47 @@ namespace olc static unsigned int m_nBlockCurrent; static short* m_pBlockMemory; static WAVEHDR *m_pWaveHeaders; - static HWAVEOUT m_hwDevice; + static HWAVEOUT m_hwDevice; static std::atomic m_nBlockFree; static std::condition_variable m_cvBlockNotZero; static std::mutex m_muxBlockNotZero; #endif +#ifdef USE_ALSA + static snd_pcm_t *m_pPCM; + static unsigned int m_nSampleRate; + static unsigned int m_nChannels; + static unsigned int m_nBlockSamples; + static short* m_pBlockMemory; +#endif + +#ifdef USE_OPENAL + static std::queue m_qAvailableBuffers; + static ALuint *m_pBuffers; + static ALuint m_nSource; + static ALCdevice *m_pDevice; + static ALCcontext *m_pContext; + static unsigned int m_nSampleRate; + static unsigned int m_nChannels; + static unsigned int m_nBlockCount; + static unsigned int m_nBlockSamples; + static short* m_pBlockMemory; +#endif + static void AudioThread(); static std::thread m_AudioThread; static std::atomic m_bAudioThreadActive; static std::atomic m_fGlobalTime; static std::function funcUserSynth; static std::function funcUserFilter; - - }; } -#ifdef WIN32 -#pragma comment(lib, "winmm.lib") +// Implementation, platform-independent + +#ifdef OLC_PGEX_SOUND +#undef OLC_PGEX_SOUND namespace olc { @@ -187,20 +247,20 @@ namespace olc // which are not in the wav file // Just check if wave format is compatible with olcPGE - if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) return olc::FAIL; - + // Search for audio data chunk - long nChunksize = 0; + uint32_t nChunksize = 0; is.read(dump, sizeof(char) * 4); // Read chunk header - is.read((char*)&nChunksize, sizeof(long)); // Read chunk size + is.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size while (strncmp(dump, "data", 4) != 0) { // Not audio data, so just skip it //std::fseek(f, nChunksize, SEEK_CUR); is.seekg(nChunksize, std::istream::cur); is.read(dump, sizeof(char) * 4); - is.read((char*)&nChunksize, sizeof(long)); + is.read((char*)&nChunksize, sizeof(uint32_t)); } // Finally got to data, so read it all in and convert to float samples @@ -221,20 +281,21 @@ namespace olc { is.read((char*)&s, sizeof(short)); - *pSample = (float)s / (float)(MAXSHORT); + *pSample = (float)s / (float)(SHRT_MAX); pSample++; } } } - // All done, flag sound as valid + // All done, flag sound as valid bSampleValid = true; return olc::OK; }; - + if (pack != nullptr) { - std::istream is(&(pack->GetStreamBuffer(sWavFile))); + olc::ResourcePack::sEntry entry = pack->GetStreamBuffer(sWavFile); + std::istream is(&entry); return ReadWave(is); } else @@ -250,6 +311,131 @@ namespace olc } } + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + + void SOUND::SetUserSynthFunction(std::function func) + { + funcUserSynth = func; + } + + void SOUND::SetUserFilterFunction(std::function func) + { + funcUserFilter = func; + } + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) + { + + olc::SOUND::AudioSample a(sWavFile, pack); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return (unsigned int)vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void SOUND::PlaySample(int id, bool bLoop) + { + olc::SOUND::sCurrentlyPlayingSample a; + a.nAudioSampleID = id; + a.nSamplePosition = 0; + a.bFinished = false; + a.bFlagForStop = false; + a.bLoop = bLoop; + SOUND::listActiveSamples.push_back(a); + } + + void SOUND::StopSample(int id) + { + // Find first occurence of sample id + auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); + if (s != listActiveSamples.end()) + s->bFlagForStop = true; + } + + void SOUND::StopAll() + { + for (auto &s : listActiveSamples) + { + s.bFlagForStop = true; + } + } + + float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + if (m_bAudioThreadActive) + { + if (s.bFlagForStop) + { + s.bLoop = false; + s.bFinished = true; + } + else + { + // Calculate sample position + s.nSamplePosition += roundf((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + { + if (s.bLoop) + { + s.nSamplePosition = 0; + } + else + s.bFinished = true; // Else sound has completed + } + } + } + else + return 0.0f; + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + if (funcUserSynth != nullptr) + fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + if (funcUserFilter != nullptr) + return funcUserFilter(nChannel, fGlobalTime, fMixerSample); + else + return fMixerSample; + } + + std::thread SOUND::m_AudioThread; + std::atomic SOUND::m_bAudioThreadActive{ false }; + std::atomic SOUND::m_fGlobalTime{ 0.0f }; + std::list SOUND::listActiveSamples; + std::function SOUND::funcUserSynth = nullptr; + std::function SOUND::funcUserFilter = nullptr; +} + +// Implementation, Windows-specific +#ifdef USE_WINDOWS +#pragma comment(lib, "winmm.lib") + +namespace olc +{ bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) { // Initialise Sound Engine @@ -386,292 +572,321 @@ namespace olc } } - // This vector holds all loaded sound samples in memory - std::vector vecAudioSamples; + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockCount = 0; + unsigned int SOUND::m_nBlockSamples = 0; + unsigned int SOUND::m_nBlockCurrent = 0; + short* SOUND::m_pBlockMemory = nullptr; + WAVEHDR *SOUND::m_pWaveHeaders = nullptr; + HWAVEOUT SOUND::m_hwDevice; + std::atomic SOUND::m_nBlockFree = 0; + std::condition_variable SOUND::m_cvBlockNotZero; + std::mutex SOUND::m_muxBlockNotZero; +} - // This structure represents a sound that is currently playing. It only - // holds the sound ID and where this instance of it is up to for its - // current playback - - void SOUND::SetUserSynthFunction(std::function func) - { - funcUserSynth = func; - } +#elif defined(USE_ALSA) - void SOUND::SetUserFilterFunction(std::function func) +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) { - funcUserFilter = func; - } + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockSamples = nBlockSamples; + m_pBlockMemory = nullptr; - // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID - // number is returned if successful, otherwise -1 - unsigned int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) - { + // Open PCM stream + int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (rc < 0) + return DestroyAudio(); - olc::SOUND::AudioSample a(sWavFile, pack); - if (a.bSampleValid) - { - vecAudioSamples.push_back(a); - return vecAudioSamples.size(); - } - else - return -1; - } - // Add sample 'id' to the mixers sounds to play list - void SOUND::PlaySample(int id, bool bLoop) - { - olc::SOUND::sCurrentlyPlayingSample a; - a.nAudioSampleID = id; - a.nSamplePosition = 0; - a.bFinished = false; - a.bFlagForStop = false; - a.bLoop = bLoop; - SOUND::listActiveSamples.push_back(a); - } + // 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); - void SOUND::StopSample(int id) - { - // Find first occurence of sample id - auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); - if(s != listActiveSamples.end()) - s->bFlagForStop = true; + // Set other parameters + snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0); + snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels); + snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0); + snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0); + + // Save these parameters + rc = snd_pcm_hw_params(m_pPCM, params); + if (rc < 0) + return DestroyAudio(); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + // Unsure if really needed, helped prevent underrun on my setup + snd_pcm_start(m_pPCM); + for (unsigned int i = 0; i < nBlocks; i++) + rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512); + + snd_pcm_start(m_pPCM); + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + + return true; } - void SOUND::StopAll() + // Stop and clean up audio system + bool SOUND::DestroyAudio() { - for (auto &s : listActiveSamples) - { - s.bFlagForStop = true; - } + m_bAudioThreadActive = false; + m_AudioThread.join(); + snd_pcm_drain(m_pPCM); + snd_pcm_close(m_pPCM); + return false; } - float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() { - // Accumulate sample for this channel - float fMixerSample = 0.0f; + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; - for (auto &s : listActiveSamples) + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) { - if (m_bAudioThreadActive) + short nNewSample = 0; + + auto clip = [](float fSample, float fMax) { - if (s.bFlagForStop) - { - s.bLoop = false; - s.bFinished = true; - } + if (fSample >= 0.0) + return fmin(fSample, fMax); else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) { - // Calculate sample position - s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; + } - // If sample position is valid add to the mix - if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) - fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; - else - { - if (s.bLoop) - { - s.nSamplePosition = 0; - } - else - s.bFinished = true; // Else sound has completed - } + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + snd_pcm_uframes_t nLeft = m_nBlockSamples; + short *pBlockPos = m_pBlockMemory; + while (nLeft > 0) + { + int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft); + if (rc > 0) + { + pBlockPos += rc * m_nChannels; + nLeft -= rc; } + if (rc == -EAGAIN) continue; + if (rc == -EPIPE) // an underrun occured, prepare the device for more data + snd_pcm_prepare(m_pPCM); } - else - return 0.0f; } - - // If sounds have completed then remove them - listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); - - // The users application might be generating sound, so grab that if it exists - if(funcUserSynth != nullptr) - fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); - - // Return the sample via an optional user override to filter the sound - if (funcUserFilter != nullptr) - return funcUserFilter(nChannel, fGlobalTime, fMixerSample); - else - return fMixerSample; } - + snd_pcm_t* SOUND::m_pPCM = nullptr; unsigned int SOUND::m_nSampleRate = 0; unsigned int SOUND::m_nChannels = 0; - unsigned int SOUND::m_nBlockCount = 0; unsigned int SOUND::m_nBlockSamples = 0; - unsigned int SOUND::m_nBlockCurrent = 0; short* SOUND::m_pBlockMemory = nullptr; - WAVEHDR *SOUND::m_pWaveHeaders = nullptr; - HWAVEOUT SOUND::m_hwDevice; - std::thread SOUND::m_AudioThread; - std::atomic SOUND::m_bAudioThreadActive = false; - std::atomic SOUND::m_nBlockFree = 0; - std::condition_variable SOUND::m_cvBlockNotZero; - std::mutex SOUND::m_muxBlockNotZero; - std::atomic SOUND::m_fGlobalTime = 0.0f; - std::list SOUND::listActiveSamples; - std::function SOUND::funcUserSynth = nullptr; - std::function SOUND::funcUserFilter = nullptr; } -#else // Non Windows +#elif defined(USE_OPENAL) + namespace olc { - SOUND::AudioSample::AudioSample() - {} - - SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack) + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) { - LoadFromFile(sWavFile, pack); - } + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_pBlockMemory = nullptr; - olc::rcode SOUND::AudioSample::LoadFromFile(std::string sWavFile, olc::ResourcePack *pack) - { - return olc::OK; - } + // Open the device and create the context + m_pDevice = alcOpenDevice(NULL); + if (m_pDevice) + { + m_pContext = alcCreateContext(m_pDevice, NULL); + alcMakeContextCurrent(m_pContext); + } + else + return DestroyAudio(); - bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) - { + // Allocate memory for sound data + alGetError(); + m_pBuffers = new ALuint[m_nBlockCount]; + alGenBuffers(m_nBlockCount, m_pBuffers); + alGenSources(1, &m_nSource); + + for (unsigned int i = 0; i < m_nBlockCount; i++) + m_qAvailableBuffers.push(m_pBuffers[i]); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); return true; } // Stop and clean up audio system bool SOUND::DestroyAudio() { + m_bAudioThreadActive = false; + m_AudioThread.join(); + + alDeleteBuffers(m_nBlockCount, m_pBuffers); + delete[] m_pBuffers; + alDeleteSources(1, &m_nSource); + + alcMakeContextCurrent(NULL); + alcDestroyContext(m_pContext); + alcCloseDevice(m_pDevice); return false; } - + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' // with audio data. If no requests are available it goes dormant until the sound // card is ready for more data. The block is fille by the "user" in some manner // and then issued to the soundcard. void SOUND::AudioThread() { - - } - - // This vector holds all loaded sound samples in memory - std::vector vecAudioSamples; - - // This structure represents a sound that is currently playing. It only - // holds the sound ID and where this instance of it is up to for its - // current playback + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; - void SOUND::SetUserSynthFunction(std::function func) - { - funcUserSynth = func; - } + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; - void SOUND::SetUserFilterFunction(std::function func) - { - funcUserFilter = func; - } + std::vector vProcessed; - // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID - // number is returned if successful, otherwise -1 - unsigned int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) - { - olc::SOUND::AudioSample a(sWavFile, pack); - if (a.bSampleValid) + while (m_bAudioThreadActive) { - vecAudioSamples.push_back(a); - return vecAudioSamples.size(); - } - else - return -1; - } + ALint nState, nProcessed; + alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState); + alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed); - // Add sample 'id' to the mixers sounds to play list - void SOUND::PlaySample(int id, bool bLoop) - { - olc::SOUND::sCurrentlyPlayingSample a; - a.nAudioSampleID = id; - a.nSamplePosition = 0; - a.bFinished = false; - a.bFlagForStop = false; - a.bLoop = bLoop; - SOUND::listActiveSamples.push_back(a); - } + // Add processed buffers to our queue + vProcessed.resize(nProcessed); + alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data()); + for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf); - void SOUND::StopSample(int id) - { - // Find first occurence of sample id - auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); - if (s != listActiveSamples.end()) - s->bFlagForStop = true; - } + // Wait until there is a free buffer (ewww) + if (m_qAvailableBuffers.empty()) continue; - void SOUND::StopAll() - { - for (auto &s : listActiveSamples) - { - s.bFlagForStop = true; - } - } + short nNewSample = 0; - float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) - { - // Accumulate sample for this channel - float fMixerSample = 0.0f; + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; - for (auto &s : listActiveSamples) - { - if (m_bAudioThreadActive) + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) { - if (s.bFlagForStop) + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) { - s.bLoop = false; - s.bFinished = true; + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; } - else - { - // Calculate sample position - s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); - // If sample position is valid add to the mix - if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) - fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; - else - { - if (s.bLoop) - { - s.nSamplePosition = 0; - } - else - s.bFinished = true; // Else sound has completed - } - } + m_fGlobalTime = m_fGlobalTime + fTimeStep; } - else - return 0.0f; + + // Fill OpenAL data buffer + alBufferData( + m_qAvailableBuffers.front(), + m_nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, + m_pBlockMemory, + 2 * m_nBlockSamples, + m_nSampleRate + ); + // Add it to the OpenAL queue + alSourceQueueBuffers(m_nSource, 1, &m_qAvailableBuffers.front()); + // Remove it from ours + m_qAvailableBuffers.pop(); + + // If it's not playing for some reason, change that + if (nState != AL_PLAYING) + alSourcePlay(m_nSource); } + } - // If sounds have completed then remove them - listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + std::queue SOUND::m_qAvailableBuffers; + ALuint *SOUND::m_pBuffers = nullptr; + ALuint SOUND::m_nSource = 0; + ALCdevice *SOUND::m_pDevice = nullptr; + ALCcontext *SOUND::m_pContext = nullptr; + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockCount = 0; + unsigned int SOUND::m_nBlockSamples = 0; + short* SOUND::m_pBlockMemory = nullptr; +} - // The users application might be generating sound, so grab that if it exists - if (funcUserSynth != nullptr) - fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); +#else // Some other platform - // Return the sample via an optional user override to filter the sound - if (funcUserFilter != nullptr) - return funcUserFilter(nChannel, fGlobalTime, fMixerSample); - else - return fMixerSample; +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + return true; } - std::thread SOUND::m_AudioThread; - std::atomic SOUND::m_bAudioThreadActive{ false }; - std::atomic SOUND::m_fGlobalTime{ 0.0f }; - std::list SOUND::listActiveSamples; - std::function SOUND::funcUserSynth = nullptr; - std::function SOUND::funcUserFilter = nullptr; -} -#endif + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + return false; + } -#endif \ No newline at end of file + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { } +} + +#endif +#endif +#endif // OLC_PGEX_SOUND \ No newline at end of file