olcPixelGameEngine v2.28
The official distribution of olcPixelGameEngine, a tool used in javidx9's YouTube videos and projects
Loading...
Searching...
No Matches
olcPGEX_Sound.h
Go to the documentation of this file.
1/*
2 olcPGEX_Sound.h
3
4 +-------------------------------------------------------------+
5 | OneLoneCoder Pixel Game Engine Extension |
6 | Sound - v0.4 |
7 +-------------------------------------------------------------+
8
9 What is this?
10 ~~~~~~~~~~~~~
11 This is an extension to the olcPixelGameEngine, which provides
12 sound generation and wave playing routines.
13
14 Special Thanks:
15 ~~~~~~~~~~~~~~~
16 Slavka - For entire non-windows system back end!
17 Gorbit99 - Testing, Bug Fixes
18 Cyberdroid - Testing, Bug Fixes
19 Dragoneye - Testing
20 Puol - Testing
21
22 License (OLC-3)
23 ~~~~~~~~~~~~~~~
24
25 Copyright 2018 - 2019 OneLoneCoder.com
26
27 Redistribution and use in source and binary forms, with or without
28 modification, are permitted provided that the following conditions
29 are met:
30
31 1. Redistributions or derivations of source code must retain the above
32 copyright notice, this list of conditions and the following disclaimer.
33
34 2. Redistributions or derivative works in binary form must reproduce
35 the above copyright notice. This list of conditions and the following
36 disclaimer must be reproduced in the documentation and/or other
37 materials provided with the distribution.
38
39 3. Neither the name of the copyright holder nor the names of its
40 contributors may be used to endorse or promote products derived
41 from this software without specific prior written permission.
42
43 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
44 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
45 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
46 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
47 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
49 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
50 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
51 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
52 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
53 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54
55 Links
56 ~~~~~
57 YouTube: https://www.youtube.com/javidx9
58 Discord: https://discord.gg/WhwHUMV
59 Twitter: https://www.twitter.com/javidx9
60 Twitch: https://www.twitch.tv/javidx9
61 GitHub: https://www.github.com/onelonecoder
62 Homepage: https://www.onelonecoder.com
63 Patreon: https://www.patreon.com/javidx9
64
65 Author
66 ~~~~~~
67 David Barr, aka javidx9, ©OneLoneCoder 2019
68*/
69
70
71#ifndef OLC_PGEX_SOUND_H
72#define OLC_PGEX_SOUND_H
73
74#include <istream>
75#include <cstring>
76#include <climits>
77#include <condition_variable>
78#include <algorithm>
79
80#include "olcPixelGameEngine.h"
81
82#undef min
83#undef max
84
85// Choose a default sound backend
86#if !defined(USE_ALSA) && !defined(USE_OPENAL) && !defined(USE_WINDOWS)
87#ifdef __linux__
88#define USE_ALSA
89#endif
90
91#ifdef __EMSCRIPTEN__
92#define USE_OPENAL
93#endif
94
95#ifdef _WIN32
96#define USE_WINDOWS
97#endif
98
99#endif
100
101#ifdef USE_ALSA
102#define ALSA_PCM_NEW_HW_PARAMS_API
103#include <alsa/asoundlib.h>
104#endif
105
106#ifdef USE_OPENAL
107#include <AL/al.h>
108#include <AL/alc.h>
109#include <queue>
110#endif
111
112#pragma pack(push, 1)
113typedef struct {
114 uint16_t wFormatTag;
115 uint16_t nChannels;
118 uint16_t nBlockAlign;
120 uint16_t cbSize;
122#pragma pack(pop)
123
124namespace olc
125{
126 // Container class for Advanced 2D Drawing functions
127 class SOUND : public olc::PGEX
128 {
129 // A representation of an affine transform, used to rotate, scale, offset & shear space
130 public:
132 {
133 public:
135 AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr);
136 olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr);
137
138 public:
140 float *fSample = nullptr;
141 long nSamples = 0;
142 int nChannels = 0;
143 bool bSampleValid = false;
144 };
145
147 {
150 bool bFinished = false;
151 bool bLoop = false;
152 bool bFlagForStop = false;
153 };
154
155 static std::list<sCurrentlyPlayingSample> listActiveSamples;
156
157 public:
158 static bool InitialiseAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, unsigned int nBlocks = 8, unsigned int nBlockSamples = 512);
159 static bool DestroyAudio();
160 static void SetUserSynthFunction(std::function<float(int, float, float)> func);
161 static void SetUserFilterFunction(std::function<float(int, float, float)> func);
162
163 public:
164 static int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr);
165 static void PlaySample(int id, bool bLoop = false);
166 static void StopSample(int id);
167 static void StopAll();
168 static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep);
169
170
171 private:
172#ifdef USE_WINDOWS // Windows specific sound management
173 static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2);
174 static unsigned int m_nSampleRate;
175 static unsigned int m_nChannels;
176 static unsigned int m_nBlockCount;
177 static unsigned int m_nBlockSamples;
178 static unsigned int m_nBlockCurrent;
179 static short* m_pBlockMemory;
180 static WAVEHDR *m_pWaveHeaders;
181 static HWAVEOUT m_hwDevice;
182 static std::atomic<unsigned int> m_nBlockFree;
183 static std::condition_variable m_cvBlockNotZero;
184 static std::mutex m_muxBlockNotZero;
185#endif
186
187#ifdef USE_ALSA
188 static snd_pcm_t *m_pPCM;
189 static unsigned int m_nSampleRate;
190 static unsigned int m_nChannels;
191 static unsigned int m_nBlockSamples;
192 static short* m_pBlockMemory;
193#endif
194
195#ifdef USE_OPENAL
196 static std::queue<ALuint> m_qAvailableBuffers;
197 static ALuint *m_pBuffers;
198 static ALuint m_nSource;
199 static ALCdevice *m_pDevice;
200 static ALCcontext *m_pContext;
201 static unsigned int m_nSampleRate;
202 static unsigned int m_nChannels;
203 static unsigned int m_nBlockCount;
204 static unsigned int m_nBlockSamples;
205 static short* m_pBlockMemory;
206#endif
207
208 static void AudioThread();
209 static std::thread m_AudioThread;
210 static std::atomic<bool> m_bAudioThreadActive;
211 static std::atomic<float> m_fGlobalTime;
212 static std::function<float(int, float, float)> funcUserSynth;
213 static std::function<float(int, float, float)> funcUserFilter;
214 };
215}
216
217
218// Implementation, platform-independent
219
220#ifdef OLC_PGEX_SOUND
221#undef OLC_PGEX_SOUND
222
223namespace olc
224{
226 { }
227
228 SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack)
229 {
230 LoadFromFile(sWavFile, pack);
231 }
232
234 {
235 auto ReadWave = [&](std::istream &is)
236 {
237 char dump[4];
238 is.read(dump, sizeof(char) * 4); // Read "RIFF"
239 if (strncmp(dump, "RIFF", 4) != 0) return olc::FAIL;
240 is.read(dump, sizeof(char) * 4); // Not Interested
241 is.read(dump, sizeof(char) * 4); // Read "WAVE"
242 if (strncmp(dump, "WAVE", 4) != 0) return olc::FAIL;
243
244 // Read Wave description chunk
245 is.read(dump, sizeof(char) * 4); // Read "fmt "
246 unsigned int nHeaderSize = 0;
247 is.read((char*)&nHeaderSize, sizeof(unsigned int)); // Not Interested
248 is.read((char*)&wavHeader, nHeaderSize);// sizeof(WAVEFORMATEX)); // Read Wave Format Structure chunk
249 // Note the -2, because the structure has 2 bytes to indicate its own size
250 // which are not in the wav file
251
252 // Just check if wave format is compatible with olcPGE
253 if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100)
254 return olc::FAIL;
255
256 // Search for audio data chunk
257 uint32_t nChunksize = 0;
258 is.read(dump, sizeof(char) * 4); // Read chunk header
259 is.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size
260 while (strncmp(dump, "data", 4) != 0)
261 {
262 // Not audio data, so just skip it
263 //std::fseek(f, nChunksize, SEEK_CUR);
264 is.seekg(nChunksize, std::istream::cur);
265 is.read(dump, sizeof(char) * 4);
266 is.read((char*)&nChunksize, sizeof(uint32_t));
267 }
268
269 // Finally got to data, so read it all in and convert to float samples
270 nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3));
271 nChannels = wavHeader.nChannels;
272
273 // Create floating point buffer to hold audio sample
274 fSample = new float[nSamples * nChannels];
275 float *pSample = fSample;
276
277 // Read in audio data and normalise
278 for (long i = 0; i < nSamples; i++)
279 {
280 for (int c = 0; c < nChannels; c++)
281 {
282 short s = 0;
283 if (!is.eof())
284 {
285 is.read((char*)&s, sizeof(short));
286
287 *pSample = (float)s / (float)(SHRT_MAX);
288 pSample++;
289 }
290 }
291 }
292
293 // All done, flag sound as valid
294 bSampleValid = true;
295 return olc::OK;
296 };
297
298 if (pack != nullptr)
299 {
300 olc::ResourceBuffer rb = pack->GetFileBuffer(sWavFile);
301 std::istream is(&rb);
302 return ReadWave(is);
303 }
304 else
305 {
306 // Read from file
307 std::ifstream ifs(sWavFile, std::ifstream::binary);
308 if (ifs.is_open())
309 {
310 return ReadWave(ifs);
311 }
312 else
313 return olc::FAIL;
314 }
315 }
316
317 // This vector holds all loaded sound samples in memory
318 std::vector<olc::SOUND::AudioSample> vecAudioSamples;
319
320 // This structure represents a sound that is currently playing. It only
321 // holds the sound ID and where this instance of it is up to for its
322 // current playback
323
324 void SOUND::SetUserSynthFunction(std::function<float(int, float, float)> func)
325 {
326 funcUserSynth = func;
327 }
328
329 void SOUND::SetUserFilterFunction(std::function<float(int, float, float)> func)
330 {
331 funcUserFilter = func;
332 }
333
334 // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID
335 // number is returned if successful, otherwise -1
336 int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack)
337 {
338
339 olc::SOUND::AudioSample a(sWavFile, pack);
340 if (a.bSampleValid)
341 {
342 vecAudioSamples.push_back(a);
343 return (unsigned int)vecAudioSamples.size();
344 }
345 else
346 return -1;
347 }
348
349 // Add sample 'id' to the mixers sounds to play list
350 void SOUND::PlaySample(int id, bool bLoop)
351 {
353 a.nAudioSampleID = id;
354 a.nSamplePosition = 0;
355 a.bFinished = false;
356 a.bFlagForStop = false;
357 a.bLoop = bLoop;
358 SOUND::listActiveSamples.push_back(a);
359 }
360
361 void SOUND::StopSample(int id)
362 {
363 // Find first occurence of sample id
364 auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; });
365 if (s != listActiveSamples.end())
366 s->bFlagForStop = true;
367 }
368
369 void SOUND::StopAll()
370 {
371 for (auto &s : listActiveSamples)
372 {
373 s.bFlagForStop = true;
374 }
375 }
376
377 float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep)
378 {
379 // Accumulate sample for this channel
380 float fMixerSample = 0.0f;
381
382 for (auto &s : listActiveSamples)
383 {
384 if (m_bAudioThreadActive)
385 {
386 if (s.bFlagForStop)
387 {
388 s.bLoop = false;
389 s.bFinished = true;
390 }
391 else
392 {
393 // Calculate sample position
394 s.nSamplePosition += roundf((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep);
395
396 // If sample position is valid add to the mix
397 if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples)
398 fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel];
399 else
400 {
401 if (s.bLoop)
402 {
403 s.nSamplePosition = 0;
404 }
405 else
406 s.bFinished = true; // Else sound has completed
407 }
408 }
409 }
410 else
411 return 0.0f;
412 }
413
414 // If sounds have completed then remove them
415 listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; });
416
417 // The users application might be generating sound, so grab that if it exists
418 if (funcUserSynth != nullptr)
419 fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep);
420
421 // Return the sample via an optional user override to filter the sound
422 if (funcUserFilter != nullptr)
423 return funcUserFilter(nChannel, fGlobalTime, fMixerSample);
424 else
425 return fMixerSample;
426 }
427
428 std::thread SOUND::m_AudioThread;
429 std::atomic<bool> SOUND::m_bAudioThreadActive{ false };
430 std::atomic<float> SOUND::m_fGlobalTime{ 0.0f };
431 std::list<SOUND::sCurrentlyPlayingSample> SOUND::listActiveSamples;
432 std::function<float(int, float, float)> SOUND::funcUserSynth = nullptr;
433 std::function<float(int, float, float)> SOUND::funcUserFilter = nullptr;
434}
435
436// Implementation, Windows-specific
437#ifdef USE_WINDOWS
438#pragma comment(lib, "winmm.lib")
439
440namespace olc
441{
442 bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
443 {
444 // Initialise Sound Engine
445 m_bAudioThreadActive = false;
446 m_nSampleRate = nSampleRate;
447 m_nChannels = nChannels;
448 m_nBlockCount = nBlocks;
449 m_nBlockSamples = nBlockSamples;
450 m_nBlockFree = m_nBlockCount;
451 m_nBlockCurrent = 0;
452 m_pBlockMemory = nullptr;
453 m_pWaveHeaders = nullptr;
454
455 // Device is available
456 WAVEFORMATEX waveFormat;
457 waveFormat.wFormatTag = WAVE_FORMAT_PCM;
458 waveFormat.nSamplesPerSec = m_nSampleRate;
459 waveFormat.wBitsPerSample = sizeof(short) * 8;
460 waveFormat.nChannels = m_nChannels;
461 waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
462 waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
463 waveFormat.cbSize = 0;
464
465 listActiveSamples.clear();
466
467 // Open Device if valid
468 if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)SOUND::waveOutProc, (DWORD_PTR)0, CALLBACK_FUNCTION) != S_OK)
469 return DestroyAudio();
470
471 // Allocate Wave|Block Memory
472 m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples];
473 if (m_pBlockMemory == nullptr)
474 return DestroyAudio();
475 ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples);
476
477 m_pWaveHeaders = new WAVEHDR[m_nBlockCount];
478 if (m_pWaveHeaders == nullptr)
479 return DestroyAudio();
480 ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount);
481
482 // Link headers to block memory
483 for (unsigned int n = 0; n < m_nBlockCount; n++)
484 {
485 m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short);
486 m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples));
487 }
488
489 m_bAudioThreadActive = true;
490 m_AudioThread = std::thread(&SOUND::AudioThread);
491
492 // Start the ball rolling with the sound delivery thread
493 std::unique_lock<std::mutex> lm(m_muxBlockNotZero);
494 m_cvBlockNotZero.notify_one();
495 return true;
496 }
497
498 // Stop and clean up audio system
500 {
501 m_bAudioThreadActive = false;
502 if(m_AudioThread.joinable())
503 m_AudioThread.join();
504 return false;
505 }
506
507 // Handler for soundcard request for more data
508 void CALLBACK SOUND::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2)
509 {
510 if (uMsg != WOM_DONE) return;
511 m_nBlockFree++;
512 std::unique_lock<std::mutex> lm(m_muxBlockNotZero);
513 m_cvBlockNotZero.notify_one();
514 }
515
516 // Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
517 // with audio data. If no requests are available it goes dormant until the sound
518 // card is ready for more data. The block is fille by the "user" in some manner
519 // and then issued to the soundcard.
520 void SOUND::AudioThread()
521 {
522 m_fGlobalTime = 0.0f;
523 static float fTimeStep = 1.0f / (float)m_nSampleRate;
524
525 // Goofy hack to get maximum integer for a type at run-time
526 short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
527 float fMaxSample = (float)nMaxSample;
528 short nPreviousSample = 0;
529
530 auto tp1 = std::chrono::system_clock::now();
531 auto tp2 = std::chrono::system_clock::now();
532
533 while (m_bAudioThreadActive)
534 {
535 // Wait for block to become available
536 if (m_nBlockFree == 0)
537 {
538 std::unique_lock<std::mutex> lm(m_muxBlockNotZero);
539 while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly
540 m_cvBlockNotZero.wait(lm);
541 }
542
543 // Block is here, so use it
544 m_nBlockFree--;
545
546 // Prepare block for processing
547 if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED)
548 waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
549
550 short nNewSample = 0;
551 int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples;
552
553 auto clip = [](float fSample, float fMax)
554 {
555 if (fSample >= 0.0)
556 return fmin(fSample, fMax);
557 else
558 return fmax(fSample, -fMax);
559 };
560
561 tp2 = std::chrono::system_clock::now();
562 std::chrono::duration<float> elapsedTime = tp2 - tp1;
563 tp1 = tp2;
564
565 // Our time per frame coefficient
566 float fElapsedTime = elapsedTime.count();
567
568 for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels)
569 {
570 // User Process
571 for (unsigned int c = 0; c < m_nChannels; c++)
572 {
573 nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample);
574 m_pBlockMemory[nCurrentBlock + n + c] = nNewSample;
575 nPreviousSample = nNewSample;
576 }
577 }
578
579 m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples;
580
581 // Send block to sound device
582 waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
583 waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
584 m_nBlockCurrent++;
585 m_nBlockCurrent %= m_nBlockCount;
586 }
587 }
588
589 unsigned int SOUND::m_nSampleRate = 0;
590 unsigned int SOUND::m_nChannels = 0;
591 unsigned int SOUND::m_nBlockCount = 0;
592 unsigned int SOUND::m_nBlockSamples = 0;
593 unsigned int SOUND::m_nBlockCurrent = 0;
594 short* SOUND::m_pBlockMemory = nullptr;
595 WAVEHDR *SOUND::m_pWaveHeaders = nullptr;
596 HWAVEOUT SOUND::m_hwDevice;
597 std::atomic<unsigned int> SOUND::m_nBlockFree = 0;
598 std::condition_variable SOUND::m_cvBlockNotZero;
599 std::mutex SOUND::m_muxBlockNotZero;
600}
601
602#elif defined(USE_ALSA)
603
604namespace olc
605{
606 bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
607 {
608 // Initialise Sound Engine
609 m_bAudioThreadActive = false;
610 m_nSampleRate = nSampleRate;
611 m_nChannels = nChannels;
612 m_nBlockSamples = nBlockSamples;
613 m_pBlockMemory = nullptr;
614
615 // Open PCM stream
616 int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0);
617 if (rc < 0)
618 return DestroyAudio();
619
620
621 // Prepare the parameter structure and set default parameters
622 snd_pcm_hw_params_t *params;
623 snd_pcm_hw_params_alloca(&params);
624 snd_pcm_hw_params_any(m_pPCM, params);
625
626 // Set other parameters
627 snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED);
628 snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE);
629 snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0);
630 snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels);
631 snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0);
632 snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0);
633
634 // Save these parameters
635 rc = snd_pcm_hw_params(m_pPCM, params);
636 if (rc < 0)
637 return DestroyAudio();
638
639 listActiveSamples.clear();
640
641 // Allocate Wave|Block Memory
642 m_pBlockMemory = new short[m_nBlockSamples];
643 if (m_pBlockMemory == nullptr)
644 return DestroyAudio();
645 std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0);
646
647 // Unsure if really needed, helped prevent underrun on my setup
648 snd_pcm_start(m_pPCM);
649 for (unsigned int i = 0; i < nBlocks; i++)
650 rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512);
651
652 snd_pcm_start(m_pPCM);
653 m_bAudioThreadActive = true;
654 m_AudioThread = std::thread(&SOUND::AudioThread);
655
656 return true;
657 }
658
659 // Stop and clean up audio system
661 {
662 m_bAudioThreadActive = false;
663 if(m_AudioThread.joinable())
664 m_AudioThread.join();
665 snd_pcm_drain(m_pPCM);
666 snd_pcm_close(m_pPCM);
667 return false;
668 }
669
670
671 // Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
672 // with audio data. If no requests are available it goes dormant until the sound
673 // card is ready for more data. The block is fille by the "user" in some manner
674 // and then issued to the soundcard.
675 void SOUND::AudioThread()
676 {
677 m_fGlobalTime = 0.0f;
678 static float fTimeStep = 1.0f / (float)m_nSampleRate;
679
680 // Goofy hack to get maximum integer for a type at run-time
681 short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
682 float fMaxSample = (float)nMaxSample;
683 short nPreviousSample = 0;
684
685 while (m_bAudioThreadActive)
686 {
687 short nNewSample = 0;
688
689 auto clip = [](float fSample, float fMax)
690 {
691 if (fSample >= 0.0)
692 return fmin(fSample, fMax);
693 else
694 return fmax(fSample, -fMax);
695 };
696
697 for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels)
698 {
699 // User Process
700 for (unsigned int c = 0; c < m_nChannels; c++)
701 {
702 nNewSample = (short)(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample;
703 m_pBlockMemory[n + c] = nNewSample;
704 nPreviousSample = nNewSample;
705 }
706 }
707
708 m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples;
709
710 // Send block to sound device
711 snd_pcm_uframes_t nLeft = m_nBlockSamples;
712 short *pBlockPos = m_pBlockMemory;
713 while (nLeft > 0)
714 {
715 int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft);
716 if (rc > 0)
717 {
718 pBlockPos += rc * m_nChannels;
719 nLeft -= rc;
720 }
721 if (rc == -EAGAIN) continue;
722 if (rc == -EPIPE) // an underrun occured, prepare the device for more data
723 snd_pcm_prepare(m_pPCM);
724 }
725 }
726 }
727
728 snd_pcm_t* SOUND::m_pPCM = nullptr;
729 unsigned int SOUND::m_nSampleRate = 0;
730 unsigned int SOUND::m_nChannels = 0;
731 unsigned int SOUND::m_nBlockSamples = 0;
732 short* SOUND::m_pBlockMemory = nullptr;
733}
734
735#elif defined(USE_OPENAL)
736
737namespace olc
738{
739 bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
740 {
741 // Initialise Sound Engine
742 m_bAudioThreadActive = false;
743 m_nSampleRate = nSampleRate;
744 m_nChannels = nChannels;
745 m_nBlockCount = nBlocks;
746 m_nBlockSamples = nBlockSamples;
747 m_pBlockMemory = nullptr;
748
749 // Open the device and create the context
750 m_pDevice = alcOpenDevice(NULL);
751 if (m_pDevice)
752 {
753 m_pContext = alcCreateContext(m_pDevice, NULL);
754 alcMakeContextCurrent(m_pContext);
755 }
756 else
757 return DestroyAudio();
758
759 // Allocate memory for sound data
760 alGetError();
761 m_pBuffers = new ALuint[m_nBlockCount];
762 alGenBuffers(m_nBlockCount, m_pBuffers);
763 alGenSources(1, &m_nSource);
764
765 for (unsigned int i = 0; i < m_nBlockCount; i++)
766 m_qAvailableBuffers.push(m_pBuffers[i]);
767
768 listActiveSamples.clear();
769
770 // Allocate Wave|Block Memory
771 m_pBlockMemory = new short[m_nBlockSamples];
772 if (m_pBlockMemory == nullptr)
773 return DestroyAudio();
774 std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0);
775
776 m_bAudioThreadActive = true;
777 m_AudioThread = std::thread(&SOUND::AudioThread);
778 return true;
779 }
780
781 // Stop and clean up audio system
783 {
784 m_bAudioThreadActive = false;
785 if(m_AudioThread.joinable())
786 m_AudioThread.join();
787
788 alDeleteBuffers(m_nBlockCount, m_pBuffers);
789 delete[] m_pBuffers;
790 alDeleteSources(1, &m_nSource);
791
792 alcMakeContextCurrent(NULL);
793 alcDestroyContext(m_pContext);
794 alcCloseDevice(m_pDevice);
795 return false;
796 }
797
798
799 // Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
800 // with audio data. If no requests are available it goes dormant until the sound
801 // card is ready for more data. The block is fille by the "user" in some manner
802 // and then issued to the soundcard.
803 void SOUND::AudioThread()
804 {
805 m_fGlobalTime = 0.0f;
806 static float fTimeStep = 1.0f / (float)m_nSampleRate;
807
808 // Goofy hack to get maximum integer for a type at run-time
809 short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1;
810 float fMaxSample = (float)nMaxSample;
811 short nPreviousSample = 0;
812
813 std::vector<ALuint> vProcessed;
814
815 while (m_bAudioThreadActive)
816 {
817 ALint nState, nProcessed;
818 alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState);
819 alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed);
820
821 // Add processed buffers to our queue
822 vProcessed.resize(nProcessed);
823 alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data());
824 for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf);
825
826 // Wait until there is a free buffer (ewww)
827 if (m_qAvailableBuffers.empty()) continue;
828
829 short nNewSample = 0;
830
831 auto clip = [](float fSample, float fMax)
832 {
833 if (fSample >= 0.0)
834 return fmin(fSample, fMax);
835 else
836 return fmax(fSample, -fMax);
837 };
838
839 for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels)
840 {
841 // User Process
842 for (unsigned int c = 0; c < m_nChannels; c++)
843 {
844 nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample);
845 m_pBlockMemory[n + c] = nNewSample;
846 nPreviousSample = nNewSample;
847 }
848
849 m_fGlobalTime = m_fGlobalTime + fTimeStep;
850 }
851
852 // Fill OpenAL data buffer
853 alBufferData(
854 m_qAvailableBuffers.front(),
855 m_nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
856 m_pBlockMemory,
857 2 * m_nBlockSamples,
858 m_nSampleRate
859 );
860 // Add it to the OpenAL queue
861 alSourceQueueBuffers(m_nSource, 1, &m_qAvailableBuffers.front());
862 // Remove it from ours
863 m_qAvailableBuffers.pop();
864
865 // If it's not playing for some reason, change that
866 if (nState != AL_PLAYING)
867 alSourcePlay(m_nSource);
868 }
869 }
870
871 std::queue<ALuint> SOUND::m_qAvailableBuffers;
872 ALuint *SOUND::m_pBuffers = nullptr;
873 ALuint SOUND::m_nSource = 0;
874 ALCdevice *SOUND::m_pDevice = nullptr;
875 ALCcontext *SOUND::m_pContext = nullptr;
876 unsigned int SOUND::m_nSampleRate = 0;
877 unsigned int SOUND::m_nChannels = 0;
878 unsigned int SOUND::m_nBlockCount = 0;
879 unsigned int SOUND::m_nBlockSamples = 0;
880 short* SOUND::m_pBlockMemory = nullptr;
881}
882
883#else // Some other platform
884
885namespace olc
886{
887 bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples)
888 {
889 return true;
890 }
891
892 // Stop and clean up audio system
894 {
895 return false;
896 }
897
898
899 // Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
900 // with audio data. If no requests are available it goes dormant until the sound
901 // card is ready for more data. The block is fille by the "user" in some manner
902 // and then issued to the soundcard.
903 void SOUND::AudioThread()
904 { }
905}
906
907#endif
908#endif
909#endif // OLC_PGEX_SOUND
Definition olcPixelGameEngine.h:1615
Definition olcPixelGameEngine.h:1015
ResourceBuffer GetFileBuffer(const std::string &sFile)
Definition olcPGEX_Sound.h:132
AudioSample(std::string sWavFile, olc::ResourcePack *pack=nullptr)
long nSamples
Definition olcPGEX_Sound.h:141
bool bSampleValid
Definition olcPGEX_Sound.h:143
olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack=nullptr)
float * fSample
Definition olcPGEX_Sound.h:140
int nChannels
Definition olcPGEX_Sound.h:142
OLC_WAVEFORMATEX wavHeader
Definition olcPGEX_Sound.h:139
Definition olcPGEX_Sound.h:128
static void PlaySample(int id, bool bLoop=false)
static bool InitialiseAudio(unsigned int nSampleRate=44100, unsigned int nChannels=1, unsigned int nBlocks=8, unsigned int nBlockSamples=512)
static bool DestroyAudio()
static void SetUserFilterFunction(std::function< float(int, float, float)> func)
static int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack=nullptr)
static void StopAll()
static std::list< sCurrentlyPlayingSample > listActiveSamples
Definition olcPGEX_Sound.h:155
static void StopSample(int id)
static void SetUserSynthFunction(std::function< float(int, float, float)> func)
static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep)
Definition olcPixelGameEngine.h:593
rcode
Definition olcPixelGameEngine.h:917
@ OK
Definition olcPixelGameEngine.h:917
@ FAIL
Definition olcPixelGameEngine.h:917
Definition olcPGEX_Sound.h:113
uint16_t nBlockAlign
Definition olcPGEX_Sound.h:118
uint32_t nAvgBytesPerSec
Definition olcPGEX_Sound.h:117
uint16_t nChannels
Definition olcPGEX_Sound.h:115
uint16_t wFormatTag
Definition olcPGEX_Sound.h:114
uint16_t cbSize
Definition olcPGEX_Sound.h:120
uint32_t nSamplesPerSec
Definition olcPGEX_Sound.h:116
uint16_t wBitsPerSample
Definition olcPGEX_Sound.h:119
Definition olcPixelGameEngine.h:1009
Definition olcPGEX_Sound.h:147
bool bFlagForStop
Definition olcPGEX_Sound.h:152
bool bLoop
Definition olcPGEX_Sound.h:151
bool bFinished
Definition olcPGEX_Sound.h:150
long nSamplePosition
Definition olcPGEX_Sound.h:149
int nAudioSampleID
Definition olcPGEX_Sound.h:148