From: hgn Date: Wed, 5 Mar 2025 00:53:13 +0000 (+0000) Subject: Unshittifying audio system X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=147afe2248947df978caa62528042829ab46d027;p=vg.git Unshittifying audio system --- diff --git a/submodules/SDL_GameControllerDB b/submodules/SDL_GameControllerDB index 6ed8d05..c5b4df0 160000 --- a/submodules/SDL_GameControllerDB +++ b/submodules/SDL_GameControllerDB @@ -1 +1 @@ -Subproject commit 6ed8d054340ee8a93a684e11360b66cd8a5c168e +Subproject commit c5b4df0e1061175cb11e3ebbf8045178339864a5 diff --git a/submodules/anyascii b/submodules/anyascii index 44e971c..eb5332d 160000 --- a/submodules/anyascii +++ b/submodules/anyascii @@ -1 +1 @@ -Subproject commit 44e971c774d9ec67ca6c1f16c5a476724821ab63 +Subproject commit eb5332d0b5e48d58397e6f27475a18e058330d23 diff --git a/submodules/qoi b/submodules/qoi index b8d77df..dfc056e 160000 --- a/submodules/qoi +++ b/submodules/qoi @@ -1 +1 @@ -Subproject commit b8d77df1e80b652a57f0b7270449b179a6b91f40 +Subproject commit dfc056e813c98d307238d35f7f041a725d699dfc diff --git a/submodules/stb b/submodules/stb index 8b5f1f3..5736b15 160000 --- a/submodules/stb +++ b/submodules/stb @@ -1 +1 @@ -Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55 +Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 diff --git a/vg_audio.c b/vg_audio.c index f85bd99..77d5eb5 100644 --- a/vg_audio.c +++ b/vg_audio.c @@ -11,8 +11,8 @@ struct vg_audio _vg_audio = { - .master_volume = 1.0f, - .dsp_enabled = 1 + .master_volume_ui = 1.0f, + .dsp_enabled_ui = 1 }; static struct vg_profile @@ -25,9 +25,9 @@ static struct vg_profile static f64 _vg_audio_budget() { - audio_lock(); + vg_audio_lock(); f64 ms = ((double)_vg_audio.samples_written_last_audio_frame / 44100.0) * 1000.0; - audio_unlock(); + vg_audio_unlock(); return ms; } @@ -42,1080 +42,1096 @@ struct vg_profile_set static _vg_prof_audio = } }; -#if 0 - +_Thread_local static bool _vg_audio_thread_has_lock = 0; -/* - * These functions are called from the main thread and used to prevent bad - * access. TODO: They should be no-ops in release builds. - */ -static int audio_lock_checker_load(void) +void vg_audio_lock(void) { - int value; - SDL_AtomicLock( &vg_audio.sl_checker ); - value = vg_audio.sync_locked; - SDL_AtomicUnlock( &vg_audio.sl_checker ); - return value; + SDL_LockMutex( _vg_audio.mutex ); + _vg_audio_thread_has_lock = 1; } -static void audio_lock_checker_store( int value ) +void vg_audio_unlock(void) { - SDL_AtomicLock( &vg_audio.sl_checker ); - vg_audio.sync_locked = value; - SDL_AtomicUnlock( &vg_audio.sl_checker ); + _vg_audio_thread_has_lock = 0; + SDL_UnlockMutex( _vg_audio.mutex ); } -static void audio_require_lock(void) +static void vg_audio_assert_lock(void) { - if( audio_lock_checker_load() ) - return; - - vg_error( "Modifying sound effects systems requires locking\n" ); - abort(); + if( _vg_audio_thread_has_lock == 0 ) + { + vg_error( "vg_audio function requires locking\n" ); + abort(); + } } -/* - * thread 1 +/* clip loading from disk + * ------------------------------------------------------------------------------- */ +void audio_clip_load( audio_clip *clip, void *lin_alloc ) +{ + if( lin_alloc == NULL ) + lin_alloc = _vg_audio.data_allocator; -#define AUDIO_EDIT_VOLUME_SLOPE 0x1 -#define AUDIO_EDIT_VOLUME 0x2 -#define AUDIO_EDIT_LFO_PERIOD 0x4 -#define AUDIO_EDIT_LFO_WAVE 0x8 -#define AUDIO_EDIT_LFO_ATTACHMENT 0x10 -#define AUDIO_EDIT_SPACIAL 0x20 -#define AUDIO_EDIT_OWNERSHIP 0x40 -#define AUDIO_EDIT_SAMPLING_RATE 0x80 + if( _vg_audio.always_keep_clips_compressed ) + { + if( (clip->flags & AUDIO_FLAG_FORMAT) != k_audio_format_bird ) + { + clip->flags &= ~AUDIO_FLAG_FORMAT; + clip->flags |= k_audio_format_vorbis; + } + } -int audio_channel_finished( audio_channel *ch ) -{ - audio_require_lock(); - if( ch->readable_activity == k_channel_activity_end ) - return 1; - else - return 0; -} + /* load in directly */ + u32 format = clip->flags & AUDIO_FLAG_FORMAT; -/* - * Committers - * ----------------------------------------------------------------------------- - */ -int audio_channel_load_source( audio_channel *ch ) -{ - u32 format = ch->clip->flags & AUDIO_FLAG_FORMAT; + /* TODO: This contains audio_lock() and unlock, but i don't know why + * can probably remove them. Low priority to check this */ - if( format == k_audio_format_vorbis ){ - /* Setup vorbis decoder */ - u32 index = ch - vg_audio.channels; + /* TODO: packed files for vorbis etc, should take from data if its not not + * NULL when we get the clip + */ + + if( format == k_audio_format_vorbis ) + { + if( !clip->path ) + vg_error( "No path specified, embeded vorbis unsupported\n" ); + + vg_audio_lock(); + clip->any_data = vg_file_read( lin_alloc, clip->path, &clip->size ); + vg_audio_unlock(); + + if( !clip->any_data ) + vg_error( "Audio failed to load\n" ); + + float mb = (float)(clip->size) / (1024.0f*1024.0f); + vg_info( "Loaded audio clip '%s' (%.1fmb)\n", clip->path, mb ); + } + else if( format == k_audio_format_stereo ) + { + vg_error( "Unsupported format (Stereo uncompressed)\n" ); + } + else if( format == k_audio_format_bird ) + { + if( !clip->any_data ) + { + vg_error( "No data, external birdsynth unsupported\n" ); + } + + u32 total_size = clip->size + sizeof(struct synth_bird); + total_size -= sizeof(struct synth_bird_settings); + total_size = vg_align8( total_size ); + + if( total_size > AUDIO_DECODE_SIZE ) + vg_error( "Bird coding too long, and exceeds maximum decode size\n" ); + + struct synth_bird *bird = vg_linear_alloc( lin_alloc, total_size ); + memcpy( &bird->settings, clip->any_data, clip->size ); - u8 *buf = (u8*)vg_audio.decode_buffer, - *loc = &buf[AUDIO_DECODE_SIZE*index]; + clip->any_data = bird; + clip->size = total_size; + + vg_info( "Loaded bird synthesis pattern (%u bytes)\n", total_size ); + } + else + { + if( !clip->path ) + { + vg_error( "No path specified, embeded mono unsupported\n" ); + } + + vg_linear_clear( vg_mem.scratch ); + u32 fsize; stb_vorbis_alloc alloc = { - .alloc_buffer = (char *)loc, + .alloc_buffer = vg_linear_alloc( vg_mem.scratch, AUDIO_DECODE_SIZE ), .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE }; + void *filedata = vg_file_read( vg_mem.scratch, clip->path, &fsize ); + int err; - stb_vorbis *decoder = stb_vorbis_open_memory( - ch->clip->data, - ch->clip->size, &err, &alloc ); - - if( !decoder ){ - vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", - ch->clip->path, err ); - return 0; - } - else{ - ch->clip_length = stb_vorbis_stream_length_in_samples( decoder ); - ch->handle.vorbis = decoder; + stb_vorbis *decoder = stb_vorbis_open_memory( filedata, fsize, &err, &alloc ); + + if( !decoder ) + { + vg_fatal_condition(); + vg_info( "Vorbis decode error\n" ); + vg_info( "stb_vorbis_open_memory failed on '%s' (%d)\n", clip->path, err ); + vg_fatal_exit(); } - } - else if( format == k_audio_format_bird ){ - u32 index = ch - vg_audio.channels; - u8 *buf = (u8*)vg_audio.decode_buffer; - struct synth_bird *loc = (void *)&buf[AUDIO_DECODE_SIZE*index]; + /* only mono is supported in uncompressed */ + u32 length_samples = stb_vorbis_stream_length_in_samples( decoder ), + data_size = length_samples * sizeof(i16); - memcpy( loc, ch->clip->data, ch->clip->size ); - synth_bird_reset( loc ); + vg_audio_lock(); + clip->any_data = vg_linear_alloc( lin_alloc, vg_align8(data_size) ); + clip->size = length_samples; + vg_audio_unlock(); - ch->handle.bird = loc; - ch->clip_length = synth_bird_get_length_in_samples( loc ); - } - else if( format == k_audio_format_stereo ){ - ch->clip_length = ch->clip->size / 2; - } - else if( format == k_audio_format_gen ){ - ch->clip_length = 0xffffffff; - } - else{ - ch->clip_length = ch->clip->size; - } + int read_samples = stb_vorbis_get_samples_i16_downmixed( decoder, clip->any_data, length_samples ); - return 1; + if( read_samples != length_samples ) + vg_error( "Decode error, read_samples did not match length_samples\n" ); + } } -static void audio_decode_uncompressed_mono( i16 *src, u32 count, float *dst ) +void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) { - for( u32 i=0; itime ++; +/* + * ------------------------------------------------------------------------------- + */ - if( lfo->time >= lfo->_.period ) - lfo->time = 0; +static audio_channel *get_audio_channel( audio_channel_id id ) +{ + VG_ASSERT( (id > 0) && (id <= AUDIO_CHANNELS) ); + return &_vg_audio.channels[ id-1 ]; +} - float t = lfo->time; - t /= (float)lfo->_.period; +static struct audio_channel_controls *get_audio_channel_controls( audio_channel_id id ) +{ + vg_audio_assert_lock(); + VG_ASSERT( (id > 0) && (id <= AUDIO_CHANNELS) ); + return &_vg_audio.channels[ id-1 ].controls; +} - if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ - /* - * # - * # # - * # # - * # # - * ### # ### - * ## # - * # # - * # # - * ## - */ +static struct audio_channel_state *get_audio_channel_state( audio_channel_id id ) +{ + VG_ASSERT( (id > 0) && (id <= AUDIO_CHANNELS) ); + return &_vg_audio.channels[ id-1 ].state; +} - t *= 2.0f; - t -= 1.0f; +static audio_lfo *get_audio_lfo( audio_channel_id lfo_id ) +{ + VG_ASSERT( (lfo_id > 0) && (lfo_id <= AUDIO_LFOS) ); + return &_vg_audio.lfos[ lfo_id-1 ]; +} - return (( 2.0f * lfo->sqrt_polynomial_coefficient * t ) / - /* --------------------------------------- */ - ( 1.0f + lfo->_.polynomial_coefficient * t*t ) - - ) * (1.0f-fabsf(t)); - } - else{ - return 0.0f; - } +static struct audio_lfo_controls *get_audio_lfo_controls( audio_channel_id lfo_id ) +{ + vg_audio_assert_lock(); + VG_ASSERT( (lfo_id > 0) && (lfo_id <= AUDIO_LFOS) ); + return &_vg_audio.lfos[ lfo_id-1 ].controls; } -static void audio_channel_get_samples( audio_channel *ch, - u32 count, float *buf ) +static struct audio_lfo_state *get_audio_lfo_state( audio_channel_id lfo_id ) { - vg_profile_begin( &_vg_prof_audio_decode ); + VG_ASSERT( (lfo_id > 0) && (lfo_id <= AUDIO_LFOS) ); + return &_vg_audio.lfos[ lfo_id-1 ].state; +} - u32 remaining = count; - u32 buffer_pos = 0; +static void audio_channel_wake( audio_channel_id id ) +{ + audio_channel *channel = get_audio_channel( id ); + VG_ASSERT( channel->stage == k_channel_stage_active ); - u32 format = ch->clip->flags & AUDIO_FLAG_FORMAT; + struct audio_channel_state *channel_state = get_audio_channel_state( id ); + VG_ASSERT( channel_state->activity == k_channel_activity_wake ); - while( remaining ){ - u32 samples_this_run = VG_MIN(remaining, ch->clip_length - ch->cursor); - remaining -= samples_this_run; + u32 format = channel->clip->flags & AUDIO_FLAG_FORMAT; + if( format == k_audio_format_vorbis ) + { + /* Setup vorbis decoder */ + u8 *buf = (u8*)_vg_audio.decoding_buffer, + *loc = &buf[AUDIO_DECODE_SIZE*id]; - float *dst = &buf[ buffer_pos * 2 ]; - - if( format == k_audio_format_stereo ){ - for( int i=0;ihandle.vorbis, - dst, - samples_this_run ); + stb_vorbis_alloc alloc = { + .alloc_buffer = (char *)loc, + .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE + }; - if( read_samples != samples_this_run ){ - vg_warn( "Invalid samples read (%s)\n", ch->clip->path ); + int err; + stb_vorbis *decoder = stb_vorbis_open_memory( channel->clip->any_data, channel->clip->size, &err, &alloc ); - for( int i=0; ihandle.bird, dst, samples_this_run ); + if( !decoder ) + { + vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", channel->clip->path, err ); + channel_state->activity = k_channel_activity_error; } - else if( format == k_audio_format_gen ){ - void (*fn)( void *data, f32 *buf, u32 count ) = ch->clip->func; - fn( ch->clip->data, dst, samples_this_run ); + else + { + channel_state->loaded_clip_length = stb_vorbis_stream_length_in_samples( decoder ); + channel_state->decoder_handle.vorbis = decoder; + channel_state->activity = k_channel_activity_playing; } - else{ - i16 *src_buffer = ch->clip->data, - *src = &src_buffer[ch->cursor]; + } + else if( format == k_audio_format_bird ) + { + u8 *buf = (u8*)_vg_audio.decoding_buffer; + struct synth_bird *loc = (void *)&buf[AUDIO_DECODE_SIZE*id]; - audio_decode_uncompressed_mono( src, samples_this_run, dst ); - } + memcpy( loc, channel->clip->any_data, channel->clip->size ); + synth_bird_reset( loc ); - ch->cursor += samples_this_run; - buffer_pos += samples_this_run; - - if( (ch->flags & AUDIO_FLAG_LOOP) && remaining ){ - if( format == k_audio_format_vorbis ) - stb_vorbis_seek_start( ch->handle.vorbis ); - else if( format == k_audio_format_bird ) - synth_bird_reset( ch->handle.bird ); + channel_state->decoder_handle.bird = loc; + channel_state->loaded_clip_length = synth_bird_get_length_in_samples( loc ); + channel_state->activity = k_channel_activity_playing; + } + else if( format == k_audio_format_stereo ) + { + channel_state->loaded_clip_length = channel->clip->size / 2; + channel_state->activity = k_channel_activity_playing; + } + else if( format == k_audio_format_gen ) + { + channel_state->loaded_clip_length = 0xffffffff; + channel_state->activity = k_channel_activity_playing; + } + else + { + channel_state->loaded_clip_length = channel->clip->size; + channel_state->activity = k_channel_activity_playing; + } +} - ch->cursor = 0; - continue; - } - else - break; +static void audio_decode_uncompressed_mono( i16 *src, u32 count, f32 *dst ) +{ + for( u32 i=0; istage == k_channel_stage_none ) + { + channel->stage = k_channel_stage_allocation; + channel->ui_name[0] = 0; + channel->ui_colour = 0x00333333; + channel->group = 0; + channel->clip = NULL; + channel->ui_volume = 0; + channel->ui_pan = 0; + channel->ui_activity = k_channel_activity_wake; + + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->flags = 0x00; + controls->volume_target = AUDIO_VOLUME_100; + controls->volume_slew_rate_per_sample = (f64)AUDIO_VOLUME_100 / (0.1*44100.0); + controls->pan_target = 0; + controls->pan_slew_rate_per_sample = (f64)AUDIO_PAN_RIGHT_100 / (0.1*44100.0); + controls->sampling_rate_multiplier = 1.0f; + controls->lfo_id = 0; + controls->lfo_attenuation_amount = 0.0f; + v4_copy( (v4f){0,0,0,1}, controls->spacial_falloff ); + + struct audio_channel_state *state = get_audio_channel_state( id ); + state->activity = k_channel_activity_wake; + state->loaded_clip_length = 0; + state->decoder_handle.bird = NULL; + state->decoder_handle.vorbis = NULL; + state->cursor = 0; + state->volume = AUDIO_VOLUME_100; + state->pan = 0; + state->spacial_volume = 0; + state->spacial_pan = 0; + state->spacial_warm = 0; + return id; + } } - vg_profile_end( &_vg_prof_audio_decode ); + return 0; } -static void audio_channel_mix( audio_channel *ch, float *buffer ) +void vg_audio_set_channel_clip( audio_channel_id id, audio_clip *clip ) { - float framevol_l = vg_audio.internal_global_volume, - framevol_r = vg_audio.internal_global_volume; - - float frame_samplerate = ch->_.sampling_rate; + vg_audio_assert_lock(); - if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ - v3f delta; - v3_sub( ch->_.spacial_falloff, vg_audio.internal_listener_pos, delta ); + audio_channel *channel = get_audio_channel( id ); + VG_ASSERT( channel->stage == k_channel_stage_allocation ); + VG_ASSERT( channel->clip == NULL ); - float dist = v3_length( delta ), - vol = vg_maxf( 0.0f, 1.0f - ch->_.spacial_falloff[3]*dist ); + channel->clip = clip; - if( dist <= 0.01f ){ - - } - else{ - v3_muls( delta, 1.0f/dist, delta ); - float pan = v3_dot( vg_audio.internal_listener_ears, delta ); - vol = powf( vol, 5.0f ); + u32 audio_format = channel->clip->flags & AUDIO_FLAG_FORMAT; + if( audio_format == k_audio_format_bird ) + strcpy( channel->ui_name, "[array]" ); + else if( audio_format == k_audio_format_gen ) + strcpy( channel->ui_name, "[program]" ); + else + vg_strncpy( clip->path, channel->ui_name, 32, k_strncpy_always_add_null ); +} - framevol_l *= (vol * 0.5f) * (1.0f - pan); - framevol_r *= (vol * 0.5f) * (1.0f + pan); +void vg_audio_set_channel_group( audio_channel_id id, u16 group ) +{ + vg_audio_assert_lock(); - if( !(ch->clip->flags & AUDIO_FLAG_NO_DOPPLER) ){ - const float vs = 323.0f; + audio_channel *channel = get_audio_channel( id ); + VG_ASSERT( channel->stage == k_channel_stage_allocation ); + VG_ASSERT( channel->group == 0 ); + channel->group = group; + if( group ) + channel->ui_colour = (((u32)group * 29986577) & 0x00ffffff) | 0xff000000; +} - float dv = v3_dot(delta,vg_audio.internal_listener_velocity); - float doppler = (vs+dv)/vs; - doppler = vg_clampf( doppler, 0.6f, 1.4f ); - - if( fabsf(doppler-1.0f) > 0.01f ) - frame_samplerate *= doppler; - } - } +u32 vg_audio_count_channels_in_group( u16 group ) +{ + vg_audio_assert_lock(); - if( !vg_validf( framevol_l ) || - !vg_validf( framevol_r ) || - !vg_validf( frame_samplerate ) ) + u32 count = 0; + for( int id=1; id<=AUDIO_CHANNELS; id ++ ) + { + audio_channel *channel = get_audio_channel( id ); + if( channel->stage != k_channel_stage_none ) { - vg_fatal_condition(); - vg_info( "Invalid sampling conditions.\n" - "This crash is to protect your ears.\n" ); - vg_info( " channel: %p (%s)\n", ch, ch->name ); - vg_info( " sample_rate: %f\n", frame_samplerate ); - vg_info( " volume: L%f R%f\n", framevol_l, framevol_r ); - vg_info( " listener: %.2f %.2f %.2f [%.2f %.2f %.2f]\n", - vg_audio.internal_listener_pos[0], - vg_audio.internal_listener_pos[1], - vg_audio.internal_listener_pos[2], - vg_audio.internal_listener_ears[0], - vg_audio.internal_listener_ears[1], - vg_audio.internal_listener_ears[2] ); - vg_fatal_exit(); + if( channel->group == group ) + count ++; } } + + return count; +} - u32 buffer_length = AUDIO_MIX_FRAME_SIZE; - if( frame_samplerate != 1.0f ){ - float l = ceilf( (float)(AUDIO_MIX_FRAME_SIZE) * frame_samplerate ); - buffer_length = l+1; +audio_channel_id vg_audio_get_first_active_channel_in_group( u16 group ) +{ + vg_audio_assert_lock(); + for( int id=1; id<=AUDIO_CHANNELS; id ++ ) + { + audio_channel *channel = get_audio_channel( id ); + if( (channel->stage != k_channel_stage_none) && (channel->group == group) ) + return id; } + return 0; +} - float pcf[ AUDIO_MIX_FRAME_SIZE * 2 * 2 ]; +void vg_audio_sidechain_lfo_to_channel( audio_channel_id id, audio_channel_id lfo_id, f32 amount ) +{ + vg_audio_assert_lock(); + + audio_lfo *lfo = get_audio_lfo( lfo_id ); + VG_ASSERT( lfo->stage == k_channel_stage_active ); - audio_channel_get_samples( ch, buffer_length, pcf ); + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->lfo_id = lfo_id; + controls->lfo_attenuation_amount = amount; +} - vg_profile_begin( &_vg_prof_audio_mix ); +void vg_audio_add_channel_flags( audio_channel_id id, u32 flags ) +{ + vg_audio_assert_lock(); + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->flags |= flags; +} - float volume_movement = ch->volume_movement; - float const fvolume_rate = vg_maxf( 1.0f, ch->_.volume_rate ); - const float inv_volume_rate = 1.0f/fvolume_rate; +void vg_audio_set_channel_spacial_falloff( audio_channel_id id, v3f co, f32 range ) +{ + vg_audio_assert_lock(); - float volume = ch->_.volume; - const float volume_start = ch->volume_movement_start; - const float volume_target = ch->_.volume_target; + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + v3_copy( co, controls->spacial_falloff ); + controls->spacial_falloff[3] = range == 0.0f? 1.0f: 1.0f/range; - for( u32 j=0; j_.lfo ) - vol_norm *= 1.0f + audio_lfo_pull_sample(ch->_.lfo) * ch->_.lfo_amount; + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->volume_target = ((f64)AUDIO_VOLUME_100) * volume; - float vol_l = vol_norm * framevol_l, - vol_r = vol_norm * framevol_r, - sample_l, - sample_r; - - if( frame_samplerate != 1.0f ){ - /* absolutely garbage resampling, but it will do - */ + if( instant ) + { + audio_channel *channel = get_audio_channel( id ); + VG_ASSERT( channel->stage == k_channel_stage_allocation ); - float sample_index = frame_samplerate * (float)j; - float t = vg_fractf( sample_index ); + struct audio_channel_state *state = get_audio_channel_state( id ); + state->volume = controls->volume_target; + } +} - u32 i0 = floorf( sample_index ), - i1 = i0+1; +void vg_audio_set_channel_volume_slew_duration( audio_channel_id id, f64 length_seconds ) +{ + vg_audio_assert_lock(); + + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->volume_slew_rate_per_sample = (f64)AUDIO_VOLUME_100 / (length_seconds * 44100.0); +} - sample_l = pcf[ i0*2+0 ]*(1.0f-t) + pcf[ i1*2+0 ]*t; - sample_r = pcf[ i0*2+1 ]*(1.0f-t) + pcf[ i1*2+1 ]*t; - } - else{ - sample_l = pcf[ j*2+0 ]; - sample_r = pcf[ j*2+1 ]; - } +void vg_audio_set_channel_pan( audio_channel_id id, f64 pan, bool instant ) +{ + vg_audio_assert_lock(); + + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->pan_target = ((f64)AUDIO_PAN_RIGHT_100) * pan; + + if( instant ) + { + audio_channel *channel = get_audio_channel( id ); + VG_ASSERT( channel->stage == k_channel_stage_allocation ); - buffer[ j*2+0 ] += sample_l * vol_l; - buffer[ j*2+1 ] += sample_r * vol_r; + struct audio_channel_state *state = get_audio_channel_state( id ); + state->pan = controls->pan_target; } +} - ch->volume_movement += AUDIO_MIX_FRAME_SIZE; - ch->volume_movement = VG_MIN( ch->volume_movement, ch->_.volume_rate ); - ch->_.volume = volume; +void vg_audio_set_channel_pan_slew_duration( audio_channel_id id, f64 length_seconds ) +{ + vg_audio_assert_lock(); - vg_profile_end( &_vg_prof_audio_mix ); + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + controls->pan_slew_rate_per_sample = (f64)AUDIO_PAN_RIGHT_100 / (length_seconds * 44100.0); } -static void audio_mixer_callback( void *user, u8 *stream, int byte_count ) +void vg_audio_set_channel_sampling_rate( audio_channel_id id, f32 rate ) { - /* - * Copy data and move edit flags to commit flags - * ------------------------------------------------------------- */ - audio_lock(); - int use_dsp = vg_audio.dsp_enabled; - - v3_copy( vg_audio.external_listener_pos, vg_audio.internal_listener_pos ); - v3_copy( vg_audio.external_listener_ears, vg_audio.internal_listener_ears ); - v3_copy( vg_audio.external_lister_velocity, - vg_audio.internal_listener_velocity ); - vg_audio.internal_global_volume = vg_audio.external_global_volume; - - for( int i=0; isampling_rate_multiplier = rate; +} - if( !ch->allocated ) - continue; +void vg_audio_start_channel( audio_channel_id id ) +{ + vg_audio_assert_lock(); - if( ch->activity == k_channel_activity_alive ) - { - if( (ch->cursor >= ch->clip_length) && - !(ch->flags & AUDIO_FLAG_LOOP) ) - { - ch->activity = k_channel_activity_end; - } - } + audio_channel *channel = get_audio_channel( id ); + VG_ASSERT( channel->stage == k_channel_stage_allocation ); + VG_ASSERT( channel->clip ); + channel->stage = k_channel_stage_active; +} - /* process relinquishments */ - if( (ch->activity != k_channel_activity_reset) && ch->_.relinquished ){ - if( (ch->activity == k_channel_activity_end) - || (ch->_.volume == 0.0f) - || (ch->activity == k_channel_activity_error) ) - { - ch->_.relinquished = 0; - ch->allocated = 0; - ch->activity = k_channel_activity_reset; - continue; - } - } +audio_channel_id vg_audio_crossfade( audio_channel_id id, audio_clip *new_clip, f32 transition_seconds ) +{ + vg_audio_assert_lock(); - /* process new channels */ - if( ch->activity == k_channel_activity_reset ) + audio_channel *channel = get_audio_channel( id ); + audio_channel_id new_id = 0; + if( new_clip ) + { + new_id = vg_audio_get_first_idle_channel(); + if( new_id ) { - ch->_ = ch->editable_state; - ch->cursor = 0; - ch->clip_length = 0; - ch->activity = k_channel_activity_wake; - } - - if( ch->editble_state_write_mask & AUDIO_EDIT_OWNERSHIP ) - ch->_.relinquished = ch->editable_state.relinquished; - else - ch->editable_state.relinquished = ch->_.relinquished; - + vg_audio_set_channel_clip( new_id, new_clip ); + vg_audio_set_channel_volume_slew_duration( new_id, transition_seconds ); + vg_audio_set_channel_volume( new_id, 1.0, 0 ); + vg_audio_set_channel_group( new_id, channel->group ); - if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME ) - { - ch->_.volume = ch->editable_state.volume; - ch->_.volume_target = ch->editable_state.volume; - } - else - ch->editable_state.volume = ch->_.volume; - + struct audio_channel_controls *existing_controls = get_audio_channel_controls( id ), + *new_controls = get_audio_channel_controls( new_id ); - if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME_SLOPE ) - { - ch->volume_movement_start = ch->_.volume; - ch->volume_movement = 0; - - ch->_.volume_target = ch->editable_state.volume_target; - ch->_.volume_rate = ch->editable_state.volume_rate; - } - else - { - ch->editable_state.volume_target = ch->_.volume_target; - ch->editable_state.volume_rate = ch->_.volume_rate; + memcpy( new_controls, existing_controls, sizeof( struct audio_channel_controls ) ); + vg_audio_start_channel( new_id ); } + } + vg_audio_set_channel_volume_slew_duration( id, transition_seconds ); + vg_audio_set_channel_volume( id, 0.0, 0 ); + vg_audio_add_channel_flags( id, AUDIO_FLAG_RELINQUISHED ); - if( ch->editble_state_write_mask & AUDIO_EDIT_SAMPLING_RATE ) - ch->_.sampling_rate = ch->editable_state.sampling_rate; - else - ch->editable_state.sampling_rate = ch->_.sampling_rate; - + return new_id; +} - if( ch->editble_state_write_mask & AUDIO_EDIT_LFO_ATTACHMENT ) +void vg_audio_fadeout_flagged_audio( u32 flag, f32 length ) +{ + vg_audio_lock(); + for( u32 id=1; id<=AUDIO_CHANNELS; id ++ ) + { + audio_channel *channel = get_audio_channel( id ); + if( channel->stage != k_channel_stage_none ) { - ch->_.lfo = ch->editable_state.lfo; - ch->_.lfo_amount = ch->editable_state.lfo_amount; + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + if( controls->flags & flag ) + vg_audio_crossfade( id, NULL, 1.0f ); } - else{ - ch->editable_state.lfo = ch->_.lfo; - ch->editable_state.lfo_amount = ch->_.lfo_amount; - } - - - if( ch->editble_state_write_mask & AUDIO_EDIT_SPACIAL ) - v4_copy( ch->editable_state.spacial_falloff,ch->_.spacial_falloff ); - else - v4_copy( ch->_.spacial_falloff,ch->editable_state.spacial_falloff ); - - - /* currently readonly, i guess */ - ch->editable_state.pan_target = ch->_.pan_target; - ch->editable_state.pan = ch->_.pan; - ch->editble_state_write_mask = 0x00; } + vg_audio_unlock(); +} - for( int i=0; ieditble_state_write_mask & AUDIO_EDIT_LFO_WAVE ) - { - lfo->_.wave_type = lfo->editable_state.wave_type; - - if( lfo->_.wave_type == k_lfo_polynomial_bipolar ) - { - lfo->_.polynomial_coefficient = - lfo->editable_state.polynomial_coefficient; - lfo->sqrt_polynomial_coefficient = - sqrtf(lfo->_.polynomial_coefficient); - } - } - - if( lfo->editble_state_write_mask & AUDIO_EDIT_LFO_PERIOD ) + audio_channel *channel = get_audio_channel( id ); + if( channel->stage != k_channel_stage_none ) { - if( lfo->_.period ) - { - float t = lfo->time; - t/= (float)lfo->_.period; - - lfo->_.period = lfo->editable_state.period; - lfo->time = lfo->_.period * t; - } - else + struct audio_channel_controls *controls = get_audio_channel_controls( id ); + if( controls->flags & flag ) { - lfo->time = 0; - lfo->_.period = lfo->editable_state.period; + vg_audio_unlock(); + return 0; } } - - lfo->editble_state_write_mask = 0x00; } + vg_audio_unlock(); + return 1; +} - dsp_update_tunings(); - audio_unlock(); +void vg_audio_oneshot_3d( audio_clip *clip, v3f co, f32 range, f32 volume, u16 group, u32 flags ) +{ + vg_audio_assert_lock(); + audio_channel_id id = vg_audio_get_first_idle_channel(); - /* - * Process spawns - * ------------------------------------------------------------- */ - for( int i=0; iactivity == k_channel_activity_wake ) - { - if( audio_channel_load_source( ch ) ) - ch->activity = k_channel_activity_alive; - else - ch->activity = k_channel_activity_error; - } + vg_audio_set_channel_clip( id, clip ); + vg_audio_set_channel_spacial_falloff( id, co, range ); + vg_audio_set_channel_group( id, group ); + vg_audio_set_channel_volume( id, volume, 1 ); + vg_audio_add_channel_flags( id, AUDIO_FLAG_RELINQUISHED | flags ); + vg_audio_start_channel( id ); } +} - /* - * Mix everything - * -------------------------------------------------------- */ - int frame_count = byte_count/(2*sizeof(float)); - - /* Clear buffer */ - float *pOut32F = (float *)stream; - for( int i=0; itime_startframe = lfo->time; + vg_audio_set_channel_clip( id, clip ); + vg_audio_set_channel_group( id, group ); + vg_audio_set_channel_volume( id, volume, 1 ); + vg_audio_set_channel_pan( id, volume, 1 ); + vg_audio_add_channel_flags( id, AUDIO_FLAG_RELINQUISHED ); + vg_audio_start_channel( id ); } +} - for( int j=0; j<2; j++ ) - { - for( int i=0; iflags & AUDIO_FLAG_NO_DSP ) - { - if( j==0 ) - continue; - } - else - { - if( j==1 ) - continue; - } - } - if( ch->activity == k_channel_activity_alive ) - { - if( ch->_.lfo ) - ch->_.lfo->time = ch->_.lfo->time_startframe; +/* lfos + * ---------------------------------------------------------------------------------------- */ - u32 remaining = frame_count, - subpos = 0; +audio_channel_id vg_audio_get_first_idle_lfo(void) +{ + vg_audio_assert_lock(); - while( remaining ) - { - audio_channel_mix( ch, pOut32F+subpos ); - remaining -= AUDIO_MIX_FRAME_SIZE; - subpos += AUDIO_MIX_FRAME_SIZE*2; - } - } - } + for( int id=1; id<=AUDIO_LFOS; id ++ ) + { + audio_lfo *lfo = get_audio_lfo( id ); - if( use_dsp ) + if( lfo->stage == k_channel_stage_none ) { - if( j==0 ) - { - vg_profile_begin( &_vg_prof_dsp ); - for( int i=0; istage = k_channel_stage_allocation; + + const u32 default_lfo_period = 44100; + + struct audio_lfo_controls *controls = get_audio_lfo_controls( id ); + controls->period_in_samples = default_lfo_period; + controls->wave_type = k_lfo_triangle; + controls->polynomial_coefficient = 0.0f; + controls->flags = 0x00; + + struct audio_lfo_state *state = get_audio_lfo_state( id ); + state->time = 0; + state->last_period_in_samples = default_lfo_period; + state->frame_reference_count = 0; + state->time_at_frame_start = 0; + return id; } - else - break; - } - - audio_lock(); - - for( int i=0; ireadable_activity = ch->activity; } - /* Profiling information - * ----------------------------------------------- */ - vg_profile_increment( &_vg_prof_audio_decode ); - vg_profile_increment( &_vg_prof_audio_mix ); - vg_profile_increment( &_vg_prof_dsp ); + return 0; +} - if( vg_audio.inspector_open ) - { - _vg_prof_audio_mix_ui = _vg_prof_audio_mix; - _vg_prof_audio_decode_ui = _vg_prof_audio_decode; - _vg_prof_audio_dsp_ui = _vg_prof_dsp; - vg_audio.samples_last = frame_count; - } +void vg_audio_set_lfo_polynomial_bipolar( audio_channel_id lfo_id, f32 coefficient ) +{ + vg_audio_assert_lock(); - audio_unlock(); + struct audio_lfo_controls *controls = get_audio_lfo_controls( lfo_id ); + controls->polynomial_coefficient = coefficient; + controls->sqrt_polynomial_coefficient = sqrtf(coefficient); + controls->wave_type = k_lfo_polynomial_bipolar; } -void audio_clip_load( audio_clip *clip, void *lin_alloc ) +void vg_audio_set_lfo_frequency( audio_channel_id lfo_id, f32 freq ) { - if( lin_alloc == NULL ) - lin_alloc = vg_audio.audio_pool; + vg_audio_assert_lock(); + + struct audio_lfo_controls *controls = get_audio_lfo_controls( lfo_id ); + u32 length = 44100.0f / freq; + controls->period_in_samples = length; - if( vg_audio.always_keep_compressed ) + audio_lfo *lfo = get_audio_lfo( lfo_id ); + if( lfo->stage == k_channel_stage_allocation ) { - if( (clip->flags & AUDIO_FLAG_FORMAT) != k_audio_format_bird ){ - clip->flags &= ~AUDIO_FLAG_FORMAT; - clip->flags |= k_audio_format_vorbis; - } + struct audio_lfo_state *state = get_audio_lfo_state( lfo_id ); + state->last_period_in_samples = length; } +} - /* load in directly */ - u32 format = clip->flags & AUDIO_FLAG_FORMAT; +void vg_audio_start_lfo( audio_channel_id lfo_id ) +{ + vg_audio_assert_lock(); + audio_lfo *lfo = get_audio_lfo( lfo_id ); + lfo->stage = k_channel_stage_active; +} - /* TODO: This contains audio_lock() and unlock, but i don't know why - * can probably remove them. Low priority to check this */ +static void audio_channel_get_samples( audio_channel_id id, struct audio_channel_controls *controls, + u32 count, f32 *out_stereo ) +{ + vg_profile_begin( &_vg_prof_audio_decode ); - /* TODO: packed files for vorbis etc, should take from data if its not not - * NULL when we get the clip - */ + u32 remaining = count; + u32 buffer_pos = 0; - if( format == k_audio_format_vorbis ) + audio_channel *channel = get_audio_channel( id ); + struct audio_channel_state *state = get_audio_channel_state( id ); + u32 format = channel->clip->flags & AUDIO_FLAG_FORMAT; + + while( remaining ) { - if( !clip->path ) + u32 samples_this_run = VG_MIN( remaining, state->loaded_clip_length - state->cursor ); + remaining -= samples_this_run; + + f32 *dst = &out_stereo[ buffer_pos * 2 ]; + + if( format == k_audio_format_stereo ) { - vg_error( "No path specified, embeded vorbis unsupported\n" ); + for( u32 i=0; idecoder_handle.vorbis, + dst, samples_this_run ); + if( read_samples != samples_this_run ) + { + vg_warn( "Invalid samples read (%s)\n", channel->clip->path ); - audio_lock(); - clip->data = vg_file_read( lin_alloc, clip->path, &clip->size ); - audio_unlock(); - - if( !clip->data ) + for( u32 i=0; idecoder_handle.bird, dst, samples_this_run ); } - - float mb = (float)(clip->size) / (1024.0f*1024.0f); - vg_info( "Loaded audio clip '%s' (%.1fmb)\n", clip->path, mb ); - } - else if( format == k_audio_format_stereo ) - { - vg_error( "Unsupported format (Stereo uncompressed)\n" ); - } - else if( format == k_audio_format_bird ) - { - if( !clip->data ) + else if( format == k_audio_format_gen ) { - vg_error( "No data, external birdsynth unsupported\n" ); + void (*fn)( void *data, f32 *buf, u32 count ) = channel->clip->generative_function; + fn( channel->clip->any_data, dst, samples_this_run ); } - - u32 total_size = clip->size + sizeof(struct synth_bird); - total_size -= sizeof(struct synth_bird_settings); - total_size = vg_align8( total_size ); - - if( total_size > AUDIO_DECODE_SIZE ) + else { - vg_error( "Bird coding too long, and exceeds maximum decode size\n" ); + i16 *src_buffer = channel->clip->any_data, + *src = &src_buffer[ state->cursor ]; + audio_decode_uncompressed_mono( src, samples_this_run, dst ); } - struct synth_bird *bird = vg_linear_alloc( lin_alloc, total_size ); - memcpy( &bird->settings, clip->data, clip->size ); - - clip->data = bird; - clip->size = total_size; - - vg_info( "Loaded bird synthesis pattern (%u bytes)\n", total_size ); - } - else - { - if( !clip->path ) + state->cursor += samples_this_run; + buffer_pos += samples_this_run; + + if( (controls->flags & AUDIO_FLAG_LOOP) && remaining ) { - vg_error( "No path specified, embeded mono unsupported\n" ); + if( format == k_audio_format_vorbis ) + stb_vorbis_seek_start( state->decoder_handle.vorbis ); + else if( format == k_audio_format_bird ) + synth_bird_reset( state->decoder_handle.bird ); + + state->cursor = 0; + continue; } + else + break; + } - vg_linear_clear( vg_mem.scratch ); - u32 fsize; + while( remaining ) + { + out_stereo[ buffer_pos*2 + 0 ] = 0.0f; + out_stereo[ buffer_pos*2 + 1 ] = 0.0f; + buffer_pos ++; + remaining --; + } - stb_vorbis_alloc alloc = { - .alloc_buffer = vg_linear_alloc( vg_mem.scratch, AUDIO_DECODE_SIZE ), - .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE - }; + vg_profile_end( &_vg_prof_audio_decode ); +} - void *filedata = vg_file_read( vg_mem.scratch, clip->path, &fsize ); +static f32 audio_lfo_get_sample( audio_channel_id lfo_id, struct audio_lfo_controls *controls ) +{ + struct audio_lfo_state *state = get_audio_lfo_state( lfo_id ); - int err; - stb_vorbis *decoder = stb_vorbis_open_memory( - filedata, fsize, &err, &alloc ); + state->time ++; - if( !decoder ) - { - vg_fatal_condition(); - vg_info( "Vorbis decode error\n" ); - vg_info( "stb_vorbis_open_memory failed on '%s' (%d)\n", - clip->path, err ); - vg_fatal_exit(); - } + if( state->time >= controls->period_in_samples ) + state->time = 0; - /* only mono is supported in uncompressed */ - u32 length_samples = stb_vorbis_stream_length_in_samples( decoder ), - data_size = length_samples * sizeof(i16); + f32 t = state->time; + t /= (f32)controls->period_in_samples; - audio_lock(); - clip->data = vg_linear_alloc( lin_alloc, vg_align8(data_size) ); - clip->size = length_samples; - audio_unlock(); + if( controls->wave_type == k_lfo_polynomial_bipolar ) + { + /* + * # + * # # + * # # + * # # + * ### # ### + * ## # + * # # + * # # + * ## + */ - int read_samples = stb_vorbis_get_samples_i16_downmixed( - decoder, clip->data, length_samples ); + t *= 2.0f; + t -= 1.0f; - if( read_samples != length_samples ) - { - vg_error( "Decode error, read_samples did not match length_samples\n" ); - } + return (( 2.0f * controls->sqrt_polynomial_coefficient * t ) / + /* --------------------------------------- */ + ( 1.0f + controls->polynomial_coefficient * t*t ) + + ) * (1.0f-fabsf(t)); + } + else + { + return 0.0f; } } -void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) -{ - for( int i=0; idata && clip->size ) + i32 sign = target - *value; + if( sign == 0 ) return; - audio_unlock(); + sign = sign>0? 1: -1; + i32 c = *value + sign*rate; - vg_fatal_error( "Must load audio clip before playing! \n" ); + if( target*sign < c*sign ) *value = target; + else *value = c; } -#endif - - - - - - - - - - - +static void audio_channel_mix( audio_channel_id id, + struct audio_channel_controls *controls, + struct audio_master_controls *master_controls, f32 *inout_buffer ) +{ + struct audio_channel_state *state = get_audio_channel_state( id ); + bool is_3d = controls->flags & AUDIO_FLAG_SPACIAL_3D? 1: 0; + bool use_doppler = controls->flags & AUDIO_FLAG_NO_DOPPLER? 0: 1; + f32 frame_sample_rate = controls->sampling_rate_multiplier; + i32 spacial_volume_target = 0, + spacial_pan_target = 0; + if( is_3d ) + { + v3f delta; + v3_sub( controls->spacial_falloff, master_controls->listener_position, delta ); + f32 dist = v3_length( delta ); + if( dist <= 0.01f ) + { + spacial_pan_target = 0; + spacial_volume_target = AUDIO_VOLUME_100; + } + else if( dist > 20000.0f || !vg_validf( dist ) ) + { + spacial_pan_target = 0; + spacial_volume_target = 0; + } + else + { + f32 vol = vg_maxf( 0.0f, 1.0f - controls->spacial_falloff[3]*dist ); + vol = powf( vol, 5.0f ); + spacial_volume_target = (f64)vg_clampf( vol, 0.0f, 1.0f ) * (f64)AUDIO_VOLUME_100 * 0.5; + v3_muls( delta, 1.0f/dist, delta ); + f32 pan = v3_dot( master_controls->listener_right_ear_direction, delta ); + spacial_pan_target = (f64)vg_clampf( pan, -1.0f, 1.0f ) * (f64)AUDIO_PAN_RIGHT_100; -_Thread_local static bool _vg_audio_thread_has_lock = 0; + if( use_doppler ) + { + const float vs = 323.0f; -void vg_audio_lock(void) -{ - SDL_LockMutex( _vg_audio.mutex ); - _vg_audio_thread_has_lock = 1; -} + f32 dv = v3_dot( delta, master_controls->listener_velocity ); + f32 doppler = (vs+dv)/vs; + doppler = vg_clampf( doppler, 0.6f, 1.4f ); + + if( fabsf(doppler-1.0f) > 0.01f ) + frame_sample_rate *= doppler; + } + } -void vg_audio_unlock(void) -{ - _vg_audio_thread_has_lock = 0; - SDL_UnlockMutex( _vg_audio.mutex ); -} + if( !state->spacial_warm ) + { + state->spacial_volume = spacial_volume_target; + state->spacial_pan = spacial_pan_target; + state->spacial_warm = 1; + } + } -static void vg_audio_assert_lock(void) -{ - if( _vg_audio_thread_has_lock == 0 ) + u32 buffer_length = AUDIO_MIX_FRAME_SIZE; + if( frame_sample_rate != 1.0f ) { - vg_error( "vg_audio function requires locking\n" ); - abort(); + float l = ceilf( (float)(AUDIO_MIX_FRAME_SIZE) * frame_sample_rate ); + buffer_length = l+1; } -} + f32 samples[ AUDIO_MIX_FRAME_SIZE*2 * 2 ]; + audio_channel_get_samples( id, controls, buffer_length, samples ); -/* main channels - * ---------------------------------------------------------------------------------------- */ + vg_profile_begin( &_vg_prof_audio_mix ); -audio_channel *audio_get_first_idle_channel(void) -{ - vg_audio_assert_lock(); - for( int i=0; ivolume, controls->volume_target, controls->volume_slew_rate_per_sample ); + audio_slew_i32( &state->pan, controls->pan_target, controls->pan_slew_rate_per_sample ); + + f64 v_c = (f64)state->volume / (f64)AUDIO_VOLUME_100; - if( channel->activity == k_channel_activity_none ) + if( controls->lfo_id ) { - channel->activity = k_channel_activity_allocating; - channel->ui_name[0] = 0; - channel->ui_colour[0] = 0x00333333; - channel->flags = 0x00; - channel->group = 0; - channel->clip = NULL; - channel->clip_length = 0; - channel->decoder_handle.bird = NULL; - channel->cursor = 0; - channel->volume = AUDIO_VOLUME_100; - channel->volume_target = AUDIO_VOLUME_100; - channel->volume_slew_rate_per_sample = AUDIO_VOLUME_100 / (44100*10); /* 1/10th second */ - channel->pan = 0; - channel->pan_target = 0; - channel->pan_slew_rate_per_sample = AUDIO_PAN_RIGHT_100 / (44100*10); - channel->sampling_rate_multiplier = 1.0f; - v4_copy( (v4f){0,0,0,1}, channel->spacial_falloff ); - channel->lfo = NULL; - channel->lfo_attenuation_amount = 0.0f; - return channel; + struct audio_lfo_state *state = get_audio_lfo_state( controls->lfo_id ); + f32 lfo_value = audio_lfo_get_sample( controls->lfo_id, state->controls ); + v_c *= 1.0 + lfo_value * controls->lfo_attenuation_amount; } - } - return NULL; -} - -void vg_audio_set_channel_clip( audio_channel *channel, audio_clip *clip ) -{ - vg_audio_assert_lock(); - VG_ASSERT( channel->activity == k_channel_activity_allocating ); - VG_ASSERT( channel->clip == NULL ); + f64 v_l = v_c*v_c, + v_r = v_c*v_c; - channel->clip = clip; + if( is_3d ) + { + const i32 vol_rate = (f64)AUDIO_VOLUME_100 / (0.05 * 44100.0), + pan_rate = (f64)AUDIO_PAN_RIGHT_100 / (0.05 * 44100.0); - u32 audio_format = channel->clip->flags & AUDIO_FLAG_FORMAT; - if( audio_format == k_audio_format_bird ) - strcpy( channel->name, "[array]" ); - else if( audio_format == k_audio_format_gen ) - strcpy( channel->name, "[program]" ); - else - vg_strncpy( clip->path, channel->name, 32, k_strncpy_always_add_null ); -} + audio_slew_i32( &state->spacial_volume, spacial_volume_target, vol_rate ); + audio_slew_i32( &state->spacial_pan, spacial_pan_target, pan_rate ); -void vg_audio_set_channel_group( audio_channel *channel, u16 group ) -{ - vg_audio_assert_lock(); - VG_ASSERT( channel->activity == k_channel_activity_allocating ); - VG_ASSERT( channel->group = NULL ); - channel->group = group; - if( group ) - channel->ui_colour = (((u32)group * 29986577) & 0x00ffffff) | 0xff000000; -} + f64 v_s = (f64)state->spacial_volume / (f64)AUDIO_VOLUME_100, + v_p = (f64)state->spacial_pan / (f64)AUDIO_PAN_RIGHT_100; -u32 vg_audio_count_channels_in_group( u16 group ) -{ - vg_audio_assert_lock(); + v_l *= v_s * (1.0-v_p); + v_r *= v_s * (1.0+v_p); + } + + f32 s_l, s_r; + if( frame_sample_rate != 1.0f ) + { + /* absolutely garbage resampling, but it will do + */ + f32 sample_index = frame_sample_rate * (f32)j; + f32 t = vg_fractf( sample_index ); - u32 count = 0; - for( int i=0; iactivity != k_channel_activity_none ) - count ++; - } - - return count; -} + s_l = samples[ i0*2+0 ]*(1.0f-t) + samples[ i1*2+0 ]*t; + s_r = samples[ i0*2+1 ]*(1.0f-t) + samples[ i1*2+1 ]*t; + } + else + { + s_l = samples[ j*2+0 ]; + s_r = samples[ j*2+1 ]; + } -audio_channel *vg_audio_get_first_active_channel_in_group( u16 group ) -{ - vg_audio_assert_lock(); - for( int i=0; iactivity != k_channel_activity_none) && (channel->group == group) ) - return channel; + inout_buffer[ j*2+0 ] += s_l * v_l; + inout_buffer[ j*2+1 ] += s_r * v_r; } - return NULL; -} - -void vg_audio_sidechain_lfo_to_channel( audio_channel *channel, audio_lfo *lfo, f32 amount ) -{ - vg_audio_assert_lock(); - channel->lfo = lfo; - channel->lfo_attenuation_amount = ammount; -} - -void vg_audio_set_channel_spacial_falloff( audio_channel *channel, v3f co, f32 range ) -{ - vg_audio_assert_lock(); - channel->flags |= AUDIO_FLAG_SPACIAL_3D; - v3_copy( co, channel->spacial_falloff ); - channel->spacial_falloff[3] = range == 0.0f? 1.0f: 1.0f/range; -} - -void vg_audio_set_channel_volume( audio_channel *channel, f64 volume, bool instant ) -{ - vg_audio_assert_lock(); - channel->volume_target = ((f64)AUDIO_VOLUME_100) * volume; - - if( instant ) - channel->volume = channel->volume_target; -} -void vg_audio_set_channel_volume_slew_duration( audio_channel *channel, f64 length_seconds ) -{ - vg_audio_assert_lock(); - channel->volume_slew_rate_per_sample = (f64)AUDIO_VOLUME_100 / (length_seconds * 44100.0); -} - -void vg_audio_set_channel_pan_slew_duration( audio_channel *channel, f64 length_seconds ) -{ - vg_audio_assert_lock(); - channel->pan_slew_rate_per_sample = (f64)AUDIO_PAN_RIGHT_100 / (length_seconds * 44100.0); + vg_profile_end( &_vg_prof_audio_mix ); } -void vg_audio_relinquish_channel( audio_channel *channel ) -{ - vg_audio_assert_lock(); - channel->flags |= AUDIO_FLAG_RELINQUISHED; -} -void vg_audio_channel_start( audio_channel *channel ) +static void _vg_audio_mixer( void *user, u8 *stream, int byte_count ) { - vg_audio_assert_lock(); - VG_ASSERT( channel->activity == k_channel_activity_allocation ); - VG_ASSERT( channel->clip ); - channel->activity = k_channel_activity_wake; -} + int sample_count = byte_count/(2*sizeof(f32)); + + f32 *output_stereo = (f32 *)stream; + for( int i=0; igroup ); - replacement->flags = channel->flags; - replacement->lfo = channel->lfo; - replacement->lfo_attenuation_amount = channel->attenuation_amount; - v4_copy( channel->spacial_falloff, replacement->spacial_falloff ); - vg_audio_channel_start( replacement ); + audio_channel *channel = get_audio_channel( id ); + if( channel->stage == k_channel_stage_active ) + { + active_channel_list[ active_channel_count ] = id; + memcpy( &channel_controls[ active_channel_count ], get_audio_channel_controls(id), + sizeof( struct audio_channel_controls ) ); + active_channel_count ++; + } } - - return replacement; -} - -void vg_audio_oneshot_3d( audio_clip *clip, v3f co, f32 range, f32 volume, u16 group ) -{ - vg_audio_assert_lock(); - audio_channel *channel = vg_audio_get_first_idle_channel(); - - if( channel ) + for( u32 id=1; id<=AUDIO_LFOS; id ++ ) { - vg_audio_set_channel_clip( channel, clip ); - vg_audio_set_channel_spacial_falloff( channel, co, range ); - vg_audio_set_channel_group( channel, group ); - vg_audio_set_ - vg_audio_start_channel( channel ); - - audio_channel_edit_volume( ch, volume, 1 ); - audio_relinquish_channel( ch ); - } -} + audio_lfo *lfo = get_audio_lfo( id ); + if( lfo->stage == k_channel_stage_active ) + { + struct audio_lfo_controls *local_controls = &lfo_controls[ active_lfo_count ]; + active_lfo_list[ active_lfo_count ] = id; + memcpy( local_controls, get_audio_lfo_controls(id), sizeof(struct audio_lfo_controls) ); + active_lfo_count ++; -audio_channel *audio_oneshot( audio_clip *clip, f32 volume, f32 pan ) -{ - audio_require_lock(); - audio_channel *ch = audio_get_first_idle_channel(); + struct audio_lfo_state *state = get_audio_lfo_state(id); + state->controls = local_controls; + } + } + dsp_update_tunings(); + vg_audio_unlock(); - if( ch ) + /* init step */ + for( u32 i=0; iactivity == k_channel_activity_wake ) + audio_channel_wake( id ); } - else - return NULL; -} - - -/* lfos - * ---------------------------------------------------------------------------------------- */ - -audio_lfo *vg_audio_get_first_idle_lfo(void) -{ - vg_audio_assert_lock(); - - for( int i=0; iactivity == k_channel_activity_none ) + /* if the period changes we need to remap the time value to prevent hitching */ + if( controls->period_in_samples != state->last_period_in_samples ) { - lfo->activity = k_channel_activity_allocation; - lfo->time = 0; - lfo->period_in_samples = 44100; - lfo->last_period_in_samples = 4410; - lfo->wave_type = k_lfo_triangle; - lfo->polynomial_coefficient = 0.0f; - lfo->flags = 0x00; - return lfo; + state->last_period_in_samples = controls->period_in_samples; + f64 t = state->time; + t/= (f64)controls->period_in_samples; + state->time = (f64)controls->period_in_samples * t; } + + state->time_at_frame_start = state->time; + state->frame_reference_count = 0; } - return NULL; -} + /* mix step */ + bool dsp_enabled = 1; -void vg_audio_set_lfo_polynomial_bipolar( audio_lfo *lfo, f32 coefficient ) -{ - vg_audio_assert_lock(); + for( u32 dry_layer=0; dry_layer<=1; dry_layer ++ ) + { + for( u32 i=0; ipolynomial_coefficient = coefficient; - lfo->wave_type = k_lfo_polynomial_bipolar; -} + if( state->activity == k_channel_activity_playing ) + { + if( dsp_enabled ) + { + if( controls->flags & AUDIO_FLAG_NO_DSP ) + { + if( !dry_layer ) + continue; + } + else + { + if( dry_layer ) + continue; + } + } -void vg_audio_set_lfo_frequency( audio_lfo *lfo, f32 freq ) -{ - vg_audio_assert_lock(); + if( controls->lfo_id ) + { + struct audio_lfo_state *lfo_state = get_audio_lfo_state( controls->lfo_id ); + lfo_state->time = lfo_state->time_at_frame_start; + lfo_state->frame_reference_count ++; + } - u32 length = 44100.0f / freq; - lfo->period_in_samples = length; + u32 remaining = sample_count, + subpos = 0; - if( lfo->activity == k_channel_activity_allocation ) - lfo->last_period_in_samples = length; -} + while( remaining ) + { + audio_channel_mix( id, controls, &master_controls, output_stereo+subpos ); + remaining -= AUDIO_MIX_FRAME_SIZE; + subpos += AUDIO_MIX_FRAME_SIZE*2; + } -void vg_audio_start_lfo( audio_lfo *lfo ) -{ - vg_audio_assert_lock(); - lfo->activity = k_achannel_activity_alive; -} + if( (state->cursor >= state->loaded_clip_length) && !(controls->flags & AUDIO_FLAG_LOOP) ) + state->activity = k_channel_activity_end; + } + } + if( dsp_enabled ) + { + if( !dry_layer ) + { + vg_profile_begin( &_vg_prof_dsp ); + for( int i=0; iui_activity = state->activity; + channel->ui_volume = state->volume; + channel->ui_pan = state->pan; + if( controls->flags & AUDIO_FLAG_RELINQUISHED ) + { + bool die = 0; + if( state->activity == k_channel_activity_end ) die = 1; + if( state->activity == k_channel_activity_error ) die = 1; + if( state->volume == 0 ) die = 1; + if( die ) + { + channel->stage = k_channel_stage_none; + } + } + } -static void _vg_audio_mixer( void *user, u8 *stream, int byte_count ) -{ - int sample_count = byte_count/(2*sizeof(f32)); - - f32 *output_stereo = (f32 *)stream; - for( int i=0; iview_3d ); - audio_lock(); + vg_audio_lock(); char perf[128]; ui_rect overlap_buffer[ AUDIO_CHANNELS ]; u32 overlap_length = 0; /* Draw audio stack */ - for( int i=0; iactivity == k_channel_activity_none ) + if( channel->stage == k_channel_stage_none ) { if( show_row ) ui_fill( ctx, row, 0x50333333 ); - - continue; } - - const char *formats[] = + else if( channel->stage == k_channel_stage_allocation ) { - " mono ", - " stereo ", - " vorbis ", - " none0 ", - " none1 ", - " none2 ", - " none3 ", - " none4 ", - "synth:bird", - " none5 ", - " none6 ", - " none7 ", - " none8 ", - " none9 ", - " none10 ", - " none11 ", - }; - - const char *activties[] = + if( show_row ) + ui_fill( ctx, row, 0x50ff3333 ); + } + else if( channel->stage == k_channel_stage_active ) { - "reset", - "wake ", - "alive", - "end ", - "error" - }; + if( show_row ) + { + char buf[256]; + vg_str str; + vg_strnull( &str, buf, sizeof(buf) ); + vg_strcati32r( &str, id, 2, ' ' ); - u32 format_index = (ch->clip->flags & AUDIO_FLAG_FORMAT)>>9; - f32 volume = audio_volume_integer_to_float( ch->volume ); + if( channel->group ) + { + vg_strcat( &str, " grp" ); + vg_strcati32r( &str, channel->group, 6, ' ' ); + } + else + vg_strcat( &str, " " ); - snprintf( perf, 127, "%02d[%#06x]%c%c%cD %s [%s] %4.2fv'%s'", - i, ch->group, - (ch->flags & AUDIO_FLAG_RELINQUISHED)? 'r': '_', - 0? 'r': '_', - 0? '3': '2', - formats[format_index], - activties[ch->activity], - volume, - ch->ui_name ); + vg_strcat( &str, " flags:" ); + u32 flags = get_audio_channel_controls( id )->flags; + vg_strcatch( &str, (flags & AUDIO_FLAG_RELINQUISHED)? 'R': '_' ); + vg_strcatch( &str, (flags & AUDIO_FLAG_SPACIAL_3D)? 'S': '_' ); + vg_strcatch( &str, (flags & AUDIO_FLAG_WORLD)? 'W': '_' ); + vg_strcatch( &str, (flags & AUDIO_FLAG_NO_DOPPLER)? '_':'D' ); + vg_strcatch( &str, (flags & AUDIO_FLAG_NO_DSP)? '_':'E' ); - if( show_row ) - { - ui_fill( ctx, row, 0xa0000000 | ch->ui_colour ); - ui_text( ctx, row, perf, 1, k_ui_align_middle_left, 0 ); + const char *formats[] = + { + " mono ", + " stereo ", + " vorbis ", + " none0 ", + " none1 ", + " none2 ", + " none3 ", + " none4 ", + "synth:bird", + " none5 ", + " none6 ", + " none7 ", + " none8 ", + " none9 ", + " none10 ", + " none11 ", + }; + u32 format_index = (channel->clip->flags & AUDIO_FLAG_FORMAT)>>9; + vg_strcat( &str, " format:" ); + vg_strcat( &str, formats[format_index] ); + + const char *activties[] = + { + "wake ", + "play ", + "pause", + "end ", + "error" + }; + vg_strcat( &str, " " ); + vg_strcat( &str, activties[channel->ui_activity] ); + + vg_strcat( &str, " " ); + f32 volume = audio_volume_integer_to_float( channel->ui_volume ); + vg_strcati32r( &str, volume * 100.0f, 3, ' ' ); + vg_strcatch( &str, '%' ); + + vg_strcat( &str, " " ); + vg_strcat( &str, channel->ui_name ); + + ui_rect row_l, row_r; + ui_split( row, k_ui_axis_v, 32, 2, row_l, row_r ); + + //ui_rect indicator_l, indicator_r; + //ui_split_ratio( row, k_ui_axis_v, 0.5f, 1, indicator_l, indicator_r ); + + //f32 volume = audio_volume_integer_to_float( channel->ui_volume ); + //ui_rect vol_bar; + //ui_split_ratio( indicator_l, k_ui_axis_h, -volume, 0, vol_bar, indicator_l ); + //ui_fill( ctx, indicator_l, 0xff000000 ); + //ui_fill( ctx, vol_bar, 0xff00ff00 ); + + //ui_fill( ctx, indicator_r, 0xff111111 ); + + ui_fill( ctx, row_r, 0xa0000000 | channel->ui_colour ); + ui_text( ctx, row_r, buf, 1, k_ui_align_middle_left, 0 ); + } } +#if 0 #ifdef VG_3D if( vd->view_3d && (ch->flags & AUDIO_FLAG_SPACIAL_3D) ) { @@ -1254,10 +1311,11 @@ static void cb_vg_audio_view( ui_context *ctx, ui_rect rect, struct vg_magi_pane rect_copy( wr, overlap_buffer[ overlap_length ++ ] ); } } +#endif #endif } - audio_unlock(); + vg_audio_unlock(); } static void cb_vg_audio_close( struct vg_magi_panel *me ) @@ -1293,7 +1351,7 @@ void vg_audio_register(void) vg_console_reg_cmd( "vg_audio", cmd_vg_audio, NULL ); vg_console_reg_var( "volume", &_vg_audio.master_volume_ui, k_var_dtype_f32, VG_VAR_PERSISTENT ); vg_console_reg_var( "vg_audio_device", &_vg_audio.device_choice, k_var_dtype_str, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_dsp", &_vg_audio.dsp_enabled, k_var_dtype_i32, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_dsp", &_vg_audio.dsp_enabled_ui, k_var_dtype_i32, VG_VAR_PERSISTENT ); } void vg_audio_device_init(void) @@ -1347,6 +1405,13 @@ void vg_audio_init(void) u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; _vg_audio.decoding_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); + struct audio_master_controls *master_controls = &_vg_audio.controls; + master_controls->dsp_enabled = _vg_audio.dsp_enabled_ui; + master_controls->volume = (f64)_vg_audio.master_volume_ui * (f64)AUDIO_VOLUME_100; + v3_copy( (v3f){1,0,0}, master_controls->listener_right_ear_direction ); + v3_zero( master_controls->listener_velocity ); + v3_zero( master_controls->listener_position ); + vg_dsp_init(); vg_audio_device_init(); } diff --git a/vg_audio.h b/vg_audio.h index 4765a25..6a25d7c 100644 --- a/vg_audio.h +++ b/vg_audio.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ +/* Copyright (C) 2021-2025 Mt.Zero Software - All Rights Reserved */ #pragma once @@ -21,6 +21,7 @@ #define AUDIO_FLAG_WORLD 0x20 #define AUDIO_FLAG_FORMAT 0x1E00 #define AUDIO_FLAG_RELINQUISHED 0x2000 +#define AUDIO_FLAG_CUTSCENE 0x4000 enum audio_format { @@ -52,14 +53,21 @@ enum audio_format typedef struct audio_clip audio_clip; typedef struct audio_channel audio_channel; typedef struct audio_lfo audio_lfo; +typedef u16 audio_channel_id; +typedef u16 audio_channel_group; /* TODO: Create a generation system for this */ + +enum channel_stage +{ + k_channel_stage_none = 0, + k_channel_stage_allocation, + k_channel_stage_active +}; enum channel_activity { - k_channel_activity_none, - k_channel_activity_allocation, k_channel_activity_wake, - k_channel_activity_alive, - k_channel_activity_pause, + k_channel_activity_playing, + k_channel_activity_paused, k_channel_activity_end, k_channel_activity_error }; @@ -86,57 +94,84 @@ struct audio_clip struct audio_lfo { - enum channel_activity activity; - u32 time, period_in_samples, last_period_in_samples; + enum channel_stage stage; - enum lfo_wave_type + struct audio_lfo_controls { - k_lfo_triangle, - k_lfo_square, - k_lfo_saw, - k_lfo_polynomial_bipolar + u32 period_in_samples; + enum lfo_wave_type + { + k_lfo_triangle, + k_lfo_square, + k_lfo_saw, + k_lfo_polynomial_bipolar + } + wave_type; + + f32 polynomial_coefficient, sqrt_polynomial_coefficient; + u32 flags; } - wave_type; + controls; - f32 polynomial_coefficient; - u32 flags; + struct audio_lfo_state + { + u32 time, last_period_in_samples, frame_reference_count, time_at_frame_start; + struct audio_lfo_controls *controls; + } + state; }; #define LFO_FLAG_PERIOD_CHANGED 0x1 struct audio_channel { - enum channel_activity activity; + enum channel_stage stage; - /* properties */ char ui_name[32]; u32 ui_colour; - u32 flags; + i32 ui_volume, ui_pan; + enum channel_activity ui_activity; u16 group; audio_clip *clip; - u32 clip_length; - union + /* the controls structure is copied into the stack of the mixer function so it can work without locking. */ + struct audio_channel_controls { - struct synth_bird *bird; - stb_vorbis *vorbis; - } - decoder_handle; + u32 flags; - u32 cursor; + i32 volume_target, volume_slew_rate_per_sample; + i32 pan_target, pan_slew_rate_per_sample; + f32 sampling_rate_multiplier; - i32 volume, volume_target, volume_slew_rate_per_sample; - i32 pan, pan_target, pan_slew_rate_per_sample; + audio_channel_id lfo_id; + f32 lfo_attenuation_amount; /* multiply volume by (1 + value) */ - f32 sampling_rate_multiplier; + v4f spacial_falloff; /* xyz, range */ + } + controls; - v4f spacial_falloff; /* xyz, range */ + /* the channel state can be accessed when channel stage is in allocation, or by the mixer thread post allocation. */ + struct audio_channel_state + { + enum channel_activity activity; - audio_lfo *lfo; - f32 lfo_attenuation_amount; /* multiply volume by (1 + value) */ -}; + u32 cursor; + i32 volume, pan, + spacial_volume, spacial_pan; + bool spacial_warm; + union + { + struct synth_bird *bird; + stb_vorbis *vorbis; + } + decoder_handle; + + u32 loaded_clip_length; + } + state; +}; struct vg_audio { @@ -154,14 +189,20 @@ struct vg_audio stb_vorbis_alloc vorbis_decoders[ AUDIO_CHANNELS ]; bool inspector_open; - i32 dsp_enabled; - v3f listener_position, - listener_right_ear_direction, - listener_velocity; + struct audio_master_controls + { + i32 dsp_enabled; + v3f listener_position, + listener_right_ear_direction, + listener_velocity; + i32 volume; + } + controls; + i32 dsp_enabled_ui; f32 master_volume_ui; - i32 master_volume; + bool always_keep_clips_compressed; } extern _vg_audio; @@ -176,167 +217,32 @@ void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ); void vg_audio_lock(void); void vg_audio_unlock(void); -void vg_audio_channel_init( audio_channel *ch, audio_clip *clip, u32 flags ); -void vg_audio_channel_group( audio_channel *ch, u16 group ); -audio_channel *vg_audio_get_first_idle_channel(void); -audio_channel *vg_audio_get_group_idle_channel( u16 group, u32 max_count ); -audio_channel *vg_audio_get_group_first_active_channel( u16 group ); - -audio_lfo *vg_audio_get_first_idle_lfo(void); - -#if 0 - -struct vg_audio_system -{ - SDL_AudioDeviceID sdl_output_device; - vg_str device_choice; /* buffer is null? use default from OS */ - - bool always_keep_compressed; - - void *audio_pool, - *decode_buffer; - u32 samples_last; - - /* synchro */ - int sync_locked; - - SDL_SpinLock sl_checker, - sl_sync; - - struct audio_lfo{ - u32 time, time_startframe; - float sqrt_polynomial_coefficient; - - struct{ - enum lfo_wave_type{ - k_lfo_triangle, - k_lfo_square, - k_lfo_saw, - k_lfo_polynomial_bipolar - } - wave_type; - - u32 period; - float polynomial_coefficient; - } - _, editable_state; - u32 editble_state_write_mask; - } - oscillators[ AUDIO_LFOS ]; - - struct audio_channel - { - /* properties */ - char name[32]; - u32 flags; - u32 colour; - u16 group; - - audio_clip *source; - u32 source_length; - - u32 cursor; - -#if 0 - float volume_movement_start, - pan_movement_start; - u32 volume_movement, - pan_movement; -#endif - - union - { - struct synth_bird *bird; - stb_vorbis *vorbis; - } - handle; - stb_vorbis_alloc vorbis_alloc; - - enum channel_activity - { - k_channel_activity_none, - k_channel_activity_allocation, - k_channel_activity_reset, /* will advance if allocated==1, to wake */ - k_channel_activity_wake, /* will advance to either of next two */ - k_channel_activity_alive, - k_channel_activity_end, - k_channel_activity_error - } - activity; -#if 0 - struct channel_state{ - int relinquished; - - float volume, /* current volume */ - volume_target, /* target volume */ - pan, - pan_target, - sampling_rate; - - u32 volume_rate, - pan_rate; - - v4f spacial_falloff; /* xyz, range */ - - audio_lfo *lfo; - float lfo_amount; - } - _, editable_state; - u32 editble_state_write_mask; -#endif - - - } - channels[ AUDIO_CHANNELS ]; - - bool inspector_open; - int dsp_enabled; - - v3f internal_listener_pos, - internal_listener_ears, - internal_listener_velocity, - - external_listener_pos, - external_listener_ears, - external_lister_velocity; - - float internal_global_volume, - external_global_volume; -} -extern vg_audio; - -void audio_clip_load( audio_clip *clip, void *lin_alloc ); -void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ); - -void vg_audio_register(void); -void vg_audio_device_init(void); -void vg_audio_init(void); -void vg_audio_free(void); - -void vg_audio_lock(void); -void vg_audio_unlock(void); - -void audio_channel_init( audio_channel *ch, audio_clip *clip, u32 flags ); -void audio_channel_group( audio_channel *ch, u16 group ); -audio_channel *audio_get_first_idle_channel(void); -audio_channel *audio_get_group_idle_channel( u16 group, u32 max_count ); -audio_channel *audio_get_group_first_active_channel( u16 group ); -int audio_channel_finished( audio_channel *ch ); -audio_channel *audio_relinquish_channel( audio_channel *ch ); -void audio_channel_slope_volume( audio_channel *ch, f32 length, f32 new_vol ); -void audio_channel_set_sampling_rate( audio_channel *ch, float rate ); -void audio_channel_edit_volume( audio_channel *ch, f32 new_vol, int instant ); -audio_channel *audio_channel_fadeout( audio_channel *ch, float length ); -void audio_channel_fadein( audio_channel *ch, float length ); -audio_channel *audio_channel_crossfade( audio_channel *ch, - audio_clip *new_clip, - float length, u32 flags ); -void audio_channel_sidechain_lfo( audio_channel *ch, int lfo_id, f32 amount ); -void audio_channel_set_spacial( audio_channel *ch, v3f co, float range ); -audio_channel *audio_oneshot_3d( audio_clip *clip, v3f position, f32 range, f32 volume ); -audio_channel *audio_oneshot( audio_clip *clip, f32 volume, f32 pan ); -void audio_set_lfo_wave( int id, enum lfo_wave_type type, f32 coefficient ); -void audio_set_lfo_frequency( int id, float freq ); -int audio_channel_load_source( audio_channel *ch ); - -#endif +/* channel API */ +audio_channel_id vg_audio_get_first_idle_channel(void); +void vg_audio_set_channel_clip( audio_channel_id id, audio_clip *clip ); +void vg_audio_set_channel_group( audio_channel_id id, u16 group ); +u32 vg_audio_count_channels_in_group( u16 group ); +audio_channel_id vg_audio_get_first_active_channel_in_group( u16 group ); +void vg_audio_sidechain_lfo_to_channel( audio_channel_id id, audio_channel_id lfo_id, f32 amount ); +void vg_audio_set_channel_spacial_falloff( audio_channel_id id, v3f co, f32 range ); +void vg_audio_set_channel_volume( audio_channel_id id, f64 volume, bool instant ); +void vg_audio_set_channel_volume_slew_duration( audio_channel_id id, f64 length_seconds ); +void vg_audio_set_channel_pan( audio_channel_id id, f64 pan, bool instant ); +void vg_audio_set_channel_pan_slew_duration( audio_channel_id id, f64 length_seconds ); +void vg_audio_set_channel_sampling_rate( audio_channel_id id, f32 rate ); +void vg_audio_start_channel( audio_channel_id id ); +void vg_audio_add_channel_flags( audio_channel_id id, u32 flags ); + +audio_channel_id vg_audio_get_first_idle_lfo(void); +void vg_audio_set_lfo_polynomial_bipolar( audio_channel_id lfo_id, f32 coefficient ); +void vg_audio_set_lfo_frequency( audio_channel_id lfo_id, f32 freq ); +void vg_audio_start_lfo( audio_channel_id lfo_id ); + +/* high level functions */ +audio_channel_id vg_audio_crossfade( audio_channel_id id, audio_clip *new_clip, f32 transition_seconds ); +void vg_audio_oneshot_3d( audio_clip *clip, v3f co, f32 range, f32 volume, u16 group, u32 flags ); +void vg_audio_oneshot( audio_clip *clip, f32 volume, f32 pan, u16 group, u32 flags ); + +/* half measures... Don't expect these functions to stay. */ +void vg_audio_fadeout_flagged_audio( u32 flag, f32 length ); +bool vg_audio_flagged_stopped( u32 flag ); diff --git a/vg_engine.c b/vg_engine.c index e523666..4ff81ef 100644 --- a/vg_engine.c +++ b/vg_engine.c @@ -873,7 +873,7 @@ static vg_settings = { .actual_value = &vg_settings.temp_audio_choice, .options = NULL, .option_count = 0 }, .dsp = { .label = "Audio effects (reverb etc.)", - .actual_value = &_vg_audio.dsp_enabled, + .actual_value = &_vg_audio.dsp_enabled_ui, .options = vg_settings_dsp_enum, .option_count=2 }, }; @@ -991,8 +991,7 @@ void vg_settings_ui_header( ui_context *ctx, } -bool vg_settings_apply_button( ui_context *ctx, - ui_rect inout_panel, bool validated ) +bool vg_settings_apply_button( ui_context *ctx, ui_rect inout_panel, bool validated ) { ui_rect last_row; ui_px height = ui_standard_widget_height( ctx, 1 ); @@ -1153,10 +1152,10 @@ static void vg_settings_audio_apply(void) *vg_settings.audio_devices.actual_value = vg_settings.audio_devices.new_value; } - audio_lock(); + vg_audio_lock(); if( vg_settings_enum_diff( &vg_settings.dsp ) ) *vg_settings.dsp.actual_value = vg_settings.dsp.new_value; - audio_unlock(); + vg_audio_unlock(); } static void vg_settings_audio_gui( ui_context *ctx, ui_rect panel )