medfall

A super great game engine
Log | Files | Refs

commit 6c61b7f9a4f72ddd1b6d904f5b982f97ef3f3103
parent dd0b302cc1ba8a0b64070efb39caa40a4ad6a526
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Wed, 12 Sep 2018 18:17:29 +0300

Audio output overhaul, only tested on Linux

Add a ring buffer to AudioOutputDevice and mix into that from the game
thread. The audio thread now just memcpys from that and goes back to
sleep.

Diffstat:
audio.cc | 4+++-
darwin_audio_output.cc | 9+++++++--
linux_audio_output.cc | 14+++++++++-----
linux_audio_output.h | 4++--
memory_arena.h | 7++++++-
mixer.cc | 103++++++++++++++++++++++---------------------------------------------------------
mixer.h | 6++----
platform_audio_output.h | 11+++++++----
win32_audio_output.cc | 18++++++++++++++----
win32_audio_output.h | 3++-
10 files changed, 80 insertions(+), 99 deletions(-)

diff --git a/audio.cc b/audio.cc @@ -38,7 +38,7 @@ int main( int argc, char ** argv ) { mixer_init(); AudioOutputDevice output_device; - audio_output_open( &output_device, mixer_mix ); + audio_output_open( &output_device ); size_t wave_len; u8 * wave = file_get_contents( "02 - Unbreakable.wav", &wave_len ); @@ -94,6 +94,8 @@ int main( int argc, char ** argv ) { break; } #endif + + mixer_mix( &output_device.buffer, &arena, 8192, 0 ); } audio_output_close( &output_device ); diff --git a/darwin_audio_output.cc b/darwin_audio_output.cc @@ -14,16 +14,21 @@ static OSStatus audio_output_callback( u32 inBusNumber, u32 num_samples, AudioBufferList * buffers ) { + ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 ); + AudioOutputDevice * device = ( AudioOutputDevice * ) data; s16 * samples = ( s16 * ) buffers->mBuffers[ 0 ].mData; - device->callback( samples, checked_cast< s32 >( num_samples ) ); + + u32 cursor = load_acquire( &device->buffer.cursor ); + memcpy( samples, &device->buffer.samples[ cursor ], num_samples * sizeof( s16 ) ); + store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) ); return 0; } void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ) { - device->callback = callback; + memset( &device->buffer, 0, sizeof( device->buffer ) ); AudioComponentDescription desc = { }; desc.componentType = kAudioUnitType_Output; diff --git a/linux_audio_output.cc b/linux_audio_output.cc @@ -94,15 +94,18 @@ static THREAD( audio_output_thread ) { AudioOutputDevice * device = ( AudioOutputDevice * ) data; while( load_acquire( &device->shutting_down ) == 0 ) { - s16 samples[ 1024 ]; - device->callback( samples, ARRAY_COUNT( samples ) ); - audio_output_write( device->pcm, samples, ARRAY_COUNT( samples ) ); + constexpr u32 num_samples = 1024; + STATIC_ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 ); + + u32 cursor = load_acquire( &device->buffer.cursor ); + audio_output_write( device->pcm, &device->buffer.samples[ cursor ], num_samples ); + store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) ); } THREAD_END; } -void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ) { +void audio_output_open( AudioOutputDevice * device ) { const int channels = 2; const int sample_rate = 44100; const int ms = 1000; @@ -120,7 +123,8 @@ void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback FATAL( "Couldn't configure sound output: {}", _snd_strerror( err_params ) ); } - device->callback = callback; + memset( &device->buffer, 0, sizeof( device->buffer ) ); + store_release( &device->shutting_down, 0 ); thread_init( &device->thread, audio_output_thread, device ); } diff --git a/linux_audio_output.h b/linux_audio_output.h @@ -3,11 +3,11 @@ #include <alsa/asoundlib.h> #include "platform_thread.h" -#include "platform_atomic.h" struct AudioOutputDevice { snd_pcm_t * pcm; Thread thread; - AudioOutputCallback * callback; atomic_u32 shutting_down; + + AudioBuffer buffer; }; diff --git a/memory_arena.h b/memory_arena.h @@ -34,8 +34,13 @@ T * alloc( MemoryArena * arena, uptr alignment = alignof( T ) ) { } template< typename T > +T * alloc_many( MemoryArena * arena, size_t n, uptr alignment = alignof( T ) ) { + return ( T * ) memarena_push_size( arena, sizeof( T ) * n, alignment ); +} + +template< typename T > array< T > alloc_array( MemoryArena * arena, size_t n, uptr alignment = alignof( T ) ) { - return array< T >( ( T * ) memarena_push_size( arena, sizeof( T ) * n, alignment ), n ); + return array< T >( alloc_many< T >( arena, n, alignment ), n ); } template< typename T > diff --git a/mixer.cc b/mixer.cc @@ -1,8 +1,8 @@ #include "intrinsics.h" +#include "memory_arena.h" #include "int_conversions.h" #include "mixer.h" #include "pool.h" -#include "spsc.h" struct PlayingSound { PlayingSoundID id; @@ -28,13 +28,10 @@ struct MixerCommand { bool loop; }; -#define COMMAND_QUEUE_SIZE 256 - #define RESET_SOUND_COUNTER 0 STATIC_ASSERT( RESET_SOUND_COUNTER != INVALID_SOUND_ID ); static Pool< PlayingSound, MAX_CONCURRENT_SOUNDS > playing_sounds; -static FixedSPSC< MixerCommand, COMMAND_QUEUE_SIZE > command_queue; static PlayingSoundID sound_counter; // TODO: this should really use a hash index/pool with handles @@ -52,37 +49,6 @@ static void mixer_stop_all_impl() { playing_sounds.clear(); } -static void mixer_process_command( MixerCommand cmd ) { - // TODO: most of these should make smooth adjustments to avoid popping - switch( cmd.type ) { - case MIXER_PLAY: { - PlayingSound * ps = playing_sounds.acquire(); - if( ps != NULL ) { - ps->id = cmd.sound_id; - ps->data = cmd.sound_to_play; - ps->num_played_samples = cmd.start_pos; - ps->volume = 1.0f; - ps->loop = cmd.loop; - } - } break; - - case MIXER_VOLUME: { - PlayingSound * ps = find_playing_sound( cmd.sound_id ); - if( ps != NULL ) { - ps->volume = cmd.volume; - } - } break; - - case MIXER_STOP: { - playing_sounds.release( find_playing_sound( cmd.sound_id ) ); - } break; - - case MIXER_STOP_ALL: { - mixer_stop_all_impl(); - } break; - } -} - static void mixer_mix_sound( PlayingSound * ps, float * samples, size_t num_samples ) { // TODO: simd @@ -106,71 +72,58 @@ static void mixer_mix_sound( PlayingSound * ps, float * samples, size_t num_samp } void mixer_init() { - mixer_stop_all_impl(); + mixer_stop_all(); } -AUDIO_OUTPUT_CALLBACK( mixer_mix ) { - for( size_t i = 0; i < COMMAND_QUEUE_SIZE; i++ ) { - MixerCommand cmd; - if( !command_queue.dequeue( &cmd ) ) break; - mixer_process_command( cmd ); - } +void mixer_mix( AudioBuffer * buffer, MemoryArena * arena, u32 samples_to_mix, u32 mix_ahead ) { + MEMARENA_SCOPED_CHECKPOINT( arena ); - // TODO: channels - const s32 MAX_OUTPUT_SAMPLES = 4096; - float samples[ MAX_OUTPUT_SAMPLES ]; - ASSERT( num_output_samples <= MAX_OUTPUT_SAMPLES ); - for( s32 i = 0; i < num_output_samples; i++ ) { - samples[ i ] = 0.0f; + float * float_samples = alloc_many< float >( arena, samples_to_mix ); + for( u32 i = 0; i < samples_to_mix; i++ ) { + float_samples[ i ] = 0.0f; } for( PlayingSound & ps : playing_sounds ) { - mixer_mix_sound( &ps, samples, num_output_samples ); + mixer_mix_sound( &ps, float_samples, samples_to_mix ); } - for( s32 i = 0; i < num_output_samples; i++ ) { - output_samples[ i ] = checked_cast< s16 >( quantize11s( clamp11( samples[ i ] ), 16 ) ); + u32 cursor = load_acquire( &buffer->cursor ); + for( u32 i = 0; i < samples_to_mix; i++ ) { + s16 q = checked_cast< s16 >( quantize11s( clamp11( float_samples[ i ] ), 16 ) ); + buffer->samples[ ( i + cursor ) % ARRAY_COUNT( buffer->samples ) ] = q; } } PlayingSoundID mixer_play( SoundData sound, MixerLoopBool loop, s32 start_pos ) { - MixerCommand cmd; - cmd.type = MIXER_PLAY; - cmd.sound_to_play = sound; - cmd.start_pos = start_pos; - cmd.sound_id = sound_counter; - cmd.loop = loop == MIXER_LOOP; - command_queue.enqueue_spin( cmd ); + PlayingSound * ps = playing_sounds.acquire(); + if( ps == NULL ) + return INVALID_SOUND_ID; + + ps->id = sound_counter; + ps->data = sound; + ps->num_played_samples = start_pos; + ps->volume = 1.0f; + ps->loop = loop; sound_counter++; if( sound_counter == INVALID_SOUND_ID ) { sound_counter = RESET_SOUND_COUNTER; } - return cmd.sound_id; -} - -PlayingSoundID mixer_play( SoundData sound, s32 start_pos ) { - return mixer_play( sound, MIXER_DONTLOOP, start_pos ); + return ps->id; } void mixer_volume( PlayingSoundID sound_id, float volume ) { - MixerCommand cmd; - cmd.type = MIXER_VOLUME; - cmd.sound_id = sound_id; - cmd.volume = saturate( volume ); - command_queue.enqueue_spin( cmd ); + PlayingSound * ps = find_playing_sound( sound_id ); + if( ps != NULL ) { + ps->volume = volume; + } } void mixer_stop( PlayingSoundID sound_id ) { - MixerCommand cmd; - cmd.type = MIXER_STOP; - cmd.sound_id = sound_id; - command_queue.enqueue_spin( cmd ); + playing_sounds.release( find_playing_sound( sound_id ) ); } void mixer_stop_all() { - MixerCommand cmd; - cmd.type = MIXER_STOP_ALL; - command_queue.enqueue_spin( cmd ); + playing_sounds.clear(); } diff --git a/mixer.h b/mixer.h @@ -3,6 +3,7 @@ #define MAX_CONCURRENT_SOUNDS 1024 #include "intrinsics.h" +#include "memory_arena.h" #include "assets.h" #include "platform_audio_output.h" @@ -17,13 +18,10 @@ enum MixerLoopBool { void mixer_init(); -AUDIO_OUTPUT_CALLBACK( mixer_mix ); +void mixer_mix( AudioBuffer * buffer, MemoryArena * arena, u32 samples_to_mix, u32 mix_ahead ); -// TODO: maybe make these take a QueueWriter PlayingSoundID mixer_play( SoundData sound, MixerLoopBool loop = MIXER_DONTLOOP, s32 start_pos = 0 ); PlayingSoundID mixer_play( SoundData sound, s32 start_pos ); void mixer_volume( PlayingSoundID sound_id, float volume ); void mixer_stop( PlayingSoundID sound_id ); void mixer_stop_all(); - -// TODO: seek, set position, set playback speed/pitch diff --git a/platform_audio_output.h b/platform_audio_output.h @@ -2,16 +2,19 @@ #include "intrinsics.h" #include "platform.h" - -#define AUDIO_OUTPUT_CALLBACK( f ) void f( s16 * output_samples, s32 num_output_samples ) -typedef AUDIO_OUTPUT_CALLBACK( AudioOutputCallback ); +#include "platform_atomic.h" struct AudioOutputDevice; +struct AudioBuffer { + s16 samples[ 8192 ]; // about 0.2s at 44100Hz + atomic_u32 cursor; +}; + void audio_output_init(); void audio_output_term(); -void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ); +void audio_output_open( AudioOutputDevice * device ); void audio_output_close( AudioOutputDevice * device ); #if COMPILER_MINGW diff --git a/win32_audio_output.cc b/win32_audio_output.cc @@ -71,8 +71,14 @@ void audio_output_term() { static THREAD( audio_output_thread ) { AudioOutputDevice * device = ( AudioOutputDevice * ) data; + // TODO: can probably get rid of this, don't need double buffers when + // we have a big ring buffer + // double buffering - s16 buffers[ 2 ][ 512 ]; + constexpr u32 num_samples = 512; + STATIC_ASSERT( ARRAY_COUNT( device->buffer.samples ) % num_samples == 0 ); + + s16 buffers[ 2 ][ num_samples ]; u32 buffer_idx = 0; while( load_acquire( &device->shutting_down ) == 0 ) { @@ -86,7 +92,8 @@ static THREAD( audio_output_thread ) { WaitForSingleObject( device->event, INFINITE ); } - device->callback( buffers[ buffer_idx ], ARRAY_COUNT( buffers[ buffer_idx ] ) ); + u32 cursor = load_acquire( &device->buffer.cursor ); + memcpy( buffers[ buffer_idx ], &device->buffer[ cursor ], sizeof( buffers[ buffer_idx ] ) ); XAUDIO2_BUFFER buf = { }; buf.AudioBytes = sizeof( buffers[ buffer_idx ] ); @@ -98,12 +105,14 @@ static THREAD( audio_output_thread ) { } buffer_idx = ( buffer_idx + 1 ) % ARRAY_COUNT( buffers ); + + store_release( &device->buffer.cursor, ( cursor + num_samples ) % ARRAY_COUNT( device->buffer.samples ) ); } THREAD_END; } -void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback ) { +void audio_output_open( AudioOutputDevice * device ) { WAVEFORMATEX format = { }; format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = 1; @@ -123,7 +132,8 @@ void audio_output_open( AudioOutputDevice * device, AudioOutputCallback callback device->voice->Start( 0 ); - device->callback = callback; + memset( &device->buffer, 0, sizeof( device->buffer ) ); + store_release( &device->shutting_down, 0 ); thread_init( &device->thread, audio_output_thread, device ); } diff --git a/win32_audio_output.h b/win32_audio_output.h @@ -15,6 +15,7 @@ struct AudioOutputDevice { IXAudio2SourceVoice * voice; HANDLE event; Thread thread; - AudioOutputCallback * callback; atomic_u32 shutting_down; + + AudioBuffer buffer; };