@@ -33,7 +33,7 @@ const MAX_CATCHUP_TICKS: u64 = MASTER_CLOCK as u64 / 30; // ≈ 2 frames at 60 H
3333
3434pub ( crate ) struct AudioState {
3535 _handle : rodio:: MixerDeviceSink ,
36- _player : rodio:: Player ,
36+ pub ( crate ) player : rodio:: Player ,
3737}
3838
3939#[ derive( Default , Clone ) ]
@@ -46,11 +46,18 @@ impl Iterator for ApuSource {
4646 type Item = f32 ;
4747
4848 fn next ( & mut self ) -> Option < Self :: Item > {
49- let val = self
50- . apu
51- . as_ref ( )
52- . and_then ( |a| a. lock ( ) . unwrap ( ) . next ( ) )
53- . unwrap_or ( self . last_val ) ;
49+ let val = if let Some ( apu) = & self . apu {
50+ let mut apu_lock = apu. lock ( ) . unwrap ( ) ;
51+ match apu_lock. next ( ) {
52+ Some ( v) => v,
53+ None => {
54+ // Decay the signal smoothly to 0.0 to prevent popping when underflowed
55+ self . last_val * 0.95
56+ }
57+ }
58+ } else {
59+ self . last_val * 0.95
60+ } ;
5461 self . last_val = val;
5562 Some ( val)
5663 }
@@ -231,7 +238,7 @@ impl App {
231238 player. append ( self . apu_source . clone ( ) ) ;
232239 self . audio = Some ( AudioState {
233240 _handle : handle,
234- _player : player,
241+ player,
235242 } ) ;
236243 }
237244
@@ -252,7 +259,25 @@ impl App {
252259 let elapsed_nanos = WebInstant :: now ( )
253260 . saturating_duration_since ( self . emulation_anchor )
254261 . as_nanos ( ) ;
255- let target_ticks = ( elapsed_nanos * MASTER_CLOCK as u128 / NANOS_PER_SECOND ) as u64 ;
262+
263+ // Default target ticks
264+ let mut target_ticks = ( elapsed_nanos * MASTER_CLOCK as u128 / NANOS_PER_SECOND ) as u64 ;
265+
266+ // Dynamic Resampling:
267+ // Adjust the target ticks slightly based on how full the audio queue is.
268+ // We want to keep the queue around 1024 - 2048 samples.
269+ let q_len = self . nes . borrow ( ) . apu . lock ( ) . unwrap ( ) . queue_len ( ) ;
270+ if q_len < 1024 {
271+ // Buffer running low! Emulate slightly faster to catch up audio
272+ // (add up to 10% more ticks)
273+ target_ticks += ( target_ticks - self . completed_ticks ) / 10 ;
274+ } else if q_len > 3000 {
275+ // Buffer is filling up! Emulate slightly slower
276+ // (run 10% fewer ticks)
277+ let mut diff = target_ticks. saturating_sub ( self . completed_ticks ) ;
278+ diff -= diff / 10 ;
279+ target_ticks = self . completed_ticks + diff;
280+ }
256281
257282 // Drop a large scheduling gap completely. Merely clamping this call to
258283 // `completed_ticks + MAX_CATCHUP_TICKS` leaves the rest of the debt in
0 commit comments