fix: main session có thể play midi item

This commit is contained in:
2026-07-23 20:52:50 +07:00
parent dac661e6c6
commit f0210dd019
4 changed files with 62 additions and 33 deletions
+46 -19
View File
@@ -4307,7 +4307,7 @@ const AIPresetModal = ({ isOpen, onClose }) => {
}, "Đóng"))));
};
const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNotes, onSaveNotes, setSubTabs }) => {
const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying }) => {
const [activeRollTool, setActiveRollTool] = React.useState('select');
const [snapVal, setSnapVal] = React.useState('1/16');
const [ccMode, setCcMode] = React.useState('velocity');
@@ -4320,11 +4320,11 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
const rulerScrollRef = React.useRef(null);
const NoteHeight = 18;
const PITCH_START = 36; // C2
const KeybedPixelHeight = (127 - PITCH_START + 1) * NoteHeight;
const PITCH_START = 0; // C0 (render all 128 keys)
const KeybedPixelHeight = (128 - PITCH_START) * NoteHeight;
const pixelsPerBeat = rollZoom;
const timeSigNum = 4;
const totalBeats = (st.duration || 4) * timeSigNum;
const totalBeats = Math.max((st.duration || 4) * timeSigNum, 64); // at least 64 beats (16 bars) for scrolling
const drawWidth = totalBeats * pixelsPerBeat;
const [notes, setNotes] = React.useState(st.notes || []);
@@ -4523,7 +4523,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(phX, 0);
ctx.lineTo(phX, (127 - PITCH_START) * NoteHeight);
ctx.lineTo(phX, (128 - PITCH_START) * NoteHeight);
ctx.stroke();
}
}
@@ -4575,7 +4575,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
React.useEffect(() => {
if (gridScrollRef.current) {
gridScrollRef.current.scrollTop = (127 - 48) * NoteHeight - 180;
gridScrollRef.current.scrollTop = (127 - 48) * NoteHeight;
}
}, []);
@@ -4852,7 +4852,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
/*#__PURE__*/React.createElement("div", {
key: pitch,
style: { height: `${NoteHeight}px` },
className: `w-[60px] shrink-0 border-b border-zinc-800 text-[9px] font-bold flex items-center justify-end pr-2 transition ${isBlack ? 'bg-zinc-950 text-slate-500 border-zinc-900' : 'bg-white text-zinc-800 border-r border-zinc-400'}`
className: `w-[60px] shrink-0 border-b border-zinc-800 text-[9px] font-bold flex items-center justify-end pr-2 transition cursor-pointer ${isBlack ? 'bg-zinc-950 text-slate-500 border-zinc-900 hover:bg-zinc-800' : 'bg-white text-zinc-800 border-r border-zinc-400 hover:bg-amber-100'}`,
onMouseDown: (e) => {
e.stopPropagation();
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 500, undefined, st.instrumentProgram, null);
}
}
}, showLabel && label)
);
}
@@ -4886,7 +4892,12 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
left: `${x}px`,
top: '4px'
},
className: "pl-1 border-l border-zinc-700 h-full select-none"
className: "pl-1 border-l border-zinc-700 h-full select-none cursor-pointer hover:bg-zinc-800/30",
onClick: (e) => {
e.stopPropagation();
const barTime = bar * 4 * beatSec;
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, currentTime: barTime } : s));
}
}, `Bar ${bar}`)
);
}
@@ -4933,6 +4944,18 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
}, mode)))), /*#__PURE__*/React.createElement("div", {
className: "flex items-center gap-1"
}, /*#__PURE__*/React.createElement("button", {
onClick: () => onPlayPause && onPlayPause(),
className: `px-2.5 py-1 rounded text-xs flex items-center gap-1 transition font-bold ${st.isPlaying ? 'bg-amber-600 text-white' : 'bg-zinc-700 hover:bg-zinc-600 text-zinc-200'}`
}, /*#__PURE__*/React.createElement("i", {
"data-lucide": st.isPlaying ? "pause" : "play",
className: "w-3 h-3"
}), st.isPlaying ? "Pause" : "Play"), /*#__PURE__*/React.createElement("button", {
onClick: () => onStop && onStop(),
className: "px-2.5 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-300 rounded text-xs flex items-center gap-1 transition"
}, /*#__PURE__*/React.createElement("i", {
"data-lucide": "square",
className: "w-3 h-3"
}), "Stop"), /*#__PURE__*/React.createElement("button", {
onClick: () => onSaveNotes(st.id, st.trackId, st.target_id, notes),
className: "px-2.5 py-1 bg-emerald-600 hover:bg-emerald-500 text-white rounded text-xs flex items-center gap-1 transition font-semibold"
}, /*#__PURE__*/React.createElement("i", {
@@ -8246,6 +8269,8 @@ const App = () => {
const pannerNode = context.createStereoPanner();
pannerNode.pan.setValueAtTime((track.pan ?? 0) / 100, context.currentTime);
pannerNode.connect(context.destination);
// Connect gain panner for all tracks (audio clips AND MIDI items)
gainNode.connect(pannerNode);
activeTrackNodesRef.current[track.id] = {
gainNode,
pannerNode
@@ -8263,7 +8288,6 @@ const App = () => {
source.buffer = clip.buffer;
source.playbackRate.value = clip.speed || 1.0;
source.connect(gainNode);
gainNode.connect(pannerNode);
const clipStart = clip.startTime || 0;
const clipDuration = clip.buffer.duration / (clip.speed || 1.0);
const clipEnd = clipStart + clipDuration;
@@ -13896,16 +13920,19 @@ const App = () => {
const st = subTabs.find(s => s.id === activeTab);
if (!st) return null;
if (st.type === 'PIANO_ROLL') {
return /*#__PURE__*/React.createElement(PianoRollTabEditor, {
st: st,
zoom: zoom,
bpm: bpm,
viewportWidth: viewportWidth,
onClose: () => closeSubTab(st.id),
onUpdateNotes: handleUpdateMidiNotes,
onSaveNotes: handleSaveMidiNotes,
setSubTabs: setSubTabs
});
return /*#__PURE__*/React.createElement(PianoRollTabEditor, {
st: st,
zoom: zoom,
bpm: bpm,
viewportWidth: viewportWidth,
onClose: () => closeSubTab(st.id),
onUpdateNotes: handleUpdateMidiNotes,
onSaveNotes: handleSaveMidiNotes,
setSubTabs: setSubTabs,
onPlayPause: handlePlayPause,
onStop: stopAllPlayback,
isPlaying: isPlaying
});
}
const subTrack = tracks.find(t => t.id === st.trackId);
const vTrack = subTrack ? {
File diff suppressed because one or more lines are too long
Binary file not shown.
+7 -7
View File
@@ -10,13 +10,13 @@
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script src="/static/js/services/api.js?v=202607232030"></script>
<script src="/static/js/services/audioEngine.js?v=202607232030"></script>
<script src="/static/js/services/storage.js?v=202607232030"></script>
<script src="/static/js/services/soundfontPlayer.js?v=202607232030"></script>
<script src="/static/js/services/aiGateway.js?v=202607232030"></script>
<script src="/static/js/services/dawCommandDispatcher.js?v=202607232030"></script>
<script src="/static/js/app.precompiled.js?v=202607232030" defer></script>
<script src="/static/js/services/api.js?v=202607232050"></script>
<script src="/static/js/services/audioEngine.js?v=202607232050"></script>
<script src="/static/js/services/storage.js?v=202607232050"></script>
<script src="/static/js/services/soundfontPlayer.js?v=202607232050"></script>
<script src="/static/js/services/aiGateway.js?v=202607232050"></script>
<script src="/static/js/services/dawCommandDispatcher.js?v=202607232050"></script>
<script src="/static/js/app.precompiled.js?v=202607232050" defer></script>
<style>
:root {
--right-sidebar-width: 320px;