commit f5c74091885914aa528bea6f53a4ccbe0defa7f3 parent a8e84c1c59f7d4c6dd65310d75a9d48011403c79 Author: Michael Savage <mikejsavage@gmail.com> Date: Tue Dec 15 19:40:40 +0000 Big audio work - Rename Sound to SoundData - Lots of house cleaning - The mixer runs concurrently but isn't tested much Diffstat:
assets.h | | | 4 | ++-- |
audio.cc | | | 214 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
audio.h | | | 47 | +++++++++++++++++++++++++++++------------------ |
linux_audio.cc | | | 44 | +++++++++++++++++++++++++++++++++----------- |
wave.cc | | | 2 | +- |
wave.h | | | 2 | +- |
diff --git a/assets.h b/assets.h @@ -23,7 +23,7 @@ struct Bitmap { u8 * data; }; -struct Sound { +struct SoundData { u32 num_samples; u32 sample_rate; u32 num_channels; @@ -38,7 +38,7 @@ struct Asset { Bitmap bitmap; GLuint texture; }; - Sound sound; + SoundData sound; }; }; diff --git a/audio.cc b/audio.cc @@ -5,135 +5,185 @@ #include "platform_barrier.h" #include "platform_atomic.h" -static PlayingSound ps_head; +// TODO: check all soundstates are stopped after a certain one in the mixer loop? + +// queue normally looks like: +// dummy -> ... -> before_last_free -> ... -> last_free -> ... -> tail +struct SoundStateFreeList { + // TODO: order these + // TODO: volatiles? we need something for mfences. + SoundState * tail; + SoundState * dummy; + SoundState * before_last_free; + + CACHE_LINE_PADDING; + + SoundState * volatile last_free; +}; + +static SoundState * ss_head; +static SoundStateFreeList ss_freelist; + +static void freelist_init( MemoryArena * arena, SoundStateFreeList * freelist ) { + ss_head = memarena_push_type( arena, SoundState ); + *ss_head = { }; + ss_head->stopped = true; + printf( "%p\n", ss_head ); + freelist->tail = freelist->dummy = freelist->before_last_free = freelist->last_free = ss_head; + write_barrier(); +} -static PlayingSound free_ps_head; -static PlayingSound * free_ps_tail = &free_ps_head; +static void pc( SoundStateFreeList * freelist, const char * msg ) { + size_t n = 0; + SoundState * ss = freelist->dummy; + while( ss->next_free ) { + n++; + ss = ss->next_free; + } + printf( "%s: %zu\n", msg, n ); +} -static PlaySoundOptions free_ps_options_head; +static void freelist_add( SoundStateFreeList * freelist, SoundState * ss ) { + printf( "add to freelist\n" ); + freelist->tail->next_free = ss; + write_barrier(); + freelist->tail = ss; +} -// 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 bool freelist_remove( SoundStateFreeList * freelist, SoundState ** out ) { + // pc( freelist, "remove" ); + SoundState * last_free = freelist->last_free; + if( last_free->next_free == NULL ) return false; -static PlayingSound * audio_get_free_sound() { - if( free_ps_head.next == nullptr ) return nullptr; + freelist->last_free = last_free->next_free; - // 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; - } + write_barrier(); - PlayingSound * ps = free_ps_head.next; - // TODO: because of this - free_ps_head.next = ps->next; + *out = last_free; + return true; +} - return ps; +void audio_init( MemoryArena * arena ) { + freelist_init( arena, &ss_freelist ); } -static PlayingSound * audio_put_free_sound( PlayingSound * ps ) { - ps->next = nullptr; - write_barrier(); - free_ps_tail->next = ps; +static bool audio_alloc_soundstate( MemoryArena * arena, SoundState ** out ) { + bool ok = freelist_remove( &ss_freelist, out ); + if( ok ) return true; + + *out = memarena_push_type( arena, SoundState ); + ( *out )->generation = 0; + return false; } -PlayingSound * audio_play_sound( MemoryArena * arena, Sound sound, bool loop ) { - PlayingSound * ps = audio_get_free_sound(); - if( ps == nullptr ) ps = memarena_push_type( arena, PlayingSound ); +Sound audio_play_sound( MemoryArena * arena, SoundData data, bool loop ) { + SoundState * ss; + bool reused = audio_alloc_soundstate( arena, &ss ); + printf( "use freelist? %s. %p\n", reused ? "yes" : "no", ss ); - *ps = { }; - ps->sound = sound; + const u32 new_generation = ss->generation + 1; - // 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; + // *ss = { }; + ss->data = data; + ss->generation = new_generation; + ss->volume = { UINT16_MAX, UINT16_MAX }; + ss->loop = loop; + ss->stopped = false; - ps->options = options; - ps->next = ps_head.next; + if( !reused ) ss->next = ss_head->next; + ss->next_free = NULL; + ss->freelisted = false; write_barrier(); - ps_head.next = ps; + if( !reused ) ss_head->next = ss; - // TODO: return a PS tagged with generation - return ps; -} + Sound sound; + sound.generation = new_generation; + sound.state = ss; -// TODO: check generation -void audio_stop_sound( PlayingSound * ps ) { - ps->stopped = true; + return sound; } -// TODO: check generation -void audio_set_volume( PlayingSound * ps, float left, float right ) { - if( right == -1.0f ) right = left; +// TODO: this might be bunk if a sound gets stopped and reused between the if +// and the stop +// the mixer thread will stop a sound if it finishes. maybe distinguish between +// stopped by the mixer and stopped by the game thread +// CAS is also an option as this runs on the game thread +void audio_stop_sound( Sound sound ) { + if( sound.state->stopped || sound.generation == sound.state->generation ) { + sound.state->stopped = true; + + write_barrier(); + } +} - // TODO: grab options from free list - PlayingSoundOptions * options = ( PlayingSoundOptions * ) malloc( sizeof( PlayingSoundOptions ) ); - *options = *ps->options; +// TODO: same here +// TODO: should add a desired volume member and blend towards it +void audio_set_volume( Sound sound, float left, float right ) { + if( sound.state->stopped || sound.generation != sound.state->generation ) { + return; + } - options->volume[ 0 ] = left; - options->volume[ 1 ] = right; + SoundState * ss = sound.state; - // PlayingSoundOptions * old_options = ps->options; - ps->options = options; + if( right == -1.0f ) right = left; - write_barrier(); + u16 left_u16 = ( u16 ) ( left * UINT16_MAX ); + u16 right_u16 = ( u16 ) ( right * UINT16_MAX ); + u32 combined = ( left_u16 << 16 ) | right_u16; - // TODO: add old_options to free list + // set both channels atomically + ss->volume.combined = combined; } -void audio_mix( AudioOutputBuffer * buffer ) { +void audio_mix( MemoryArena * arena, AudioOutputBuffer * buffer ) { for( u32 i = 0; i < buffer->num_samples; i++ ) { buffer->samples[ i ] = 0; } - PlayingSound * prev = &ps_head; - PlayingSound * ps = prev->next; + SoundState * ss = ss_head; - while( ps ) { - assert( ps->sound.sample_rate == buffer->sample_rate ); - - if( ps->stopped ) { - prev->next = ps->next; - // TODO: add ps to free list - // TODO: add ps->options to free list + while( ss ) { + if( ss->stopped ) { + if( !ss->freelisted ) { + ss->freelisted = true; + freelist_add( &ss_freelist, ss ); + } } 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; + // TODO: resampling + assert( ss->data.sample_rate == buffer->sample_rate ); 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 ); + // if we aren't looping this sound then make sure we play to the end + if( !ss->loop ) { + samples_to_write = min( ss->data.num_samples - ss->num_played_samples, samples_to_write ); } 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 ]; + // TODO: we should do this in float buffers or we get horrible clipping when things overflow + // the modulo is so the sound loops properly + buffer->samples[ i ] += ( ( float ) ss->volume.left / UINT16_MAX ) * ss->data.samples[ ( i + ss->num_played_samples ) % ss->data.num_samples ]; } - 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; + ss->num_played_samples += samples_to_write; + if( ss->num_played_samples >= ss->data.num_samples ) { + if( ss->loop ) { + ss->num_played_samples %= ss->data.num_samples; } else { - // TODO: add ps to free list - // TODO: add ps->options to free list - prev->next = ps->next; + if( !ss->freelisted ) { + ss->freelisted = true; + write_barrier(); + ss->stopped = true; + write_barrier(); + freelist_add( &ss_freelist, ss ); + } } } } - prev = ps; - ps = ps->next; + ss = ss->next; } } diff --git a/audio.h b/audio.h @@ -1,40 +1,49 @@ #ifndef _AUDIO_H_ #define _AUDIO_H_ +#include <glm/glm.hpp> + #include "intrinsics.h" #include "assets.h" #include "memory_arena.h" -struct PlayingSoundOptions { - float volume[ 2 ]; - bool loop; - - bool positional; - glm::vec3 position; +union Volume { + struct { + u16 left; + u16 right; + }; - PlayingSoundOptions * next; + u32 combined; }; -struct PlayingSound { - Sound sound; +// TODO: cache friendly layout +struct SoundState { + SoundData data; // TODO: make this signed so we can delay sounds? u32 num_played_samples; - - PlayingSoundOptions * options; volatile bool stopped; + bool loop; + + Volume volume; + + bool positional; + glm::vec3 position; u32 generation; - PlayingSound * next; + SoundState * volatile next; + SoundState * volatile next_free; + volatile bool freelisted; }; -struct TaggedPlayingSound { +struct Sound { + SoundState * state; u32 generation; - PlayingSound ps; }; struct AudioOutputBuffer { + // num_channels? u32 sample_rate; u32 num_samples; @@ -45,11 +54,13 @@ struct PlaySoundOptions { bool loop; }; -PlayingSound * audio_play_sound( MemoryArena * arena, Sound sound, bool loop = false ); -void audio_stop_sound( PlayingSound * ps ); +void audio_init( MemoryArena * arena ); + +Sound audio_play_sound( MemoryArena * arena, SoundData data, bool loop = false ); +void audio_stop_sound( Sound sound ); -void audio_set_volume( PlayingSound * ps, float left, float right = -1.0f ); +void audio_set_volume( Sound sound, float left, float right = -1.0f ); -void audio_mix( AudioOutputBuffer * buffer ); +void audio_mix( MemoryArena * arena, AudioOutputBuffer * buffer ); #endif // _AUDIO_H_ diff --git a/linux_audio.cc b/linux_audio.cc @@ -18,6 +18,8 @@ static int milliseconds( int ms ) { } static u32 sample_rate; +static u8 mix_memory[ kilobytes( 512 ) ]; +static MemoryArena mix_arena; static THREAD( mix_worker ) { snd_pcm_t * pcm = ( snd_pcm_t * ) data; @@ -29,7 +31,7 @@ static THREAD( mix_worker ) { buffer.samples = samples; while( true ) { - audio_mix( &buffer ); + audio_mix( &mix_arena, &buffer ); for( u32 i = 0; i < buffer.num_samples; ) { // s16 * channels[ 2 ] = { sound.samples + i, sound.samples + sound.num_samples + i }; @@ -58,10 +60,10 @@ static void error_handler( const char * file, int line, const char * function, i printf( "\n" ); } -static Sound * make_sin_wave( u32 sample_rate, u32 frequency ) { +static SoundData * make_sin_wave( u32 sample_rate, u32 frequency ) { const u32 num_samples = sample_rate * ( 1.0f / frequency ); - Sound * sound = ( Sound * ) malloc( sizeof( Sound ) ); + SoundData * sound = ( SoundData * ) malloc( sizeof( SoundData ) ); sound->samples = ( s16 * ) malloc( num_samples * sizeof( s16 ) ); sound->num_samples = num_samples; @@ -83,18 +85,26 @@ int main( int argc, char ** argv ) { MemoryArena arena; memarena_init( &arena, memory, sizeof( memory ) ); - u8 * wave = file_get_contents( "01 - Abandon.wav" ); - Sound sound; + memarena_init( &mix_arena, mix_memory, sizeof( mix_memory ) ); + + audio_init( &arena ); + + u8 * wave = file_get_contents( "02 - Unbreakable.wav" ); + SoundData sound; bool ok = wave_decode( &arena, wave, &sound ); assert( ok ); + SoundData * t = make_sin_wave( sound.sample_rate, 200 ); + sound.samples = t->samples; + sound.num_samples = t->num_samples; // TODO: remove this when the mixer can do resampling sample_rate = sound.sample_rate; - Sound * sin_wave = make_sin_wave( sound.sample_rate, 200 ); + SoundData * sin_data = make_sin_wave( sound.sample_rate, 500 ); - audio_play_sound( &arena, sound ); - PlayingSound * ps_sin = audio_play_sound( &arena, *sin_wave, true ); + audio_set_volume( audio_play_sound( &arena, sound, true ), 0.8 ); + Sound sound_sin = audio_play_sound( &arena, *sin_data, true ); + bool playing = true; const char * pcmname = argc == 2 ? argv[ 1 ] : "default"; @@ -130,18 +140,30 @@ int main( int argc, char ** argv ) { size_t n; float volume = 0.1f; - audio_set_volume( ps_sin, volume ); + audio_set_volume( sound_sin, volume ); while( true ) { getline( &line, &n, stdin ); if( strcmp( line, "+\n" ) == 0 ) { volume += 0.1f; - audio_set_volume( ps_sin, volume ); + audio_set_volume( sound_sin, volume ); } else if( strcmp( line, "-\n" ) == 0 ) { volume -= 0.1f; - audio_set_volume( ps_sin, volume ); + audio_set_volume( sound_sin, volume ); } + else if( strcmp( line, "s\n" ) == 0 ) { + if( playing ) { + audio_stop_sound( sound_sin ); + } + else { + sound_sin = audio_play_sound( &arena, *sin_data, true ); + audio_set_volume( sound_sin, volume ); + } + playing = !playing; + } + + printf( "%f\n", volume ); } assert( snd_pcm_close( pcm ) == 0 ); diff --git a/wave.cc b/wave.cc @@ -49,7 +49,7 @@ static bool wave_supported_format( const WaveFMT * fmt ) { return true; } -bool wave_decode( MemoryArena * arena, u8 * data, Sound * sound ) { +bool wave_decode( MemoryArena * arena, u8 * data, SoundData * sound ) { WaveHeader * header = ( WaveHeader * ) data; if( header->id_riff != WAVEID_RIFF || header->id_wave != WAVEID_WAVE ) { diff --git a/wave.h b/wave.h @@ -5,6 +5,6 @@ #include "memory_arena.h" #include "assets.h" -bool wave_decode( MemoryArena * arena, u8 * data, Sound * sound ); +bool wave_decode( MemoryArena * arena, u8 * data, SoundData * sound ); #endif // _WAVE_H_