fix: brush shape draw + remove transport + loop repeat sound

This commit is contained in:
2026-07-26 10:41:31 +07:00
parent 3ae48f2f2f
commit fe379c45e2
3 changed files with 51 additions and 86 deletions
+41 -78
View File
@@ -5217,25 +5217,20 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
const snappedPitch = snapToScaleRef.current ? snapPitchToScale(pitch, selectedScaleRef.current) : pitch;
if (!visited.includes(snappedPitch)) {
const newPitches = [...visited, snappedPitch];
const totalSpan = Math.max(0.125, beat - draggedNote.initialBeat);
const perNoteDur = totalSpan / newPitches.length;
const brushIds = draggedNote.brushIds || [];
setNotes(prev => {
const cleaned = prev.filter(n => !brushIds.includes(n.id) && n.id !== draggedNote.drawNoteId);
const brushNotes = newPitches.map((p, i) => ({
id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8) + '_' + i,
pitch: p,
start_beat: draggedNote.initialBeat + i * perNoteDur,
duration_beats: Math.max(0.125, perNoteDur * 0.9),
velocity: brushVelocityRef.current,
pan: 0.0
}));
const newBrushIds = brushNotes.map(bn => bn.id);
setSelectedNoteIds(newBrushIds);
draggedNote.brushIds = newBrushIds;
draggedNote.visitedPitches = newPitches;
return [...cleaned, ...brushNotes];
});
const snappedBeat = getSnapBeat(beat, snapVal);
const newNote = {
id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8) + '_' + newPitches.length,
pitch: snappedPitch,
start_beat: snappedBeat,
duration_beats: getSnapDuration(snapVal),
velocity: brushVelocityRef.current,
pan: 0.0
};
setNotes(prev => [...prev, newNote]);
setSelectedNoteIds(prev => [...prev, newNote.id]);
draggedNote.brushIds = [...brushIds, newNote.id];
draggedNote.visitedPitches = newPitches;
}
const container = gridScrollRef.current;
if (container) {
@@ -5757,27 +5752,6 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
title: st.instrumentName || "Synth",
className: `px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 max-w-[50px] ${st.instrumentName ? 'bg-violet-900 text-violet-300 border-violet-700' : 'bg-zinc-800 text-zinc-500 border-transparent hover:text-zinc-300'}`
}, React.createElement("i", { "data-lucide": "music", className: "w-3 h-3 shrink-0" }), React.createElement("span", { className: "truncate text-[9px]" }, st.instrumentName || 'Synth')), React.createElement("div", {
className: "flex items-center gap-0.5 ml-1"
}, React.createElement("button", {
onClick: () => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, currentTime: Math.max(0, (st.currentTime || 0) - beatSec * 4) } : s)),
className: "w-6 h-6 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700",
title: "Back 1 bar"
}, React.createElement("i", { "data-lucide": "step-back", className: "w-3 h-3" })), React.createElement("button", {
onClick: onPlayPause,
className: `w-6 h-6 flex items-center justify-center rounded border ${isPlaying ? 'bg-emerald-600 text-black' : 'bg-cyan-600 text-white'} border-cyan-500`,
title: isPlaying ? "Pause" : "Play"
}, React.createElement("i", { "data-lucide": isPlaying ? "pause" : "play", className: "w-3 h-3 fill-current" })), React.createElement("button", {
onClick: onStop,
className: "w-6 h-6 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700",
title: "Stop"
}, React.createElement("i", { "data-lucide": "square", className: "w-3 h-3 fill-current" })), React.createElement("button", {
onClick: onRecord,
className: `w-6 h-6 flex items-center justify-center rounded border ${recordingState === 'RECORDING' ? 'bg-red-600 text-white border-red-500 animate-pulse' : 'bg-zinc-800 text-red-500 border-zinc-700'}`
}, React.createElement("i", { "data-lucide": "circle", className: "w-3 h-3 fill-current" })), React.createElement("button", {
onClick: () => setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, currentTime: (st.currentTime || 0) + beatSec * 4 } : s)),
className: "w-6 h-6 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700",
title: "Forward 1 bar"
}, React.createElement("i", { "data-lucide": "step-forward", className: "w-3 h-3" }))), React.createElement("div", {
className: "flex items-center gap-1 ml-1 text-xs"
}, React.createElement("span", { className: "text-zinc-500" }, "AI:"), React.createElement("input", {
type: "number", value: aiBarStart, onChange: e => setAiBarStart(parseInt(e.target.value) || 0),
@@ -9480,6 +9454,7 @@ const App = () => {
currentTime: start,
isPlaying: true
} : s));
schedulePianoRollMidi(st, start);
startSubTabPlayback(st, start);
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
return;
@@ -9968,6 +9943,32 @@ const App = () => {
});
}
};
const schedulePianoRollMidi = (st, offsetSeconds) => {
if (st.type !== 'PIANO_ROLL') return;
const context = getAudioContext();
const midiNotes = st.notes || [];
const bpmVal = parseInt(bpm) || 120;
const secondsPerBeat = 60.0 / bpmVal;
const startWallTime = context.currentTime;
const track = activeTracksRef.current ? activeTracksRef.current.find(t => t.id === st.trackId) : null;
const destNode = getOrCreateTrackNode(track, context);
const instrumentProgram = track ? track.instrumentProgram : undefined;
midiNotes.forEach(note => {
const noteOnBeat = note.start_beat || 0;
const noteDurBeat = note.duration_beats || 1;
const noteStartSec = noteOnBeat * secondsPerBeat;
const noteDurSec = noteDurBeat * secondsPerBeat;
if (noteStartSec + noteDurSec > offsetSeconds) {
const effectiveStart = Math.max(0, noteStartSec - offsetSeconds);
const effectiveDur = noteDurSec - Math.max(0, offsetSeconds - noteStartSec);
const scheduledTime = startWallTime + effectiveStart;
const durMs = effectiveDur * 1000;
if (window.SonicSF) {
window.SonicSF.playNote(note.pitch || 60, note.velocity || 0.8, durMs, scheduledTime, instrumentProgram, destNode);
}
}
});
};
const handlePlayPause = () => {
if (activeTab !== 'main' && !activeTab.startsWith('session_')) {
// Sub-tab playback transport
@@ -9984,45 +9985,7 @@ const App = () => {
const startOffset = st.currentTime || 0;
if (st.type === 'PIANO_ROLL') {
// Piano Roll: play MIDI notes via SonicSF
const context = getAudioContext();
const notes = st.notes || [];
const bpmVal = parseInt(bpm) || 120;
const secondsPerBeat = 60.0 / bpmVal;
const startWallTime = context.currentTime;
// Find the track to get instrument settings
const track = activeTracks.find(t => t.id === st.trackId);
const destNode = getOrCreateTrackNode(track, context);
const instrumentProgram = track ? track.instrumentProgram : undefined;
const instrumentName = track ? track.instrumentName : undefined;
notes.forEach(note => {
const noteOnBeat = note.start_beat || 0;
const noteDurBeat = note.duration_beats || 1;
const noteStartSec = noteOnBeat * secondsPerBeat;
const noteDurSec = noteDurBeat * secondsPerBeat;
if (noteStartSec + noteDurSec > startOffset) {
const effectiveStart = Math.max(0, noteStartSec - startOffset);
const effectiveDur = noteDurSec - Math.max(0, startOffset - noteStartSec);
const scheduledTime = startWallTime + effectiveStart;
const durMs = effectiveDur * 1000;
if (window.SonicSF) {
window.SonicSF.playNote(
note.pitch || 60,
note.velocity || 0.8,
durMs,
scheduledTime,
instrumentProgram,
destNode
);
}
}
});
// Call startSubTabPlayback to spin up the silent buffer playhead timing/references
schedulePianoRollMidi(st, startOffset);
startSubTabPlayback(st, startOffset);
} else {
startSubTabPlayback(st, startOffset);
File diff suppressed because one or more lines are too long