fix: maintain item duration in bars when BPM changes
- Add length_bars field to midiItems and sections at creation - useEffect on bpm recalculates duration = length_bars * secondsPerBar - Fix section creation: use secondsPerBar instead of hardcoded 4s - Handle existing items without length_bars (no-op)
This commit is contained in:
+19
-1
@@ -6165,6 +6165,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
startTime: item.start_bar * secondsPerBar,
|
startTime: item.start_bar * secondsPerBar,
|
||||||
duration: item.duration_bars * secondsPerBar,
|
duration: item.duration_bars * secondsPerBar,
|
||||||
|
length_bars: item.duration_bars || 4,
|
||||||
notes: (src.notes || []).map(n => ({
|
notes: (src.notes || []).map(n => ({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
pitch: n.pitch || 60,
|
pitch: n.pitch || 60,
|
||||||
@@ -6183,6 +6184,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
start: item.start_bar * secondsPerBar,
|
start: item.start_bar * secondsPerBar,
|
||||||
duration: item.duration_bars * secondsPerBar,
|
duration: item.duration_bars * secondsPerBar,
|
||||||
|
length_bars: item.duration_bars || 4,
|
||||||
sectionId: secId,
|
sectionId: secId,
|
||||||
tracks: secContainer ? deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore) : null
|
tracks: secContainer ? deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore) : null
|
||||||
});
|
});
|
||||||
@@ -6414,6 +6416,16 @@ const App = () => {
|
|||||||
const [bpm, setBpm] = useState(localStorage.getItem('studio_bpm') || '120');
|
const [bpm, setBpm] = useState(localStorage.getItem('studio_bpm') || '120');
|
||||||
const [draggedClip, setDraggedClip] = useState(null); // { trackId, clickOffset, buffer, name, volume, color }
|
const [draggedClip, setDraggedClip] = useState(null); // { trackId, clickOffset, buffer, name, volume, color }
|
||||||
const [hoveredTrackId, setHoveredTrackId] = useState(null);
|
const [hoveredTrackId, setHoveredTrackId] = useState(null);
|
||||||
|
// Recalculate item/section durations when BPM changes (maintain bar count)
|
||||||
|
useEffect(() => {
|
||||||
|
const bpmVal = parseFloat(bpm) || 120;
|
||||||
|
const secondsPerBar = (60.0 / bpmVal) * 4;
|
||||||
|
updateActiveTracks(prev => prev.map(t => ({
|
||||||
|
...t,
|
||||||
|
midiItems: (t.midiItems || []).map(m => m.length_bars ? { ...m, duration: m.length_bars * secondsPerBar } : m),
|
||||||
|
sections: (t.sections || []).map(s => s.length_bars ? { ...s, duration: s.length_bars * secondsPerBar } : s)
|
||||||
|
})));
|
||||||
|
}, [bpm]);
|
||||||
const openPanel = id => {
|
const openPanel = id => {
|
||||||
if (id === 'export') setShowExportPanel(true); else if (id === 'ai') setShowAIPanel(true); else if (id === 'python_tools') setShowPythonToolsPanel(true); else if (id === 'selection') setShowSelectionPanel(true);
|
if (id === 'export') setShowExportPanel(true); else if (id === 'ai') setShowAIPanel(true); else if (id === 'python_tools') setShowPythonToolsPanel(true); else if (id === 'selection') setShowSelectionPanel(true);
|
||||||
};
|
};
|
||||||
@@ -10594,6 +10606,7 @@ const App = () => {
|
|||||||
name: 'Recorded MIDI',
|
name: 'Recorded MIDI',
|
||||||
startTime: recordingStartTimeRef.current,
|
startTime: recordingStartTimeRef.current,
|
||||||
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
||||||
|
length_bars: Math.ceil(totalDurationBeats / 4),
|
||||||
notes: recordedNotes
|
notes: recordedNotes
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@@ -10613,6 +10626,7 @@ const App = () => {
|
|||||||
name: 'Recorded MIDI',
|
name: 'Recorded MIDI',
|
||||||
startTime: recordingStartTimeRef.current,
|
startTime: recordingStartTimeRef.current,
|
||||||
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
||||||
|
length_bars: Math.ceil(totalDurationBeats / 4),
|
||||||
notes: recordedNotes
|
notes: recordedNotes
|
||||||
};
|
};
|
||||||
updateActiveTracks(prev => prev.map(t => {
|
updateActiveTracks(prev => prev.map(t => {
|
||||||
@@ -11860,11 +11874,13 @@ const App = () => {
|
|||||||
const curTracks = activeTracks;
|
const curTracks = activeTracks;
|
||||||
const track = curTracks.find(t => t.id === selectedTrackId);
|
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||||
|
const secondsPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
|
||||||
const section = {
|
const section = {
|
||||||
id: `sec_${Date.now()}`,
|
id: `sec_${Date.now()}`,
|
||||||
name: 'Section',
|
name: 'Section',
|
||||||
start: currentTime,
|
start: currentTime,
|
||||||
duration: 4,
|
duration: 4 * secondsPerBar,
|
||||||
|
length_bars: 4,
|
||||||
color: track.color || '#06b6d4'
|
color: track.color || '#06b6d4'
|
||||||
};
|
};
|
||||||
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||||
@@ -11885,6 +11901,7 @@ const App = () => {
|
|||||||
name: 'MIDI Item',
|
name: 'MIDI Item',
|
||||||
startTime: currentTime,
|
startTime: currentTime,
|
||||||
duration: 4 * secondsPerBar,
|
duration: 4 * secondsPerBar,
|
||||||
|
length_bars: 4,
|
||||||
notes: [],
|
notes: [],
|
||||||
color: '#a78bfa'
|
color: '#a78bfa'
|
||||||
};
|
};
|
||||||
@@ -13861,6 +13878,7 @@ const App = () => {
|
|||||||
name: `${composition_title || 'AI Theme'} - ${aiTrack.track_name}`,
|
name: `${composition_title || 'AI Theme'} - ${aiTrack.track_name}`,
|
||||||
startTime: itemStartTimeSec,
|
startTime: itemStartTimeSec,
|
||||||
duration: durationSec,
|
duration: durationSec,
|
||||||
|
length_bars: total_bars,
|
||||||
notes: aiTrack.notes.map((note, index) => ({
|
notes: aiTrack.notes.map((note, index) => ({
|
||||||
id: `note_ai_${Date.now()}_${index}`,
|
id: `note_ai_${Date.now()}_${index}`,
|
||||||
pitch: note.pitch,
|
pitch: note.pitch,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user