fix(vsti): crackle fix P1-P3 — SHM 8-slot ring + drain-delta pump + AudioWorklet sink
- SharedMemoryIPC.h/main.cpp: render into ring slot (idx & ringMask), 8 slots - shm.rs: ring fields + read_ring_audio; lib.rs pump drains FULL delta, not just latest - bridgeAudioNode.js: AudioWorklet primary (bridge-audio-sink), ScriptProcessor fallback - bridgeAudioWorklet.js: ring 64 blocks, underrun fade, overrun drop + crossfade - layout tests: sizeof 27560 (8-align via u64)
This commit is contained in:
+22
-11
@@ -451,23 +451,34 @@ pub fn run() {
|
||||
restart_attempts = 0;
|
||||
}
|
||||
// Audio emission = writeIndex advancing (audio loop alive).
|
||||
let audio_advanced = guard.as_ref().map(|shm| {
|
||||
let idx = shm.write_index();
|
||||
// VSTi crackle fix: drain the FULL delta from the multi-slot
|
||||
// ring (8 slots) — a delayed pump no longer drops whole
|
||||
// blocks that a single-slot SHM would have overwritten.
|
||||
let mut audio_advanced = None;
|
||||
if let Some(s) = guard.as_ref() {
|
||||
let idx = s.write_index();
|
||||
if idx != last_index {
|
||||
let delta = idx.wrapping_sub(last_index);
|
||||
// Only the last 8 blocks are still intact in the ring;
|
||||
// if the pump stalled longer, older blocks are lost.
|
||||
let start = if delta as usize > 8 { idx - 8 } else { last_index };
|
||||
for k in start..idx {
|
||||
let (l, r) = s.read_ring_audio(k);
|
||||
let _ = pump_handle.emit(
|
||||
"bridge-audio",
|
||||
AudioFrame { l: l.to_vec(), r: r.to_vec() },
|
||||
);
|
||||
}
|
||||
if delta as usize > 8 {
|
||||
eprintln!("[tauri] audio pump lost {} blocks (stalled > ring)", delta - 8);
|
||||
}
|
||||
last_index = idx;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
audio_advanced = Some(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if audio_advanced == Some(true) {
|
||||
last_audio_change = std::time::Instant::now();
|
||||
stall_logged = false;
|
||||
let (l, r) = guard.as_ref().map(|s| s.read_audio()).unwrap_or(([0f32; shm::AUDIO_BLOCK_SIZE], [0f32; shm::AUDIO_BLOCK_SIZE]));
|
||||
let _ = pump_handle.emit(
|
||||
"bridge-audio",
|
||||
AudioFrame { l: l.to_vec(), r: r.to_vec() },
|
||||
);
|
||||
}
|
||||
// G3.1 FIX: nhả guard TRƯỚC stall log / watchdog kill+spawn.
|
||||
// kill_bridge -> flush_bridge_state khóa chính ShmState mutex
|
||||
|
||||
+22
-1
@@ -67,6 +67,13 @@ pub struct SharedAudioBufferIPC {
|
||||
// independent of the audio loop. Watchdog liveness keys on this.
|
||||
// Append-only field: offsets above are unchanged.
|
||||
pub heartbeat: u32,
|
||||
// VSTi crackle fix: multi-slot audio ring (appended after heartbeat —
|
||||
// offsets above are unchanged). Bridge renders block N into slot
|
||||
// (bridge_write_index & ring_mask), then increments bridge_write_index.
|
||||
// Pump drains EVERY slot between its last index and write_index, in order.
|
||||
pub ring_mask: u32,
|
||||
pub ring_left: [[f32; AUDIO_BLOCK_SIZE]; 8],
|
||||
pub ring_right: [[f32; AUDIO_BLOCK_SIZE]; 8],
|
||||
}
|
||||
|
||||
pub struct Shm {
|
||||
@@ -159,6 +166,17 @@ impl Shm {
|
||||
(ipc.master_left, ipc.master_right)
|
||||
}
|
||||
|
||||
/// Read ring slot for block index `idx` (slot = idx & ring_mask).
|
||||
pub fn read_ring_audio(&self, idx: u32) -> ([f32; AUDIO_BLOCK_SIZE], [f32; AUDIO_BLOCK_SIZE]) {
|
||||
let ipc = self.ipc();
|
||||
let slot = (idx & ipc.ring_mask) as usize;
|
||||
(ipc.ring_left[slot], ipc.ring_right[slot])
|
||||
}
|
||||
|
||||
pub fn ring_mask(&self) -> u32 {
|
||||
self.ipc().ring_mask
|
||||
}
|
||||
|
||||
pub fn write_index(&self) -> u32 {
|
||||
self.ipc().bridge_write_index
|
||||
}
|
||||
@@ -196,6 +214,9 @@ mod tests {
|
||||
// native_bridge/tests/shm_selfcheck.cpp asserts the same sizes.
|
||||
assert_eq!(std::mem::size_of::<MidiEventIPC>(), 12);
|
||||
assert_eq!(std::mem::size_of::<ControlEventIPC>(), 1040);
|
||||
assert_eq!(std::mem::size_of::<SharedAudioBufferIPC>(), 11168);
|
||||
// 11168 + ring_mask(4) + ring_left(8*256*4) + ring_right(8*256*4)
|
||||
// = 27556, rounded up to 8-byte struct alignment (u64 block_timestamp)
|
||||
// => 27560. Matches MSVC sizeof (verified against the C header).
|
||||
assert_eq!(std::mem::size_of::<SharedAudioBufferIPC>(), 27560);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user