2023-07-30 19:18:50 +00:00
///////////////////////////////////////////////////////////////////////////////
// sts_mixer.h - v0.02
// written 2016 by Sebastian Steinhauer
//
// LICENSE
// Public domain. See "unlicense" statement at the end of this file.
//
// ABOUT
// A simple stereo audio mixer which is capable of mixing samples and audio streams.
// Samples can be played with different gain, pitch and panning.
// Streams can be played with different gain.
// This library has no malloc/free. All structs have to be "prepared" by the user. So you can enroll your own memory management.
// You have to implement/provide a real audio-backend to hear something from the speakers.
// A good starting point would be SDL2 where you can use an audio callback to feed the audio device.
//
// USAGE
// Please note that most audio systems will run in a separate thread. So you have to take care about locking before modifying the sts_mixer_t state.
// See the example at the end of the file.
//
// VERSION HISTORY
2023-12-12 10:56:44 +00:00
// 0.03 (2022-12-12) add an ability to stop audio stream via callback, add a method to check if voice is already stopped
2023-07-30 19:18:50 +00:00
// 0.02 (2022-05-10) allow voice queueing in same channel. ie, chain another sample on same voice channel after current sample playback is done (@r-lyeh)
// 0.01 (2016-05-01) initial version
//
# ifndef __INCLUDED__STS_MIXER_H__
# define __INCLUDED__STS_MIXER_H__
// The number of concurrent voices (channels) which are used to mix the audio.
// If you need more, use a higher number by setting #define STS_MIXER_VOICE n before including this header.
# ifndef STS_MIXER_VOICES
# define STS_MIXER_VOICES 32
# endif // STS_MIXER_VOICES
// Defines the various audio formats. Note that they are all on system endianess.
enum {
STS_MIXER_SAMPLE_FORMAT_NONE , // no format
STS_MIXER_SAMPLE_FORMAT_8 , // signed 8-bit
STS_MIXER_SAMPLE_FORMAT_16 , // signed 16-bit
STS_MIXER_SAMPLE_FORMAT_32 , // signed 32-bit
STS_MIXER_SAMPLE_FORMAT_FLOAT // floats
} ;
////////////////////////////////////////////////////////////////////////////////
//
// SAMPLES
//
// A sample is a *MONO* piece of audio which is loaded fully to memory.
// It can be played with various gains, pitches and pannings.
//
typedef struct {
unsigned int length ; // length in samples (so 1024 samples of STS_MIXER_SAMPLE_FORMAT_16 would be 2048 bytes)
unsigned int frequency ; // frequency of this sample (e.g. 44100, 22000 ...)
int audio_format ; // one of STS_MIXER_SAMPLE_FORMAT_*
void * data ; // pointer to the sample data, sts_mixer makes no copy, so you have to keep them in memory
void * next ; // next sample in chain (if any) //< @r-lyeh
} sts_mixer_sample_t ;
////////////////////////////////////////////////////////////////////////////////
//
// STREAMS
//
// A stream is *STEREO* audio which will be decoded/loaded as needed.
// It can be played with various gains. No panning or pitching.
//
// The callback which will be called when the stream needs more data.
2023-12-12 10:56:44 +00:00
typedef bool ( * sts_mixer_stream_callback ) ( sts_mixer_sample_t * sample , void * userdata ) ;
2023-07-30 19:18:50 +00:00
typedef struct {
void * userdata ; // a userdata pointer which will passed to the callback
sts_mixer_stream_callback callback ; // this callback will be called when the stream needs more data
sts_mixer_sample_t sample ; // the current stream "sample" which holds the current piece of audio
} sts_mixer_stream_t ;
////////////////////////////////////////////////////////////////////////////////
//
// VOICES
//
// A voice is an audio source which will be used during mixing.
// It can play nothing, a sample or a stream.
// Most of those fields are considered "private" and you should not play around with those.
//
typedef struct {
int state ;
sts_mixer_sample_t * sample ;
sts_mixer_stream_t * stream ;
float position ;
float gain ;
float pitch ;
float pan ;
} sts_mixer_voice_t ;
////////////////////////////////////////////////////////////////////////////////
//
// MIXER
//
// The mixer state.
//
typedef struct {
float gain ; // the global gain (you can change it if you want to change to overall volume)
unsigned int frequency ; // the frequency for the output of mixed audio data
int audio_format ; // the audio format for the output of mixed audio data
sts_mixer_voice_t voices [ STS_MIXER_VOICES ] ; // holding all audio voices for this state
} sts_mixer_t ;
////////////////////////////////////////////////////////////////////////////////
//
// API
//
// "Initializes" a new sts_mixer state.
void sts_mixer_init ( sts_mixer_t * mixer , unsigned int frequency , int audio_format ) ;
// "Shutdown" the mixer state. It will simply reset all fields.
void sts_mixer_shutdown ( sts_mixer_t * mixer ) ;
// Return the number of active voices. Active voices are voices that play either a stream or a sample.
int sts_mixer_get_active_voices ( sts_mixer_t * mixer ) ;
// Play the given sample with the gain, pitch and panning.
// Panning can be something between -1.0f (fully left) ... +1.0f (fully right)
// Please note that pitch will be clamped so it cannot reach 0.0f (would be useless).
// Returns the number of the voice where this sample will be played or -1 if no voice was free.
int sts_mixer_play_sample ( sts_mixer_t * mixer , sts_mixer_sample_t * sample , float gain , float pitch , float pan ) ;
// Plays the given stream with the gain.
// Returns the number of the voice where this stream will be played or -1 if no voice was free.
int sts_mixer_play_stream ( sts_mixer_t * mixer , sts_mixer_stream_t * stream , float gain ) ;
// Stops voice with the given voice no. You can pass the returned number of sts_mixer_play_sample / sts_mixer_play_stream here.
void sts_mixer_stop_voice ( sts_mixer_t * mixer , int voice ) ;
2023-12-12 10:56:44 +00:00
// Returns whether the given sample has already stopped playing.
bool sts_mixer_sample_stopped ( sts_mixer_t * mixer , sts_mixer_sample_t * sample ) ;
// Returns whether the given stream has already stopped playing.
bool sts_mixer_stream_stopped ( sts_mixer_t * mixer , sts_mixer_stream_t * stream ) ;
2023-07-30 19:18:50 +00:00
// Stops all voices playing the given sample. Useful when you want to delete the sample and make sure it is not used anymore.
void sts_mixer_stop_sample ( sts_mixer_t * mixer , sts_mixer_sample_t * sample ) ;
// Stops all voices playing the given stream. Useful when you want to delete the stream and make sure it is not used anymore.
void sts_mixer_stop_stream ( sts_mixer_t * mixer , sts_mixer_stream_t * stream ) ;
// The mixing function. You should call the function if you need to pass more audio data to the audio device.
// Typically this function is called in a separate thread or something like that.
// It will write audio data in the specified format and frequency of the mixer state.
void sts_mixer_mix_audio ( sts_mixer_t * mixer , void * output , unsigned int samples ) ;
# endif // __INCLUDED__STS_MIXER_H__
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
////
//// IMPLEMENTATION
////
////
# ifdef STS_MIXER_IMPLEMENTATION
enum {
STS_MIXER_VOICE_STOPPED ,
STS_MIXER_VOICE_PLAYING ,
STS_MIXER_VOICE_STREAMING
} ;
static float sts_mixer__clamp ( const float value , const float min , const float max ) {
if ( value < min ) return min ;
else if ( value > max ) return max ;
else return value ;
}
static float sts_mixer__clamp_sample ( const float sample ) {
if ( sample < - 1.0f ) return - 1.0f ;
else if ( sample > 1.0f ) return 1.0f ;
else return sample ;
}
static float sts_mixer__get_sample ( sts_mixer_sample_t * sample , unsigned int position ) {
switch ( sample - > audio_format ) {
case STS_MIXER_SAMPLE_FORMAT_8 :
return ( float ) ( ( char * ) sample - > data ) [ position ] / 127.0f ;
case STS_MIXER_SAMPLE_FORMAT_16 :
return ( float ) ( ( short * ) sample - > data ) [ position ] / 32767.0f ;
case STS_MIXER_SAMPLE_FORMAT_32 :
return ( float ) ( ( int * ) sample - > data ) [ position ] / 2147483647.0f ;
case STS_MIXER_SAMPLE_FORMAT_FLOAT :
return ( ( float * ) sample - > data ) [ position ] ;
default :
return 0.0f ;
}
}
static void sts_mixer__reset_voice ( sts_mixer_t * mixer , const int i ) {
sts_mixer_voice_t * voice = & mixer - > voices [ i ] ;
voice - > state = STS_MIXER_VOICE_STOPPED ;
voice - > sample = 0 ;
voice - > stream = 0 ;
voice - > position = voice - > gain = voice - > pitch = voice - > pan = 0.0f ;
}
static int sts_mixer__find_free_voice ( sts_mixer_t * mixer ) {
int i ;
for ( i = 0 ; i < STS_MIXER_VOICES ; + + i ) {
if ( mixer - > voices [ i ] . state = = STS_MIXER_VOICE_STOPPED ) return i ;
}
return - 1 ;
}
void sts_mixer_init ( sts_mixer_t * mixer , unsigned int frequency , int audio_format ) {
int i ;
for ( i = 0 ; i < STS_MIXER_VOICES ; + + i ) sts_mixer__reset_voice ( mixer , i ) ;
mixer - > frequency = frequency ;
mixer - > gain = 1.0f ;
mixer - > audio_format = audio_format ;
}
void sts_mixer_shutdown ( sts_mixer_t * mixer ) {
sts_mixer_init ( mixer , 0 , 0 ) ;
}
int sts_mixer_get_active_voices ( sts_mixer_t * mixer ) {
int i , active ;
for ( i = 0 , active = 0 ; i < STS_MIXER_VOICES ; + + i ) {
if ( mixer - > voices [ i ] . state ! = STS_MIXER_VOICE_STOPPED ) + + active ;
}
return active ;
}
int sts_mixer_play_sample ( sts_mixer_t * mixer , sts_mixer_sample_t * sample , float gain , float pitch , float pan ) {
int i ;
sts_mixer_voice_t * voice ;
i = sts_mixer__find_free_voice ( mixer ) ;
if ( i > = 0 ) {
voice = & mixer - > voices [ i ] ;
voice - > gain = gain ;
voice - > pitch = sts_mixer__clamp ( pitch , 0.1f , 10.0f ) ;
voice - > pan = sts_mixer__clamp ( pan * 0.5f , - 0.5f , 0.5f ) ;
voice - > position = 0.0f ;
voice - > sample = sample ;
voice - > stream = 0 ;
voice - > state = STS_MIXER_VOICE_PLAYING ;
}
return i ;
}
int sts_mixer_play_stream ( sts_mixer_t * mixer , sts_mixer_stream_t * stream , float gain ) {
int i ;
sts_mixer_voice_t * voice ;
i = sts_mixer__find_free_voice ( mixer ) ;
if ( i > = 0 ) {
voice = & mixer - > voices [ i ] ;
voice - > gain = gain ;
voice - > position = 0.0f ;
voice - > sample = 0 ;
voice - > stream = stream ;
voice - > state = STS_MIXER_VOICE_STREAMING ;
}
return i ;
}
void sts_mixer_stop_voice ( sts_mixer_t * mixer , int voice ) {
if ( voice > = 0 & & voice < STS_MIXER_VOICES ) sts_mixer__reset_voice ( mixer , voice ) ;
}
2023-12-12 10:56:44 +00:00
bool sts_mixer_sample_stopped ( sts_mixer_t * mixer , sts_mixer_sample_t * sample ) {
for ( int i = 0 ; i < STS_MIXER_VOICES ; + + i ) {
if ( mixer - > voices [ i ] . sample = = sample & & mixer - > voices [ i ] . state ! = STS_MIXER_VOICE_STOPPED ) return false ;
}
return true ;
}
bool sts_mixer_stream_stopped ( sts_mixer_t * mixer , sts_mixer_stream_t * stream ) {
for ( int i = 0 ; i < STS_MIXER_VOICES ; + + i ) {
if ( mixer - > voices [ i ] . stream = = stream & & mixer - > voices [ i ] . state ! = STS_MIXER_VOICE_STOPPED ) return false ;
}
return true ;
}
2023-07-30 19:18:50 +00:00
void sts_mixer_stop_sample ( sts_mixer_t * mixer , sts_mixer_sample_t * sample ) {
int i ;
for ( i = 0 ; i < STS_MIXER_VOICES ; + + i ) {
if ( mixer - > voices [ i ] . sample = = sample ) sts_mixer__reset_voice ( mixer , i ) ;
}
}
void sts_mixer_stop_stream ( sts_mixer_t * mixer , sts_mixer_stream_t * stream ) {
int i ;
for ( i = 0 ; i < STS_MIXER_VOICES ; + + i ) {
if ( mixer - > voices [ i ] . stream = = stream ) sts_mixer__reset_voice ( mixer , i ) ;
}
}
void sts_mixer_mix_audio ( sts_mixer_t * mixer , void * output , unsigned int samples ) {
sts_mixer_voice_t * voice ;
unsigned int i , position ;
float left , right , advance , sample ;
char * out_8 = ( char * ) output ;
short * out_16 = ( short * ) output ;
int * out_32 = ( int * ) output ;
float * out_float = ( float * ) output ;
// mix all voices
advance = 1.0f / ( float ) mixer - > frequency ;
for ( ; samples > 0 ; - - samples ) {
left = right = 0.0f ;
for ( i = 0 ; i < STS_MIXER_VOICES ; + + i ) {
voice = & mixer - > voices [ i ] ;
if ( voice - > state = = STS_MIXER_VOICE_PLAYING ) {
position = ( int ) voice - > position ;
if ( position < voice - > sample - > length ) {
sample = sts_mixer__clamp_sample ( sts_mixer__get_sample ( voice - > sample , position ) * voice - > gain ) ;
left + = sts_mixer__clamp_sample ( sample * ( 0.5f - voice - > pan ) ) ;
right + = sts_mixer__clamp_sample ( sample * ( 0.5f + voice - > pan ) ) ;
voice - > position + = ( float ) voice - > sample - > frequency * advance * voice - > pitch ;
} else if ( voice - > sample - > next ) { //< @r-lyeh
* voice - > sample = * ( sts_mixer_sample_t * ) voice - > sample - > next ; //< @r-lyeh
voice - > position = 0 ; //< @r-lyeh
} else sts_mixer__reset_voice ( mixer , i ) ;
} else if ( voice - > state = = STS_MIXER_VOICE_STREAMING ) {
position = ( ( int ) voice - > position ) * 2 ;
if ( position > = voice - > stream - > sample . length ) {
// buffer empty...refill
2023-12-12 10:56:44 +00:00
if ( voice - > stream - > callback ( & voice - > stream - > sample , voice - > stream - > userdata ) ) {
voice - > position = 0.0f ;
position = 0 ;
} else {
sts_mixer__reset_voice ( mixer , i ) ;
continue ;
}
2023-07-30 19:18:50 +00:00
}
left + = sts_mixer__clamp_sample ( sts_mixer__get_sample ( & voice - > stream - > sample , position ) * voice - > gain ) ;
right + = sts_mixer__clamp_sample ( sts_mixer__get_sample ( & voice - > stream - > sample , position + 1 ) * voice - > gain ) ;
voice - > position + = ( float ) voice - > stream - > sample . frequency * advance ;
}
}
// write to buffer.
float _g = mixer - > gain ; //< @r-lyeh: added master gain
float _127 = 127.0f * _g ; //< @r-lyeh: added master gain
float _32767 = 32767.0f * _g ; //< @r-lyeh: added master gain
float _2147483647 = 2147483647.0f * _g ; //< @r-lyeh: added master gain
left = sts_mixer__clamp_sample ( left ) ;
right = sts_mixer__clamp_sample ( right ) ;
switch ( mixer - > audio_format ) {
case STS_MIXER_SAMPLE_FORMAT_8 :
* out_8 + + = ( char ) ( left * _127 ) ; //< @r-lyeh: added master gain
* out_8 + + = ( char ) ( right * _127 ) ; //< @r-lyeh: added master gain
break ;
case STS_MIXER_SAMPLE_FORMAT_16 :
* out_16 + + = ( short ) ( left * _32767 ) ; //< @r-lyeh: added master gain
* out_16 + + = ( short ) ( right * _32767 ) ; //< @r-lyeh: added master gain
break ;
case STS_MIXER_SAMPLE_FORMAT_32 :
* out_32 + + = ( int ) ( left * _2147483647 ) ; //< @r-lyeh: added master gain
* out_32 + + = ( int ) ( right * _2147483647 ) ; //< @r-lyeh: added master gain
break ;
case STS_MIXER_SAMPLE_FORMAT_FLOAT :
* out_float + + = left * _g ; //< @r-lyeh: added master gain
* out_float + + = right * _g ; //< @r-lyeh: added master gain
break ;
}
}
}
# endif // STS_MIXER_IMPLEMENTATION
////////////////////////////////////////////////////////////////////////////////
// EXAMPLE
// This is a very simple example loading a stream and a sample using
// dr_flac.h (https://github.com/mackron/dr_libs) and SDL2. You can of course also use stb_vorbis or something similar :)
// Please note how the audio thread of SDL2 will be locked when the mixer state get's modified. This is important!
// Also there's no error checking in the entire example code, so beware.
//
#if 0
# include "SDL.h"
# define DR_FLAC_IMPLEMENTATION
# include "dr_flac.h"
# define STS_MIXER_IMPLEMENTATION
# include "sts_mixer.h"
SDL_AudioDeviceID audio_device = 0 ;
sts_mixer_t mixer ;
// encapsulate drflac and some buffer with the sts_mixer_stream_t
typedef struct {
drflac * flac ; // FLAC decoder state
sts_mixer_stream_t stream ; // mixer stream
int32_t data [ 4096 * 2 ] ; // static sample buffer
} mystream_t ;
// SDL2 audio callback
static void audio_callback ( void * userdata , Uint8 * stream , int len ) {
( void ) ( userdata ) ;
sts_mixer_mix_audio ( & mixer , stream , len / ( sizeof ( int ) * 2 ) ) ;
}
// load a sample
static void load_sample ( sts_mixer_sample_t * sample , const char * filename ) {
drflac * flac = drflac_open_file ( filename ) ;
sample - > frequency = flac - > sampleRate ;
sample - > audio_format = STS_MIXER_SAMPLE_FORMAT_32 ;
sample - > length = flac - > totalSampleCount ;
sample - > data = malloc ( sample - > length * sizeof ( int32_t ) ) ;
drflac_read_s32 ( flac , sample - > length , ( int32_t * ) sample - > data ) ;
drflac_close ( flac ) ;
}
// the callback to refill the stream data
static void refill_stream ( sts_mixer_sample_t * sample , void * userdata ) {
mystream_t * stream = ( mystream_t * ) userdata ;
if ( drflac_read_s32 ( stream - > flac , sample - > length , stream - > data ) < sample - > length ) drflac_seek_to_sample ( stream - > flac , 0 ) ;
}
// load a stream
static void load_stream ( mystream_t * stream , const char * filename ) {
stream - > flac = drflac_open_file ( filename ) ;
stream - > stream . userdata = stream ;
stream - > stream . callback = refill_stream ;
stream - > stream . sample . frequency = stream - > flac - > sampleRate ;
stream - > stream . sample . audio_format = STS_MIXER_SAMPLE_FORMAT_32 ;
stream - > stream . sample . length = 4096 * 2 ;
stream - > stream . sample . data = stream - > data ;
refill_stream ( & stream - > stream . sample , stream ) ;
}
// helper to get random [0.0f..1.0f values
static float randf ( ) {
return ( float ) ( rand ( ) ) / ( float ) RAND_MAX ;
}
int main ( int argc , char * argv [ ] ) {
SDL_AudioSpec want , have ;
sts_mixer_sample_t sample ;
mystream_t stream ;
( void ) ( argc ) ; ( void ) ( argv ) ;
// init SDL2 + audio
want . format = AUDIO_S32SYS ;
want . freq = 44100 ;
want . channels = 2 ;
want . userdata = NULL ;
want . samples = 4096 ;
want . callback = audio_callback ;
SDL_Init ( SDL_INIT_AUDIO ) ;
audio_device = SDL_OpenAudioDevice ( NULL , 0 , & want , & have , 0 ) ;
// init sts_mixer and load things
sts_mixer_init ( & mixer , 44100 , STS_MIXER_SAMPLE_FORMAT_32 ) ;
load_sample ( & sample , " effect.flac " ) ;
load_stream ( & stream , " music.flac " ) ;
// play the stream
sts_mixer_play_stream ( & mixer , & stream . stream , 0.7f ) ;
// start audio processing and do a loop for audio effects
SDL_PauseAudioDevice ( audio_device , 0 ) ;
for ( ; ; ) {
// !!!IMPORTANT!!! lock the audio thread before modifying data in the sts_mixer !!!
SDL_LockAudioDevice ( audio_device ) ;
// play a sample with random gain, pitch and panning
sts_mixer_play_sample ( & mixer , & sample , randf ( ) , 0.5f + randf ( ) , - 1.0f + randf ( ) * 2.0f ) ;
// unlock audio thread again
SDL_UnlockAudioDevice ( audio_device ) ;
// wait ...
SDL_Delay ( 76 ) ;
}
SDL_PauseAudioDevice ( audio_device , 1 ) ;
SDL_CloseAudioDevice ( audio_device ) ;
SDL_Quit ( ) ;
return 0 ;
}
# endif // 0
/*
This is free and unencumbered software released into the public domain .
Anyone is free to copy , modify , publish , use , compile , sell , or
distribute this software , either in source code form or as a compiled
binary , for any purpose , commercial or non - commercial , and by any
means .
In jurisdictions that recognize copyright laws , the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain . We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors . We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND ,
EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM , DAMAGES OR
OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE ,
ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE .
For more information , please refer to < http : //unlicense.org/>
*/