fix: piano roll record via ARM + INPUT routes to piano roll tab

This commit is contained in:
2026-07-25 18:25:29 +07:00
parent 2c91c0156a
commit 9498a0d4b0
2 changed files with 60 additions and 9 deletions
+54 -4
View File
@@ -4548,7 +4548,6 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
const [snapVal, setSnapVal] = React.useState('1/16');
const [ccMode, setCcMode] = React.useState('velocity');
const [rollZoom, setRollZoom] = React.useState(60); // local horizontal zoom factor
const [isArmed, setIsArmed] = React.useState(false);
const [aiBarStart, setAiBarStart] = React.useState(0);
const [aiBarEnd, setAiBarEnd] = React.useState(4);
@@ -5598,8 +5597,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
key: v,
value: v
}, v)))), /*#__PURE__*/React.createElement("button", {
onClick: () => setIsArmed(!isArmed),
className: `px-2 py-1 rounded text-xs font-bold ${isArmed ? 'bg-red-600 text-white' : 'bg-zinc-800 text-zinc-400'}`
onClick: () => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, isArmed: !s.isArmed } : s)),
className: `px-2 py-1 rounded text-xs font-bold ${st.isArmed ? 'bg-red-600 text-white' : 'bg-zinc-800 text-zinc-400'}`
}, "ARM"), /*#__PURE__*/React.createElement("select", {
value: selectedMidiInputId || '',
onChange: e => onMidiInputSelect(e.target.value),
@@ -6313,6 +6312,7 @@ const App = () => {
const activeMIDIRecordersRef = useRef({});
const activeAudioRecordersRef = useRef({});
const pianoRollRecorderRef = useRef(null);
const recordingPCMDataRef = useRef({});
const recordingStartTimeRef = useRef(0);
const recordingSyncRef = useRef(null);
@@ -9775,6 +9775,24 @@ const App = () => {
return;
}
// Check if piano roll tab is active and armed
const activePianoRoll = subTabs.find(s => s.id === activeTab && s.type === 'PIANO_ROLL' && s.isArmed);
if (activePianoRoll && selectedMidiInputId) {
setRecordingState('COUNT_IN');
const secondsPerBeat = 60.0 / (parseInt(bpm) || 120);
const countInDuration = secondsPerBeat * 4;
const audioCtx = getAudioContext();
const now = audioCtx.currentTime;
showToast('Metronome Count-in: 4... 3... 2... 1... (MIDI Piano Roll)', 'info');
for (let i = 0; i < 4; i++) {
playMetronomeClick(now + i * secondsPerBeat, i === 0);
}
setTimeout(() => {
startPianoRollRecording(activePianoRoll);
}, countInDuration * 1000);
return;
}
const armed = activeTracks.filter(t => t.isArmed && t.inputSource?.deviceType && t.inputSource.deviceType !== 'NONE');
if (armed.length === 0) {
showToast('Vui lòng Arm (R) ít nhất một track và chọn cổng Input để ghi âm.', 'warning');
@@ -9798,6 +9816,22 @@ const App = () => {
}, countInDuration * 1000);
};
const startPianoRollRecording = (tab) => {
const context = getAudioContext();
const secondsPerBeat = 60.0 / (parseInt(bpm) || 120);
const startTime = currentTime;
const midiRec = new ClientMIDIRecorder(context, parseInt(bpm) || 120, 4);
midiRec.tempTabId = tab.id;
pianoRollRecorderRef.current = midiRec;
activeMIDIRecordersRef.current['piano_roll'] = midiRec;
if (selectedMidiInputId) midiRec.setSelectedMidiInputId(selectedMidiInputId);
setRecordingState('RECORDING');
setRecTempMidiNotes([]);
startTrackPlayback(startTime);
setIsPlaying(true);
showToast('Recording MIDI to Piano Roll...', 'info');
};
const startRecordingTake = async (armedTracks) => {
const context = getAudioContext();
if (context.state === 'suspended') {
@@ -9986,7 +10020,23 @@ const App = () => {
const midiRec = midiRecorders[trackId];
const recordedNotes = midiRec.stop();
if (midiRec.tempMidiItemId) {
if (midiRec.tempTabId) {
const recordedNotes = midiRec.stop();
if (recordedNotes.length > 0) {
const newNotes = recordedNotes.map((n, i) => ({
id: 'rec_' + Date.now() + '_' + i,
pitch: n.pitch,
start_beat: Math.max(0, n.start_beat || 0),
duration_beats: Math.max(0.125, n.duration_beats || 0.25),
velocity: Math.min(1, (n.velocity || 0.8)),
pan: 0.0
}));
setSubTabs(prev => prev.map(s => s.id === midiRec.tempTabId ? { ...s, notes: [...(s.notes || []), ...newNotes], isDirty: true } : s));
setCanvasRedrawCount(n => n + 1);
showToast(`Đã ghi ${recordedNotes.length} notes vào Piano Roll.`, 'success');
}
hasRecordedAnything = true;
} else if (midiRec.tempMidiItemId) {
const recCurrentTimeSec = Math.max(0, context.currentTime - midiRec.recStartAudioTime - midiRec.latencyCompSec);
const recElapsedBeats = recCurrentTimeSec / (60.0 / midiRec.bpm);
const totalDurationBeats = Math.max(4.0, recordedNotes.length > 0 ? Math.max(recElapsedBeats, ...recordedNotes.map(n => n.start_beat + n.duration_beats)) : recElapsedBeats);
File diff suppressed because one or more lines are too long