+ for( u32 j=0; j<AUDIO_MIX_FRAME_SIZE; j++ ){
+ volume_movement += 1.0f;
+ float movement_t = volume_movement * inv_volume_rate;
+ movement_t = vg_minf( movement_t, 1.0f );
+ volume = vg_lerpf( volume_start, volume_target, movement_t );
+
+ float vol_norm = volume * volume;
+
+ if( ch->_.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; i<AUDIO_CHANNELS; i++ ){
+ audio_channel *ch = &vg_audio.channels[i];
+
+ if( !ch->allocated )
+ 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) )