/* Copyright (c) 2019 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 __AudioPlayer_H__ #define __AudioPlayer_H__ #ifdef __cplusplus extern "C" { #endif #include #include "nativeaudio/AudioOut.h" #include "pcmaudio/PCMAudio.h" #include "pcmaudio/PCMAudioStream.h" #include "utilities/Atom.h" #include #include typedef unsigned int AudioPlayer_category; typedef unsigned int AudioPlayer_soundInstanceID; #define AUDIO_PLAYER_INSTANCE_ID_NONE ((AudioPlayer_soundInstanceID) UINT_MAX) typedef PCMAudio * (* AudioPlayer_referenceSoundEffectDataCallback)(Atom identifier, void * context); typedef void (* AudioPlayer_releaseSoundEffectDataCallback)(Atom identifier, PCMAudio * data, void * context); // Must be called before calling any other AudioPlayer functions. If you call this function, AudioPlayer expects to be // the sole caller of AudioOut functions for the program's lifetime, so it's recommended not to directly call any // AudioOut_*() functions while using it. AudioPlayer will initialize AudioOut and set its host and transport format // to the optimal settings for its purposes. // referenceSoundEffectDataCallback is called to retrieve an instance of audio data to play when // AudioPlayer_playSoundEffect() is called. Once that sound effect has finished playing, the specified // releaseSourceEffectDataCallback is invoked with its identifier and data. // sourceCountMax and streamCountMax respectively determine the total number of sound effects and audio streams that // can be playing simultaneously. Larger values of these have performance implications, so it's recommended to choose // the lowest reasonable values that will allow you to play as many concurrent sounds as your program needs. // categoryCount determines the number of separate volume controls that can be manipulated by // AudioPlayer_setCategoryVolume(). Values passed in the category parameter to AudioPlayer_playSoundEffect() or // AudioPlayer_startAudioStream() should be less than the value of categoryCount. // sampleFormat will be requested as AudioOut's host format, and used as its transport format regardless of resulting // host format settings. For optimal performance, this format should match the format of the majority of audio data // to be played by AudioPlayer. Other formats will be converted automatically at a cost to performance and quality. void AudioPlayer_init(const char * processName, unsigned int sourceCountMax, unsigned int streamCountMax, unsigned int categoryCount, AudioOut_sampleFormat sampleFormat, AudioPlayer_referenceSoundEffectDataCallback referenceSoundEffectDataCallback, AudioPlayer_releaseSoundEffectDataCallback releaseSoundEffectDataCallback, void * callbackContext); AudioOut_sampleFormat AudioPlayer_getSampleFormat(void); void AudioPlayer_setSampleFormat(AudioOut_sampleFormat sampleFormat); // When a sound effect is started, the referenceSoundEffectDataCallback provided to AudioPlayer_globalInit() is called, // using soundEffectIdentifier as its first argument in order to load this sound before playing it. To avoid delays, // this function should return as immediately as possible, so it may be advisable to preload sounds that need to be able // to start immediately. releaseSourceEffectDataCallback will be called in AudioPlayer_run() after the sound has // naturally ended. // Returns a globally-unique sound effect instance identifier that can be passed to AudioPlayer_cancelSoundEffect() to // stop just this sound effect before it finishes naturally. If all sources are busy when this function is called, // or the sound effect couldn't be loaded, AUDIO_PLAYER_INSTANCE_ID_NONE is returned. AudioPlayer_soundInstanceID AudioPlayer_playSoundEffect(Atom soundEffectIdentifier, AudioPlayer_category category); // Same behavior as AudioPlayer_playSoundEffect, but instead of fetching the audio by identifier, directly plays the // PCM data specified as the first argument. // If startFrameIndex is a number other than 0, playback will start at that offset from the beginning of the audio. // If endFrameIndex is a number other than 0, playback will end after that frame has been played. // If endedCallback is provided, it will be called on the main thread (from AudioPlayer_run()) after playback has // completed or been canceled. AudioPlayer_soundInstanceID AudioPlayer_playAudioData(PCMAudio * audio, AudioPlayer_category category, float amplitude, AudioFrameIndex startFrameIndex, AudioFrameIndex endFrameIndex, void (* endedCallback)(PCMAudio * audio, void * context), void * callbackContext); // Cancels a single sound effect instance identified by a previous return value from AudioPlayer_playSoundEffect(). void AudioPlayer_cancelSoundEffect(AudioPlayer_soundInstanceID soundInstanceID); // Cancels all sound effect instances playing audio data from the specified pointer as returned from the provided // referenceSoundEffectDataCallback. Note that it is not safe to free this data immediately after calling this // function, as it may still be busy in the audio thread; call AudioPlayer_anyPendingCanceledSoundEffects() to check // whether the audio thread has safely stopped reading from it, and sleep until it returns false if synchronization // is needed. void AudioPlayer_cancelSoundEffectsPlayingAudioData(PCMAudio * audioData); // Cancels all active sound effects. void AudioPlayer_cancelAllSoundEffects(void); // Sound effects canceled with AudioPlayer_cancel*() functions may still be accessed from the audio thread for a short // time. If synchronization is needed (such as to free the audio data being canceled), this function can be called // repeatedly until it returns false, confirming that the audio thread has stopped reading from the audio data of // canceled sound effects. This interval is expected to be short, but may vary depending on the latency of the host // platform's nativeaudio implementation. bool AudioPlayer_anyPendingCanceledSoundEffects(void); // If paused, a playing sound effect's state will be kept, but will not be advanced or output. Must be canceled or // unpaused and allowed to complete to release the slot it occupies. void AudioPlayer_setSoundEffectPaused(AudioPlayer_soundInstanceID soundInstanceID, bool paused); // Adds stream to AudioPlayer's list of playing streams. Note that this does not reset the playback position of the // stream to 0 when called; if you want it to start a specific position, you should call seek() manually on the stream. // Stopping and restarting the same stream will have the effect of pausing and resuming it. // The loop parameter will be passed unchanged to the stream's read() method for the duration of playback. // If lowLatency is true, the stream's read() method will be called inside the audio thread for small chunks of audio // data at a time very close to when it will be sent to the speakers. This should only be used when the implementation // of the stream's seek() method is very fast and needs to change dynamically. For anything static (such as streaming // Vorbis-compressed music from disk), lowLatency should be set to false. When false, streaming audio is buffered // ahead of time in the main thread by AudioPlayer_run(). // Returns a globally-unique audio stream instance identifier that can be passed to AudioPlayer_stopAudioStream(). // If all stream sources are busy when this function is called, AUDIO_PLAYER_INSTANCE_ID_NONE is returned. AudioPlayer_soundInstanceID AudioPlayer_startAudioStream(PCMAudioStream * stream, bool loop, bool lowLatency, AudioPlayer_category category, void (* endedCallback)(PCMAudioStream * stream, bool canceled, void * context), void * callbackContext); void AudioPlayer_stopAudioStream(AudioPlayer_soundInstanceID streamInstanceID); void AudioPlayer_stopAllAudioStreams(void); // If paused, a playing stream's state will be kept, but will not be advanced or output. Must be canceled or // unpaused and allowed to complete to release the slot it occupies. void AudioPlayer_setAudioStreamPaused(AudioPlayer_soundInstanceID streamInstanceID, bool paused); // After calling this function, calls to AudioPlayer_playSoundEffect(), AudioPlayer_playAudioData(), and // AudioPlayer_startAudioStream() will be queued to start after the next call to AudioPlayer_endPlayGroup(). // Use to atomically start playing multiple sounds on the same frame. Must be balanced with corresponding calls // to AudioPlayer_endPlayGroup(). When groups are nested, all groups will be queued together until the top // level group is ended. void AudioPlayer_beginPlayGroup(void); // Commits all calls made to AudioPlayer_playSoundEffect(), AudioPlayer_playAudioData(), and // AudioPlayer_startAudioStream() since the last call to AudioPlayer_beginPlayGroup() to start playing // simultaneously, ensuring that they all start on the same frame regardless of call timing. Must be // balanced with corrsponding calls to AudioPlayer_beginPlayGroup(). void AudioPlayer_endPlayGroup(void); // Call periodically (once per draw call should be sufficient; once per second minimum) to perform main thread // cleanup of audio sources that have naturally ended in the audio thread, and to buffer upcoming chunks of // non-low-latency streaming audio if necessary. void AudioPlayer_run(void); // Note: These functions return true for paused sounds bool AudioPlayer_isSoundEffectPlaying(AudioPlayer_soundInstanceID soundInstanceID); bool AudioPlayer_isAudioStreamPlaying(AudioPlayer_soundInstanceID streamInstanceID); bool AudioPlayer_isAnyAudioPlaying(void); bool AudioPlayer_isSoundEffectPaused(AudioPlayer_soundInstanceID soundInstanceID); bool AudioPlayer_isAudioStreamPaused(AudioPlayer_soundInstanceID streamInstanceID); AudioFrameIndex AudioPlayer_getSoundEffectApproximateFramesPlayed(AudioPlayer_soundInstanceID soundInstanceID); AudioFrameIndex AudioPlayer_getAudioStreamApproximateFramesPlayed(AudioPlayer_soundInstanceID streamInstanceID); // Pauses and unpauses all audio playback. While paused, the state of all playing sounds and streams is kept, but // not advanced until unpaused. void AudioPlayer_pause(void); bool AudioPlayer_isPaused(void); void AudioPlayer_unpause(void); // Sets an amplitude multiplier to be subsequently used for all sound effects and streams assigned to the specified // category. void AudioPlayer_setCategoryVolume(AudioPlayer_category category, float volume); // If true, AudioPlayer will call AudioOut_stopOutput() when all sounds have finished playing, and wait to call // AudioOut_startOutput() when a new sound starts. If false, AudioOut_stopOutput() will only be called when // AudioPlayer_pause() is called. Defaults to true. void AudioPlayer_setPausesAudioDeviceWhenIdle(bool pauseWhenIdle); // By default, AudioPlayer sends its output to AudioOut. If you want to redirect its output somewhere else, this // function can be used to disable automatic output. When speaker output is disabled, call AudioPlayer_readSamples() // to retrieve chunks of mixed waveform data. void AudioPlayer_setSpeakerOutputEnabled(bool outputToSpeakers); // When an audio stream's category volume is set to zero, it may be desireable to not read data from it, thereby // not advancing its playback position. By default, AudioPlayer will continue reading streams at amplitude 0, but // this function can be used to change this behavior. void AudioPlayer_setAdvanceStreamsAtAmplitudeZero(bool advanceAtZero); // Writes a chunk of raw audio to outSamples, of a length specified by frameCount and the sample format that // AudioPlayer was initialized with. Consumes the audio data when read; typically should only be called if speaker // output is disabled. void AudioPlayer_readSamples(void * outSamples, unsigned int frameCount); #ifdef __cplusplus } #endif #endif