commit aeeb02e1911ba2b1ac3b97d60eccd628576a5b3e parent b303462d738b75ae6e329babcf9b45074c281168 Author: Michael Savage <mikejsavage@gmail.com> Date: Sat Aug 20 20:53:06 +0100 Make the mixer less insane and split out AudioOutputDevice (i.e. ALSA) Diffstat:
Makefile | | | 5 | ++--- |
audio.cc | | | 225 | ++++++++++++++++++++++--------------------------------------------------------- |
linux_audio.cc | | | 172 | ------------------------------------------------------------------------------- |
linux_audio.h | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
mixer.cc | | | 236 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
mixer.h | | | 32 | ++++++++++++++++++++++++++++++++ |
platform_audio.h | | | 10 | ++++++++++ |
diff --git a/Makefile b/Makefile @@ -1,5 +1,5 @@ BINS := medfall pp sound -MODULES := bsp.so btt.so hm.so sm.so audio.so +MODULES := bsp.so btt.so hm.so sm.so STBS := truetype image image_write perlin all: opengl33.cc $(BINS) $(MODULES) test_lockfree @@ -7,13 +7,12 @@ all: opengl33.cc $(BINS) $(MODULES) test_lockfree # Binary dependencies medfall: main.o gl.o log.o memory_arena.o opengl33.o pp: pp.o stb_image.o log.o stb_image_write.o -sound: linux_audio.o wave.o audio.o memory_arena.o +sound: audio.o mixer.o log.o memory_arena.o wave.o # Module dependencies bsp.so: bsp.o bsp_renderer.o hm.so: hm.o heightmap.o terrain_manager.o stb_perlin.o btt.so: btt.o heightmap.o gpubtt.o skybox.o -audio.so: test_audio.o audio.o wave.o linux_audio.o sm.so: shadow_map.o test_lockfree: relacy.cc nonblocking_fixed_spmc_queue.h nonblocking_fixed_spsc_queue.h diff --git a/audio.cc b/audio.cc @@ -1,187 +1,88 @@ +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + #include "intrinsics.h" -#include "audio.h" +#include "mixer.h" +#include "wave.h" #include "assets.h" -#include "memory_arena.h" -#include "platform_atomic.h" - -// TODO: check all soundstates are stopped after a certain one in the mixer loop? - -// queue normally looks like: -// dummy -> ... -> last_free -> ... -> tail -struct SoundStateFreeList { - // TODO: order these - // TODO: volatiles? we need something for mfences. - SoundState * tail; - SoundState * dummy; - - 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->last_free = ss_head; - write_barrier(); -} - -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 void freelist_add( SoundStateFreeList * freelist, SoundState * ss ) { - // printf( "add to freelist\n" ); - freelist->tail->next_free = ss; - write_barrier(); - freelist->tail = ss; -} - -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; - - freelist->last_free = last_free->next_free; - - write_barrier(); - - *out = last_free; - return true; -} - -void audio_init( MemoryArena * arena ) { - freelist_init( arena, &ss_freelist ); -} - -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; -} - -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 ); - const u32 new_generation = ss->generation + 1; +static SoundData make_sin_wave( u32 sample_rate, u32 frequency ) { + const u32 num_samples = sample_rate * ( 1.0f / frequency ); - // *ss = { }; - ss->data = data; - ss->generation = new_generation; - ss->volume = { UINT16_MAX, UINT16_MAX }; - ss->loop = loop; - ss->stopped = false; + SoundData sound; + sound.samples = ( s16 * ) malloc( num_samples * sizeof( s16 ) ); - if( !reused ) ss->next = ss_head->next; - ss->next_free = NULL; - ss->freelisted = false; + sound.num_samples = num_samples; + sound.sample_rate = sample_rate; + sound.num_channels = 1; - write_barrier(); - - if( !reused ) ss_head->next = ss; - - Sound sound; - sound.generation = new_generation; - sound.state = ss; + for( u32 i = 0; i < num_samples; i++ ) { + const float t = ( float ) i / num_samples; + sound.samples[ i ] = S16_MAX * sinf( t * 2.0f * M_PI ); + } return sound; } -// 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; +static u8 memory[ megabytes( 64 ) ]; +int main( int argc, char ** argv ) { + MemoryArena arena; + memarena_init( &arena, memory, sizeof( memory ) ); - write_barrier(); - } -} + AudioOutputDevice output_device; + audio_output_open( &output_device ); + mixer_init( &output_device ); -// 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; - } + u8 * wave = file_get_contents( "02 - Unbreakable.wav" ); + SoundData sound; + bool ok = wave_decode( &arena, wave, &sound ); + assert( ok ); - SoundState * ss = sound.state; + // mixer_play( make_sin_wave( sound.sample_rate, 200 ), MIXER_LOOP ); - if( right == -1.0f ) right = left; + PlayingSoundID song = mixer_play( sound ); + mixer_volume( song, 0.8f ); - u16 left_u16 = ( u16 ) ( left * UINT16_MAX ); - u16 right_u16 = ( u16 ) ( right * UINT16_MAX ); - u32 combined = ( left_u16 << 16 ) | right_u16; + // snd_output_t *log; + // if (snd_output_stdio_attach(&log, stderr, 0) >= 0) { + // snd_pcm_dump(pcm, log); snd_output_close(log); } - // set both channels atomically - ss->volume.combined = combined; -} + char * line = NULL; + size_t n; -void audio_mix( MemoryArena * arena, AudioOutputBuffer * buffer ) { - for( u32 i = 0; i < buffer->num_samples; i++ ) { - buffer->samples[ i ] = 0; - } + SoundData sin_data = make_sin_wave( sound.sample_rate, 500 ); + PlayingSoundID sound_sin = 0; + float volume = 1.0f; + bool playing = false; + // audio_set_volume( sound_sin, volume ); - SoundState * ss = ss_head; + for( ;; ) { + printf( "%f %s %u\n", volume, playing ? "playing" : "stopped", sound_sin ); - while( ss ) { - if( ss->stopped ) { - if( !ss->freelisted ) { - ss->freelisted = true; - freelist_add( &ss_freelist, ss ); - } - } - else { - // TODO: resampling - assert( ss->data.sample_rate == buffer->sample_rate ); - - u32 samples_to_write = buffer->num_samples; - // 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 ); - } + getline( &line, &n, stdin ); - for( u32 i = 0; i < samples_to_write; i++ ) { - // 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 ]; + if( strcmp( line, "+\n" ) == 0 ) { + volume = clamp01( volume + 0.1f ); + mixer_volume( sound_sin, volume ); + } + else if( strcmp( line, "-\n" ) == 0 ) { + volume = clamp01( volume - 0.1f ); + mixer_volume( sound_sin, volume ); + } + else if( strcmp( line, "s\n" ) == 0 ) { + if( playing ) { + mixer_stop( sound_sin ); } - - 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 { - if( !ss->freelisted ) { - ss->freelisted = true; - write_barrier(); - ss->stopped = true; - write_barrier(); - freelist_add( &ss_freelist, ss ); - } - } + else { + sound_sin = mixer_play( sin_data, true ); + mixer_volume( sound_sin, volume ); } + playing = !playing; } - - ss = ss->next; } + + mixer_term(); + + return 0; } diff --git a/linux_audio.cc b/linux_audio.cc @@ -1,172 +0,0 @@ -#include <stdlib.h> -#include <stdarg.h> -#include <unistd.h> -#include <math.h> - -#include <alsa/asoundlib.h> - -#include "intrinsics.h" -#include "wave.h" -#include "assets.h" -#include "audio.h" -#include "platform_thread.h" - -#define FRAMES 1024 - -static int milliseconds( int ms ) { - return ms * 1000; -} - -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; - - s16 samples[ 2048 ]; - AudioOutputBuffer buffer; - buffer.sample_rate = sample_rate; - buffer.num_samples = array_count( samples ); - buffer.samples = samples; - - while( true ) { - 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 }; - 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; - } - } - } - - THREAD_END; -} - -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 ); - - va_list args; - va_start( args, fmt ); - vprintf( fmt, args ); - va_end( args ); - printf( "\n" ); -} - -static SoundData * make_sin_wave( u32 sample_rate, u32 frequency ) { - const u32 num_samples = sample_rate * ( 1.0f / frequency ); - - SoundData * sound = ( SoundData * ) malloc( sizeof( SoundData ) ); - sound->samples = ( s16 * ) malloc( num_samples * sizeof( s16 ) ); - - sound->num_samples = num_samples; - sound->sample_rate = sample_rate; - sound->num_channels = 1; - - for( u32 i = 0; i < num_samples; i++ ) { - const float t = ( float ) i / num_samples; - sound->samples[ i ] = INT16_MAX * sinf( t * 2.0f * M_PI ); - } - - return sound; -} - -static u8 memory[ megabytes( 64 ) ]; -int main( int argc, char ** argv ) { - snd_lib_error_set_handler( error_handler ); - - MemoryArena arena; - memarena_init( &arena, memory, sizeof( memory ) ); - - 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; - - SoundData * sin_data = make_sin_wave( sound.sample_rate, 500 ); - - 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"; - - snd_pcm_t * pcm; - const int ok_open = snd_pcm_open( &pcm, pcmname, SND_PCM_STREAM_PLAYBACK, 0 ); - - if( ok_open != 0 ) { - printf( "nope: %s\n", snd_strerror( ok_open ) ); - return 1; - } - - const int channels = sound.num_channels; - const int sample_rate = sound.sample_rate; - const int latency = milliseconds( 10 ); - - const int ok_set_params = snd_pcm_set_params( pcm, - SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_NONINTERLEAVED, - channels, sample_rate, 1, latency ); - - if( ok_set_params != 0 ) { - printf( "nope: %s\n", snd_strerror( ok_open ) ); - return 1; - } - - // snd_output_t *log; - // if (snd_output_stdio_attach(&log, stderr, 0) >= 0) { - // snd_pcm_dump(pcm, log); snd_output_close(log); } - - Thread mix_thread; - thread_init( &mix_thread, mix_worker, pcm ); - - char * line = NULL; - size_t n; - - float volume = 0.1f; - audio_set_volume( sound_sin, volume ); - while( true ) { - getline( &line, &n, stdin ); - - if( strcmp( line, "+\n" ) == 0 ) { - volume += 0.1f; - audio_set_volume( sound_sin, volume ); - } - else if( strcmp( line, "-\n" ) == 0 ) { - volume -= 0.1f; - 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 ); - - return 0; -} diff --git a/linux_audio.h b/linux_audio.h @@ -0,0 +1,72 @@ +#ifndef _LINUX_AUDIO_H_ +#define _LINUX_AUDIO_H_ + +#include <stdio.h> +#include <stdarg.h> + +#include <alsa/asoundlib.h> + +#include "intrinsics.h" +#include "log.h" + +struct AudioOutputDevice { + snd_pcm_t * pcm; +}; + +static inline void audio_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 ); + + va_list args; + va_start( args, fmt ); + vprintf( fmt, args ); + va_end( args ); + printf( "\n" ); +} + +inline void audio_output_open( AudioOutputDevice * device ) { + // TODO: make an audio_output_init_lib function to do this + dlload + snd_lib_error_set_handler( audio_error_handler ); + + const int channels = 2; + const int sample_rate = 44100; + const int ms = 1000; + const int latency = 10 * ms; + + int err_open = snd_pcm_open( &device->pcm, "default", SND_PCM_STREAM_PLAYBACK, 0 ); + if( err_open != 0 ) { + // TODO: maybe don't kill the program + ERROR( "Couldn't open sound output: %s", snd_strerror( err_open ) ); + } + + int err_params = snd_pcm_set_params( device->pcm, + SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_NONINTERLEAVED, + channels, sample_rate, 1, latency ); + if( err_params != 0 ) { + // TODO: maybe don't kill the program + ERROR( "Couldn't configure sound output: %s", snd_strerror( err_params ) ); + } +} + +inline void audio_output_close( AudioOutputDevice * device ) { + int err = snd_pcm_close( device->pcm ); + if( err != 0 ) { + // TODO: maybe don't kill the program + ERROR( "Couldn't close sound output: %s", snd_strerror( err ) ); + } +} + +inline void audio_output_write( AudioOutputDevice * device, s16 * samples, size_t num_samples ) { + for( u32 i = 0; i < num_samples; ) { + s16 * channels[ 2 ] = { samples + i, samples + i }; + + snd_pcm_sframes_t written = snd_pcm_writen( device->pcm, ( void ** ) channels, num_samples - i ); + if( written <= 0 ) { + snd_pcm_recover( device->pcm, written, 1 ); + } + else { + i += written; + } + } +} + +#endif // _LINUX_AUDIO_H_ diff --git a/mixer.cc b/mixer.cc @@ -0,0 +1,236 @@ +#include "intrinsics.h" +#include "mixer.h" +#include "platform_audio.h" +#include "nonblocking_fixed_spsc_queue.h" + +struct PlayingSound { + SoundData data; + s32 num_played_samples; // TODO: ssize_t? + float volume; + u16 next_free; + // TODO: next_playing? allows us to make playing implicit + bool loop; + bool playing; +}; + +enum MixerCommandType { + MIXER_PLAY, + MIXER_VOLUME, + MIXER_STOP, + MIXER_STOP_ALL, +}; + +struct MixerCommand { + MixerCommandType type; + SoundData sound_to_play; + PlayingSoundID sound_id; + s32 start_pos; + float volume; // TODO: per channel volume? + bool loop; +}; + +#define COMMAND_QUEUE_SIZE 256 + +#define RESET_SOUND_COUNTER 0 +static_assert( RESET_SOUND_COUNTER != INVALID_SOUND_ID ); + +static PlayingSound playing_sounds[ MAX_CONCURRENT_SOUNDS ]; +static PlayingSoundID ids[ MAX_CONCURRENT_SOUNDS ]; +static u16 first_free_sound; +static NonblockingFixedSPSCQueue< MixerCommand, COMMAND_QUEUE_SIZE > command_queue; +static Thread mixer_thread; +static PlayingSoundID sound_counter; + +static bool mixer_sound_id_to_slot( PlayingSoundID sound_id, size_t * slot ) { + for( size_t i = 0; i < array_count( playing_sounds ); i++ ) { + if( ids[ i ] == sound_id ) { + *slot = i; + return true; + } + } + + return false; +} + +// TODO: replace this with generic memory pool +static bool mixer_find_free_slot( size_t * free_slot ) { + u16 slot = first_free_sound; + u16 * prev = &first_free_sound; + + while( slot < MAX_CONCURRENT_SOUNDS ) { + PlayingSound & ps = playing_sounds[ slot ]; + if( !ps.playing ) { + *prev = ps.next_free; + *free_slot = slot; + return true; + } + + prev = &ps.next_free; + } + + return false; +} + +static void mixer_stop_all_impl() { + first_free_sound = 0; + for( size_t i = 0; i < array_count( playing_sounds ); i++ ) { + playing_sounds[ i ].playing = false; + playing_sounds[ i ].next_free = i + 1; + ids[ i ] = INVALID_SOUND_ID; + } +} + +static void mixer_process_command( MixerCommand cmd ) { + // TODO: most of these should make smooth adjustments to avoid popping + switch( cmd.type ) { + case MIXER_PLAY: { + size_t slot; + if( mixer_find_free_slot( &slot ) ) { + PlayingSound & ps = playing_sounds[ slot ]; + ps.data = cmd.sound_to_play; + ps.num_played_samples = cmd.start_pos; + ps.volume = 1.0f; + ps.loop = cmd.loop; + ps.playing = true; + ids[ slot ] = cmd.sound_id; + } + } break; + + case MIXER_VOLUME: { + size_t slot; + if( mixer_sound_id_to_slot( cmd.sound_id, &slot ) ) { + playing_sounds[ slot ].volume = cmd.volume; + } + } break; + + case MIXER_STOP: { + size_t slot; + if( mixer_sound_id_to_slot( cmd.sound_id, &slot ) ) { + playing_sounds[ slot ].playing = false; + playing_sounds[ slot ].next_free = first_free_sound; + first_free_sound = slot; + } + } 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 + + // TODO: there is some funny casting in here + s32 samples_to_write = num_samples; + if( !ps->loop ) { + samples_to_write = min( samples_to_write, ( s32 ) ps->data.num_samples - ps->num_played_samples ); + } + + float scale = 1.0f / float( S16_MAX ); + for( size_t i = 0; i < samples_to_write; i++ ) { + // TODO: this is not quite right + size_t j = ( ps->num_played_samples + i ) % ps->data.num_samples; + samples[ i ] += float( ps->data.samples[ j ] ) * scale * ps->volume; + } + + ps->num_played_samples += samples_to_write; + if( ps->loop ) { + ps->num_played_samples %= ps->data.num_samples; + } + else if( ps->num_played_samples == ps->data.num_samples ) { + ps->playing = false; + } +} + +static THREAD( mixer_thread_proc ) { + AudioOutputDevice * output_device = ( AudioOutputDevice * ) data; + + for( ;; ) { + // TODO: limit how many commands we process at once so people + // can't jam the queue and make us skip + for( size_t i = 0; i < COMMAND_QUEUE_SIZE; i++ ) { + MixerCommand cmd; + if( !command_queue.dequeue( &cmd ) ) break; + mixer_process_command( cmd ); + } + + // TODO: channels + float samples[ 2048 ]; + for( size_t i = 0; i < array_count( samples ); i++ ) { + samples[ i ] = 0.0f; + } + + for( size_t i = 0; i < array_count( playing_sounds ); i++ ) { + PlayingSound & ps = playing_sounds[ i ]; + if( ps.playing ) { + mixer_mix_sound( &ps, samples, array_count( samples ) ); + } + } + + s16 finalised_samples[ 2048 ]; + for( size_t i = 0; i < array_count( samples ); i++ ) { + // TODO: this float -> s16 is not quite right + finalised_samples[ i ] = clamp11( samples[ i ] ) * S16_MAX; + } + + audio_output_write( output_device, finalised_samples, array_count( finalised_samples ) ); + } + + THREAD_END; +} + +void mixer_init( AudioOutputDevice * output_device ) { + mixer_stop_all_impl(); + sound_counter = RESET_SOUND_COUNTER; + thread_init( &mixer_thread, mixer_thread_proc, output_device ); +} + +void mixer_term() { + thread_cancel( &mixer_thread ); + thread_join( &mixer_thread ); + command_queue.clear(); +} + +PlayingSoundID mixer_play( SoundData sound, MixerLoopBool loop, s32 start_pos ) { + // TODO: check start_pos fits in PlayingSound.num_played_samples + 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; + while( !command_queue.enqueue( cmd ) ); + + sound_counter++; + if( sound_counter == INVALID_SOUND_ID ) { + sound_counter++; + } + + return cmd.sound_id; +} + +PlayingSoundID mixer_play( SoundData sound, s32 start_pos ) { + return mixer_play( sound, MIXER_DONTLOOP, start_pos ); +} + +void mixer_volume( PlayingSoundID sound_id, float volume ) { + MixerCommand cmd; + cmd.type = MIXER_VOLUME; + cmd.sound_id = sound_id; + cmd.volume = clamp01( volume ); + while( !command_queue.enqueue( cmd ) ); +} + +void mixer_stop( PlayingSoundID sound_id ) { + MixerCommand cmd; + cmd.type = MIXER_STOP; + cmd.sound_id = sound_id; + while( !command_queue.enqueue( cmd ) ); +} + +void mixer_stop_all() { + MixerCommand cmd; + cmd.type = MIXER_STOP_ALL; + while( !command_queue.enqueue( cmd ) ); +} diff --git a/mixer.h b/mixer.h @@ -0,0 +1,32 @@ +#ifndef _MIXER_H_ +#define _MIXER_H_ + +#define MAX_CONCURRENT_SOUNDS 1024 + +#include "intrinsics.h" +#include "assets.h" + +#include "platform_audio.h" + +typedef u64 PlayingSoundID; +#define INVALID_SOUND_ID U64_MAX + +enum MixerLoopBool { + MIXER_DONTLOOP, + MIXER_LOOP, +}; + +// TODO: maybe make this take a QueueReader +void mixer_init( AudioOutputDevice * output_device ); +void mixer_term(); + +// 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 + +#endif // _MIXER_H_ diff --git a/platform_audio.h b/platform_audio.h @@ -0,0 +1,10 @@ +#ifndef _PLATFORM_AUDIO_H_ +#define _PLATFORM_AUDIO_H_ + +#if defined( __linux__ ) +#include "linux_audio.h" +#else +#error new platform +#endif + +#endif // _PLATFORM_AUDIO_H_