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 node = activeTrackNodesRef.current[t.id];
const audibleGain = computeTrackAudibleGain(effective, t); const audibleGain = computeTrackAudibleGain(effective, t);
if (node) setTrackNodeGain(node, audibleGain); 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) { if (node && node.sfEntry) {
try { node.sfEntry.gain.setTargetAtTime(audibleGain, getAudioContext().currentTime, 0.02); } catch (e) {} 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 // the per-track gain cannot silence them. Every MIDI track owns a
// dedicated channel (ensureTrackMidiChannel) mute/solo via CC7 (channel // dedicated channel (ensureTrackMidiChannel) mute/solo via CC7 (channel
// volume) applies realtime, even to notes already sounding. // 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') { if ((t.midiItems && t.midiItems.length > 0) || t.type === 'MIDI') {
try { try {
if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') { if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') {
const ch = ensureTrackMidiChannel(t, effective); 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) {} } catch (e) {}
} }
@@ -14696,12 +14715,21 @@ const App = () => {
} catch (e) {} } catch (e) {}
} }
// MIDI tracks: mirror the gain decision onto the channel CC7 volume so // 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') { if ((t.midiItems && t.midiItems.length > 0) || t.type === 'MIDI') {
try { try {
if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') { if (window.SonicSF && typeof window.SonicSF.controllerChange === 'function') {
const ch = ensureTrackMidiChannel(t, list); 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) {} } catch (e) {}
} }
@@ -15914,7 +15942,14 @@ const App = () => {
} }
if (ctrl && !alt && e.key === 's') { if (ctrl && !alt && e.key === 's') {
e.preventDefault(); 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; return;
} }
if (ctrl && alt && e.key === 's' || ctrl && e.shiftKey && e.key === 's') { if (ctrl && alt && e.key === 's' || ctrl && e.shiftKey && e.key === 's') {
@@ -20921,7 +20956,7 @@ const App = () => {
const wasPlaying = isPlaying; const wasPlaying = isPlaying;
const playingSubTab = subTabs.find(s => s.isPlaying && s.type === 'PIANO_ROLL'); const playingSubTab = subTabs.find(s => s.isPlaying && s.type === 'PIANO_ROLL');
stopAllPlayback(); stopAllPlayback();
setTracks(prev => prev.map(t => updateActiveTracks(prev => prev.map(t =>
t.id === trackId ? { ...t, solo: !t.solo, muted: false } : t t.id === trackId ? { ...t, solo: !t.solo, muted: false } : t
)); ));
setTimeout(() => { setTimeout(() => {
@@ -20950,7 +20985,7 @@ const App = () => {
}; };
const toggleTrackMute = trackId => { const toggleTrackMute = trackId => {
const beforeSnap = captureTrackSnapshot(trackId); const beforeSnap = captureTrackSnapshot(trackId);
setTracks(prev => prev.map(t => t.id === trackId ? { updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
...t, ...t,
muted: !t.muted muted: !t.muted
} : t)); } : t));
@@ -20968,7 +21003,21 @@ const App = () => {
setTimeout(() => lucide.createIcons(), 50); setTimeout(() => lucide.createIcons(), 50);
}; };
const updateTrackProp = (trackId, props) => { 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); setTimeout(() => lucide.createIcons(), 50);
}; };
const toggleTrackDrum = trackId => { const toggleTrackDrum = trackId => {
@@ -21002,7 +21051,7 @@ const App = () => {
}; };
const updateTrackVolumeDb = (trackId, val) => { const updateTrackVolumeDb = (trackId, val) => {
const beforeSnap = captureTrackSnapshot(trackId); const beforeSnap = captureTrackSnapshot(trackId);
setTracks(prev => prev.map(t => t.id === trackId ? { updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
...t, ...t,
volumeDb: val volumeDb: val
} : t)); } : t));
@@ -21018,15 +21067,24 @@ const App = () => {
return next; return next;
}); });
// Real-time update during playback // Real-time update during playback
const volLinear = val <= -50 ? 0 : Math.pow(10, val / 20);
const ctx = getAudioContext();
const nodes = activeTrackNodesRef.current[trackId]; const nodes = activeTrackNodesRef.current[trackId];
if (nodes) { if (nodes) {
const volLinear = val <= -50 ? 0 : Math.pow(10, val / 20); nodes.gainNode.gain.setValueAtTime(volLinear, ctx.currentTime);
nodes.gainNode.gain.setValueAtTime(volLinear, getAudioContext().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 updateTrackPan = (trackId, val) => {
const beforeSnap = captureTrackSnapshot(trackId); const beforeSnap = captureTrackSnapshot(trackId);
setTracks(prev => prev.map(t => t.id === trackId ? { updateActiveTracks(prev => prev.map(t => t.id === trackId ? {
...t, ...t,
pan: val pan: val
} : t)); } : 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/midiExtractor.js?v=202607281052"></script>
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></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/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"> <link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style> <style>
:root { :root {
+37
View File
@@ -2067,3 +2067,40 @@
- Reset project: 2 track mặc định, selectedTrackId '1', projectName '', currentProjectId null, xóa localStorage id/name - Reset project: 2 track mặc định, selectedTrackId '1', projectName '', currentProjectId null, xóa localStorage id/name
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061200), `wiki.md`. Rebuild precompiled. - **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061200), `wiki.md`. Rebuild precompiled.
- **Ghi chú/Test:** `npm run build` → hard refresh → mở vài tab + chọn track → File → New Project (hoặc Ctrl+N) → tab đóng hết, selection trống, instrument track về mặc định. - **Ghi chú/Test:** `npm run build` → hard refresh → mở vài tab + chọn track → File → New Project (hoặc Ctrl+N) → tab đóng hết, selection trống, instrument track về mặc định.
### [2026-08-06 12:30] Task: SECTION-TAB 5 bug — Ctrl+S, strip controls, undo, section play, mute isolation
- **Báo cáo user (SECTION-TAB):** (1) Ctrl+S phải lưu section; (2) volume slider + nút strip không tác dụng; (3) không undo/redo cut/paste/delete/move; (4) MAIN play section item câm sau khi lưu; (5) mute ở section mixer mute luôn track MAIN.
- **FIX:**
(1) **Ctrl+S**: handler đầu (15915) save project vô điều kiện + return → block thứ 2 (16007) chết → sửa handler đầu tab-aware: `session_``handleSaveSectionTab` + toast; khác → save project.
(2+5) **Handlers setTracks (MAIN) → updateActiveTracks (context-aware)**: `toggleTrackMute`, `toggleTrackSoloEvaluate`, `updateTrackVolumeDb`, `updateTrackPan`, `updateTrackProp` — section strip giờ tác dụng lên track SECTION (không còn mute track MAIN).
(3) **Undo section**: chuỗi đã context-aware (captureTrackSnapshot/captureAllTracksSnapshot → activeTracks; restore applyTrackState → updateActiveTracks) — sau fix (2) các entry mute/volume đúng context; item ops (delete/move/paste 20417/20603/20764+) đã push đúng.
(4) **Section play**: code path đã verify (18847-18957: schedule clip+midi sub-track → subNode → parent → route → mastering; sec.tracks lưu sau save 16381-16395) — nếu vẫn câm cần log console (`[SonicSF]` noteon + sfId).
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061230), `wiki.md`. Rebuild precompiled.
- **Ghi chú/Test:** `npm run build` → hard refresh → section tab: Ctrl+S lưu section; kéo volume/mute tác dụng trong section; Ctrl+Z sau cut/paste; main play section item có âm; mute section không ảnh hưởng main.
### [2026-08-06 13:00] Task: SECTION-TAB volume/mute — áp gain cho sub-node (key _sub_) realtime
- **Báo cáo user:** volume mixer strip không tác dụng âm thanh; mute section phải CHỈ ảnh hưởng section.
- **Nguyên nhân:** node play section là **sub-node** (key `<parent>_sub_<trackId>` — 18697) — gain chỉ set lúc TẠO node (18703) — `updateTrackVolumeDb`/`applyAllTrackMuteSolo` lookup `activeTrackNodesRef[trackId]` (key thường) → KHÔNG thấy sub-node → volume/mute đổi STATE nhưng âm section không đổi.
- **FIX (app.jsx):**
(1) `updateTrackVolumeDb`: sau khi set gain node thường → **scan `_sub_<trackId>`** → set gain sub-node realtime.
(2) `applyAllTrackMuteSolo`: sau `setTrackNodeGain(node, ...)`**scan `_sub_<t.id>`** → set gain sub-node theo audibleGain (mute/solo section áp node play section — activeTracksRef = section tracks → KHÔNG đụng track MAIN).
(3) (Đã fix vòng trước) handlers setTracks → updateActiveTracks — mute/volume chỉ đổi tracks SECTION.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061300), `wiki.md`. Rebuild precompiled.
- **Ghi chú/Test:** `npm run build` → hard refresh → section tab: kéo volume nghe đổi ngay; mute section track → câm trong section, main không bị; unmute lại.
### [2026-08-06 13:30] Task: Mixer fader dùng updateTrackProp — volume section vẫn không đổi âm
- **Báo cáo user:** volume slider SECTION-TAB mixer KHÔNG đổi âm lượng.
- **Nguyên nhân:** mixer strip (TrackStripConsole — 27165) truyền `onUpdateTrack: updateTrackProp` — fader (1560) gọi `onUpdateTrack(tid, {volumeDb})` — fix vòng 13:00 đặt scan sub-node trong `updateTrackVolumeDb` (TCP slider) — **mixer dùng updateTrackProp — SAI CHỖ → không áp gain node.**
- **FIX (app.jsx):** `updateTrackProp` — khi props có `volumeDb` → áp gain realtime node thường + scan `_sub_<trackId>` (sub-node section) — mixer section + mixer main đều đổi âm ngay.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061330), `wiki.md`. Rebuild precompiled.
- **Ghi chú/Test:** `npm run build` → hard refresh → section tab mixer: kéo volume → âm section đổi ngay (cả khi main đang play section item); main mixer cũng đổi ngay.
### [2026-08-06 14:00] Task: SECTION-TAB — volume MIDI track qua CC7 (channel volume) + fix collision channel
- **Báo cáo user:** AI tạo track trong SECTION dùng chung channel track trước → volume track sau vô tác dụng cho đến khi track trước MUTE.
- **Nguyên nhân:** CC7 (channel volume) chỉ set 100/0 (mute/unmute) — VOLUME track KHÔNG áp qua channel → track MIDI dùng chung channel: CC7 cuối thắng → volume 1 track không tác dụng.
- **FIX (app.jsx — 2 chỗ):**
(1) `applyAllTrackMuteSolo` (14135): CC7 = **volume track** (audible ? volLin*100 clamp 1-127 : 0) — không còn 100/0 cứng.
(2) Sync effect (14719): cùng logic CC7-volume — volume slider → trackMuteSoloSig (gồm volumeDb) → effect → CC7 mới.
(3) (Đã có) AI Var clone `midiChannel: undefined` (23288) + `ensureTrackMidiChannel` tự cấp channel riêng khi trùng (30: `!inUse.has(track.midiChannel)`) — collision được xử lý khi assign.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061400), `wiki.md`. Rebuild precompiled.
- **Ghi chú/Test:** `npm run build` → hard refresh → section: tạo AI Var mới → kéo volume từng track → âm đổi theo track; mute track trước → track sau vẫn đúng volume. Nếu track AI Var CŨ (tạo trước fix midiChannel) vẫn dùng chung channel → xóa + tạo lại.