X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=vg_audio.h;h=bff400f340b345eed97f81e31c16f0aeffa7245d;hb=e1f9315d807caad59c038b0796cf8792e1f96a9e;hp=d5ad5d26ec422ea940dd48ab020f84988f17447e;hpb=a7938c00a8c486ad0e2a765c3092017487ba761e;p=vg.git diff --git a/vg_audio.h b/vg_audio.h index d5ad5d2..bff400f 100644 --- a/vg_audio.h +++ b/vg_audio.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */ +/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */ #ifndef VG_AUDIO_H #define VG_AUDIO_H @@ -14,8 +14,10 @@ #include "vg/vg_console.h" #include "vg/vg_store.h" #include "vg/vg_profiler.h" +#include "vg/vg_audio_synth_bird.h" #include +#include #ifdef __GNUC__ #ifndef __clang__ @@ -39,82 +41,64 @@ #endif #endif -#define SFX_MAX_SYSTEMS 32 +#define AUDIO_FRAME_SIZE 512 +#define AUDIO_MIX_FRAME_SIZE 256 + +#define AUDIO_CHANNELS 32 +#define AUDIO_LFOS 8 +#define AUDIO_FILTERS 16 #define AUDIO_FLAG_LOOP 0x1 -#define AUDIO_FLAG_ONESHOT 0x2 #define AUDIO_FLAG_SPACIAL_3D 0x4 #define AUDIO_FLAG_AUTO_START 0x8 -#define AUDIO_FLAG_KILL 0x10 -#define FADEOUT_LENGTH 1100 -#define FADEOUT_DIVISOR (1.0f/(float)FADEOUT_LENGTH) +/* Vorbis will ALWAYS use the maximum amount of channels it can */ +//#define AUDIO_FLAG_MONO 0x100 NOTE: This is the default, so its not used +//#define AUDIO_FLAG_STEREO 0x200 +//#define AUDIO_FLAG_VORBIS 0x400 +//#define AUDIO_FLAG_BIRD_SYNTH 0x800 -#define AUDIO_DECODE_SIZE (1024*256) /* 256 kb decoding buffers */ +#define AUDIO_FLAG_FORMAT 0x1E00 -enum audio_source_mode +enum audio_format { - k_audio_source_mono, - k_audio_source_compressed, + k_audio_format_mono = 0x000u, + k_audio_format_stereo = 0x200u, + k_audio_format_vorbis = 0x400u, + k_audio_format_none0 = 0x600u, + k_audio_format_none1 = 0x800u, + k_audio_format_none2 = 0xA00u, + k_audio_format_none3 = 0xC00u, + k_audio_format_none4 = 0xE00u, + + k_audio_format_bird = 0x1000u, + k_audio_format_none5 = 0x1200u, + k_audio_format_none6 = 0x1400u, + k_audio_format_none7 = 0x1600u, + k_audio_format_none8 = 0x1800u, + k_audio_format_none9 = 0x1A00u, + k_audio_format_none10 = 0x1C00u, + k_audio_format_none11 = 0x1E00u, }; +#define AUDIO_DECODE_SIZE (1024*256) /* 256 kb decoding buffers */ +#define AUDIO_MUTE_VOLUME 0.0f +#define AUDIO_BASE_VOLUME 1.0f + typedef struct audio_clip audio_clip; +typedef struct audio_channel audio_channel; +typedef struct audio_lfo audio_lfo; + struct audio_clip { const char *path; - enum audio_source_mode source_mode; + u32 flags; u32 size; void *data; }; -typedef struct audio_mix_info audio_mix_info; -struct audio_mix_info -{ - audio_clip *source; - v3f world_position; - - float vol, pan; - u32 flags; -}; - -typedef struct audio_player audio_player; -struct audio_player -{ - aatree_ptr active_entity; /* non-nil if currently playing */ - audio_mix_info info; - int enqued, init; - - /* Diagnostic */ - const char *name; -}; - -typedef struct audio_entity audio_entity; -struct audio_entity -{ - audio_player *player; - audio_mix_info info; - - u32 length, cur; - - /* Effects */ - u32 fadeout, fadeout_current; - const char *name; -}; - -/* - * TODO list sunday - * - * play again: if already playing, leave in queue while it fadeouts - * oneshot: create a ghost entity - * - */ - static struct vg_audio_system { -#if 0 - ma_device miniaudio_device; - ma_device_config miniaudio_dconfig; -#endif SDL_AudioDeviceID sdl_output_device; void *audio_pool, @@ -127,41 +111,121 @@ static struct vg_audio_system SDL_mutex *mux_checker, *mux_sync; - /* Audio engine, thread 1 */ - struct active_audio_player + 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 { - int active; + int allocated; + char name[32]; /* only editable while allocated == 0 */ + audio_clip *source; /* ... */ + u32 flags; /* ... */ + u32 colour; /* ... */ + + /* internal non-readable state + * -----------------------------*/ + u32 cursor, source_length; + + float volume_movement_start, + pan_movement_start; + + u32 volume_movement, + pan_movement; + union { - audio_entity ent; - aatree_pool_node pool_node; + struct synth_bird *bird_handle; + stb_vorbis *vorbis_handle; }; - - stb_vorbis *vorbis_handle; + stb_vorbis_alloc vorbis_alloc; - } - active_players[ SFX_MAX_SYSTEMS ]; - aatree active_pool_info; /* note: just using the pool */ - aatree_ptr active_pool_head; + enum channel_activity + { + 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, + readable_activity; + + /* + * editable structure, can be modified inside _lock and _unlock + * the edit mask tells which to copy into internal _, or to discard + * ---------------------------------------------------------------------- + */ + 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; + } + channels[ AUDIO_CHANNELS ]; /* System queue, and access from thread 0 */ - audio_entity entity_queue[SFX_MAX_SYSTEMS]; - int queue_len; int debug_ui, debug_ui_3d; v3f listener_pos, - listener_ears; + listener_ears, + listener_velocity; + + float volume, + volume_target, + volume_target_internal, + volume_console; } -vg_audio; +vg_audio = { .volume_console = 1.0f }; + +#include "vg/vg_audio_dsp.h" static struct vg_profile _vg_prof_audio_decode = {.mode = k_profile_mode_accum, .name = "[T2] audio_decode()"}, _vg_prof_audio_mix = {.mode = k_profile_mode_accum, .name = "[T2] audio_mix()"}, + _vg_prof_dsp = {.mode = k_profile_mode_accum, + .name = "[T2] dsp_process()"}, vg_prof_audio_decode, - vg_prof_audio_mix; + vg_prof_audio_mix, + vg_prof_audio_dsp; /* * These functions are called from the main thread and used to prevent bad @@ -211,14 +275,22 @@ VG_STATIC void vg_audio_init(void) vg_audio.mux_sync = SDL_CreateMutex(); /* TODO: Move here? */ - vg_convar_push( (struct vg_convar){ + vg_var_push( (struct vg_var){ .name = "debug_audio", .data = &vg_audio.debug_ui, - .data_type = k_convar_dtype_i32, + .data_type = k_var_dtype_i32, .opt_i32 = { .min=0, .max=1, .clamp=1 }, .persistent = 1 }); + vg_var_push( (struct vg_var){ + .name = "volume", + .data = &vg_audio.volume_console, + .data_type = k_var_dtype_f32, + .opt_f32 = { .min=0.0f, .max=2.0f, .clamp=1 }, + .persistent = 1 + }); + /* allocate memory */ /* 32mb fixed */ @@ -227,16 +299,10 @@ VG_STATIC void vg_audio_init(void) VG_MEMORY_SYSTEM ); /* fixed */ - u32 decode_size = AUDIO_DECODE_SIZE * SFX_MAX_SYSTEMS; + u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; vg_audio.decode_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); - /* setup pool */ - vg_audio.active_pool_info.base = vg_audio.active_players; - vg_audio.active_pool_info.offset = offsetof(struct active_audio_player, - pool_node ); - vg_audio.active_pool_info.stride = sizeof(struct active_audio_player); - vg_audio.active_pool_info.p_cmp = NULL; - aatree_init_pool( &vg_audio.active_pool_info, SFX_MAX_SYSTEMS ); + vg_dsp_init(); SDL_AudioSpec spec_desired, spec_got; spec_desired.callback = audio_mixer_callback; @@ -244,14 +310,13 @@ VG_STATIC void vg_audio_init(void) spec_desired.format = AUDIO_F32; spec_desired.freq = 44100; spec_desired.padding = 0; - spec_desired.samples = 512; + spec_desired.samples = AUDIO_FRAME_SIZE; spec_desired.silence = 0; spec_desired.size = 0; spec_desired.userdata = NULL; vg_audio.sdl_output_device = - SDL_OpenAudioDevice( NULL, 0, &spec_desired, &spec_got, - SDL_AUDIO_ALLOW_SAMPLES_CHANGE ); + SDL_OpenAudioDevice( NULL, 0, &spec_desired, &spec_got,0 ); if( vg_audio.sdl_output_device ) { @@ -272,6 +337,7 @@ VG_STATIC void vg_audio_init(void) VG_STATIC void vg_audio_free(void) { + vg_dsp_free(); SDL_CloseAudioDevice( vg_audio.sdl_output_device ); } @@ -279,228 +345,260 @@ VG_STATIC void vg_audio_free(void) * thread 1 */ -static aatree_ptr audio_alloc_entity_internal(void) +#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 + +static audio_channel *audio_request_channel( audio_clip *clip, u32 flags ) { - aatree_ptr playerid = aatree_pool_alloc( &vg_audio.active_pool_info, - &vg_audio.active_pool_head ); + for( int i=0; iallocated ) + { + ch->source = clip; + ch->flags = flags; + ch->colour = 0x00333333; - struct active_audio_player *aap = &vg_audio.active_players[ playerid ]; - aap->active = 1; + if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_bird ) + strcpy( ch->name, "[array]" ); + else + strncpy( ch->name, clip->path, 31 ); + + ch->allocated = 1; + + ch->editable_state.relinquished = 0; + ch->editable_state.volume = 1.0f; + ch->editable_state.volume_target = 1.0f; + ch->editable_state.pan = 0.0f; + ch->editable_state.pan_target = 0.0f; + ch->editable_state.volume_rate = 0; + ch->editable_state.pan_rate = 0; + v4_copy((v4f){0.0f,0.0f,0.0f,1.0f},ch->editable_state.spacial_falloff); + ch->editable_state.lfo = NULL; + ch->editable_state.lfo_amount = 0.0f; + ch->editable_state.sampling_rate = 1.0f; + ch->editble_state_write_mask = 0x00; + return ch; + } + } - return playerid; + return NULL; } -VG_STATIC void audio_entity_free_internal( aatree_ptr id ) +static int audio_channel_finished( audio_channel *ch ) { - struct active_audio_player *aap = &vg_audio.active_players[ id ]; - aap->active = 0; + if( ch->readable_activity == k_channel_activity_end ) + return 1; + else + return 0; +} - /* Notify player that we've finished */ - if( aap->ent.player ) - aap->ent.player->active_entity = AATREE_PTR_NIL; +static audio_channel *audio_relinquish_channel( audio_channel *ch ) +{ + ch->editable_state.relinquished = 1; + ch->editble_state_write_mask |= AUDIO_EDIT_OWNERSHIP; + return NULL; +} - /* delete */ - aatree_pool_free( &vg_audio.active_pool_info, id, - &vg_audio.active_pool_head ); +static void audio_channel_slope_volume( audio_channel *ch, float length, + float new_volume ) +{ + ch->editable_state.volume_target = new_volume; + ch->editable_state.volume_rate = length * 44100.0f; + ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME_SLOPE; } -VG_STATIC void *audio_entity_vorbis_ptr( aatree_ptr entid ) +static void audio_channel_set_sampling_rate( audio_channel *ch, float rate ) { - u8 *buf = (u8*)vg_audio.decode_buffer, - *loc = &buf[AUDIO_DECODE_SIZE*entid]; + ch->editable_state.sampling_rate = rate; + ch->editble_state_write_mask |= AUDIO_EDIT_SAMPLING_RATE; +} - return (void *)loc; +static void audio_channel_edit_volume( audio_channel *ch, + float new_volume, int instant ) +{ + if( instant ) + { + ch->editable_state.volume = new_volume; + ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME; + } + else + { + audio_channel_slope_volume( ch, 0.05f, new_volume ); + } } -VG_STATIC void audio_entity_start( audio_entity *src ) +static audio_channel *audio_channel_fadeout( audio_channel *ch, float length ) { - aatree_ptr entid = audio_alloc_entity_internal(); - if( entid == AATREE_PTR_NIL ) - return; + audio_channel_slope_volume( ch, length, 0.0f ); + return audio_relinquish_channel( ch ); +} - audio_entity *ent = &vg_audio.active_players[ entid ].ent; +static void audio_channel_fadein( audio_channel *ch, float length ) +{ + audio_channel_edit_volume( ch, 0.0f, 1 ); + audio_channel_slope_volume( ch, length, 1.0f ); +} - ent->info = src->info; - ent->name = src->info.source->path; - ent->cur = 0; - ent->player = src->player; +static audio_channel *audio_channel_crossfade( audio_channel *ch, + audio_clip *new_clip, + float length, u32 flags ) +{ + u32 cursor = 0; - ent->fadeout = 0; - ent->fadeout_current = 0; + if( ch ) + ch = audio_channel_fadeout( ch, length ); - /* Notify main player we are dequeud and playing */ - if( src->player ) - { - src->player->enqued = 0; - src->player->active_entity = entid; - } + audio_channel *replacement = audio_request_channel( new_clip, flags ); - if( src->info.source->source_mode == k_audio_source_compressed ) - { - /* Setup vorbis decoder */ - struct active_audio_player *aap = &vg_audio.active_players[ entid ]; - - stb_vorbis_alloc alloc = { - .alloc_buffer = (char *)audio_entity_vorbis_ptr( entid ), - .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE - }; + if( replacement ) + audio_channel_fadein( replacement, length ); - int err; - stb_vorbis *decoder = stb_vorbis_open_memory( - src->info.source->data, - src->info.source->size, &err, &alloc ); + return replacement; +} - if( !decoder ) - { - vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", - src->info.source->path, err ); +static void audio_channel_sidechain_lfo( audio_channel *ch, int lfo_id, + float amount ) +{ + ch->editable_state.lfo = &vg_audio.oscillators[ lfo_id ]; + ch->editable_state.lfo_amount = amount; + ch->editble_state_write_mask |= AUDIO_EDIT_LFO_ATTACHMENT; +} - audio_entity_free_internal( entid ); - return; - } +static void audio_channel_set_spacial( audio_channel *ch, v3f co, float range ) +{ + if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ + v3_copy( co, ch->editable_state.spacial_falloff ); + + if( range == 0.0f ) + ch->editable_state.spacial_falloff[3] = 1.0f; else - { - ent->length = stb_vorbis_stream_length_in_samples( decoder ); - } - - aap->vorbis_handle = decoder; + ch->editable_state.spacial_falloff[3] = 1.0f/range; + + ch->editble_state_write_mask |= AUDIO_EDIT_SPACIAL; } - else - { - ent->length = src->info.source->size; + else{ + vg_warn( "Tried to set spacialization paramaters for 2D channel (%s)\n", + ch->name ); } } -/* - * Read everything from the queue - */ -VG_STATIC void audio_system_enque(void) +static int audio_oneshot_3d( audio_clip *clip, v3f position, + float range, float volume ) { - /* Process incoming sound queue */ - audio_lock(); - - int wr = 0; - for( int i=0; iplayer ) - { - /* Start new */ - if( src->player->active_entity == AATREE_PTR_NIL ) - { - audio_entity_start( src ); - } - else - { - /* Otherwise try start fadeout but dont remove from queue */ - - aatree_ptr entid = src->player->active_entity; - audio_entity *ent = &vg_audio.active_players[ entid ].ent; - if( !ent->fadeout ) - { - ent->fadeout = FADEOUT_LENGTH; - ent->fadeout_current = FADEOUT_LENGTH; - } + if( ch ){ + audio_channel_set_spacial( ch, position, range ); + audio_channel_edit_volume( ch, volume, 1 ); + ch = audio_relinquish_channel( ch ); - vg_audio.entity_queue[ wr ++ ] = *src; - } - } - else - { - audio_entity_start( src ); - } + return 1; } - - vg_audio.queue_len = wr; - - /* Localize others memory */ - for( int i=0; iactive ) - continue; + else + return 0; +} - if( aap->ent.player ) - { - /* Only copy information in whilst not requeing */ - if( aap->ent.player->enqued == 0 ) - { - aap->ent.info = aap->ent.player->info; +static int audio_oneshot( audio_clip *clip, float volume, float pan ) +{ + audio_channel *ch = audio_request_channel( clip, 0x00 ); - if( (aap->ent.info.flags & AUDIO_FLAG_KILL) && !aap->ent.fadeout ) - { - aap->ent.fadeout = FADEOUT_LENGTH; - aap->ent.fadeout_current = FADEOUT_LENGTH; - } - } - } + if( ch ){ + audio_channel_edit_volume( ch, volume, 1 ); + ch = audio_relinquish_channel( ch ); + + return 1; } - - audio_unlock(); + else + return 0; } -/* - * Redistribute sound systems - */ -VG_STATIC void audio_system_cleanup(void) +static void audio_set_lfo_wave( int id, enum lfo_wave_type type, + float coefficient ) { - audio_lock(); + audio_lfo *lfo = &vg_audio.oscillators[ id ]; + lfo->editable_state.polynomial_coefficient = coefficient; + lfo->editable_state.wave_type = type; - for( int i=0; iactive ) - { - audio_entity *src = &aap->ent; - if( src->cur < src->length || (src->info.flags & AUDIO_FLAG_LOOP )) - { - /* Good to keep */ - } - else - { - audio_entity_free_internal( i ); - } - } - } + lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_WAVE; +} - audio_unlock(); +static void audio_set_lfo_frequency( int id, float freq ) +{ + audio_lfo *lfo = &vg_audio.oscillators[ id ]; + lfo->editable_state.period = 44100.0f / freq; + lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_PERIOD; } + /* - * Get effective volume and pan from this entity + * Committers + * ----------------------------------------------------------------------------- */ -VG_STATIC void audio_entity_spacialize( audio_entity *ent, float *vol, float *pan ) +static int audio_channel_load_source( audio_channel *ch ) { - if( ent->info.vol < 0.01f ) + u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; + + if( format == k_audio_format_vorbis ) { - *vol = ent->info.vol; - *pan = 0.0f; - return; + /* Setup vorbis decoder */ + u32 index = ch - vg_audio.channels; + + u8 *buf = (u8*)vg_audio.decode_buffer, + *loc = &buf[AUDIO_DECODE_SIZE*index]; + + stb_vorbis_alloc alloc = { + .alloc_buffer = (char *)loc, + .alloc_buffer_length_in_bytes = AUDIO_DECODE_SIZE + }; + + int err; + stb_vorbis *decoder = stb_vorbis_open_memory( + ch->source->data, + ch->source->size, &err, &alloc ); + + if( !decoder ) + { + vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", + ch->source->path, err ); + return 0; + } + else + { + ch->source_length = stb_vorbis_stream_length_in_samples( decoder ); + ch->vorbis_handle = decoder; + } } + else if( format == k_audio_format_bird ) + { + u32 index = ch - vg_audio.channels; - v3f delta; - v3_sub( ent->info.world_position, vg_audio.listener_pos, delta ); + u8 *buf = (u8*)vg_audio.decode_buffer; + struct synth_bird *loc = (void *)&buf[AUDIO_DECODE_SIZE*index]; - float dist2 = v3_length2( delta ); + memcpy( loc, ch->source->data, ch->source->size ); + synth_bird_reset( loc ); - if( dist2 < 0.0001f ) + ch->bird_handle = loc; + ch->source_length = synth_bird_get_length_in_samples( loc ); + } + else if( format == k_audio_format_stereo ) { - *pan = 0.0f; - *vol = 1.0f; + ch->source_length = ch->source->size / 2; } else { - float dist = sqrtf( dist2 ), - attn = (dist / ent->info.vol) +1.0f; - - v3_muls( delta, 1.0f/dist, delta ); - *pan = v3_dot( vg_audio.listener_ears, delta ); - *vol = 1.0f/(attn*attn); + ch->source_length = ch->source->size; } + + return 1; } VG_STATIC void audio_decode_uncompressed_mono( i16 *src, u32 count, float *dst ) @@ -557,15 +655,13 @@ stb_vorbis_get_samples_i16_downmixed( stb_vorbis *f, i16 *buffer, int len ) int n = 0, c = VG_MIN( 1, f->channels - 1 ); - while( n < len ) - { + while( n < len ) { int k = f->channel_buffer_end - f->channel_buffer_start; if( n+k >= len ) k = len - n; - for( int j=0; j < k; ++j ) - { + for( int j=0; j < k; ++j ) { float sl = f->channel_buffers[ 0 ][f->channel_buffer_start+j], sr = f->channel_buffers[ c ][f->channel_buffer_start+j]; @@ -586,65 +682,107 @@ stb_vorbis_get_samples_i16_downmixed( stb_vorbis *f, i16 *buffer, int len ) return n; } -VG_STATIC void audio_entity_get_samples( aatree_ptr id, u32 count, float *buf ) +static inline float audio_lfo_pull_sample( audio_lfo *lfo ) { - vg_profile_begin( &_vg_prof_audio_decode ); + lfo->time ++; + + if( lfo->time >= lfo->_.period ) + lfo->time = 0; + + float t = lfo->time; + t /= (float)lfo->_.period; + + if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ + /* + * # + * # # + * # # + * # # + * ### # ### + * ## # + * # # + * # # + * ## + */ + + t *= 2.0f; + t -= 1.0f; + + return (( 2.0f * lfo->sqrt_polynomial_coefficient * t ) / + /* --------------------------------------- */ + ( 1.0f + lfo->_.polynomial_coefficient * t*t ) + + ) * (1.0f-fabsf(t)); + } + else{ + return 0.0f; + } +} - struct active_audio_player *aap = &vg_audio.active_players[id]; - audio_entity *ent = &aap->ent; +static void audio_channel_get_samples( audio_channel *ch, + u32 count, float *buf ) +{ + vg_profile_begin( &_vg_prof_audio_decode ); u32 remaining = count; - u32 cursor = ent->cur; u32 buffer_pos = 0; - while( remaining ) - { - u32 samples_this_run = VG_MIN( remaining, ent->length - cursor ); + u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; + + while( remaining ){ + u32 samples_this_run = VG_MIN(remaining, ch->source_length - ch->cursor); remaining -= samples_this_run; float *dst = &buf[ buffer_pos * 2 ]; - - int source_mode = ent->info.source->source_mode; - if( source_mode == k_audio_source_mono ) - { - i16 *src_buffer = ent->info.source->data, - *src = &src_buffer[cursor]; - - audio_decode_uncompressed_mono( src, samples_this_run, dst ); + if( format == k_audio_format_stereo ){ + for( int i=0;ivorbis_handle, + ch->vorbis_handle, dst, - samples_this_run ); + samples_this_run ); - if( read_samples != samples_this_run ) - { - vg_warn( "Invalid samples read (%s)\n", ent->info.source->path ); + if( read_samples != samples_this_run ){ + vg_warn( "Invalid samples read (%s)\n", ch->source->path ); + + for( int i=0; ibird_handle, dst, samples_this_run ); + } + else{ + i16 *src_buffer = ch->source->data, + *src = &src_buffer[ch->cursor]; + + audio_decode_uncompressed_mono( src, samples_this_run, dst ); + } - cursor += samples_this_run; + ch->cursor += samples_this_run; buffer_pos += samples_this_run; - if( (ent->info.flags & AUDIO_FLAG_LOOP) && remaining ) - { - if( source_mode == k_audio_source_compressed ) - { - stb_vorbis_seek_start( aap->vorbis_handle ); - } + if( (ch->flags & AUDIO_FLAG_LOOP) && remaining ){ + if( format == k_audio_format_vorbis ) + stb_vorbis_seek_start( ch->vorbis_handle ); + else if( format == k_audio_format_bird ) + synth_bird_reset( ch->bird_handle ); - cursor = 0; + ch->cursor = 0; continue; } else break; } - while( remaining ) - { + while( remaining ){ buf[ buffer_pos*2 + 0 ] = 0.0f; buf[ buffer_pos*2 + 1 ] = 0.0f; buffer_pos ++; @@ -652,65 +790,279 @@ VG_STATIC void audio_entity_get_samples( aatree_ptr id, u32 count, float *buf ) remaining --; } - ent->cur = cursor; vg_profile_end( &_vg_prof_audio_decode ); } -VG_STATIC void audio_entity_mix( aatree_ptr id, float *buffer, - u32 frame_count ) +static void audio_channel_mix( audio_channel *ch, float *buffer ) { - audio_entity *ent = &vg_audio.active_players[id].ent; + float framevol_l = 1.0f, + framevol_r = 1.0f; - u32 cursor = ent->cur, buffer_pos = 0; - float *pcf = alloca( frame_count * 2 * sizeof(float) ); - - u32 frames_write = frame_count; - float fadeout_divisor = 1.0f / (float)ent->fadeout; + float frame_samplerate = ch->_.sampling_rate; + + if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ + v3f delta; + v3_sub( ch->_.spacial_falloff, vg_audio.listener_pos, delta ); + + float dist = v3_length( delta ), + vol = vg_maxf( 0.0f, 1.0f - ch->_.spacial_falloff[3]*dist ); + + if( dist <= 0.01f ){ + + } + else{ + v3_muls( delta, 1.0f/dist, delta ); + float pan = v3_dot( vg_audio.listener_ears, delta ); + vol = powf( vol, 5.0f ); + + framevol_l *= (vol * 0.5f) * (1.0f - pan); + framevol_r *= (vol * 0.5f) * (1.0f + pan); + + const float vs = 100.0f; + float doppler = (vs+v3_dot(delta,vg_audio.listener_velocity))/vs; + doppler = vg_clampf( doppler, 0.6f, 1.4f ); + + if( fabsf(doppler-1.0f) > 0.01f ) + frame_samplerate *= doppler; + } + + if( !vg_validf( framevol_l ) ) vg_fatal_exit_loop( "NaN left channel" ); + if( !vg_validf( framevol_r ) ) vg_fatal_exit_loop( "NaN right channel" ); + if( !vg_validf( frame_samplerate ) ) + vg_fatal_exit_loop( "NaN sample rate" ); + } - float vol = ent->info.vol, - pan = ent->info.pan; + 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_entity_get_samples( id, frame_count, pcf ); + float pcf[ AUDIO_MIX_FRAME_SIZE * 2 * 2 ]; + + audio_channel_get_samples( ch, buffer_length, pcf ); vg_profile_begin( &_vg_prof_audio_mix ); - if( ent->info.flags & AUDIO_FLAG_SPACIAL_3D ) - audio_entity_spacialize( ent, &vol, &pan ); + 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; + + float volume = ch->_.volume; + const float volume_start = ch->volume_movement_start; + const float volume_target = ch->_.volume_target; + + for( u32 j=0; j_.lfo ) + vol_norm *= 1.0f + audio_lfo_pull_sample(ch->_.lfo) * ch->_.lfo_amount; + + 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 + */ + + float sample_index = frame_samplerate * (float)j; + float t = vg_fractf( sample_index ); - for( u32 j=0; jfadeout ) + 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 { - /* Force this system to be removed now */ - if( ent->fadeout_current == 0 ) + sample_l = pcf[ j*2+0 ]; + sample_r = pcf[ j*2+1 ]; + } + + buffer[ j*2+0 ] += sample_l * vol_l; + buffer[ j*2+1 ] += sample_r * vol_r; + } + + ch->volume_movement += AUDIO_MIX_FRAME_SIZE; + ch->volume_movement = VG_MIN( ch->volume_movement, ch->_.volume_rate ); + ch->_.volume = volume; + + vg_profile_end( &_vg_prof_audio_mix ); +} + +VG_STATIC void audio_mixer_callback( void *user, u8 *stream, int byte_count ) +{ + /* + * Copy data and move edit flags to commit flags + * ------------------------------------------------------------- */ + audio_lock(); + for( int i=0; iallocated ) + continue; + + if( ch->activity == k_channel_activity_alive ){ + if( (ch->cursor >= ch->source_length) && + !(ch->flags & AUDIO_FLAG_LOOP) ) { - ent->info.flags = 0x00; - ent->cur = ent->length; - break; + ch->activity = k_channel_activity_end; } + } - frame_vol *= (float)ent->fadeout_current * fadeout_divisor; - ent->fadeout_current --; + /* 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; + } } - float sl = 1.0f-pan, - sr = 1.0f+pan; + /* process new channels */ + if( ch->activity == k_channel_activity_reset ){ + ch->_ = ch->editable_state; + ch->cursor = 0; + ch->source_length = 0; + ch->activity = k_channel_activity_wake; + } - buffer[ buffer_pos*2+0 ] += pcf[ buffer_pos*2+0 ] * frame_vol * sl; - buffer[ buffer_pos*2+1 ] += pcf[ buffer_pos*2+1 ] * frame_vol * sr; + if( ch->editble_state_write_mask & AUDIO_EDIT_OWNERSHIP ) + ch->_.relinquished = ch->editable_state.relinquished; + else + ch->editable_state.relinquished = ch->_.relinquished; + + + 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; + } - buffer_pos ++; + + 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; + } + + + 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; + + + if( ch->editble_state_write_mask & AUDIO_EDIT_LFO_ATTACHMENT ){ + ch->_.lfo = ch->editable_state.lfo; + ch->_.lfo_amount = ch->editable_state.lfo_amount; + } + 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_profile_end( &_vg_prof_audio_mix ); -} + 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 ){ + if( lfo->_.period ){ + float t = lfo->time; + t/= (float)lfo->_.period; + + lfo->_.period = lfo->editable_state.period; + lfo->time = lfo->_.period * t; + } + else{ + lfo->time = 0; + lfo->_.period = lfo->editable_state.period; + } + } + + lfo->editble_state_write_mask = 0x00; + } + + dsp_update_tunings(); + audio_unlock(); + + /* + * 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; + } + } + + /* + * Mix everything + * -------------------------------------------------------- */ int frame_count = byte_count/(2*sizeof(float)); /* Clear buffer */ @@ -718,28 +1070,65 @@ VG_STATIC void audio_mixer_callback( void *user, u8 *stream, int byte_count ) for( int i=0; itime_startframe = lfo->time; + } + + for( int i=0; iactive ) + if( ch->activity == k_channel_activity_alive ) { - audio_entity_mix( i, pOut32F, frame_count ); + if( ch->_.lfo ) + ch->_.lfo->time = ch->_.lfo->time_startframe; + + u32 remaining = frame_count, + subpos = 0; + + while( remaining ) + { + audio_channel_mix( ch, pOut32F+subpos ); + remaining -= AUDIO_MIX_FRAME_SIZE; + subpos += AUDIO_MIX_FRAME_SIZE*2; + } } } - /* redistribute */ - audio_system_cleanup(); - + vg_profile_begin( &_vg_prof_dsp ); + + 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 ); vg_prof_audio_mix = _vg_prof_audio_mix; vg_prof_audio_decode = _vg_prof_audio_decode; + vg_prof_audio_dsp = _vg_prof_dsp; vg_audio.samples_last = frame_count; + + if( vg_audio.debug_ui ) + { + vg_dsp_update_texture(); + } + audio_unlock(); } @@ -748,7 +1137,42 @@ VG_STATIC void audio_clip_load( audio_clip *clip, void *lin_alloc ) if( lin_alloc == NULL ) lin_alloc = vg_audio.audio_pool; - if( clip->source_mode == k_audio_source_mono ) + /* load in directly */ + u32 format = 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 ) + { + audio_lock(); + clip->data = vg_file_read( lin_alloc, clip->path, &clip->size ); + audio_unlock(); + + if( !clip->data ) + vg_fatal_exit_loop( "Audio failed to load" ); + + 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_fatal_exit_loop( "Unsupported format (Stereo uncompressed)" ); + } + else if( format == k_audio_format_bird ) + { + u32 len = strlen( clip->path ), + size = synth_bird_memory_requirement( len ); + + if( size > AUDIO_DECODE_SIZE ) + vg_fatal_exit_loop( "Bird code too long\n" ); + + clip->size = size; + clip->data = vg_linear_alloc( lin_alloc, size ); + + synth_bird_load( clip->data, clip->path, len ); + } + else { vg_linear_clear( vg_mem.scratch ); u32 fsize; @@ -776,7 +1200,7 @@ VG_STATIC void audio_clip_load( audio_clip *clip, void *lin_alloc ) data_size = length_samples * sizeof(i16); audio_lock(); - clip->data = vg_linear_alloc( lin_alloc, data_size ); + clip->data = vg_linear_alloc( lin_alloc, vg_align8(data_size) ); clip->size = length_samples; audio_unlock(); @@ -790,20 +1214,6 @@ VG_STATIC void audio_clip_load( audio_clip *clip, void *lin_alloc ) vg_info( "Loaded audio clip '%s' (%.1fmb) %u samples\n", clip->path, mb, length_samples ); } - - /* load in directly */ - else if( clip->source_mode == k_audio_source_compressed ) - { - audio_lock(); - clip->data = vg_file_read( lin_alloc, clip->path, &clip->size ); - audio_unlock(); - - if( !clip->data ) - vg_fatal_exit_loop( "Audio failed to load" ); - - float mb = (float)(clip->size) / (1024.0f*1024.0f); - vg_info( "Loaded audio clip '%s' (%.1fmb)\n", clip->path, mb ); - } } VG_STATIC void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) @@ -812,38 +1222,6 @@ VG_STATIC void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) audio_clip_load( &arr[i], lin_alloc ); } -/* Mark change to be uploaded through queue system */ -VG_STATIC void audio_player_commit( audio_player *sys ) -{ - audio_require_lock(); - - if( vg_audio.queue_len >= vg_list_size( vg_audio.entity_queue ) ) - { - vg_warn( "Audio commit queue full\n" ); - return; - } - - if( sys->enqued ) - { - vg_warn( "[2] Audio commit spamming; already enqued (%s)\n", sys->name ); - return; - } - - sys->enqued = 1; - audio_entity *ent = &vg_audio.entity_queue[ vg_audio.queue_len ++ ]; - ent->info = sys->info; - ent->player = sys; -} - -VG_STATIC void audio_require_init( audio_player *player ) -{ - if( player->init ) - return; - - audio_unlock(); - vg_fatal_exit_loop( "Must init audio player before playing! \n" ); -} - VG_STATIC void audio_require_clip_loaded( audio_clip *clip ) { if( clip->data && clip->size ) @@ -853,118 +1231,6 @@ VG_STATIC void audio_require_clip_loaded( audio_clip *clip ) vg_fatal_exit_loop( "Must load audio clip before playing! \n" ); } -/* Play a clip using player. If its already playing something, it will - * fadeout quickly and start the next sound */ -VG_STATIC void audio_player_playclip( audio_player *player, audio_clip *clip ) -{ - audio_require_lock(); - audio_require_init( player ); - audio_require_clip_loaded( clip ); - - if( player->info.flags & AUDIO_FLAG_KILL ) - { - vg_error( "Can't start audio clip on player that is/has disconnected" ); - return; - } - - if( player->enqued ) - { - vg_warn( "[1] Audio commit spamming; already enqued (%s)\n", - player->name ); - return; - } - - player->info.source = clip; - audio_player_commit( player ); -} - -VG_STATIC void audio_play_oneshot( audio_clip *clip, float volume ) -{ - audio_require_lock(); - audio_require_clip_loaded( clip ); - - if( vg_audio.queue_len >= vg_list_size( vg_audio.entity_queue ) ) - { - vg_warn( "Audio commit queue full\n" ); - return; - } - - audio_entity *ent = &vg_audio.entity_queue[ vg_audio.queue_len ++ ]; - - ent->info.flags = AUDIO_FLAG_ONESHOT; - ent->info.pan = 0.0f; - ent->info.source = clip; - ent->info.vol = volume; - ent->player = NULL; -} - -VG_STATIC void audio_player_init( audio_player *player ) -{ - player->active_entity = AATREE_PTR_NIL; - player->init = 1; -} - -/* - * Effects - */ - -/* - * Safety enforced Get/set attributes - */ - -VG_STATIC int audio_player_is_playing( audio_player *sys ) -{ - audio_require_lock(); - - if( sys->active_entity != AATREE_PTR_NIL ) - return 1; - else - return 0; -} - -VG_STATIC void audio_player_set_position( audio_player *sys, v3f pos ) -{ - audio_require_lock(); - v3_copy( pos, sys->info.world_position ); -} - -VG_STATIC void audio_player_set_vol( audio_player *sys, float vol ) -{ - audio_require_lock(); - sys->info.vol = vol; -} - -VG_STATIC float audio_player_get_vol( audio_player *sys ) -{ - audio_require_lock(); - return sys->info.vol; -} - -VG_STATIC void audio_player_set_pan( audio_player *sys, float pan ) -{ - audio_require_lock(); - sys->info.pan = pan; -} - -VG_STATIC float audio_player_get_pan( audio_player *sys ) -{ - audio_require_lock(); - return sys->info.pan; -} - -VG_STATIC void audio_player_set_flags( audio_player *sys, u32 flags ) -{ - audio_require_lock(); - sys->info.flags = flags; -} - -VG_STATIC u32 audio_player_get_flags( audio_player *sys ) -{ - audio_require_lock(); - return sys->info.flags; -} - - /* * Debugging */ @@ -974,52 +1240,36 @@ VG_STATIC void audio_debug_ui( m4x4f mtx_pv ) if( !vg_audio.debug_ui ) return; - /* Get data */ - struct sound_info - { - const char *name; - u32 cursor, flags, length; - v3f pos; - float vol; - } - infos[ SFX_MAX_SYSTEMS ]; - int num_systems = 0; - audio_lock(); - - for( int i=0; iactive ) - continue; + glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); + glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 256, 256, + GL_RGBA, GL_UNSIGNED_BYTE, + vg_dsp.view_texture_buffer ); - audio_entity *ent = &aap->ent; - struct sound_info *snd = &infos[ num_systems ++ ]; - - snd->name = ent->name; - snd->cursor = ent->cur; - snd->flags = ent->info.flags; - snd->length = ent->length; - snd->vol = ent->info.vol*100.0f; - v3_copy( ent->info.world_position, snd->pos ); - } + /* + * Profiler + * ----------------------------------------------------------------------- + */ float budget = ((double)vg_audio.samples_last / 44100.0) * 1000.0; vg_profile_drawn( (struct vg_profile *[]){ &vg_prof_audio_decode, - &vg_prof_audio_mix }, 2, + &vg_prof_audio_mix, + &vg_prof_audio_dsp}, 3, budget, (ui_rect){ 4, VG_PROFILE_SAMPLE_COUNT*2 + 8, - 250, 0 }, 3 ); + 512, 0 }, 3 ); - audio_unlock(); char perf[128]; /* Draw UI */ - vg_uictx.cursor[0] = 258; - vg_uictx.cursor[1] = VG_PROFILE_SAMPLE_COUNT*2+8+24+12; + vg_uictx.cursor[0] = 512 + 8; + vg_uictx.cursor[1] = VG_PROFILE_SAMPLE_COUNT*2+8+24+12+12; vg_uictx.cursor[2] = 150; vg_uictx.cursor[3] = 12; + + ui_rect view_thing = { 4, vg.window_y-512-4, 512, 512 }; + ui_push_image( view_thing, vg_dsp.view_texture ); float mb1 = 1024.0f*1024.0f, usage = vg_linear_get_cur( vg_audio.audio_pool ) / mb1, @@ -1031,47 +1281,88 @@ VG_STATIC void audio_debug_ui( m4x4f mtx_pv ) ui_text( vg_uictx.cursor, perf, 1, 0 ); vg_uictx.cursor[1] += 20; - ui_rect overlap_buffer[ SFX_MAX_SYSTEMS ]; + ui_rect overlap_buffer[ AUDIO_CHANNELS ]; u32 overlap_length = 0; /* Draw audio stack */ - for( int i=0; icursor / (float)inf->length) * w; - - /* cursor */ - vg_uictx.cursor[2] = 2; - vg_uictx.cursor[0] = c; - ui_fill_rect( vg_uictx.cursor, 0xffffffff ); - - vg_uictx.cursor[0] = baseline + 2; - vg_uictx.cursor[1] += 2; - snprintf( perf, 127, "%s %.1f%%", infos[i].name, infos[i].vol ); - ui_text( vg_uictx.cursor, perf, 1, 0 ); - - if( inf->flags & AUDIO_FLAG_SPACIAL_3D ) - { - v4f wpos; - v3_copy( inf->pos, wpos ); - wpos[3] = 1.0f; - m4x4_mulv( mtx_pv, wpos, wpos ); - if( wpos[3] < 0.0f ) - goto projected_behind; + if( !ch->allocated ) + { + ui_fill_rect( vg_uictx.cursor, 0x50333333 ); + + ui_end_down(); + vg_uictx.cursor[1] += 1; + continue; + } + + const char *formats[] = + { + " mono ", + " stereo ", + " vorbis ", + " none0 ", + " none1 ", + " none2 ", + " none3 ", + " none4 ", + "synth:bird", + " none5 ", + " none6 ", + " none7 ", + " none8 ", + " none9 ", + " none10 ", + " none11 ", + }; + + const char *activties[] = + { + "reset", + "wake ", + "alive", + "end ", + "error" + }; + + u32 format_index = (ch->source->flags & AUDIO_FLAG_FORMAT)>>9; + + snprintf( perf, 127, "%02d %c%c%cD %s [%s] %4.2fv'%s'", + i, + (ch->editable_state.relinquished)? 'r': '_', + 0? 'r': '_', + 0? '3': '2', + formats[format_index], + activties[ch->readable_activity], + ch->editable_state.volume, + ch->name ); + + ui_fill_rect( vg_uictx.cursor, 0xa0000000 | ch->colour ); + + vg_uictx.cursor[0] += 2; + vg_uictx.cursor[1] += 2; + ui_text( vg_uictx.cursor, perf, 1, 0 ); + ui_end_down(); + vg_uictx.cursor[1] += 1; + + if( AUDIO_FLAG_SPACIAL_3D ) + { + v4f wpos; + v3_copy( ch->editable_state.spacial_falloff, wpos ); + + wpos[3] = 1.0f; + m4x4_mulv( mtx_pv, wpos, wpos ); + + if( wpos[3] > 0.0f ) + { v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos ); v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos ); @@ -1105,13 +1396,10 @@ VG_STATIC void audio_debug_ui( m4x4f mtx_pv ) ui_rect_copy( wr, overlap_buffer[ overlap_length ++ ] ); } - } - -projected_behind: - - ui_end_down(); - vg_uictx.cursor[1] += 1; + } } + + audio_unlock(); } #endif /* VG_AUDIO_H */