pull/113/head
Javidx9 6 years ago committed by GitHub
parent 85ccc8c4b7
commit 7c4889c07a
  1. 689
      olcPGEX_Sound.h

@ -3,7 +3,7 @@
+-------------------------------------------------------------+ +-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension | | OneLoneCoder Pixel Game Engine Extension |
| Sound - v0.2 | | Sound - v0.3 |
+-------------------------------------------------------------+ +-------------------------------------------------------------+
What is this? What is this?
@ -11,10 +11,18 @@
This is an extension to the olcPixelGameEngine, which provides This is an extension to the olcPixelGameEngine, which provides
sound generation and wave playing routines. 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) License (OLC-3)
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Copyright 2018 OneLoneCoder.com Copyright 2018 - 2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions modification, are permitted provided that the following conditions
@ -56,28 +64,59 @@
Author Author
~~~~~~ ~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2018 David Barr, aka javidx9, ©OneLoneCoder 2019
*/ */
#ifndef OLC_PGEX_SOUND #ifndef OLC_PGEX_SOUND_H
#define OLC_PGEX_SOUND #define OLC_PGEX_SOUND_H
#include <istream> #include <istream>
#include <cstring>
#include <climits>
#include <algorithm> #include <algorithm>
#undef min #undef min
#undef max #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 <alsa/asoundlib.h>
#endif
#ifdef USE_OPENAL
#include <AL/al.h>
#include <AL/alc.h>
#include <queue>
#endif
#pragma pack(push, 1)
typedef struct { typedef struct {
unsigned short wFormatTag; uint16_t wFormatTag;
unsigned short nChannels; uint16_t nChannels;
unsigned long nSamplesPerSec; uint32_t nSamplesPerSec;
unsigned long nAvgBytesPerSec; uint32_t nAvgBytesPerSec;
unsigned short nBlockAlign; uint16_t nBlockAlign;
unsigned short wBitsPerSample; uint16_t wBitsPerSample;
unsigned short cbSize; uint16_t cbSize;
} OLC_WAVEFORMATEX; } OLC_WAVEFORMATEX;
#pragma pack(pop)
namespace olc namespace olc
{ {
@ -119,7 +158,7 @@ namespace olc
static void SetUserFilterFunction(std::function<float(int, float, float)> func); static void SetUserFilterFunction(std::function<float(int, float, float)> func);
public: 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 PlaySample(int id, bool bLoop = false);
static void StopSample(int id); static void StopSample(int id);
static void StopAll(); static void StopAll();
@ -127,7 +166,7 @@ namespace olc
private: private:
#ifdef WIN32 // Windows specific sound management #ifdef USE_WINDOWS // Windows specific sound management
static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2);
static unsigned int m_nSampleRate; static unsigned int m_nSampleRate;
static unsigned int m_nChannels; static unsigned int m_nChannels;
@ -142,20 +181,41 @@ namespace olc
static std::mutex m_muxBlockNotZero; static std::mutex m_muxBlockNotZero;
#endif #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<ALuint> 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 void AudioThread();
static std::thread m_AudioThread; static std::thread m_AudioThread;
static std::atomic<bool> m_bAudioThreadActive; static std::atomic<bool> m_bAudioThreadActive;
static std::atomic<float> m_fGlobalTime; static std::atomic<float> m_fGlobalTime;
static std::function<float(int, float, float)> funcUserSynth; static std::function<float(int, float, float)> funcUserSynth;
static std::function<float(int, float, float)> funcUserFilter; static std::function<float(int, float, float)> funcUserFilter;
}; };
} }
#ifdef WIN32 // Implementation, platform-independent
#pragma comment(lib, "winmm.lib")
#ifdef OLC_PGEX_SOUND
#undef OLC_PGEX_SOUND
namespace olc namespace olc
{ {
@ -191,16 +251,16 @@ namespace olc
return olc::FAIL; return olc::FAIL;
// Search for audio data chunk // Search for audio data chunk
long nChunksize = 0; uint32_t nChunksize = 0;
is.read(dump, sizeof(char) * 4); // Read chunk header 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) while (strncmp(dump, "data", 4) != 0)
{ {
// Not audio data, so just skip it // Not audio data, so just skip it
//std::fseek(f, nChunksize, SEEK_CUR); //std::fseek(f, nChunksize, SEEK_CUR);
is.seekg(nChunksize, std::istream::cur); is.seekg(nChunksize, std::istream::cur);
is.read(dump, sizeof(char) * 4); 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 // Finally got to data, so read it all in and convert to float samples
@ -221,7 +281,7 @@ namespace olc
{ {
is.read((char*)&s, sizeof(short)); is.read((char*)&s, sizeof(short));
*pSample = (float)s / (float)(MAXSHORT); *pSample = (float)s / (float)(SHRT_MAX);
pSample++; pSample++;
} }
} }
@ -234,7 +294,8 @@ namespace olc
if (pack != nullptr) if (pack != nullptr)
{ {
std::istream is(&(pack->GetStreamBuffer(sWavFile))); olc::ResourcePack::sEntry entry = pack->GetStreamBuffer(sWavFile);
std::istream is(&entry);
return ReadWave(is); return ReadWave(is);
} }
else else
@ -250,6 +311,131 @@ namespace olc
} }
} }
// This vector holds all loaded sound samples in memory
std::vector<olc::SOUND::AudioSample> 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<float(int, float, float)> func)
{
funcUserSynth = func;
}
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> 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<bool> SOUND::m_bAudioThreadActive{ false };
std::atomic<float> SOUND::m_fGlobalTime{ 0.0f };
std::list<SOUND::sCurrentlyPlayingSample> SOUND::listActiveSamples;
std::function<float(int, float, float)> SOUND::funcUserSynth = nullptr;
std::function<float(int, float, float)> 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) bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{ {
// Initialise Sound Engine // Initialise Sound Engine
@ -386,161 +572,209 @@ namespace olc
} }
} }
// This vector holds all loaded sound samples in memory unsigned int SOUND::m_nSampleRate = 0;
std::vector<olc::SOUND::AudioSample> vecAudioSamples; 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<unsigned int> 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 #elif defined(USE_ALSA)
// holds the sound ID and where this instance of it is up to for its
// current playback
void SOUND::SetUserSynthFunction(std::function<float(int, float, float)> func) namespace olc
{
bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{ {
funcUserSynth = func; // Initialise Sound Engine
} m_bAudioThreadActive = false;
m_nSampleRate = nSampleRate;
m_nChannels = nChannels;
m_nBlockSamples = nBlockSamples;
m_pBlockMemory = nullptr;
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> func) // Open PCM stream
{ int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0);
funcUserFilter = func; if (rc < 0)
} return DestroyAudio();
// 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); // Prepare the parameter structure and set default parameters
if (a.bSampleValid) snd_pcm_hw_params_t *params;
{ snd_pcm_hw_params_alloca(&params);
vecAudioSamples.push_back(a); snd_pcm_hw_params_any(m_pPCM, params);
return vecAudioSamples.size();
}
else
return -1;
}
// Add sample 'id' to the mixers sounds to play list // Set other parameters
void SOUND::PlaySample(int id, bool bLoop) 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);
olc::SOUND::sCurrentlyPlayingSample a; snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels);
a.nAudioSampleID = id; snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0);
a.nSamplePosition = 0; snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0);
a.bFinished = false;
a.bFlagForStop = false;
a.bLoop = bLoop;
SOUND::listActiveSamples.push_back(a);
}
void SOUND::StopSample(int id) // Save these parameters
{ rc = snd_pcm_hw_params(m_pPCM, params);
// Find first occurence of sample id if (rc < 0)
auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); return DestroyAudio();
if(s != listActiveSamples.end())
s->bFlagForStop = true; 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) m_bAudioThreadActive = false;
{ m_AudioThread.join();
s.bFlagForStop = true; 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 m_fGlobalTime = 0.0f;
float fMixerSample = 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) if (fSample >= 0.0)
{ return fmin(fSample, fMax);
s.bLoop = false;
s.bFinished = true;
}
else 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 nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample);
s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); m_pBlockMemory[n + c] = nNewSample;
nPreviousSample = nNewSample;
}
// If sample position is valid add to the mix m_fGlobalTime = m_fGlobalTime + fTimeStep;
if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) }
fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel];
else // Send block to sound device
{ snd_pcm_uframes_t nLeft = m_nBlockSamples;
if (s.bLoop) short *pBlockPos = m_pBlockMemory;
{ while (nLeft > 0)
s.nSamplePosition = 0; {
} int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft);
else if (rc > 0)
s.bFinished = true; // Else sound has completed {
} 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_nSampleRate = 0;
unsigned int SOUND::m_nChannels = 0; unsigned int SOUND::m_nChannels = 0;
unsigned int SOUND::m_nBlockCount = 0;
unsigned int SOUND::m_nBlockSamples = 0; unsigned int SOUND::m_nBlockSamples = 0;
unsigned int SOUND::m_nBlockCurrent = 0;
short* SOUND::m_pBlockMemory = nullptr; short* SOUND::m_pBlockMemory = nullptr;
WAVEHDR *SOUND::m_pWaveHeaders = nullptr;
HWAVEOUT SOUND::m_hwDevice;
std::thread SOUND::m_AudioThread;
std::atomic<bool> SOUND::m_bAudioThreadActive = false;
std::atomic<unsigned int> SOUND::m_nBlockFree = 0;
std::condition_variable SOUND::m_cvBlockNotZero;
std::mutex SOUND::m_muxBlockNotZero;
std::atomic<float> SOUND::m_fGlobalTime = 0.0f;
std::list<SOUND::sCurrentlyPlayingSample> SOUND::listActiveSamples;
std::function<float(int, float, float)> SOUND::funcUserSynth = nullptr;
std::function<float(int, float, float)> SOUND::funcUserFilter = nullptr;
} }
#else // Non Windows #elif defined(USE_OPENAL)
namespace olc namespace olc
{ {
SOUND::AudioSample::AudioSample() bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
{}
SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack)
{ {
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) // Open the device and create the context
{ m_pDevice = alcOpenDevice(NULL);
return olc::OK; 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; return true;
} }
// Stop and clean up audio system // Stop and clean up audio system
bool SOUND::DestroyAudio() 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; return false;
} }
@ -551,127 +785,108 @@ namespace olc
// and then issued to the soundcard. // and then issued to the soundcard.
void SOUND::AudioThread() void SOUND::AudioThread()
{ {
m_fGlobalTime = 0.0f;
static float fTimeStep = 1.0f / (float)m_nSampleRate;
} // Goofy hack to get maximum integer for a type at run-time
short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
// This vector holds all loaded sound samples in memory float fMaxSample = (float)nMaxSample;
std::vector<olc::SOUND::AudioSample> vecAudioSamples; short nPreviousSample = 0;
// 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<float(int, float, float)> func) std::vector<ALuint> vProcessed;
{
funcUserSynth = func;
}
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> func) while (m_bAudioThreadActive)
{
funcUserFilter = func;
}
// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID
// number is returned if successful, otherwise -1
unsigned int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack)
{
olc::SOUND::AudioSample a(sWavFile, pack);
if (a.bSampleValid)
{ {
vecAudioSamples.push_back(a); ALint nState, nProcessed;
return vecAudioSamples.size(); alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState);
} alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed);
else
return -1;
}
// Add sample 'id' to the mixers sounds to play list // Add processed buffers to our queue
void SOUND::PlaySample(int id, bool bLoop) vProcessed.resize(nProcessed);
{ alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data());
olc::SOUND::sCurrentlyPlayingSample a; for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf);
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) // Wait until there is a free buffer (ewww)
{ if (m_qAvailableBuffers.empty()) continue;
// 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() short nNewSample = 0;
{
for (auto &s : listActiveSamples)
{
s.bFlagForStop = true;
}
}
float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) auto clip = [](float fSample, float fMax)
{ {
// Accumulate sample for this channel if (fSample >= 0.0)
float fMixerSample = 0.0f; return fmin(fSample, fMax);
else
return fmax(fSample, -fMax);
};
for (auto &s : listActiveSamples) for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels)
{
if (m_bAudioThreadActive)
{ {
if (s.bFlagForStop) // User Process
for (unsigned int c = 0; c < m_nChannels; c++)
{ {
s.bLoop = false; nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample);
s.bFinished = true; 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 m_fGlobalTime = m_fGlobalTime + fTimeStep;
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; // 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 std::queue<ALuint> SOUND::m_qAvailableBuffers;
listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); 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 #else // Some other platform
if (funcUserSynth != nullptr)
fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep);
// Return the sample via an optional user override to filter the sound namespace olc
if (funcUserFilter != nullptr) {
return funcUserFilter(nChannel, fGlobalTime, fMixerSample); bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
else {
return fMixerSample; return true;
} }
std::thread SOUND::m_AudioThread; // Stop and clean up audio system
std::atomic<bool> SOUND::m_bAudioThreadActive{ false }; bool SOUND::DestroyAudio()
std::atomic<float> SOUND::m_fGlobalTime{ 0.0f }; {
std::list<SOUND::sCurrentlyPlayingSample> SOUND::listActiveSamples; return false;
std::function<float(int, float, float)> SOUND::funcUserSynth = nullptr; }
std::function<float(int, float, float)> SOUND::funcUserFilter = nullptr;
}
#endif
// 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
#endif // OLC_PGEX_SOUND
Loading…
Cancel
Save