FIX: lỗi: CC7 (channel volume) chỉ được set 100/0 (mute/unmute) — volume track KHÔNG áp qua channel → track MIDI dùng chung channel: CC7 của track set cuối thắng → volume của track kia vô tác dụng

This commit is contained in:
2026-08-06 14:10:57 +07:00
parent 4dfab954d8
commit f1e914fa62
4 changed files with 129 additions and 20 deletions
+69 -11
View File
@@ -14119,6 +14119,15 @@ const App = () => {
const node = activeTrackNodesRef.current[t.id];
const audibleGain = computeTrackAudibleGain(effective, t);
if (node) setTrackNodeGain(node, audibleGain);
// Section sub-nodes (key: <parent>_sub_<trackId>) mute/solo ca
// SECTION-TAB phi áp gain cho node play section CH trong section
// (activeTracksRef = tracks section), KHÔNG đng track MAIN.
Object.keys(activeTrackNodesRef.current).forEach(function(k) {
if (k.endsWith('_sub_' + t.id)) {
const sn = activeTrackNodesRef.current[k];
if (sn && sn.gainNode) setTrackNodeGain(sn, audibleGain);
}
});
if (node && node.sfEntry) {
try { node.sfEntry.gain.setTargetAtTime(audibleGain, getAudioContext().currentTime, 0.02); } catch (e) {}
}
@@ -14126,11 +14135,21 @@ const App = () => {
// 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 ca track (không ch 100/0) volume slider MIDI track tác
// dng qua channel volume (yêu cu channel RIÊNG cho tng 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);
window.SonicSF.controllerChange(ch, 7, audible ? 100 : 0);
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) {}
}
@@ -14696,12 +14715,21 @@ const App = () => {
} catch (e) {}
}
// MIDI tracks: mirror the gain decision onto the channel CC7 volume so
// FluidSynth-rendered notes respect mute/solo too.
// FluidSynth-rendered notes respect mute/solo too. CC7 = VOLUME track
// (không ch 100/0) volume slider MIDI tác dng 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);
window.SonicSF.controllerChange(ch, 7, audible ? 100 : 0);
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) {}
}
@@ -15914,7 +15942,14 @@ const App = () => {
}
if (ctrl && !alt && e.key === 's') {
e.preventDefault();
handleSaveProjectRef.current();
const curTab = activeTabRef.current;
if (curTab && curTab.startsWith('session_')) {
// SECTION-TAB: Ctrl+S = Lưu section (như nút Lưu section)
handleSaveSectionTabRef.current(curTab);
showToast('Đã lưu Section', 'success');
} else {
handleSaveProjectRef.current();
}
return;
}
if (ctrl && alt && e.key === 's' || ctrl && e.shiftKey && e.key === 's') {
@@ -20921,7 +20956,7 @@ const App = () => {
const wasPlaying = isPlaying;
const playingSubTab = subTabs.find(s => s.isPlaying && s.type === 'PIANO_ROLL');
stopAllPlayback();
setTracks(prev => prev.map(t =>
updateActiveTracks(prev => prev.map(t =>
t.id === trackId ? { ...t, solo: !t.solo, muted: false } : t
));
setTimeout(() => {
@@ -20950,7 +20985,7 @@ const App = () => {
};
const toggleTrackMute = trackId => {
const beforeSnap = captureTrackSnapshot(trackId);
setTracks(prev => prev.map(t => t.id === trackId ? {
updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
...t,
muted: !t.muted
} : t));
@@ -20968,7 +21003,21 @@ const App = () => {
setTimeout(() => lucide.createIcons(), 50);
};
const updateTrackProp = (trackId, props) => {
setTracks(prev => prev.map(t => t.id === trackId ? { ...t, ...props } : t));
updateActiveTracks(prev => prev.map(t => t.id === trackId ? { ...t, ...props } : t));
// Mixer fader dùng updateTrackProp({volumeDb}) áp gain realtime cho node
// thưng + sub-node SECTION (key <parent>_sub_<trackId>)
if (props && props.volumeDb !== undefined) {
const volLinear = props.volumeDb <= -50 ? 0 : Math.pow(10, props.volumeDb / 20);
const ctx = getAudioContext();
const nodes = activeTrackNodesRef.current[trackId];
if (nodes) nodes.gainNode.gain.setValueAtTime(volLinear, ctx.currentTime);
Object.keys(activeTrackNodesRef.current).forEach(function(k) {
if (k.endsWith('_sub_' + trackId)) {
const sn = activeTrackNodesRef.current[k];
if (sn && sn.gainNode) sn.gainNode.gain.setValueAtTime(volLinear, ctx.currentTime);
}
});
}
setTimeout(() => lucide.createIcons(), 50);
};
const toggleTrackDrum = trackId => {
@@ -21002,7 +21051,7 @@ const App = () => {
};
const updateTrackVolumeDb = (trackId, val) => {
const beforeSnap = captureTrackSnapshot(trackId);
setTracks(prev => prev.map(t => t.id === trackId ? {
updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
...t,
volumeDb: val
} : t));
@@ -21018,15 +21067,24 @@ const App = () => {
return next;
});
// Real-time update during playback
const volLinear = val <= -50 ? 0 : Math.pow(10, val / 20);
const ctx = getAudioContext();
const nodes = activeTrackNodesRef.current[trackId];
if (nodes) {
const volLinear = val <= -50 ? 0 : Math.pow(10, val / 20);
nodes.gainNode.gain.setValueAtTime(volLinear, getAudioContext().currentTime);
nodes.gainNode.gain.setValueAtTime(volLinear, ctx.currentTime);
}
// Section sub-nodes (key: <parent>_sub_<trackId>) volume slider ca
// SECTION-TAB phi đi âm section ngay lp tc
Object.keys(activeTrackNodesRef.current).forEach(function(k) {
if (k.endsWith('_sub_' + trackId)) {
const sn = activeTrackNodesRef.current[k];
if (sn && sn.gainNode) sn.gainNode.gain.setValueAtTime(volLinear, ctx.currentTime);
}
});
};
const updateTrackPan = (trackId, val) => {
const beforeSnap = captureTrackSnapshot(trackId);
setTracks(prev => prev.map(t => t.id === trackId ? {
updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
...t,
pan: val
} : t));
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -24,7 +24,7 @@
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/app.precompiled.js?v=202608061200" defer></script>
<script src="/static/js/app.precompiled.js?v=202608061400" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {