You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
VirusAttack/olcCodeJam2023Entry/olcPGEX_AudioListener.h

302 lines
8.4 KiB

/*
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<sAudioSample> audioSamples;
std::vector<PlayingInstance> handles;
std::list<SoLoud::Wav> 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