fix: multitrack play select plays immediately in realtime

This commit is contained in:
2026-07-31 09:21:39 +07:00
parent 42d64f93d8
commit 4f5aee6830
2 changed files with 52 additions and 10 deletions
+48 -6
View File
@@ -5880,7 +5880,7 @@ const AIPresetModal = ({ isOpen, onClose }) => {
}))) : null);
};
const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote, showToast, midiDevices, recordingState, recTempMidiNotes, onRecord, selectedMidiInputId, onMidiInputSelect, activeMidiPitches, onInstrumentSelect, onRescheduleMidi, onSeekPlayhead, snapValue, onSnapChange }) => {
const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote, showToast, midiDevices, recordingState, recTempMidiNotes, onRecord, selectedMidiInputId, onMidiInputSelect, activeMidiPitches, onInstrumentSelect, onRescheduleMidi, onSeekPlayhead, snapValue, onSnapChange, onRealtimePlay }) => {
const [activeRollTool, setActiveRollTool] = React.useState('select');
const [renderTick, setRenderTick] = React.useState(0);
const [ccMode, setCcMode] = React.useState('velocity');
@@ -7581,10 +7581,10 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
els.push(React.createElement("button", {
key: m._trackId,
onClick: function() {
setActivePlayTrackIds(function(prev) {
var list = prev || [];
return list.indexOf(m._trackId) !== -1 ? list.filter(function(id) { return id !== m._trackId; }) : list.concat([m._trackId]);
});
var prevList = activePlayTrackIds || [];
var nextList = prevList.indexOf(m._trackId) !== -1 ? prevList.filter(function(id) { return id !== m._trackId; }) : prevList.concat([m._trackId]);
setActivePlayTrackIds(nextList);
if (onRealtimePlay) onRealtimePlay(nextList);
},
className: "flex items-center justify-center h-[20px] border border-zinc-600 rounded-md cursor-pointer outline-none mx-1 my-[2px] " + (isActive ? 'bg-yellow-600 text-black font-bold' : (isPlayOn ? 'bg-red-700 text-white font-semibold' : 'bg-zinc-700 text-zinc-300 hover:bg-zinc-600'))
}, React.createElement("span", {
@@ -13619,6 +13619,47 @@ const App = () => {
}
};
handlePlayPauseRef.current = handlePlayPause;
const handlePianoRollRealtimePlay = (trackIds) => {
const prSt = subTabs.find(s => s.id === activeTab && s.type === 'PIANO_ROLL');
if (!prSt) return;
var prLayers = [];
var ghostData = (window.SonicGhost && window.SonicGhost.extractGhostLayers) ? window.SonicGhost.extractGhostLayers(activeTracks, prSt.trackId, prSt.target_id, parseInt(bpm) || 120) : [];
(ghostData || []).forEach(function(layer) {
if (!trackIds || trackIds.indexOf(layer.track_id) === -1) return;
var ltrk = (activeTracks || []).find(function(t) { return t.id === layer.track_id; });
prLayers.push({
trackId: layer.track_id,
notes: layer.notes.map(function(n) { return { pitch: n.pitch, start_beat: n.relative_start_beat, duration_beats: n.duration_beats, velocity: n.velocity || 0.8 }; }),
instrumentProgram: ltrk ? ltrk.instrumentProgram : undefined,
instrumentName: ltrk ? ltrk.instrumentName : undefined,
synthEngine: ltrk ? ltrk.synth_engine : undefined
});
});
stopAllPlayback();
const prCtx = getAudioContext();
const prTNode = activeTrackNodesRef.current[prSt.trackId];
if (prTNode && prTNode.gainNode) {
prTNode.gainNode.gain.setValueAtTime(prTNode.gainNode.gain.value || 1, prCtx.currentTime);
prTNode.gainNode.gain.linearRampToValueAtTime(0.001, prCtx.currentTime + 0.04);
}
setTimeout(() => {
window.SonicSF.stopAll();
if (prTNode && prTNode.gainNode) {
const prTrackData = activeTracksRef.current ? activeTracksRef.current.find(t => t.id === prSt.trackId) : null;
const prVolDb = prTrackData ? (prTrackData.volumeDb ?? 0) : 0;
const prVolLinear = prVolDb <= -50 ? 0 : Math.pow(10, prVolDb / 20);
prTNode.gainNode.gain.setValueAtTime(0.001, prCtx.currentTime);
prTNode.gainNode.gain.linearRampToValueAtTime(prVolLinear || 0.8, prCtx.currentTime + 0.015);
}
const prOffset = prSt.currentTime || 0;
startOffsetTimeRef.current = prOffset;
startAudioTimeRef.current = prCtx.currentTime;
startBufferOffsetRef.current = prOffset * (prSt.speed || 1.0);
const playTab = Object.assign({}, prSt, { ghostPlayLayers: prLayers });
schedulePianoRollMidi(playTab, prOffset);
setSubTabs(prev => prev.map(s => s.id === prSt.id ? Object.assign({}, s, { ghostPlayLayers: prLayers, isPlaying: true, currentTime: prOffset }) : s));
}, 60);
};
const handlePause = () => {
if (isPlaying || subTabs.some(s => s.isPlaying)) stopAllPlayback();
};
@@ -20461,7 +20502,8 @@ const App = () => {
selectedMidiInputId: selectedMidiInputId,
onMidiInputSelect: handleMidiInputSelect,
activeMidiPitches: activeMidiPitches,
onInstrumentSelect: (trackId) => { openInstrumentSelector(trackId); },
onInstrumentSelect: (trackId) => { openInstrumentSelector(trackId); },
onRealtimePlay: handlePianoRollRealtimePlay,
onRescheduleMidi: (updatedNotes) => {
const playingSub = subTabs.find(s => s.id === activeTab && s.type === 'PIANO_ROLL' && s.isPlaying);
if (playingSub) {
File diff suppressed because one or more lines are too long