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:
+69
-11
@@ -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 của
|
||||||
|
// SECTION-TAB phải á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 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') {
|
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 dụng 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 của
|
||||||
|
// SECTION-TAB phải đổi âm section ngay lập tức
|
||||||
|
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
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user