pull/113/head
Javidx9 6 years ago committed by GitHub
parent 85ccc8c4b7
commit 7c4889c07a
  1. 713
      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
{ {
@ -87,18 +126,18 @@ namespace olc
// A representation of an affine transform, used to rotate, scale, offset & shear space // A representation of an affine transform, used to rotate, scale, offset & shear space
public: public:
class AudioSample class AudioSample
{ {
public: public:
AudioSample(); AudioSample();
AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr);
olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr); olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr);
public: public:
OLC_WAVEFORMATEX wavHeader; OLC_WAVEFORMATEX wavHeader;
float *fSample = nullptr; float *fSample = nullptr;
long nSamples = 0; long nSamples = 0;
int nChannels = 0; int nChannels = 0;
bool bSampleValid = false; bool bSampleValid = false;
}; };
struct sCurrentlyPlayingSample struct sCurrentlyPlayingSample
@ -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,8 +166,8 @@ 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;
static unsigned int m_nBlockCount; static unsigned int m_nBlockCount;
@ -136,26 +175,47 @@ namespace olc
static unsigned int m_nBlockCurrent; static unsigned int m_nBlockCurrent;
static short* m_pBlockMemory; static short* m_pBlockMemory;
static WAVEHDR *m_pWaveHeaders; static WAVEHDR *m_pWaveHeaders;
static HWAVEOUT m_hwDevice; static HWAVEOUT m_hwDevice;
static std::atomic<unsigned int> m_nBlockFree; static std::atomic<unsigned int> m_nBlockFree;
static std::condition_variable m_cvBlockNotZero; static std::condition_variable m_cvBlockNotZero;
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
{ {
@ -187,20 +247,20 @@ namespace olc
// which are not in the wav file // which are not in the wav file
// Just check if wave format is compatible with olcPGE // 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; 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,20 +281,21 @@ 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++;
} }
} }
} }
// All done, flag sound as valid // All done, flag sound as valid
bSampleValid = true; bSampleValid = true;
return olc::OK; return olc::OK;
}; };
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,292 +572,321 @@ 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)
{
funcUserSynth = func;
}
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> 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 // Open PCM stream
// number is returned if successful, otherwise -1 int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0);
unsigned int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) 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 // Prepare the parameter structure and set default parameters
void SOUND::PlaySample(int id, bool bLoop) snd_pcm_hw_params_t *params;
{ snd_pcm_hw_params_alloca(&params);
olc::SOUND::sCurrentlyPlayingSample a; snd_pcm_hw_params_any(m_pPCM, params);
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) // Set other parameters
{ snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE);
// Find first occurence of sample id snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0);
auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels);
if(s != listActiveSamples.end()) snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0);
s->bFlagForStop = true; 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) 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;
} }
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks' // 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 // 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 // card is ready for more data. The block is fille by the "user" in some manner
// 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;
// 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) // Goofy hack to get maximum integer for a type at run-time
{ short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
funcUserSynth = func; float fMaxSample = (float)nMaxSample;
} short nPreviousSample = 0;
void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> func) std::vector<ALuint> vProcessed;
{
funcUserFilter = func;
}
// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID while (m_bAudioThreadActive)
// 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
#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 // OLC_PGEX_SOUND
Loading…
Cancel
Save