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:
+30
-12
@@ -6165,6 +6165,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
||||
name: item.name,
|
||||
startTime: item.start_bar * secondsPerBar,
|
||||
duration: item.duration_bars * secondsPerBar,
|
||||
length_bars: item.duration_bars || 4,
|
||||
notes: (src.notes || []).map(n => ({
|
||||
id: n.id,
|
||||
pitch: n.pitch || 60,
|
||||
@@ -6183,6 +6184,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
||||
name: item.name,
|
||||
start: item.start_bar * secondsPerBar,
|
||||
duration: item.duration_bars * secondsPerBar,
|
||||
length_bars: item.duration_bars || 4,
|
||||
sectionId: secId,
|
||||
tracks: secContainer ? deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore) : null
|
||||
});
|
||||
@@ -6414,6 +6416,16 @@ const App = () => {
|
||||
const [bpm, setBpm] = useState(localStorage.getItem('studio_bpm') || '120');
|
||||
const [draggedClip, setDraggedClip] = useState(null); // { trackId, clickOffset, buffer, name, volume, color }
|
||||
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 => {
|
||||
if (id === 'export') setShowExportPanel(true); else if (id === 'ai') setShowAIPanel(true); else if (id === 'python_tools') setShowPythonToolsPanel(true); else if (id === 'selection') setShowSelectionPanel(true);
|
||||
};
|
||||
@@ -10589,31 +10601,33 @@ const App = () => {
|
||||
};
|
||||
return { ...t, midiItems: updatedItems };
|
||||
}
|
||||
const newMidiItem = {
|
||||
const newMidiItem = {
|
||||
id: 'midi_rec_' + Date.now(),
|
||||
name: 'Recorded MIDI',
|
||||
startTime: recordingStartTimeRef.current,
|
||||
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
||||
length_bars: Math.ceil(totalDurationBeats / 4),
|
||||
notes: recordedNotes
|
||||
};
|
||||
return {
|
||||
};
|
||||
return {
|
||||
...t,
|
||||
midiItems: [...(t.midiItems || []), newMidiItem]
|
||||
};
|
||||
};
|
||||
}));
|
||||
|
||||
if (recordedNotes.length > 0) {
|
||||
hasRecordedAnything = true;
|
||||
hasRecordedAnything = true;
|
||||
}
|
||||
} else if (recordedNotes.length > 0) {
|
||||
} else if (recordedNotes.length > 0) {
|
||||
hasRecordedAnything = true;
|
||||
const totalDurationBeats = Math.max(4.0, ...recordedNotes.map(n => n.start_beat + n.duration_beats));
|
||||
const newMidiItem = {
|
||||
id: 'midi_rec_' + Date.now(),
|
||||
name: 'Recorded MIDI',
|
||||
startTime: recordingStartTimeRef.current,
|
||||
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
||||
notes: recordedNotes
|
||||
id: 'midi_rec_' + Date.now(),
|
||||
name: 'Recorded MIDI',
|
||||
startTime: recordingStartTimeRef.current,
|
||||
duration: Math.ceil(totalDurationBeats / 4) * secondsPerBar,
|
||||
length_bars: Math.ceil(totalDurationBeats / 4),
|
||||
notes: recordedNotes
|
||||
};
|
||||
updateActiveTracks(prev => prev.map(t => {
|
||||
if (t.id !== trackId) return t;
|
||||
@@ -11860,11 +11874,13 @@ const App = () => {
|
||||
const curTracks = activeTracks;
|
||||
const track = curTracks.find(t => t.id === selectedTrackId);
|
||||
if (!track) { showToast('Chọn track trước', 'warning'); return; }
|
||||
const secondsPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
|
||||
const section = {
|
||||
id: `sec_${Date.now()}`,
|
||||
name: 'Section',
|
||||
start: currentTime,
|
||||
duration: 4,
|
||||
duration: 4 * secondsPerBar,
|
||||
length_bars: 4,
|
||||
color: track.color || '#06b6d4'
|
||||
};
|
||||
updateActiveTracks(prev => prev.map(t => t.id === selectedTrackId ? {
|
||||
@@ -11885,6 +11901,7 @@ const App = () => {
|
||||
name: 'MIDI Item',
|
||||
startTime: currentTime,
|
||||
duration: 4 * secondsPerBar,
|
||||
length_bars: 4,
|
||||
notes: [],
|
||||
color: '#a78bfa'
|
||||
};
|
||||
@@ -13861,6 +13878,7 @@ const App = () => {
|
||||
name: `${composition_title || 'AI Theme'} - ${aiTrack.track_name}`,
|
||||
startTime: itemStartTimeSec,
|
||||
duration: durationSec,
|
||||
length_bars: total_bars,
|
||||
notes: aiTrack.notes.map((note, index) => ({
|
||||
id: `note_ai_${Date.now()}_${index}`,
|
||||
pitch: note.pitch,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user