Skip to content

Commit 7ec822c

Browse files
committed
audio improvements
1 parent 36dfaa3 commit 7ec822c

4 files changed

Lines changed: 45 additions & 9 deletions

File tree

scami/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@
270270
<!-- Touch Controls Overlay -->
271271
<div
272272
id="touch-controls"
273-
class="pointer-events-none fixed inset-x-0 bottom-0 z-40 px-4 pb-1 landscape:px-12 opacity-0 transition-opacity duration-200 touch-none [&.visible]:opacity-100 [@media(hover:hover)_and_(pointer:fine)]:hidden"
273+
class="pointer-events-none fixed inset-x-0 bottom-0 z-40 px-4 pb-1 sm:pb-4 landscape:px-12 opacity-0 transition-opacity duration-200 touch-none [&.visible]:opacity-100 [@media(hover:hover)_and_(pointer:fine)]:hidden"
274274
>
275275
<div
276276
class="flex w-full flex-wrap items-end justify-between landscape:flex-nowrap"

scami/src/app.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const MAX_CATCHUP_TICKS: u64 = MASTER_CLOCK as u64 / 30; // ≈ 2 frames at 60 H
3333

3434
pub(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

scami/src/platform/wasm.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,14 @@ pub(crate) fn poll_runtime_state(app: &mut App) -> bool {
118118
}
119119

120120
if PAUSED.with(|f| f.get()) {
121+
if let Some(audio) = &app.audio {
122+
audio.player.pause();
123+
}
121124
return false;
125+
} else {
126+
if let Some(audio) = &app.audio {
127+
audio.player.play();
128+
}
122129
}
123130

124131
if let Some(rom_bytes) = PENDING_ROM.with(|p| p.borrow_mut().take()) {

scamu/src/hardware/apu/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ impl Apu {
7272

7373
pub fn connect_cpu(&mut self, _cpu: Rc<RefCell<Cpu>>) {}
7474

75+
pub fn queue_len(&self) -> usize {
76+
self.sample_queue.len()
77+
}
78+
7579
pub fn read_register(&mut self, address: u16, peek: bool) -> u8 {
7680
if address != 0x4015 {
7781
return 0xFF;

0 commit comments

Comments
 (0)