fix(ui): per-track volume/mute/solo now works over bridge — send CC7 via SonicMidiRouter pushEvent instead of local WASM controllerChange
This commit is contained in:
+56
-39
@@ -442,6 +442,37 @@ function setTrackNodeGain(node, gainLinear) {
|
||||
node.gainNode.gain.setTargetAtTime(g, t, 0.02);
|
||||
}
|
||||
|
||||
// Track thực sự phát âm MIDI/bridge (cần CC7 để đổi volume/mute/solo): có MIDI
|
||||
// items, type MIDI, hoặc instrument (VST3/soundfont qua bridge).
|
||||
function isMidiSoundTrack(t) {
|
||||
return !!(t && ((t.midiItems && t.midiItems.length > 0) || t.type === 'MIDI' || t.instrumentId ||
|
||||
(t.synth_engine && (t.synth_engine.soundfont_id || t.synth_engine.plugin_path))));
|
||||
}
|
||||
|
||||
// MIDI volume realtime: CC7 là đường DUY NHẤT đổi âm bridge — bridge trộn MỌI
|
||||
// channel vào MỘT shared audio sink (bridgeAudioNode), per-track WebAudio
|
||||
// gainNode KHÔNG ảnh hưởng âm bridge. Bridge active → gửi qua
|
||||
// SonicMidiRouter.pushEvent (đúng channel allocation của note-on, trackId làm
|
||||
// channel); bridge down → SonicSF (WASM local) fallback.
|
||||
function sendMidiVolumeCC7(t, list, audible) {
|
||||
const volDb = (typeof t.volumeDb === 'number' && isFinite(t.volumeDb)) ? t.volumeDb : 0;
|
||||
const volLin = volDb <= -50 ? 0 : Math.pow(10, volDb / 20);
|
||||
const cc7 = audible ? Math.max(1, Math.min(127, Math.round(volLin * 100))) : 0;
|
||||
try {
|
||||
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive && window.SonicMidiRouter.isBridgeActive()) {
|
||||
const isPerc = !!(t.synth_engine && t.synth_engine.soundfont_bank === 128) || !!t.is_percussion;
|
||||
window.SonicMidiRouter.pushEvent({ cmd: 'CC', channel: t.id, pitch: 7, velocity: 0, data2: cc7, percussion: isPerc });
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') {
|
||||
const ch = ensureTrackMidiChannel(t, list);
|
||||
window.SonicSF.controllerChange(ch, 7, cc7);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// MAIN SESSION end-time (seconds): endtime of the items ON the session's own
|
||||
// tracks only — audio clips, MIDI items, and section-item bounds. Section-TAB
|
||||
// content is deliberately IGNORED here: when played inside the main session the
|
||||
@@ -16095,27 +16126,11 @@ const App = () => {
|
||||
if (node && node.sfEntry) {
|
||||
try { node.sfEntry.gain.setTargetAtTime(audibleGain, getAudioContext().currentTime, 0.02); } catch (e) {}
|
||||
}
|
||||
// MIDI items: FluidSynth mixes ALL channels into ONE shared gain node, so
|
||||
// the per-track gain cannot silence them. Every MIDI track owns a
|
||||
// dedicated channel (ensureTrackMidiChannel) → mute/solo via CC7 (channel
|
||||
// volume) applies realtime, even to notes already sounding.
|
||||
// CC7 = VOLUME của track (không chỉ 100/0) — volume slider MIDI track tác
|
||||
// dụng qua channel volume (yêu cầu channel RIÊNG cho từng track).
|
||||
if ((t.midiItems && t.midiItems.length > 0) || t.type === 'MIDI') {
|
||||
try {
|
||||
if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') {
|
||||
const ch = ensureTrackMidiChannel(t, effective);
|
||||
var cc7 = 100;
|
||||
if (audible) {
|
||||
var volDb = (typeof t.volumeDb === 'number' && isFinite(t.volumeDb)) ? t.volumeDb : 0;
|
||||
var volLin = volDb <= -50 ? 0 : Math.pow(10, volDb / 20);
|
||||
cc7 = Math.max(1, Math.min(127, Math.round(volLin * 100)));
|
||||
} else {
|
||||
cc7 = 0;
|
||||
}
|
||||
window.SonicSF.controllerChange(ch, 7, cc7);
|
||||
}
|
||||
} catch (e) {}
|
||||
// MIDI/bridge tracks: CC7 (channel volume) realtime — even for notes
|
||||
// already sounding. Bridge active → qua SonicMidiRouter (bridge); bridge
|
||||
// down → SonicSF (WASM). CC7 = VOLUME của track (không chỉ 100/0).
|
||||
if (isMidiSoundTrack(t)) {
|
||||
try { sendMidiVolumeCC7(t, effective, audible); } catch (e) {}
|
||||
}
|
||||
if (audible && !wasAudible) becameAudible = true;
|
||||
trackAudibleRef.current[t.id] = audible;
|
||||
@@ -16791,24 +16806,11 @@ const App = () => {
|
||||
node.sfEntry.gain.setTargetAtTime(g, getAudioContext().currentTime, 0.02);
|
||||
} catch (e) {}
|
||||
}
|
||||
// MIDI tracks: mirror the gain decision onto the channel CC7 volume so
|
||||
// FluidSynth-rendered notes respect mute/solo too. CC7 = VOLUME track
|
||||
// (không chỉ 100/0) — volume slider MIDI tác dụng qua channel volume.
|
||||
if ((t.midiItems && t.midiItems.length > 0) || t.type === 'MIDI') {
|
||||
try {
|
||||
if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') {
|
||||
const ch = ensureTrackMidiChannel(t, list);
|
||||
var cc7 = 100;
|
||||
if (audible) {
|
||||
var volDb = (typeof t.volumeDb === 'number' && isFinite(t.volumeDb)) ? t.volumeDb : 0;
|
||||
var volLin = volDb <= -50 ? 0 : Math.pow(10, volDb / 20);
|
||||
cc7 = Math.max(1, Math.min(127, Math.round(volLin * 100)));
|
||||
} else {
|
||||
cc7 = 0;
|
||||
}
|
||||
window.SonicSF.controllerChange(ch, 7, cc7);
|
||||
}
|
||||
} catch (e) {}
|
||||
// MIDI/bridge tracks: mirror the gain decision onto CC7 (channel volume)
|
||||
// so FluidSynth/bridge-rendered notes respect mute/solo too. Bridge
|
||||
// active → SonicMidiRouter; down → SonicSF. CC7 = VOLUME track.
|
||||
if (isMidiSoundTrack(t)) {
|
||||
try { sendMidiVolumeCC7(t, list, audible); } catch (e) {}
|
||||
}
|
||||
trackAudibleRef.current[t.id] = audible;
|
||||
});
|
||||
@@ -24392,6 +24394,17 @@ const App = () => {
|
||||
if (sn && sn.gainNode) sn.gainNode.gain.setValueAtTime(volLinear, ctx.currentTime);
|
||||
}
|
||||
});
|
||||
// MIDI/bridge track: gainNode WebAudio không đổi âm bridge (bridge dùng MỘT
|
||||
// shared sink) → gửi CC7 realtime qua SonicMidiRouter (bridge active) hoặc
|
||||
// SonicSF (WASM fallback).
|
||||
const trkList = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : tracks;
|
||||
const trk = trkList.find(x => x.id === trackId);
|
||||
if (trk && isMidiSoundTrack(trk)) {
|
||||
try {
|
||||
const nt = { ...trk, volumeDb: val };
|
||||
sendMidiVolumeCC7(nt, trkList, computeTrackAudibleGain(trkList, nt) > 0);
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
const updateTrackPan = (trackId, val) => {
|
||||
const beforeSnap = captureTrackSnapshot(trackId);
|
||||
@@ -29697,6 +29710,7 @@ STRICT CONSTRAINTS:
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
toggleTrackMute(track.id);
|
||||
if (window.__applyTrackMuteSolo) window.__applyTrackMuteSolo(track.id, { muted: !track.muted });
|
||||
},
|
||||
title: "Mute",
|
||||
className: `px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.muted ? 'bg-red-950 text-red-400 border-red-700' : 'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`
|
||||
@@ -29704,6 +29718,7 @@ STRICT CONSTRAINTS:
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
toggleTrackSoloEvaluate(track.id);
|
||||
if (window.__applyTrackMuteSolo) window.__applyTrackMuteSolo(track.id, { solo: !track.solo });
|
||||
},
|
||||
title: "Solo",
|
||||
className: `px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.solo ? 'bg-amber-950 text-amber-400 border-amber-600' : 'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`
|
||||
@@ -30270,12 +30285,14 @@ STRICT CONSTRAINTS:
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
toggleTrackMute(vTrack.id);
|
||||
if (window.__applyTrackMuteSolo) window.__applyTrackMuteSolo(vTrack.id, { muted: !vTrack.muted });
|
||||
},
|
||||
className: `px-1.5 py-0.5 text-xs rounded font-mono font-bold border ${vTrack.muted ? 'bg-red-950 text-red-400 border-red-700' : 'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`
|
||||
}, "M"), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
toggleTrackSoloEvaluate(vTrack.id);
|
||||
if (window.__applyTrackMuteSolo) window.__applyTrackMuteSolo(vTrack.id, { solo: !vTrack.solo });
|
||||
},
|
||||
className: `px-1.5 py-0.5 text-xs rounded font-mono font-bold border ${vTrack.solo ? 'bg-amber-950 text-amber-400 border-amber-600' : 'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`
|
||||
}, "S"))), /*#__PURE__*/React.createElement("div", {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user