fix: MIDI keyboard preview silent after transport STOP — live flag bypass guard (V8)

This commit is contained in:
2026-08-16 15:43:23 +07:00
parent 2d63774474
commit b6950d6418
11 changed files with 1450 additions and 1448 deletions
+8 -8
View File
@@ -9457,7 +9457,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
}
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(kbTrk ? kbTrk.id : 0, kbCtx.program, kbCtx.synthEngine); } catch (er) {}
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128) });
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
} else if (window.SonicSF && !kbNative) {
// ⚠️ FIX: giữ note theo thời gian bấm phím — durationMs lớn (5s)
// chỉ là auto-off phòng hờ; mouseup/mouseleave gọi stopNote dừng
@@ -9485,7 +9485,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
}
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(kbTrk ? kbTrk.id : 0, kbCtx.program, kbCtx.synthEngine); } catch (er) {}
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128) });
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
} else if (window.SonicSF && !(isStandaloneSf() && isSfTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine))) {
// giữ note khi kéo qua phím (mouse enter) — dừng bằng mouseup/leave
window.SonicSF.playNote(pitch, 100, 5000, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
@@ -9504,7 +9504,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
try {
if (isStandaloneSf()) stopNativeSfNote('kb_' + st.trackId + '_' + pitch);
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 0, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128) });
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 0, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
} else if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(kbCtx.ch, pitch);
} catch (e) {}
if (window.__carlaKeybedTimer) { clearTimeout(window.__carlaKeybedTimer); window.__carlaKeybedTimer = null; }
@@ -9516,7 +9516,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
try {
if (isStandaloneSf()) stopNativeSfNote('kb_' + st.trackId + '_' + pitch);
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 0, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128) });
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 0, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
} else if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(kbCtx.ch, pitch);
} catch (e) {}
if (window.SonicCarlaMidi) { try { window.SonicCarlaMidi.noteOff(kbCtx.ch, pitch); } catch (e) {} }
@@ -15828,7 +15828,7 @@ const App = () => {
try {
heldMidiNotesRef.current[bt.id] = (heldMidiNotesRef.current[bt.id] || 0) + 1;
} catch (err) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: bt.id, pitch: pitch, velocity: scaledVel / 127, percussion: !!(bt.synth_engine && bt.synth_engine.soundfont_bank === 128) }); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: bt.id, pitch: pitch, velocity: scaledVel / 127, percussion: !!(bt.synth_engine && bt.synth_engine.soundfont_bank === 128), live: true }); } catch (e) {}
});
} else if (window.SonicSF) {
var allTracks = activeTracksRef.current || [];
@@ -15911,7 +15911,7 @@ const App = () => {
}
}
} catch (err) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: bst.id, pitch: pitch, velocity: 0, percussion: !!(bst.synth_engine && bst.synth_engine.soundfont_bank === 128) }); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: bst.id, pitch: pitch, velocity: 0, percussion: !!(bst.synth_engine && bst.synth_engine.soundfont_bank === 128), live: true }); } catch (e) {}
});
} else if (window.SonicSF && window.SonicSF.stopNote) {
var stopTracks = activeTracksRef.current || [];
@@ -21853,9 +21853,9 @@ const App = () => {
const isPerc = !!(track && track.synth_engine && track.synth_engine.soundfont_bank === 128);
const tch = track ? track.id : 0;
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(tch, program, track && track.synth_engine); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: tch, pitch: pitch || 60, velocity: velocity, percussion: isPerc }); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: tch, pitch: pitch || 60, velocity: velocity, percussion: isPerc, live: true }); } catch (e) {}
setTimeout(function () {
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: tch, pitch: pitch || 60, velocity: 0, percussion: isPerc }); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: tch, pitch: pitch || 60, velocity: 0, percussion: isPerc, live: true }); } catch (e) {}
}, (durationMs || 500) + 30);
return;
}
File diff suppressed because one or more lines are too long
@@ -110,7 +110,7 @@
* velocity 0..1; sampleOffset in samples within the current audio block.
* data2/data3 (A12): CC value / program / PB LSB|MSB.
*/
dispatchMidiEvent: function (cmd, channel, pitch, velocity, sampleOffset, data2, data3) {
dispatchMidiEvent: function (cmd, channel, pitch, velocity, sampleOffset, data2, data3, live) {
if (!this._tauri()) return false;
var safeVelocity = Math.floor(Math.min(1.0, Math.max(0.0, velocity)) * 127);
var byteCmd;
@@ -127,7 +127,8 @@
velocity: safeVelocity,
data2: data2 === undefined ? 0 : data2,
data3: data3 === undefined ? 0 : data3,
sampleOffset: sampleOffset || 0
sampleOffset: sampleOffset || 0,
live: !!live
}).catch(function (e) { console.warn('[BridgeService] push_midi_event:', e); });
return true;
},
+1 -1
View File
@@ -54,7 +54,7 @@
if (typeof ch === 'string') ch = this.allocateChannel(ch, opts.percussion);
if (ch === undefined || ch === null) ch = 0;
if (this.isBridgeActive()) {
window.NativeBridgeService.dispatchMidiEvent(cmd, ch, pitch, vel, sampleOffset, opts.data2, opts.data3);
window.NativeBridgeService.dispatchMidiEvent(cmd, ch, pitch, vel, sampleOffset, opts.data2, opts.data3, opts.live);
return;
}
if (this.onFallback) this.onFallback(cmd, ch, pitch, vel);
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -32,7 +32,7 @@ struct SharedAudioBufferIPC {
uint8_t velocity; // 0-127 (0x9 + vel=0 == note off)
uint8_t data2; // CC value / program number / PB LSB
uint8_t data3; // PB MSB (0xE only)
uint8_t reserved[2]; // explicit padding — keeps C/Rust layout stable
uint8_t reserved[2]; // reserved[0]: live flag (1 = live input, bypass transportStopped guard); reserved[1]: 0 — keeps C/Rust layout stable
uint32_t sampleOffset;
} midiQueue[64];
+1413 -1412
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -667,11 +667,12 @@ fn push_midi_event(
data2: u8,
data3: u8,
sample_offset: u32,
live: bool,
) -> Result<(), String> {
let state = app.state::<ShmState>();
let guard = state.0.lock().map_err(|e| e.to_string())?;
let shm = guard.as_ref().ok_or("bridge shm unavailable")?;
shm.push_midi(command, channel, pitch, velocity, data2, data3, sample_offset)
shm.push_midi(command, channel, pitch, velocity, data2, data3, sample_offset, live as u8)
.then_some(())
.ok_or_else(|| "midi queue full".to_string())
}
+3 -3
View File
@@ -34,7 +34,7 @@ pub struct MidiEventIPC {
pub velocity: u8,
pub data2: u8, // CC value / program / PB LSB
pub data3: u8, // PB MSB (0xE only)
pub reserved: [u8; 2],
pub reserved: [u8; 2], // reserved[0]: live flag (1=live input, bypass transportStopped guard); reserved[1]: 0
pub sample_offset: u32,
}
@@ -112,7 +112,7 @@ impl Shm {
unsafe { &mut *self.view }
}
pub fn push_midi(&self, cmd: u8, channel: u8, pitch: u8, velocity: u8, data2: u8, data3: u8, sample_offset: u32) -> bool {
pub fn push_midi(&self, cmd: u8, channel: u8, pitch: u8, velocity: u8, data2: u8, data3: u8, sample_offset: u32, live: u8) -> bool {
let ipc = self.ipc();
if ipc.midi_queue_count as usize >= MIDI_QUEUE_CAP {
return false;
@@ -125,7 +125,7 @@ impl Shm {
velocity,
data2,
data3,
reserved: [0u8; 2],
reserved: [live, 0u8],
sample_offset,
};
ipc.midi_queue_count += 1;