# 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 {
ResourceBuffer data ;
std : : string 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 ] = { 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 ) ) {
if ( ma_resource_manager_unregister_data ( ma_engine_get_resource_manager ( & engine ) , vecResourceData [ id ] . pathName . data ( ) ) ! = MA_SUCCESS ) ERR ( std : : format ( " WARNING! Could not clear resources for {}! " , vecResourceData [ id ] . pathName ) ) ;
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