V45.24: dao y/c - solo/mute CHI cuc bo MAIN/SECTION, PIANO ROLL TAB KHONG bi cam. REVERT V45.23 (4 gate computeTrackAudibleGain o preview) + lam PR doc lap: (1) scheduleMidiNoteDispatch them param prNote=true cho 6 nhanh PR (play, ghost, click, draw, brush, wheel) -> bo qua solo_guard + goi ensureTrackPrAudible (CC7=vol theo volumeDb bo qua mute/solo + setTrackNodeGain) tai thoi diem fire; (2) preview keybed vkbNoteOn + hardware attachMidiHandler (bridge + SonicSF fallback) khi _isPrTabNow goi ensureTrackPrAudible de channel khong con bi CC7=0 tu toggle MAIN; (3) applyAllTrackMuteSolo + sync useEffect bo qua gain=0/CC7=0/kill note khi track co sub-tab PR dang PLAY. Timeline MAIN giu solo_guard. v=202608311725
This commit is contained in:
+59
-28
@@ -785,6 +785,20 @@ function sendMidiVolumeCC7(t, list, audible) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// V45.24: PR tab audio DOC LAP voi mute/solo cua MAIN/SECTION — khoi phuc
|
||||
// volume track (theo volumeDb, bo qua mute/solo) truoc khi note PR phat.
|
||||
// sendMidiVolumeCC7(t, list, true) gui CC7=vol (channel khong con bi mute/solo
|
||||
// tu toggle MAIN); setTrackNodeGain phuc hoi gainNode (SonicSF fallback dest).
|
||||
function ensureTrackPrAudible(t, list, node) {
|
||||
if (!t) return;
|
||||
try {
|
||||
const volDb = Number(t.volumeDb);
|
||||
const volLin = (isNaN(volDb) || !isFinite(volDb)) ? 1.0 : (volDb <= -50 ? 0 : Math.pow(10, volDb / 20));
|
||||
if (node) setTrackNodeGain(node, volLin);
|
||||
sendMidiVolumeCC7(t, list, true);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ── CC11 expression (realtime) ──────────────────────────────────────────
|
||||
// Mỗi note có expression_curve → chuỗi CC11 theo grid 16ms. Timer đăng ký
|
||||
// vào _cc11Timers để stopAllPlayback hủy sạch; epoch guard (_nativePlayEpoch)
|
||||
@@ -9666,7 +9680,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
playNativeVstNote(pvTrk, n.pitch, n.velocity || 0.8, 200, undefined, 'pv_' + st.trackId);
|
||||
return;
|
||||
}
|
||||
scheduleMidiNoteDispatch(pvTrk, n.pitch, n.velocity || 0.8, 200, ctx.currentTime, pvCtx.program, null, pvCtx.ch, pvCtx.synthEngine);
|
||||
scheduleMidiNoteDispatch(pvTrk, n.pitch, n.velocity || 0.8, 200, ctx.currentTime, pvCtx.program, null, pvCtx.ch, pvCtx.synthEngine, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10181,7 +10195,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(clCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(clTrk, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, undefined, 'pv_' + st.trackId);
|
||||
} else {
|
||||
scheduleMidiNoteDispatch(clTrk, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, ctx.currentTime, clCtx.program, null, clCtx.ch, clCtx.synthEngine);
|
||||
scheduleMidiNoteDispatch(clTrk, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, ctx.currentTime, clCtx.program, null, clCtx.ch, clCtx.synthEngine, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10451,7 +10465,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
// playNote (FluidSynth — nhạc cụ THẬT của track). _playNoteFallback chỉ
|
||||
// là oscillator beep (sai âm với percussion/soundfont — user: note vẽ
|
||||
// mới nghe nhạc cụ track trước).
|
||||
scheduleMidiNoteDispatch(dwTrk, dwPitch, brushVelocityRef.current || 0.8, dwDurMs, ctx.currentTime, dwTrk ? dwTrk.instrumentProgram : undefined, null, dwCh, dwTrk ? dwTrk.synth_engine : undefined);
|
||||
scheduleMidiNoteDispatch(dwTrk, dwPitch, brushVelocityRef.current || 0.8, dwDurMs, ctx.currentTime, dwTrk ? dwTrk.instrumentProgram : undefined, null, dwCh, dwTrk ? dwTrk.synth_engine : undefined, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -10553,7 +10567,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
var pvCtx = getAudioContext();
|
||||
// playNote (FluidSynth — nhạc cụ THẬT). _playNoteFallback = oscillator
|
||||
// beep sai âm (percussion/soundfont).
|
||||
scheduleMidiNoteDispatch(pvTrk, p, brushVelocityRef.current || 0.8, durMs, pvCtx.currentTime, pvCtxInst.program, null, pvCtxInst.ch, pvCtxInst.synthEngine);
|
||||
scheduleMidiNoteDispatch(pvTrk, p, brushVelocityRef.current || 0.8, durMs, pvCtx.currentTime, pvCtxInst.program, null, pvCtxInst.ch, pvCtxInst.synthEngine, true);
|
||||
previewPitchRef.current = p;
|
||||
}
|
||||
}
|
||||
@@ -18659,10 +18673,9 @@ const App = () => {
|
||||
}
|
||||
bTracks.forEach(function (bt) {
|
||||
if (!bt.isArmed && !bPrArmed[bt.id]) return;
|
||||
// V45.23: mute/solo gate preview bridge NOTE_ON — track
|
||||
// muted/solo-out khong duoc phat (cung semantics
|
||||
// computeTrackAudibleGain nhu scheduleMidiNoteDispatch).
|
||||
if (computeTrackAudibleGain(bTracks, bt) <= 0) { try { if (window.__dbgLog) window.__dbgLog('solo_guard', 'block midi ch=' + bt.id + ' p=' + pitch); } catch (e) {} return; }
|
||||
// V45.24: PR preview doc lap voi mute/solo MAIN/SECTION —
|
||||
// khoi phuc volume track (bo qua mute/solo) khi o PR tab.
|
||||
if (_isPrTabNow) { try { ensureTrackPrAudible(bt, bTracks, activeTrackNodesRef.current[bt.id]); } catch (e) {} }
|
||||
if (window.triggerMidiVuActivity) {
|
||||
window.triggerMidiVuActivity(bt.id, scaledVel);
|
||||
}
|
||||
@@ -18679,8 +18692,8 @@ const App = () => {
|
||||
if (arSubs.length > 0) {
|
||||
arSubs.forEach(function(as) {
|
||||
var asTrk = allTracks.find(function(t) { return t.id === as.trackId; });
|
||||
// V45.23: mute/solo gate preview (SonicSF fallback).
|
||||
if (!asTrk || computeTrackAudibleGain(allTracks, asTrk) <= 0) return;
|
||||
// V45.24: PR preview doc lap voi mute/solo MAIN/SECTION.
|
||||
if (_isPrTabNow && asTrk) { try { ensureTrackPrAudible(asTrk, allTracks, activeTrackNodesRef.current[as.trackId]); } catch (e) {} }
|
||||
var asCh = asTrk ? assignTrackMidiChannel(asTrk, allTracks) : 0;
|
||||
var asProg = as.instrumentProgram;
|
||||
var asSe = as.synth_engine;
|
||||
@@ -18703,8 +18716,8 @@ const App = () => {
|
||||
}
|
||||
// Route to ALL armed tracks (not just the first one)
|
||||
armedTracks.forEach(function(at) {
|
||||
// V45.23: mute/solo gate preview (SonicSF fallback).
|
||||
if (computeTrackAudibleGain(allTracks, at) <= 0) return;
|
||||
// V45.24: PR preview doc lap voi mute/solo MAIN/SECTION.
|
||||
if (_isPrTabNow) { try { ensureTrackPrAudible(at, allTracks, activeTrackNodesRef.current[at.id]); } catch (e) {} }
|
||||
var atCh = assignTrackMidiChannel(at, allTracks);
|
||||
var atProg = at.instrumentProgram;
|
||||
var atSe = at.synth_engine;
|
||||
@@ -19029,7 +19042,15 @@ const App = () => {
|
||||
const wasAudible = trackAudibleRef.current[t.id] !== false;
|
||||
const node = activeTrackNodesRef.current[t.id];
|
||||
const audibleGain = computeTrackAudibleGain(effective, t);
|
||||
if (node) setTrackNodeGain(node, audibleGain);
|
||||
// V45.24: PR tab doc lap voi mute/solo MAIN/SECTION — track dang co
|
||||
// sub-tab PIANO_ROLL PLAY thi KHONG ap mute/solo realtime (gain=0,
|
||||
// CC7=0, kill note) de PR khong bi cam; mute/solo chi anh huong
|
||||
// MAIN/SECTION playback.
|
||||
const prPlayingForTrack = !!(subTabsRef.current || []).some(function (s) { return s.type === 'PIANO_ROLL' && s.trackId === t.id && s.isPlaying; });
|
||||
const _volDb = Number(t.volumeDb);
|
||||
const _volLin = (isNaN(_volDb) || !isFinite(_volDb)) ? 1.0 : (_volDb <= -50 ? 0 : Math.pow(10, _volDb / 20));
|
||||
const effGain = prPlayingForTrack ? _volLin : audibleGain;
|
||||
if (node) setTrackNodeGain(node, effGain);
|
||||
// Section sub-nodes (key: <parent>_sub_<trackId>) — mute/solo của
|
||||
// SECTION-TAB phải áp gain cho node play section — CHỈ trong section
|
||||
// (activeTracksRef = tracks section), KHÔNG đụng track MAIN.
|
||||
@@ -19040,19 +19061,19 @@ const App = () => {
|
||||
}
|
||||
});
|
||||
if (node && node.sfEntry) {
|
||||
try { node.sfEntry.gain.setTargetAtTime(audibleGain, getAudioContext().currentTime, 0.02); } catch (e) {}
|
||||
try { node.sfEntry.gain.setTargetAtTime(effGain, getAudioContext().currentTime, 0.02); } 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) {}
|
||||
try { sendMidiVolumeCC7(t, effective, audible || prPlayingForTrack); } catch (e) {}
|
||||
}
|
||||
// D18: track vừa MẤT tiếng (mute/solo) — dừng note đang giữ ngay. CC7=0
|
||||
// (sendMidiVolumeCC7) không ăn với VST như Qin (bỏ qua channel volume)
|
||||
// → note kêu tiếp (tremolo) khi solo track khác. NOTE_OFF từng pitch đang
|
||||
// giữ + CC123/CC120 all-notes-off trên channel track đó.
|
||||
if (!audible && wasAudible && isMidiSoundTrack(t)) {
|
||||
if (!audible && wasAudible && !prPlayingForTrack && isMidiSoundTrack(t)) {
|
||||
try {
|
||||
const _isPerc = !!(t.synth_engine && t.synth_engine.soundfont_bank === 128) || !!t.is_percussion;
|
||||
try { if (window.__dbgLog) window.__dbgLog('solo_kill', 'track=' + t.id + ' tset=' + (((timelinePitchesRef.current || {})[t.id] || new Set()).size) + ' held=' + ((activeMidiPitchesRef.current || new Set()).size)); } catch (e) {}
|
||||
@@ -19076,7 +19097,7 @@ const App = () => {
|
||||
} catch (e) {}
|
||||
}
|
||||
if (audible && !wasAudible) becameAudible = true;
|
||||
trackAudibleRef.current[t.id] = audible;
|
||||
trackAudibleRef.current[t.id] = prPlayingForTrack ? true : audible;
|
||||
});
|
||||
// A track that just became audible may have had its audio sources dropped
|
||||
// by a loop restart while it was inaudible (startTrackPlayback skips
|
||||
@@ -19833,10 +19854,16 @@ const App = () => {
|
||||
const audible = computeTrackAudibleGain(list, t) > 0;
|
||||
const node = activeTrackNodesRef.current[t.id];
|
||||
const audibleGain = computeTrackAudibleGain(list, t);
|
||||
if (node) setTrackNodeGain(node, audibleGain);
|
||||
// V45.24: PR tab doc lap — track co sub-tab PR PLAY thi khong ap
|
||||
// mute/solo realtime (gain=0/CC7=0) trong sync effect.
|
||||
const prPlayingForTrack = !!(subTabsRef.current || []).some(function (s) { return s.type === 'PIANO_ROLL' && s.trackId === t.id && s.isPlaying; });
|
||||
const _volDb = Number(t.volumeDb);
|
||||
const _volLin = (isNaN(_volDb) || !isFinite(_volDb)) ? 1.0 : (_volDb <= -50 ? 0 : Math.pow(10, _volDb / 20));
|
||||
const effGain = prPlayingForTrack ? _volLin : audibleGain;
|
||||
if (node) setTrackNodeGain(node, effGain);
|
||||
if (node && node.sfEntry) {
|
||||
try {
|
||||
const g = (typeof audibleGain === 'number' && isFinite(audibleGain) && !isNaN(audibleGain)) ? audibleGain : 1.0;
|
||||
const g = (typeof effGain === 'number' && isFinite(effGain) && !isNaN(effGain)) ? effGain : 1.0;
|
||||
node.sfEntry.gain.setTargetAtTime(g, getAudioContext().currentTime, 0.02);
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -19844,9 +19871,9 @@ const App = () => {
|
||||
// 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) {}
|
||||
try { sendMidiVolumeCC7(t, list, audible || prPlayingForTrack); } catch (e) {}
|
||||
}
|
||||
trackAudibleRef.current[t.id] = audible;
|
||||
trackAudibleRef.current[t.id] = prPlayingForTrack ? true : audible;
|
||||
});
|
||||
updateSfRouting();
|
||||
}, [tracks, sessionTabs]);
|
||||
@@ -23925,9 +23952,9 @@ const App = () => {
|
||||
setActiveMidiPitches(new Set(activeMidiPitchesRef.current));
|
||||
vkbHeldRef.current.add(pitch);
|
||||
targets.forEach(function (t) {
|
||||
// V45.23: mute/solo gate preview keybed — track muted/solo-out khong phat
|
||||
// (dung semantics computeTrackAudibleGain nhu scheduleMidiNoteDispatch).
|
||||
if (computeTrackAudibleGain(allTracks, t) <= 0) return;
|
||||
// V45.24: PR preview doc lap voi mute/solo MAIN/SECTION — khoi phuc
|
||||
// volume track khi o PR tab.
|
||||
if (_isPrTabNow) { try { ensureTrackPrAudible(t, allTracks, activeTrackNodesRef.current[t.id]); } catch (e) {} }
|
||||
const ctx = resolveTrackInstrumentCtx(t, allTracks);
|
||||
ensureSonicInstrument(ctx);
|
||||
if (window.triggerMidiVuActivity) window.triggerMidiVuActivity(t.id, Math.round(vel * 127));
|
||||
@@ -25554,7 +25581,7 @@ const App = () => {
|
||||
// SonicSF (FluidSynth WASM). Giữ setTimeout scheduling (như SonicSF cũ);
|
||||
// bridge tự render note-off sau duration. A11 (sample-accurate) sẽ thay
|
||||
// setTimeout bằng sampleOffset đẩy thẳng vào SHM.
|
||||
const scheduleMidiNoteDispatch = (track, pitch, velocity, durMs, startTime, program, destNode, ch, synthEngine) => {
|
||||
const scheduleMidiNoteDispatch = (track, pitch, velocity, durMs, startTime, program, destNode, ch, synthEngine, prNote) => {
|
||||
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
|
||||
const ctx = getAudioContext();
|
||||
const startAt = startTime || ctx.currentTime; // undefined/null = phát ngay
|
||||
@@ -25576,9 +25603,13 @@ const App = () => {
|
||||
if (!guardPlay()) return;
|
||||
// D18b: mute/solo guard tai thoi diem fire — note-on dang cho cua track
|
||||
// vua tro nen inaudible (solo track khac / mute) khong duoc phat ra.
|
||||
// V45.24: note PR tab (prNote=true) BO QUA guard + khoi phuc volume —
|
||||
// mute/solo chi cuc bo MAIN/SECTION, PR khong bao gio bi cam.
|
||||
const _list = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : (tracksRef.current && tracksRef.current.length ? tracksRef.current : activeTracks);
|
||||
const _trk = _list.find(t => t.id === trkId) || track;
|
||||
if (!_trk || computeTrackAudibleGain(_list, _trk) <= 0) { try { if (window.__dbgLog) window.__dbgLog('solo_guard', 'block ch=' + trkId + ' p=' + (pitch || 60)); } catch (e) {} return; }
|
||||
if (prNote) {
|
||||
try { ensureTrackPrAudible(_trk, _list, destNode); } catch (e) {}
|
||||
} else if (!_trk || computeTrackAudibleGain(_list, _trk) <= 0) { try { if (window.__dbgLog) window.__dbgLog('solo_guard', 'block ch=' + trkId + ' p=' + (pitch || 60)); } catch (e) {} return; }
|
||||
// D18c: track pitch dang keu (timeline) - de applyAllTrackMuteSolo kill
|
||||
// dung note khi track thanh inaudible giua chung (Qin VST2 tremolo).
|
||||
try { var _tps = timelinePitchesRef.current; (_tps[trkId] || (_tps[trkId] = new Set())).add(pitch || 60); } catch (e) {}
|
||||
@@ -26190,7 +26221,7 @@ const App = () => {
|
||||
const scheduledTime = startWallTime + effectiveStart;
|
||||
const durMs = effectiveDur * 1000;
|
||||
if (window.SonicSF) {
|
||||
scheduleMidiNoteDispatch(track, note.pitch || 60, note.velocity || 0.8, durMs, scheduledTime, instrumentProgram, destNode, mainCh, synthEngine);
|
||||
scheduleMidiNoteDispatch(track, note.pitch || 60, note.velocity || 0.8, durMs, scheduledTime, instrumentProgram, destNode, mainCh, synthEngine, true);
|
||||
}
|
||||
scheduleNoteExpressionCC11(track, note, mainCh, startWallTime, effectiveStart, effectiveDur);
|
||||
}
|
||||
@@ -26215,7 +26246,7 @@ const App = () => {
|
||||
var schedTime = startWallTime + effStart;
|
||||
var durMs = effDur * 1000;
|
||||
if (window.SonicSF) {
|
||||
scheduleMidiNoteDispatch(ghostTrack, note.pitch || 60, note.velocity || 0.8, durMs, schedTime, ghostProg, ghostDest, ghostCh, ghostSynth);
|
||||
scheduleMidiNoteDispatch(ghostTrack, note.pitch || 60, note.velocity || 0.8, durMs, schedTime, ghostProg, ghostDest, ghostCh, ghostSynth, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user