The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'! https://forums.lestoria.net
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.
 
 
 
 
 
 
AdventuresInLestoria/Adventures in Lestoria/olcPGEX_MiniAudio.h

506 lines
16 KiB

#pragma once
/*
olcPGEX_MiniAudio.h
+-------------------------------------------------------------+
| OneLoneCoder Pixel Game Engine Extension |
| MiniAudio v1.5 |
+-------------------------------------------------------------+
NOTE: UNDER ACTIVE DEVELOPMENT - THERE MAY BE BUGS/GLITCHES
What is this?
~~~~~~~~~~~~~
This extension abstracts the very robust and powerful miniaudio
library. It provides simple loading and playback of WAV and MP3
files. Because it's built on top of miniaudio, it requires next
to no addictional build configurations in order to be built
for cross-platform.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright© 2024 Moros Smith <moros1138@gmail.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.
Links
~~~~~
YouTube: https://www.youtube.com/@Moros1138
GitHub: https://www.github.com/Moros1138
Homepage: https://www.moros1138.com
*/
#include "olcPixelGameEngine.h"
#include <exception>
#ifdef OLC_PGEX_MINIAUDIO
#define MINIAUDIO_IMPLEMENTATION
#endif
#include "miniaudio.h"
namespace olc
{
class MiniAudio : public olc::PGEX
{
public:
std::string name = "olcPGEX_MiniAudio v1.5";
public:
MiniAudio();
~MiniAudio();
virtual bool OnBeforeUserUpdate(float& fElapsedTime) override;
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount);
static bool backgroundPlay;
public: // CONFIGURATION
// set whether audio will continue playing when the app has lost focus
void SetBackgroundPlay(bool state);
public: // LOADING ROUTINES
enum SoundEffectFlag{
SFX,
BGM,
};
const size_t LoadSound(const std::string& path,const SoundEffectFlag soundType=BGM); //Setting sound effect to true avoids loading it from a resource pack.
const size_t LoadResource(const std::string& path);
void UnloadSound(const int id);
public: // PLAYBACK CONTROLS
// plays a sample, can be set to loop
void Play(const int id, const bool loop = false);
// plays a sound file, as a one off, and automatically unloads it
void Play(const std::string& path,const float&vol=1.0f,const float&pan=0.0f,const float&pitch=1.0f);
// stops a sample, rewinds to beginning
void Stop(const int id);
// pauses a sample, does not change position
void Pause(const int id);
// toggle between play and pause
void Toggle(const int id, bool rewind = false);
public: // SEEKING CONTROLS
// seek to the provided position in the sound, by milliseconds
void Seek(const int id, const unsigned long long milliseconds);
// seek to the provided position in the sound, by float 0.f is beginning, 1.0f is end
void Seek(const int id, const float& location);
// seek forward from current position by the provided time
void Forward(const int id, const unsigned long long milliseconds);
// seek forward from current position by the provided time
void Rewind(const int id, const unsigned long long milliseconds);
public: // MISC CONTROLS
// set volume of a sound, 0.0f is mute, 1.0f is full
void SetVolume(const int id, const float& volume);
// set pan of a sound, -1.0f is left, 1.0f is right, 0.0f is center
void SetPan(const int id, const float& pan);
// set pitch of a sound, 1.0f is normal
void SetPitch(const int id, const float& pitch);
public: // MISC INFORMATION
// determine if a sound is playing
bool IsPlaying(const int id);
// gets the current position in the sound, in milliseconds
unsigned long long GetCursorMilliseconds(const int id);
// gets the current position in the sound, as a float between 0.0f and 1.0f
float GetCursorFloat(const int id);
public: // ADVANCED FEATURES for those who want to use more of miniaudio
// gets the currently loaded persistent sounds
const std::vector<ma_sound*>& GetSounds() const;
// gets the currently loaded one-off sounds
const std::vector<ma_sound*>& GetOneOffSounds() const;
// gets a pointer to the ma_device
ma_device* GetDevice();
// gets a pointer to the ma_engine
ma_engine* GetEngine();
private:
/*
Soooo, i'm not going to spend a whole lot of time
documenting miniaudio features, if you want to
know more I invite you to visit their very very
nicely documented webiste at:
https://miniaud.io/docs/manual/index.html
*/
ma_device device;
ma_engine engine;
ma_resource_manager resourceManager;
// sample rate for the device and engine
int sampleRate;
// this is where the sounds are kept
std::vector<ma_sound*> vecSounds;
std::vector<ma_sound*> vecOneOffSounds;
struct ResourceData{
ma_engine*engine;
ResourceBuffer data;
std::string pathName;
~ResourceData(){
if(ma_resource_manager_unregister_data(ma_engine_get_resource_manager(engine),pathName.data())!=MA_SUCCESS)ERR(std::format("WARNING! Could not clear resources for {}!",pathName));
};
};
std::unordered_map<size_t,ResourceData>vecResourceData;
};
/**
* EXCEPTIONS, long story short. I needed to be able
* to construct the PGEX in a state where it could
* fail at runtime. If you have a better way of
* accomplishing the PGEX pattern without using
* exceptions, I'm open to suggestions!
*/
struct MiniAudioDeviceException : public std::exception
{
const char* what() const throw()
{
return "Failed to initialize a device.";
}
};
struct MiniAudioResourceManagerException : public std::exception
{
const char* what() const throw()
{
return "Failed to initialize the resource manager.";
}
};
struct MiniAudioEngineException : public std::exception
{
const char* what() const throw()
{
return "Failed to initialize the audio engine.";
}
};
struct MiniAudioSoundException : public std::exception
{
const char* what() const throw()
{
return "Failed to initialize a sound.";
}
};
}
#ifdef OLC_PGEX_MINIAUDIO
#undef OLC_PGEX_MINIAUDIO
#include "AdventuresInLestoria.h"
INCLUDE_game
namespace olc
{
bool MiniAudio::backgroundPlay = false;
MiniAudio::MiniAudio() : olc::PGEX(true)
{
sampleRate = 48000;
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = ma_format_f32;
deviceConfig.playback.channels = 2;
deviceConfig.sampleRate = sampleRate;
deviceConfig.dataCallback = MiniAudio::data_callback;
deviceConfig.pUserData = &engine;
if(ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS)
throw MiniAudioDeviceException();
ma_resource_manager_config resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = ma_format_f32;
resourceManagerConfig.decodedChannels = 0;
resourceManagerConfig.decodedSampleRate = sampleRate;
#ifdef __EMSCRIPTEN__
resourceManagerConfig.jobThreadCount = 0;
resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING;
resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING;
#endif
if(ma_resource_manager_init(&resourceManagerConfig, &resourceManager) != MA_SUCCESS)
throw MiniAudioResourceManagerException();
ma_engine_config engineConfig = ma_engine_config_init();
engineConfig.pDevice = &device;
engineConfig.pResourceManager = &resourceManager;
if(ma_engine_init(&engineConfig, &engine) != MA_SUCCESS)
throw MiniAudioEngineException();
MiniAudio::backgroundPlay = false;
}
MiniAudio::~MiniAudio()
{
for(auto sound : vecSounds)
{
if(sound != nullptr)
{
ma_sound_uninit(sound);
delete sound;
}
}
ma_resource_manager_uninit(&resourceManager);
ma_engine_uninit(&engine);
}
bool MiniAudio::OnBeforeUserUpdate(float& fElapsedTime)
{
#ifdef __EMSCRIPTEN__
ma_resource_manager_process_next_job(&resourceManager);
#endif
for(int i = 0; i < vecOneOffSounds.size(); i++)
{
if(!ma_sound_is_playing(vecOneOffSounds.at(i)))
{
ma_sound_uninit(vecOneOffSounds.at(i));
vecOneOffSounds.erase(vecOneOffSounds.begin() + i);
break;
}
}
return false;
}
void MiniAudio::data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
if(!MiniAudio::backgroundPlay && !pge->IsFocused())
return;
ma_engine_read_pcm_frames((ma_engine*)(pDevice->pUserData), pOutput, frameCount, NULL);
}
void MiniAudio::SetBackgroundPlay(bool state)
{
MiniAudio::backgroundPlay = state;
}
const size_t MiniAudio::LoadSound(const std::string& path,const SoundEffectFlag soundType)
{
// create the sound
ma_sound* sound = new ma_sound();
// assume no empty slots...
size_t id = vecSounds.size();
bool foundSound{false};
// attempt to re-use an empty slot
for(int i = 0; i < vecSounds.size(); i++)
{
if(vecSounds.at(i) == nullptr)
{
vecSounds.at(i) = sound;
id=i;
foundSound=true;
break;
}
}
if(!foundSound)vecSounds.emplace_back(sound);
if(soundType==BGM){
vecResourceData[id]={&engine,game->gamepack.GetFileBuffer(path),path};
if(ma_resource_manager_register_encoded_data(ma_engine_get_resource_manager(&engine),path.data(),vecResourceData[id].data.vMemory.data(),vecResourceData[id].data.vMemory.size())!=MA_SUCCESS)ERR(std::format("WARNING! Could not load resources for {}!",path));
}
if(ma_sound_init_from_file(&engine, path.c_str(), MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, NULL, NULL, sound) != MA_SUCCESS)
throw MiniAudioSoundException();
return id;
}
void MiniAudio::UnloadSound(const int id)
{
ma_sound_uninit(vecSounds.at(id));
delete vecSounds.at(id);
vecSounds.at(id) = nullptr;
if(vecResourceData.count(id))vecResourceData.erase(id);
}
void MiniAudio::Play(const int id, const bool loop)
{
if(ma_sound_is_playing(vecSounds.at(id)))
{
ma_sound_seek_to_pcm_frame(vecSounds.at(id), 0);
return;
}
ma_sound_set_looping(vecSounds.at(id), loop);
ma_sound_start(vecSounds.at(id));
}
void MiniAudio::Play(const std::string& path,const float&vol,const float&pan,const float&pitch)
{
// create the sound
ma_sound* sound = new ma_sound();
// load it from the file and decode it
if(ma_sound_init_from_file(&engine, path.c_str(), MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, NULL, NULL, sound) != MA_SUCCESS)
throw MiniAudioSoundException();
ma_sound_set_volume(sound,vol);
ma_sound_set_pan(sound,pan);
ma_sound_set_pitch(sound,pitch);
ma_sound_start(sound);
vecOneOffSounds.push_back(sound);
}
void MiniAudio::Stop(const int id)
{
ma_sound_seek_to_pcm_frame(vecSounds.at(id), 0);
ma_sound_stop(vecSounds.at(id));
}
void MiniAudio::Pause(const int id)
{
auto it = vecSounds.begin() + id;
ma_sound_stop(vecSounds.at(id));
}
void MiniAudio::Toggle(const int id, bool rewind)
{
if(ma_sound_is_playing(vecSounds.at(id)))
{
ma_sound_stop(vecSounds.at(id));
if(rewind)
ma_sound_seek_to_pcm_frame(vecSounds.at(id), 0);
return;
}
ma_sound_start(vecSounds.at(id));
}
void MiniAudio::Seek(const int id, const unsigned long long milliseconds)
{
unsigned long long frame = (milliseconds * engine.sampleRate) / 1000;
ma_sound_seek_to_pcm_frame(vecSounds.at(id), frame);
}
void MiniAudio::Seek(const int id, const float& location)
{
unsigned long long length;
ma_sound_get_length_in_pcm_frames(vecSounds.at(id), &length);
unsigned long long frame = length * location;
ma_sound_seek_to_pcm_frame(vecSounds.at(id), frame);
}
void MiniAudio::Forward(const int id, const unsigned long long milliseconds)
{
unsigned long long cursor;
ma_sound_get_cursor_in_pcm_frames(vecSounds.at(id), &cursor);
unsigned long long frame = (milliseconds * engine.sampleRate) / 1000;
ma_sound_seek_to_pcm_frame(vecSounds.at(id), cursor + frame);
}
void MiniAudio::Rewind(const int id, const unsigned long long milliseconds)
{
unsigned long long cursor;
ma_sound_get_cursor_in_pcm_frames(vecSounds.at(id), &cursor);
unsigned long long frame = (milliseconds * engine.sampleRate) / 1000;
ma_sound_seek_to_pcm_frame(vecSounds.at(id), cursor - frame);
}
void MiniAudio::SetVolume(const int id, const float& volume)
{
ma_sound_set_volume(vecSounds.at(id), volume);
}
void MiniAudio::SetPan(const int id, const float& pan)
{
ma_sound_set_pan(vecSounds.at(id), pan);
}
void MiniAudio::SetPitch(const int id, const float& pitch)
{
ma_sound_set_pitch(vecSounds.at(id), pitch);
}
unsigned long long MiniAudio::GetCursorMilliseconds(const int id)
{
unsigned long long cursor;
ma_sound_get_cursor_in_pcm_frames(vecSounds.at(id), &cursor);
cursor *= 1000;
cursor /= sampleRate;
return cursor;
}
bool MiniAudio::IsPlaying(const int id)
{
return ma_sound_is_playing(vecSounds.at(id));
}
float MiniAudio::GetCursorFloat(const int id)
{
unsigned long long cursor;
ma_sound_get_cursor_in_pcm_frames(vecSounds.at(id), &cursor);
unsigned long long length;
ma_sound_get_length_in_pcm_frames(vecSounds.at(id), &length);
return (float)cursor / length;
}
const std::vector<ma_sound*>& MiniAudio::GetSounds() const
{
return vecSounds;
}
const std::vector<ma_sound*>& MiniAudio::GetOneOffSounds() const
{
return vecOneOffSounds;
}
ma_device* MiniAudio::GetDevice()
{
return &device;
}
ma_engine* MiniAudio::GetEngine()
{
return &engine;
}
} // olc
#endif