X-Git-Url: https://harrygodden.com/git/?p=vg.git;a=blobdiff_plain;f=vg_audio.c;fp=vg_audio.c;h=1611a622e86ccdbe7782bf3235e697219eee4bb9;hp=0000000000000000000000000000000000000000;hb=3b14f3dcd5bf9dd3c85144f2123d667bfa4bb63f;hpb=fce86711735b15bff37de0f70716808410fcf269 diff --git a/vg_audio.c b/vg_audio.c new file mode 100644 index 0000000..1611a62 --- /dev/null +++ b/vg_audio.c @@ -0,0 +1,1212 @@ +#include "vg_audio.h" +#include "vg_audio_dsp.h" +#include "vg_platform.h" +#include "vg_io.h" +#include "vg_m.h" +#include "vg_console.h" +#include "vg_profiler.h" +#include "vg_audio_synth_bird.h" +#include "vg_vorbis.h" +#include + +struct vg_audio_system vg_audio = +{ + .external_global_volume = 1.0f, + .dsp_enabled = 1 +}; + +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_dsp; + +/* + * 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) +{ + int value; + SDL_AtomicLock( &vg_audio.sl_checker ); + value = vg_audio.sync_locked; + SDL_AtomicUnlock( &vg_audio.sl_checker ); + return value; +} + +static void audio_lock_checker_store( int value ) +{ + SDL_AtomicLock( &vg_audio.sl_checker ); + vg_audio.sync_locked = value; + SDL_AtomicUnlock( &vg_audio.sl_checker ); +} + +static void audio_require_lock(void) +{ + if( audio_lock_checker_load() ) + return; + + vg_error( "Modifying sound effects systems requires locking\n" ); + abort(); +} + +void audio_lock(void) +{ + SDL_AtomicLock( &vg_audio.sl_sync ); + audio_lock_checker_store(1); +} + +void audio_unlock(void) +{ + audio_lock_checker_store(0); + SDL_AtomicUnlock( &vg_audio.sl_sync ); +} + +static void audio_mixer_callback( void *user, u8 *stream, int frame_count ); +void vg_audio_device_init(void) +{ + SDL_AudioSpec spec_desired, spec_got; + spec_desired.callback = audio_mixer_callback; + spec_desired.channels = 2; + spec_desired.format = AUDIO_F32; + spec_desired.freq = 44100; + spec_desired.padding = 0; + 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( vg_audio.device_choice.buffer, 0, + &spec_desired, &spec_got,0 ); + + vg_info( "Start audio device (%u, F32, %u) @%s\n", + spec_desired.freq, + AUDIO_FRAME_SIZE, + vg_audio.device_choice.buffer ); + + if( vg_audio.sdl_output_device ){ + SDL_PauseAudioDevice( vg_audio.sdl_output_device, 0 ); + vg_success( "Unpaused device %d.\n", vg_audio.sdl_output_device ); + } + else{ + vg_error( + "SDL_OpenAudioDevice failed. Your default audio device must support:\n" + " Frequency: 44100 hz\n" + " Buffer size: 512\n" + " Channels: 2\n" + " Format: s16 or f32\n" ); + } +} + +void vg_audio_register(void) +{ + vg_console_reg_var( "debug_audio", &vg_audio.debug_ui, + k_var_dtype_i32, VG_VAR_CHEAT ); + vg_console_reg_var( "debug_dsp", &vg_audio.debug_dsp, + k_var_dtype_i32, VG_VAR_CHEAT ); + vg_console_reg_var( "volume", &vg_audio.external_global_volume, + 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 ); +} + +void vg_audio_init(void) +{ + /* allocate memory */ + /* 32mb fixed */ + vg_audio.audio_pool = + vg_create_linear_allocator( vg_mem.rtmemory, 1024*1024*32, + VG_MEMORY_SYSTEM ); + + /* fixed */ + u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; + vg_audio.decode_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); + + vg_dsp_init(); + vg_audio_device_init(); +} + +void vg_audio_free(void) +{ + vg_dsp_free(); + SDL_CloseAudioDevice( vg_audio.sdl_output_device ); +} + +/* + * thread 1 + */ + +#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 + +void audio_channel_init( audio_channel *ch, audio_clip *clip, u32 flags ) +{ + audio_require_lock(); + ch->group = 0; + ch->world_id = 0; + ch->source = clip; + ch->flags = flags; + ch->colour = 0x00333333; + + if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_bird ) + strcpy( ch->name, "[array]" ); + else if( (ch->source->flags & AUDIO_FLAG_FORMAT) == k_audio_format_gen ) + strcpy( ch->name, "[program]" ); + else + vg_strncpy( clip->path, ch->name, 32, k_strncpy_always_add_null ); + + 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; +} + +void audio_channel_group( audio_channel *ch, u16 group ) +{ + audio_require_lock(); + ch->group = group; + ch->colour = (((u32)group * 29986577) & 0x00ffffff) | 0xff000000; +} + +void audio_channel_world( audio_channel *ch, u8 world_id ) +{ + audio_require_lock(); + ch->world_id = world_id; +} + +audio_channel *audio_get_first_idle_channel(void) +{ + audio_require_lock(); + for( int i=0; iallocated ){ + return ch; + } + } + + return NULL; +} + +audio_channel *audio_get_group_idle_channel( u16 group, u32 max_count ) +{ + audio_require_lock(); + u32 count = 0; + audio_channel *dest = NULL; + + for( int i=0; iallocated ){ + if( ch->group == group ){ + count ++; + } + } + else{ + if( !dest ) + dest = ch; + } + } + + if( dest && (count < max_count) ){ + return dest; + } + + return NULL; +} + +audio_channel *audio_get_group_first_active_channel( u16 group ) +{ + audio_require_lock(); + for( int i=0; iallocated && (ch->group == group) ) + return ch; + } + return NULL; +} + +int audio_channel_finished( audio_channel *ch ) +{ + audio_require_lock(); + if( ch->readable_activity == k_channel_activity_end ) + return 1; + else + return 0; +} + +audio_channel *audio_relinquish_channel( audio_channel *ch ) +{ + audio_require_lock(); + ch->editable_state.relinquished = 1; + ch->editble_state_write_mask |= AUDIO_EDIT_OWNERSHIP; + return NULL; +} + +void audio_channel_slope_volume( audio_channel *ch, f32 length, f32 new_vol ) +{ + audio_require_lock(); + ch->editable_state.volume_target = new_vol; + ch->editable_state.volume_rate = length * 44100.0f; + ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME_SLOPE; +} + +void audio_channel_set_sampling_rate( audio_channel *ch, float rate ) +{ + audio_require_lock(); + ch->editable_state.sampling_rate = rate; + ch->editble_state_write_mask |= AUDIO_EDIT_SAMPLING_RATE; +} + +void audio_channel_edit_volume( audio_channel *ch, f32 new_vol, int instant ) +{ + audio_require_lock(); + if( instant ){ + ch->editable_state.volume = new_vol; + ch->editble_state_write_mask |= AUDIO_EDIT_VOLUME; + } + else{ + audio_channel_slope_volume( ch, 0.05f, new_vol ); + } +} + +audio_channel *audio_channel_fadeout( audio_channel *ch, float length ) +{ + audio_require_lock(); + audio_channel_slope_volume( ch, length, 0.0f ); + return audio_relinquish_channel( ch ); +} + +void audio_channel_fadein( audio_channel *ch, float length ) +{ + audio_require_lock(); + audio_channel_edit_volume( ch, 0.0f, 1 ); + audio_channel_slope_volume( ch, length, 1.0f ); +} + +audio_channel *audio_channel_crossfade( audio_channel *ch, + audio_clip *new_clip, + float length, u32 flags ) +{ + audio_require_lock(); + u32 cursor = 0; + + if( ch ) + ch = audio_channel_fadeout( ch, length ); + + audio_channel *replacement = audio_get_first_idle_channel(); + + if( replacement ){ + audio_channel_init( replacement, new_clip, flags ); + audio_channel_fadein( replacement, length ); + } + + return replacement; +} + +void audio_channel_sidechain_lfo( audio_channel *ch, int lfo_id, f32 amount ) +{ + audio_require_lock(); + ch->editable_state.lfo = &vg_audio.oscillators[ lfo_id ]; + ch->editable_state.lfo_amount = amount; + ch->editble_state_write_mask |= AUDIO_EDIT_LFO_ATTACHMENT; +} + +void audio_channel_set_spacial( audio_channel *ch, v3f co, float range ) +{ + audio_require_lock(); + 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 + ch->editable_state.spacial_falloff[3] = 1.0f/range; + + ch->editble_state_write_mask |= AUDIO_EDIT_SPACIAL; + } + else{ + vg_warn( "Tried to set spacialization paramaters for 2D channel (%s)\n", + ch->name ); + } +} + +int audio_oneshot_3d( audio_clip *clip, v3f position, f32 range, f32 volume ) +{ + audio_require_lock(); + audio_channel *ch = audio_get_first_idle_channel(); + + if( ch ){ + audio_channel_init( ch, clip, AUDIO_FLAG_SPACIAL_3D ); + audio_channel_set_spacial( ch, position, range ); + audio_channel_edit_volume( ch, volume, 1 ); + ch = audio_relinquish_channel( ch ); + + return 1; + } + else + return 0; +} + +int audio_oneshot( audio_clip *clip, f32 volume, f32 pan ) +{ + audio_require_lock(); + audio_channel *ch = audio_get_first_idle_channel(); + + if( ch ){ + audio_channel_init( ch, clip, 0x00 ); + audio_channel_edit_volume( ch, volume, 1 ); + ch = audio_relinquish_channel( ch ); + + return 1; + } + else + return 0; +} + +void audio_set_lfo_wave( int id, enum lfo_wave_type type, f32 coefficient ) +{ + audio_require_lock(); + audio_lfo *lfo = &vg_audio.oscillators[ id ]; + lfo->editable_state.polynomial_coefficient = coefficient; + lfo->editable_state.wave_type = type; + + lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_WAVE; +} + +void audio_set_lfo_frequency( int id, float freq ) +{ + audio_require_lock(); + audio_lfo *lfo = &vg_audio.oscillators[ id ]; + lfo->editable_state.period = 44100.0f / freq; + lfo->editble_state_write_mask |= AUDIO_EDIT_LFO_PERIOD; +} + + +/* + * Committers + * ----------------------------------------------------------------------------- + */ +int audio_channel_load_source( audio_channel *ch ) +{ + u32 format = ch->source->flags & AUDIO_FLAG_FORMAT; + + if( format == k_audio_format_vorbis ){ + /* 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->handle.vorbis = decoder; + } + } + 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]; + + memcpy( loc, ch->source->data, ch->source->size ); + synth_bird_reset( loc ); + + ch->handle.bird = loc; + ch->source_length = synth_bird_get_length_in_samples( loc ); + } + else if( format == k_audio_format_stereo ){ + ch->source_length = ch->source->size / 2; + } + else if( format == k_audio_format_gen ){ + ch->source_length = 0xffffffff; + } + else{ + ch->source_length = ch->source->size; + } + + return 1; +} + +static void audio_decode_uncompressed_mono( i16 *src, u32 count, float *dst ) +{ + for( u32 i=0; itime ++; + + 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; + } +} + +static void audio_channel_get_samples( audio_channel *ch, + u32 count, float *buf ) +{ + vg_profile_begin( &_vg_prof_audio_decode ); + + u32 remaining = count; + u32 buffer_pos = 0; + + 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 ]; + + if( format == k_audio_format_stereo ){ + for( int i=0;ihandle.vorbis, + dst, + samples_this_run ); + + if( read_samples != samples_this_run ){ + vg_warn( "Invalid samples read (%s)\n", ch->source->path ); + + for( int i=0; ihandle.bird, dst, samples_this_run ); + } + else if( format == k_audio_format_gen ){ + void (*fn)( void *data, f32 *buf, u32 count ) = ch->source->func; + fn( ch->source->data, 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 ); + } + + 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 ); + + ch->cursor = 0; + continue; + } + else + break; + } + + while( remaining ){ + buf[ buffer_pos*2 + 0 ] = 0.0f; + buf[ buffer_pos*2 + 1 ] = 0.0f; + buffer_pos ++; + + remaining --; + } + + vg_profile_end( &_vg_prof_audio_decode ); +} + +static void audio_channel_mix( audio_channel *ch, float *buffer ) +{ + float framevol_l = vg_audio.internal_global_volume, + framevol_r = vg_audio.internal_global_volume; + + float frame_samplerate = ch->_.sampling_rate; + + if( ch->flags & AUDIO_FLAG_SPACIAL_3D ){ + v3f delta; + v3_sub( ch->_.spacial_falloff, vg_audio.internal_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.internal_listener_ears, delta ); + vol = powf( vol, 5.0f ); + + framevol_l *= (vol * 0.5f) * (1.0f - pan); + framevol_r *= (vol * 0.5f) * (1.0f + pan); + + if( !(ch->source->flags & AUDIO_FLAG_NO_DOPPLER) ){ + const float vs = 323.0f; + + 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; + } + } + + if( !vg_validf( framevol_l ) || + !vg_validf( framevol_r ) || + !vg_validf( frame_samplerate ) ){ + vg_fatal_error( "Invalid sampling conditions.\n" + "This crash is to protect your ears.\n" + " channel: %p (%s)\n" + " sample_rate: %f\n" + " volume: L%f R%f\n" + " listener: %.2f %.2f %.2f [%.2f %.2f %.2f]\n", + ch, ch->name, frame_samplerate, + framevol_l, framevol_r, + 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] + ); + } + } + + 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; + } + + float pcf[ AUDIO_MIX_FRAME_SIZE * 2 * 2 ]; + + audio_channel_get_samples( ch, buffer_length, pcf ); + + vg_profile_begin( &_vg_prof_audio_mix ); + + 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 ); + + u32 i0 = floorf( sample_index ), + i1 = i0+1; + + 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 ]; + } + + 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 ); +} + +static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ + /* + * 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; iallocated ) + continue; + + if( ch->activity == k_channel_activity_alive ){ + if( (ch->cursor >= ch->source_length) && + !(ch->flags & AUDIO_FLAG_LOOP) ) + { + ch->activity = k_channel_activity_end; + } + } + + /* 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; + } + } + + /* 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; + } + + 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; + } + + + 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; + } + + 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 */ + float *pOut32F = (float *)stream; + for( int i=0; itime_startframe = lfo->time; + } + + for( int i=0; iactivity == k_channel_activity_alive ){ + 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; + } + } + } + + if( use_dsp ){ + 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_dsp ) + vg_dsp_update_texture(); + + audio_unlock(); +} + +void audio_clip_load( audio_clip *clip, void *lin_alloc ) +{ + if( lin_alloc == NULL ) + lin_alloc = vg_audio.audio_pool; + + if( vg_audio.always_keep_compressed ) + { + if( (clip->flags & AUDIO_FLAG_FORMAT) != k_audio_format_bird ){ + clip->flags &= ~AUDIO_FLAG_FORMAT; + clip->flags |= k_audio_format_vorbis; + } + } + + /* 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 */ + + /* 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_fatal_error( "No path specified, embeded vorbis unsupported" ); + } + + audio_lock(); + clip->data = vg_file_read( lin_alloc, clip->path, &clip->size ); + audio_unlock(); + + if( !clip->data ) + vg_fatal_error( "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_error( "Unsupported format (Stereo uncompressed)" ); + } + else if( format == k_audio_format_bird ){ + if( !clip->data ){ + vg_fatal_error( "No data, external birdsynth unsupported" ); + } + + 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_fatal_error( "Bird coding too long\n" ); + + 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 ){ + vg_fatal_error( "No path specified, embeded mono unsupported" ); + } + + vg_linear_clear( vg_mem.scratch ); + u32 fsize; + + stb_vorbis_alloc alloc = { + .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( + filedata, fsize, &err, &alloc ); + + if( !decoder ){ + vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", + clip->path, err ); + vg_fatal_error( "Vorbis decode error" ); + } + + /* only mono is supported in uncompressed */ + u32 length_samples = stb_vorbis_stream_length_in_samples( decoder ), + data_size = length_samples * sizeof(i16); + + audio_lock(); + clip->data = vg_linear_alloc( lin_alloc, vg_align8(data_size) ); + clip->size = length_samples; + audio_unlock(); + + int read_samples = stb_vorbis_get_samples_i16_downmixed( + decoder, clip->data, length_samples ); + + if( read_samples != length_samples ) + vg_fatal_error( "Decode error" ); + +#if 0 + float mb = (float)(data_size) / (1024.0f*1024.0f); + vg_info( "Loaded audio clip '%s' (%.1fmb) %u samples\n", clip->path, mb, + length_samples ); +#endif + } +} + +void audio_clip_loadn( audio_clip *arr, int count, void *lin_alloc ) +{ + for( int i=0; idata && clip->size ) + return; + + audio_unlock(); + vg_fatal_error( "Must load audio clip before playing! \n" ); +} + +/* + * Debugging + */ + +void audio_debug_ui( + +#ifdef VG_3D + m4x4f +#else + m3x3f +#endif + mtx_pv ){ + + if( !vg_audio.debug_ui ) + return; + + audio_lock(); + + 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 ); + + /* + * 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, + &vg_prof_audio_dsp}, 3, + budget, (ui_rect){ 4, VG_PROFILE_SAMPLE_COUNT*2 + 8, + 512, 0 }, 0, 0 ); + + + char perf[128]; + + /* Draw UI */ + ui_rect window = { + 0, + 0, + 800, + AUDIO_CHANNELS * 18 + }; + + if( vg_audio.debug_dsp ){ + ui_rect view_thing = { 4, vg.window_y-512-4, 512, 512 }; + ui_image( view_thing, vg_dsp.view_texture ); + } + + ui_rect overlap_buffer[ AUDIO_CHANNELS ]; + u32 overlap_length = 0; + + /* Draw audio stack */ + for( int i=0; iallocated ){ + ui_fill( row, 0x50333333 ); + 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[%#04x.%#06x]%c%c%cD %s [%s] %4.2fv'%s'", + i, + ch->world_id, ch->group, + (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( row, 0xa0000000 | ch->colour ); + ui_text( row, perf, 1, k_ui_align_middle_left, 0 ); + +#ifdef VG_3D + 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 ); + + ui_rect wr; + wr[0] = vg_clampf(wpos[0] * vg.window_x, -32000.0f,32000.0f); + wr[1] = vg_clampf((1.0f-wpos[1]) * vg.window_y,-32000.0f,32000.0f); + wr[2] = 1000; + wr[3] = 17; + + for( int j=0; j<12; j++ ){ + int collide = 0; + for( int k=0; k= wk[0])) && + ((wr[1] <= wk[1]+wk[3]) && (wr[1]+wr[3] >= wk[1])) ) + { + collide = 1; + break; + } + } + + if( !collide ) + break; + else + wr[1] += 18; + } + + ui_text( wr, perf, 1, k_ui_align_middle_left, 0 ); + rect_copy( wr, overlap_buffer[ overlap_length ++ ] ); + } + } +#endif + } + + audio_unlock(); +}