/* olcPGEX_AudioListener.h +-------------------------------------------------------------+ | OneLoneCoder Pixel Game Engine Extension | | AudioListener v1.0 | +-------------------------------------------------------------+ What is this? ~~~~~~~~~~~~~ This is an extension to the olcPixelGameEngine v2.16 and above. Its purpose is to allow audio integration into PGE which is compatible with Emscripten for Web applications also. Unlike my other extensions, this one requires some external libraries and additional setup (refer to github for instructions). In addition to the libraries the following files are required: olcPGEX_AudioListener.h (this file) olcPGEX_AudioSource.h These can be found in the github repo as well... What it can do: ~~~~~~~~~~~~~~~ Play Audio! Using SDL2 as a backend and SoLoud as a frontend, this extension allows you to easily load and manipulate sound in a way that is cross platform and also has web support (using emscripten for PGE). In addition to the standard PLAY, PAUSE, and STOP controls you can also adjust VOLUME settings and MODULATION (playback speed). WAV and OGG are supported on most backends, however MP3 is also supported by SoLoud and SDL2 (even inside the web browser!). Limitations: ~~~~~~~~~~~~ Requires SDL2, SDL2_Mixer, and SoLoud libraries be installed and linked successfully (detailed instructions on the github repo). How Does It Work? ~~~~~~~~~~~~~~~~~ Once you have followed the setup instructions on the github repo and successfully compiled the test program you are ready to follow these instructions... Add the following defines / includes underneath your olcPixelGameEngine include: #define AUDIO_LISTENER_IMPLEMENTATION #include "olcPGEX_AudioListener.h" #define AUDIO_SOURCE_IMPLEMENTATION #include "olcPGEX_AudioSource.h" (Order matters here, they must be included exactly as above!) In your declarations add exactly (1) AudioListener olcPGEX_AudioListener AL{}; (Note: Currently, only 1 instance of an AudioListener is permitted) Now add at least (1) AudioSource (you will eventually add many of these) olcPGEX_AudioSource AS_Test{}; In the OnUserCreate function you must now initialise the AudioListener AL.AudioSystemInit(); Next we can assign our AudioListener to our AudioSource and load an audio file AS_Test.AL = &game.AL; AS_Test.LoadAudioSample(1, "./assets/mus/Test.mp3"); This assumes you have an MP3 file called "Test.mp3" in the listed folder in your project directory. Also note the ID is set to "1" in this example. It is recommended that you assign labels to your audio files as IDs instead so you can more easily keep track of them. For example enum AUDIO { NULL_SND = 0, // used as a default case TEST_SND = 1, // add other sounds here }; AS_Test.LoadAudioSample(TEST_SND, "./assets/mus/Test.mp3"); This way you can easily refer to your sound without having to remember the integer value you assigned it in the beginning... Now all that is left to do is play the sound... In OnUserUpdate we can play / stop the sound using the SPACEBAR like so if (GetKey(olc::Key::SPACE).bPressed) { if (AS_Test.bIsPlaying) AS_Test.Stop(); else AS_Test.Play(); } Those are the basics... other features can be accessed in much the same way. Enjoy! License (OLC-3) ~~~~~~~~~~~~~~~ Copyright 2018 - 2019 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions or derivations of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions or derivative works in binary form must reproduce the above copyright notice. This list of conditions and the following disclaimer must be reproduced in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Author ~~~~~~ Justin Richards */ #pragma once #ifndef OLC_PGEX_AUDIO_LISTENER #define OLC_PGEX_AUDIO_LISTENER #include "soloud.h" #include "soloud_wav.h" struct PlayingInstance{ vf2d pos; float vol; int handle; }; class olcPGEX_AudioListener : public olc::PGEX { public: // Struct to keep the Audio Sample data together struct sAudioSample { sAudioSample(int ID, SoLoud::Wav* wavPtr) { nSampleID = ID; wav = wavPtr; } int nSampleID; SoLoud::Wav* wav; }; // SoLoud Audio Engine Object SoLoud::Soloud soloud; // Position used for volume calculations olc::vf2d vecPos = { 0.0f, 0.0f }; // Global volume settings float fMusicVolume = 1.f; float fSoundFXVolume = 1.f; bool bMusicOn = true; bool bSoundOn = true; // Vector of Audio Samples std::vector audioSamples; std::vector handles; std::list wavs; // Initialise the Audio Engine, and Destroy it when done void AudioSystemInit(); void AudioSystemDestroy(); // Load a file and store it in the list of wavs void LoadAudioSample(int ID, const char* fileName); // Identify a particular Audio Sample based on its ID sAudioSample* GetAudioSampleByID(int ID); // Update the spacial position of the Audio Listener void UpdatePosition(olc::vf2d pos); // Calculate distance between listener and source float GetDistance(olc::vf2d sourcePos, bool returnRoot = true); void OnUserUpdate(float fElapsedTime); }; #ifdef AUDIO_LISTENER_IMPLEMENTATION #undef AUDIO_LISTENER_IMPLEMENTATION void olcPGEX_AudioListener::AudioSystemInit() { // Initialise the SoLoud backend soloud.init(); } void olcPGEX_AudioListener::AudioSystemDestroy() { // Clean up the SoLoud engine soloud.deinit(); } void olcPGEX_AudioListener::LoadAudioSample(int ID, const char* fileName) { // Search for any matching IDs for (auto& a : audioSamples) if (a.nSampleID == ID) return; // Audio Sample is already loaded, no need to load the same file twice! // Add a new wav to the list of wavs and get a pointer to it wavs.push_back(SoLoud::Wav()); SoLoud::Wav* wavPtr = &wavs.back(); // Use the pointer to load the file into the back of the wav list wavPtr->load(fileName); // Create a new Audio sample object in the vector of samples that links both the ID and wav file itself, for convenience audioSamples.emplace_back(sAudioSample(ID, wavPtr)); } olcPGEX_AudioListener::sAudioSample* olcPGEX_AudioListener::GetAudioSampleByID(int ID) { // Look for matching IDs for (auto& a : audioSamples) if (ID == a.nSampleID) return &a; // Match found! Return it... // No match found, need to return a reference so we return the first sample in the list return &audioSamples[0]; } void olcPGEX_AudioListener::UpdatePosition(olc::vf2d pos) { // Position vecPos = pos; } float olcPGEX_AudioListener::GetDistance(olc::vf2d sourcePos, bool returnRoot) { // Return the distance via square root if needed, or the squared version when optimisation is possible if (returnRoot) return sqrtf(abs(sourcePos.x * sourcePos.x - vecPos.x * vecPos.x) + abs(sourcePos.y * sourcePos.y - vecPos.y * vecPos.y)); else return abs(sourcePos.x * sourcePos.x - vecPos.x * vecPos.x) + abs(sourcePos.y * sourcePos.y - vecPos.y * vecPos.y); } void olcPGEX_AudioListener::OnUserUpdate(float fElapsedTime){ std::erase_if(handles,[&](PlayingInstance&handle){return !soloud.isValidVoiceHandle(handle.handle);}); for(PlayingInstance&handle:handles){ soloud.setPan(handle.handle,std::clamp((handle.pos.x-vecPos.x)/1024,-1.f,1.f)); soloud.setVolume(handle.handle,handle.vol*std::max(0.f,abs(1-std::min(1.0f,(GetDistance(handle.pos)/1024.f))))*(handle.pos!=vf2d{0,0}?fSoundFXVolume:1)); } } #endif // AUDIO_LISTENER_IMPLEMENTATION #endif