commit 78224ee6ee00351a502fa1971f44fd13bf7675ff parent 76920c4859d9277cfb84398de7c730644e9302dc Author: Michael Savage <mikejsavage@gmail.com> Date: Wed Nov 4 13:49:34 +0000 Add leaky multithreaded audio playback Diffstat:
audio.cc | | | 124 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- |
audio.h | | | 31 | +++++++++++++++++++++++++------ |
linux_audio.cc | | | 72 | +++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
diff --git a/audio.cc b/audio.cc @@ -5,53 +5,131 @@ #include "platform_barrier.h" #include "platform_atomic.h" -static PlayingSound playing_sounds_head; +static PlayingSound ps_head; + +static PlayingSound free_ps_head; +static PlayingSound * free_ps_tail = &free_ps_head; + +static PlaySoundOptions free_ps_options_head; + +// TODO: if someone calls audio_stop_sound after a sound has already been +// reused it will cause problems +// give each playingsound a counter and return the value of that counter from +// audio_play_sound, and then make other ops fail if they don't line up anymore + +static PlayingSound * audio_get_free_sound() { + if( free_ps_head.next == nullptr ) return nullptr; + + // TODO: this might not be correct if the playback thread inserts at the same time + if( free_ps_tail == free_ps_head.next ) { + free_ps_tail = &free_ps_head; + } + + PlayingSound * ps = free_ps_head.next; + // TODO: because of this + free_ps_head.next = ps->next; + + return ps; +} + +static PlayingSound * audio_put_free_sound( PlayingSound * ps ) { + ps->next = nullptr; + write_barrier(); + free_ps_tail->next = ps; +} PlayingSound * audio_play_sound( MemoryArena * arena, Sound sound, bool loop ) { - PlayingSound * ps = memarena_push_type( arena, PlayingSound ); - *ps = { }; + PlayingSound * ps = audio_get_free_sound(); + if( ps == nullptr ) ps = memarena_push_type( arena, PlayingSound ); + *ps = { }; ps->sound = sound; - ps->volume[ 0 ] = ps->volume[ 1 ] = 1.0f; - ps->loop = loop; + + // TODO: grab options from free list + PlayingSoundOptions * options = memarena_push_type( arena, PlayingSoundOptions ); + *options = { }; + options->volume[ 0 ] = options->volume[ 1 ] = 1.0f; + options->loop = loop; + + ps->options = options; + ps->next = ps_head.next; write_barrier(); - ps->next = playing_sounds_head.next; - playing_sounds_head.next = ps; + ps_head.next = ps; + // TODO: return a PS tagged with generation return ps; } -void audio_fill_output_buffer( AudioOutputBuffer * buffer ) { +// TODO: check generation +void audio_stop_sound( PlayingSound * ps ) { + ps->stopped = true; +} + +// TODO: check generation +void audio_set_volume( PlayingSound * ps, float left, float right ) { + if( right == -1.0f ) right = left; + + // TODO: grab options from free list + PlayingSoundOptions * options = ( PlayingSoundOptions * ) malloc( sizeof( PlayingSoundOptions ) ); + *options = *ps->options; + + options->volume[ 0 ] = left; + options->volume[ 1 ] = right; + + // PlayingSoundOptions * old_options = ps->options; + ps->options = options; + + write_barrier(); + + // TODO: add old_options to free list +} + +void audio_mix( AudioOutputBuffer * buffer ) { for( u32 i = 0; i < buffer->num_samples; i++ ) { buffer->samples[ i ] = 0; } - PlayingSound * prev = &playing_sounds_head; + PlayingSound * prev = &ps_head; PlayingSound * ps = prev->next; while( ps ) { assert( ps->sound.sample_rate == buffer->sample_rate ); - u32 samples_to_write = buffer->num_samples; - if( !ps->loop ) { - samples_to_write = min_u32( ps->sound.num_samples - ps->num_played_samples, samples_to_write ); + if( ps->stopped ) { + prev->next = ps->next; + // TODO: add ps to free list + // TODO: add ps->options to free list } + else { + // we want a local copy so it doesn't get changed under our noses + // eg if the game might change the volume of the left + // channel, then the mixer gets run, then the game + // changes the right channel + // it might sound a bit weird + PlayingSoundOptions options = *ps->options; - for( u32 i = 0; i < samples_to_write; i++ ) { - // we should do this in float buffers or we get horrible clipping when things overflow - buffer->samples[ i ] += ps->sound.samples[ ( i + ps->num_played_samples ) % ps->sound.num_samples ]; - } + u32 samples_to_write = buffer->num_samples; + if( !options.loop ) { + samples_to_write = min_u32( ps->sound.num_samples - ps->num_played_samples, samples_to_write ); + } - ps->num_played_samples += samples_to_write; - if( ps->num_played_samples >= ps->sound.num_samples ) { - if( ps->loop ) { - ps->num_played_samples %= ps->sound.num_samples; + for( u32 i = 0; i < samples_to_write; i++ ) { + // we should do this in float buffers or we get horrible clipping when things overflow + buffer->samples[ i ] += options.volume[ 0 ] * ps->sound.samples[ ( i + ps->num_played_samples ) % ps->sound.num_samples ]; } - else { - // atm we just leak ps - prev->next = ps->next; + + ps->num_played_samples += samples_to_write; + if( ps->num_played_samples >= ps->sound.num_samples ) { + if( options.loop ) { + ps->num_played_samples %= ps->sound.num_samples; + } + else { + // TODO: add ps to free list + // TODO: add ps->options to free list + prev->next = ps->next; + } } } diff --git a/audio.h b/audio.h @@ -5,20 +5,35 @@ #include "assets.h" #include "memory_arena.h" -struct PlayingSound { - Sound sound; - +struct PlayingSoundOptions { float volume[ 2 ]; bool loop; - u32 num_played_samples; - bool positional; glm::vec3 position; + PlayingSoundOptions * next; +}; + +struct PlayingSound { + Sound sound; + + // TODO: make this signed so we can delay sounds? + u32 num_played_samples; + + PlayingSoundOptions * options; + volatile bool stopped; + + u32 generation; + PlayingSound * next; }; +struct TaggedPlayingSound { + u32 generation; + PlayingSound ps; +}; + struct AudioOutputBuffer { u32 sample_rate; u32 num_samples; @@ -31,6 +46,10 @@ struct PlaySoundOptions { }; PlayingSound * audio_play_sound( MemoryArena * arena, Sound sound, bool loop = false ); -void audio_fill_output_buffer( AudioOutputBuffer * buffer ); +void audio_stop_sound( PlayingSound * ps ); + +void audio_set_volume( PlayingSound * ps, float left, float right = -1.0f ); + +void audio_mix( AudioOutputBuffer * buffer ); #endif // _AUDIO_H_ diff --git a/linux_audio.cc b/linux_audio.cc @@ -1,5 +1,6 @@ #include <stdlib.h> #include <stdarg.h> +#include <unistd.h> #include <math.h> #include <alsa/asoundlib.h> @@ -8,6 +9,7 @@ #include "wave.h" #include "assets.h" #include "audio.h" +#include "platform_thread.h" #define FRAMES 1024 @@ -15,6 +17,37 @@ static int milliseconds( int ms ) { return ms * 1000; } +static u32 sample_rate; + +static void * mix_worker( void * data ) { + snd_pcm_t * pcm = ( snd_pcm_t * ) data; + + s16 samples[ 4096 ]; + AudioOutputBuffer buffer; + buffer.sample_rate = sample_rate; + buffer.num_samples = array_count( samples ); + buffer.samples = samples; + + while( true ) { + audio_mix( &buffer ); + + for( u32 i = 0; i < buffer.num_samples; ) { + // s16 * channels[ 2 ] = { sound.samples + i, sound.samples + sound.num_samples + i }; + s16 * channels[ 2 ] = { buffer.samples + i, buffer.samples + i }; + + snd_pcm_sframes_t written = snd_pcm_writen( pcm, ( void ** ) channels, buffer.num_samples - i ); + if( written <= 0 ) { + snd_pcm_recover( pcm, written, 1 ); + } + else { + i += written; + } + } + } + + return nullptr; +} + static void error_handler( const char * file, int line, const char * function, int err, const char * fmt, ... ) { printf( "error in %s at %s:%d\n", file, function, line ); @@ -37,7 +70,7 @@ static Sound * make_sin_wave( u32 sample_rate, u32 frequency ) { for( u32 i = 0; i < num_samples; i++ ) { const float t = ( float ) i / num_samples; - sound->samples[ i ] = 0.25f * INT16_MAX * sinf( t * 2.0f * M_PI ); + sound->samples[ i ] = INT16_MAX * sinf( t * 2.0f * M_PI ); } return sound; @@ -55,10 +88,13 @@ int main( int argc, char ** argv ) { bool ok = wave_decode( &arena, wave, &sound ); assert( ok ); + // TODO: remove this when the mixer can do resampling + sample_rate = sound.sample_rate; + Sound * sin_wave = make_sin_wave( sound.sample_rate, 200 ); audio_play_sound( &arena, sound ); - audio_play_sound( &arena, *sin_wave, true ); + PlayingSound * ps_sin = audio_play_sound( &arena, *sin_wave, true ); const char * pcmname = argc == 2 ? argv[ 1 ] : "default"; @@ -87,26 +123,24 @@ int main( int argc, char ** argv ) { // if (snd_output_stdio_attach(&log, stderr, 0) >= 0) { // snd_pcm_dump(pcm, log); snd_output_close(log); } - s16 samples[ 4096 ]; - AudioOutputBuffer buffer; - buffer.sample_rate = sound.sample_rate; - buffer.num_samples = array_count( samples ); - buffer.samples = samples; + Thread mix_thread; + thread_init( &mix_thread, mix_worker, pcm ); - while( true ) { - audio_fill_output_buffer( &buffer ); + char * line = nullptr; + size_t n; - for( u32 i = 0; i < buffer.num_samples; ) { - // s16 * channels[ 2 ] = { sound.samples + i, sound.samples + sound.num_samples + i }; - s16 * channels[ 2 ] = { buffer.samples + i, buffer.samples + i }; + float volume = 0.1f; + audio_set_volume( ps_sin, volume ); + while( true ) { + getline( &line, &n, stdin ); - snd_pcm_sframes_t written = snd_pcm_writen( pcm, ( void ** ) channels, buffer.num_samples - i ); - if( written <= 0 ) { - snd_pcm_recover( pcm, written, 1 ); - } - else { - i += written; - } + if( strcmp( line, "+\n" ) == 0 ) { + volume += 0.1f; + audio_set_volume( ps_sin, volume ); + } + else if( strcmp( line, "-\n" ) == 0 ) { + volume -= 0.1f; + audio_set_volume( ps_sin, volume ); } }