v16: persist cc11_global_curve qua save/load (item + sub_tabs) + commitCC11Curve dong bo subTab st.notes (CC11 realtime khi Play)

This commit is contained in:
2026-08-26 16:58:44 +07:00
parent ad3dfa5c47
commit 0e555114f8
3 changed files with 28 additions and 14 deletions
+16 -4
View File
@@ -9056,6 +9056,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
target_id: match.id,
label: 'Piano Roll: ' + (match.name || 'MIDI'),
notes: match.notes || [],
cc11_global_curve: match.cc11_global_curve || null,
duration: match.duration || 4,
instrumentProgram: scope ? scope.instrument_program : (trk ? trk.instrumentProgram : undefined),
instrumentName: scope ? scope.instrument_name : (trk ? trk.instrumentName : undefined),
@@ -9635,7 +9636,10 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
// CC11 = GLOBAL curve tu note dau den note cuoi (khong per-note). Khi commit
// sinh lai per-note expression_curve (engine doc per-note) bang cach sample
// global tai vung tung note.
const [cc11GlobalCurve, setCc11GlobalCurve] = React.useState([]);
const [cc11GlobalCurve, setCc11GlobalCurve] = React.useState(() => Array.isArray(st.cc11_global_curve) && st.cc11_global_curve.length ? st.cc11_global_curve.slice() : []);
React.useEffect(() => {
setCc11GlobalCurve(Array.isArray(st.cc11_global_curve) && st.cc11_global_curve.length ? st.cc11_global_curve.slice() : []);
}, [st.cc11_global_curve]);
React.useEffect(() => {
if (cc11GlobalCurve && cc11GlobalCurve.length) return;
const n = notes.find(x => Array.isArray(x.expression_curve) && x.expression_curve.length);
@@ -10598,6 +10602,9 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
return { ...n, expression_curve: pts.sort((a, b) => a.position_ratio - b.position_ratio) };
});
setNotes(updatedNotes);
// Đồng bộ subTab NGAY (không chờ effect): schedulePianoRollMidi đọc
// st.notes khi Play (realtime) + cc11_global_curve để persist save/load.
if (setSubTabs) setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, notes: updatedNotes, cc11_global_curve: curve, isDirty: true } : s));
if (isPlaying && onRescheduleMidi) onRescheduleMidi(updatedNotes);
};
const handleCC11MouseDown = (e) => {
@@ -11894,6 +11901,7 @@ const serializeTracksList = (tracksList, secondsPerBar) => {
clip_start_offset_bars: 0.0,
source_data: {
total_buffer_bars: m.duration ? (m.duration / secondsPerBar) : 8.0,
...(Array.isArray(m.cc11_global_curve) && m.cc11_global_curve.length ? { cc11_global_curve: m.cc11_global_curve.map(p => ({ position_ratio: p.position_ratio, value: p.value })) } : {}),
notes: (m.notes || []).map(n => ({
id: n.id || 'note_' + Math.random().toString(36).substr(2, 9),
pitch: n.pitch || 60,
@@ -12014,7 +12022,8 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore, visite
velocity: n.velocity || 0.8,
pan: n.pan || 0.0,
...(Array.isArray(n.expression_curve) ? { expression_curve: n.expression_curve } : {})
}))
})),
...(Array.isArray(src.cc11_global_curve) && src.cc11_global_curve.length ? { cc11_global_curve: src.cc11_global_curve } : {})
});
} else if (item.type === "SECTION_ITEM") {
const src = item.source_data || {};
@@ -12156,6 +12165,7 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi
target_id: st.target_id,
parent_tab_id: st.parent_tab_id,
notes: st.notes || [],
...(Array.isArray(st.cc11_global_curve) && st.cc11_global_curve.length ? { cc11_global_curve: st.cc11_global_curve.map(p => ({ position_ratio: p.position_ratio, value: p.value })) } : {}),
duration: st.duration || 4,
instrument_program: st.instrumentProgram,
instrument_name: st.instrumentName,
@@ -12260,6 +12270,7 @@ const deserializeProjectFromSchema = (schemaObj) => {
target_id: st.target_id,
parent_tab_id: st.parent_tab_id,
notes: st.notes || [],
cc11_global_curve: st.cc11_global_curve || null,
duration: st.duration || 4,
instrumentProgram: st.instrument_program,
instrumentName: st.instrument_name,
@@ -21657,6 +21668,7 @@ const App = () => {
target_id: midiItemId,
parent_tab_id: activeTab === 'main' ? null : activeTab,
notes: midiItem.notes || [],
cc11_global_curve: midiItem.cc11_global_curve || null,
duration: midiItem.duration || 4,
buffer: silentBuffer,
instrumentProgram: track.instrumentProgram !== undefined ? track.instrumentProgram : (track.synth_engine ? track.synth_engine.soundfont_program : undefined),
@@ -21702,7 +21714,7 @@ const App = () => {
...t,
midiItems: (t.midiItems || []).map(m => {
if (m.id !== midiItemId) return m;
return { ...m, notes: updatedNotes };
return { ...m, notes: updatedNotes, cc11_global_curve: pianoRollTab ? (pianoRollTab.cc11_global_curve || null) : null };
})
};
})
@@ -21715,7 +21727,7 @@ const App = () => {
...t,
midiItems: (t.midiItems || []).map(m => {
if (m.id !== midiItemId) return m;
return { ...m, notes: updatedNotes };
return { ...m, notes: updatedNotes, cc11_global_curve: pianoRollTab ? (pianoRollTab.cc11_global_curve || null) : null };
})
};
}));
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -51,7 +51,7 @@
<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/chordTheory.js?v=202608211300"></script>
<script src="/static/js/app.precompiled.js?v=202608291900" defer></script>
<script src="/static/js/app.precompiled.js?v=202608292100" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {