/* Copyright (c) 2023 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #ifndef __AudioManager_H__ #define __AudioManager_H__ #include "audioplayer/AudioPlayer.h" #include "pcmaudio/PCMAudio.h" #include #include typedef uint32_t SoundID; typedef uint32_t MusicID; #define MUSIC_ID_NONE UINT32_MAX #define SOUND_ID_NONE UINT32_MAX typedef PCMAudio * (* AudioManager_loadSoundCallback)(SoundID soundID, void * context); typedef PCMAudio * (* AudioManager_loadMusicCallback)(MusicID musicID, AudioFrameIndex * outLoopDuration, void * context); // Call after AudioPlayer_init(), and before calling any other AudioManager functions. threadCount and // taskCountMaxPerThread are used to initialize an AsyncTaskQueue which will be used for loading sounds and music. // loadSoundCallback and loadMusicCallback must take a sound/music ID and synchronously return PCM audio data // representing that sound or music. loadMusicCallback must write outLoopDuration to define a loop point. // In order to use loadAllAudioAsync(), both functions must be safe to call in a secondary thread. // To avoid synchronous loading at the time loadAllAudioAsync() is called, ensure that the total number of // registered sounds and music together does not exceed threadCount * taskCountMaxPerThread. // musicLaneCount specifies the number of music streams that can be playing simultaneously, not counting any // residual crossfades. All playing music tracks are assigned a lane number; if different music is started in a // lane where music is already playing, the existing music will be interrupted. If two music tracks are started // in different lanes, they will overlap each other without interrupting. void AudioManager_init(unsigned int musicLaneCount, unsigned int threadCount, unsigned int taskCountMaxPerThread, AudioManager_loadSoundCallback loadSoundCallback, AudioManager_loadMusicCallback loadMusicCallback, void * callbackContext); // After init but before playing any sounds or music, all sound and music IDs should be registered with this pair // of functions. Sounds or music registered with higher loadPriority values will be queued for asynchronous // loading earlier than sounds or music with lower loadPriority values. void AudioManager_registerSound(SoundID soundID, AudioPlayer_category category, int loadPriority); void AudioManager_registerMusic(MusicID musicID, AudioPlayer_category category, int loadPriority); // This function can be used to specify that for the specified pair of sound IDs, when playSound() calls are // bracketed between calls to beginSoundGroup() and endSoundGroup(), overriddenSoundID will be ignored and will // not play if overridingSoundID is to be played at the same time. void AudioManager_registerSoundOverride(SoundID overridingSoundID, SoundID overriddenSoundID); // Specifies that if interruptingSoundID is played after an instance of interruptedSoundID, the playing instance // of interruptedSoundID should be canceled if it was played in the previous sound group. void AudioManager_registerSoundInterrupt(SoundID interruptingSoundID, SoundID interruptedSoundID); // Begins asynchronous loading of all registered sounds and music in priority order. If a sound or music is // played before it has naturally finished loading, it will be immediately loaded on the main thread. void AudioManager_loadAllAudioAsync(void); // Synchronously loads all registered sounds and music. This function blocks until everything has completed. // Call either this function or loadAllAudioAsync() only once, after all sounds and music have been registered. void AudioManager_loadAllAudioImmediate(void); // Immediately plays a sound that was previously registered by registerSound(), unless beginSoundGroup() has been // called, in which case playback will be deferred until endSoundGroup() has been called. void AudioManager_playSound(SoundID soundID); // This pair of functions can be used to collect a series of calls to playSound() for synchronization and // filtering prior to playing them all at once. Multiple calls to playSound() with the same sound ID are squashed // into a single sound when bracketed by these two functions, and registered sound overrides are applied. void AudioManager_beginSoundGroup(void); void AudioManager_endSoundGroup(void); // Stops the currently playing music track in the specified lane, if any, and begins playing the specified one. // If musicID is the same as the currently playing music in the specified lane, nothing happens. If musicID is // MUSIC_ID_NONE, this function has the same effect as stopMusic(). If resume is true and the same musicID was // previously played and stopped (either by stopMusic() or playMusic() with a different musicID), playback will // resume from the point at which it stopped. // // If allowAsyncLoad is true and the requested music is currently being loaded by a background thread, playback // will be queued to start when loading has completed instead of starting immediately. If allowAsyncLoad is false // and the requested music is currently being loaded by a background thread, the requested music will be loaded // synchronously and played on completion. If the requested music is already loaded, playback starts immediately. // // Fade durations are applied as follows: // - If music is currently playing, its volume will be ramped down toward zero over the interval specified by // fadeOutDuration. // - If a fade out is happening, an amount of overlap specified by crossfadeDuration between the music being // stopped and the new music being started will be allowed. If crossfadeDuration is 0, the entire fadeout of // the currently playing music will complete before the new music starts playing. // - If the new music is being resumed (resume is true and a previous playback time was established), its volume // will be ramped up from zero over the inverval specified by fadeInDuration. Fadein is never applied if the // new music is not being resumed from previous playback. // Some example situations: // - fadeOutDuration, crossfadeDuration, and fadeInDuration are all 0. Current music stops playing and new music // starts playing instantly, with no fades on either side. // - fadeOutDuration is 0.5, crossfadeDuration is 0, fadeInDuration is 0.5, and the new music to be played has a // previously established resume point. The current music track's volume ramps down to zero over the next half // second, then the new music track begins ramping its volume up from zero over the half second afterward. // - fadeOutDuration is 1, crossfadeDuration is 0.5, fadeInDuration is 1, and the new music to be played has a // previously established resume point. The current music track's volume ramps down to half over 0.5 seconds, // then continues to ramp down to 0 over the next 0.5 seconds while the new music track simultaneously ramps // from zero to half volume, then the new music track ramps up from half to full volume over the next 0.5 // seconds. void AudioManager_playMusic(MusicID musicID, unsigned int lane, bool loop, bool resume, bool allowAsyncLoad, double fadeOutDuration, double crossfadeDuration, double fadeInDuration); // Pauses the currently playing music track, if any. If fadeOutDuration is nonzero, the music will not be paused // immediately, but instead ramp its volume down to zero over the specified duration. void AudioManager_pauseMusic(unsigned int lane, double fadeOutDuration); // Resumes playback of music previous paused with pauseMusic(). If fadeInDuration is nonzero, music volume will // be ramped up to zero over the specified duration. void AudioManager_unpauseMusic(unsigned int lane, double fadeInDuration); // Stops the currently playing music track, setting its resume point to the playback position where it was // stopped. If fadeOutDuration is nonzero, the music will not be stopped immediately, but instead ramp its // volume down to zero over the specified duration. void AudioManager_stopMusic(unsigned int lane, double fadeOutDuration); // Returns the identifier of the music currently playing, or MUSIC_ID_NONE if nothing is playing. MusicID AudioManager_getPlayingMusicID(unsigned int lane); // Returns the audio data loaded for soundID, synchronously loading it first if necessary. PCMAudio * AudioManager_getSoundData(SoundID soundID); // Returns the audio data loaded for musicID, synchronously loading it first if necessary. PCMAudio * AudioManager_getMusicData(MusicID musicID); // Returns the number of frames in one loop of the specified music track. This may differ from frameCount in // the PCM data of the music; it measures a logical amount of time that lines up with the final measure // boundary, rather than the physical amount of time for all notes in the music track to end naturally. AudioFrameIndex AudioManager_getMusicLoopDuration(MusicID musicID); #endif