feat: track column buttons, ghost playback, per-track channels
- Track column: centered 12px buttons, purple active / yellow highlight - Ghost playback: ghostLayers synced to st.ghostPlayLayers via useEffect; schedulePianoRollMidi plays ghost notes from active tracks - Per-track MIDI channels: each track gets a unique channel based on its index, enabling separate instruments per track
This commit is contained in:
+58
-5
@@ -5052,6 +5052,27 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Sync ghost play data to subTab state for playback integration
|
||||
React.useEffect(function() {
|
||||
if (!sessionSyncMode || !showGhostNotes || !ghostLayers.length) {
|
||||
setSubTabs(function(prev) { return prev.map(function(s) { if (s.id !== st.id) return s; return Object.assign({}, s, { ghostPlayLayers: [] }); }); });
|
||||
return;
|
||||
}
|
||||
var layers = [];
|
||||
ghostLayers.forEach(function(layer) {
|
||||
if (activePlayTrackIds !== null && !activePlayTrackIds.has(layer.track_id)) return;
|
||||
var trk = (activeTracks || []).find(function(t) { return t.id === layer.track_id; });
|
||||
layers.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: trk ? trk.instrumentProgram : undefined,
|
||||
instrumentName: trk ? trk.instrumentName : undefined,
|
||||
synthEngine: trk ? trk.synth_engine : undefined
|
||||
});
|
||||
});
|
||||
setSubTabs(function(prev) { return prev.map(function(s) { if (s.id !== st.id) return s; return Object.assign({}, s, { ghostPlayLayers: layers }); }); });
|
||||
}, [ghostLayers, activePlayTrackIds, sessionSyncMode, showGhostNotes, st.id, activeTracks]);
|
||||
|
||||
const handleGridMouseDown = (e) => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
@@ -6110,7 +6131,7 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
|
||||
className: "absolute left-0 top-0 w-[120px] bg-[#16161a] border-r border-zinc-800 z-10 flex flex-col overflow-hidden pointer-events-none",
|
||||
style: { height: KeybedPixelHeight + 'px' }
|
||||
}, React.createElement("div", {
|
||||
className: "pointer-events-auto"
|
||||
className: "pointer-events-auto flex flex-col"
|
||||
}, (allMidiItems.length > 0 ? function() {
|
||||
var seenTracks = {};
|
||||
var els = [];
|
||||
@@ -6131,9 +6152,9 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
|
||||
return s.size === 0 ? null : s;
|
||||
});
|
||||
},
|
||||
className: "flex items-center px-1.5 h-[18px] border-b border-zinc-900 cursor-pointer " + (isActive ? 'bg-yellow-900/20' : (isPlayOn ? 'bg-zinc-800/40' : 'bg-zinc-900/60'))
|
||||
className: "flex items-center justify-center h-[18px] border-b border-zinc-900 cursor-pointer select-none " + (isActive ? 'bg-yellow-600 text-black font-bold' : (isPlayOn ? 'bg-purple-700 text-white font-semibold' : 'bg-zinc-800 text-zinc-500 hover:bg-zinc-700'))
|
||||
}, React.createElement("span", {
|
||||
className: "text-[9px] font-mono truncate " + (isActive ? 'text-yellow-400 font-bold' : (isPlayOn ? 'text-zinc-300' : 'text-zinc-600')),
|
||||
className: "text-[12px] font-sans truncate px-1",
|
||||
title: track ? track.name : m._trackName
|
||||
}, track ? track.name : m._trackName)));
|
||||
});
|
||||
@@ -6765,7 +6786,11 @@ const App = () => {
|
||||
// Trigger SpessaSynth load + program change when soundfont instrument selected
|
||||
if (window.SonicSF && window.SonicSF.selectInstrument && instrumentId && isSfInstrument) {
|
||||
const sfId = instrumentId.replace('sf_', '');
|
||||
const ch = sfBank === 128 ? 9 : 0;
|
||||
var allTracks = activeTracksRef.current || tracks;
|
||||
var tidx = 0;
|
||||
for (var i = 0; i < allTracks.length; i++) { if (allTracks[i].id === trackId) { tidx = i; break; } }
|
||||
var ch = sfBank === 128 ? 9 : (tidx >= 9 ? tidx + 1 : tidx);
|
||||
if (ch >= 16) ch = 0;
|
||||
window.SonicSF.selectInstrument(ch, sfBank || 0, sfProg || 0, sfId);
|
||||
}
|
||||
setSubTabs(prev => prev.map(s => {
|
||||
@@ -10372,6 +10397,9 @@ const App = () => {
|
||||
const destNode = getOrCreateTrackNode(track, context);
|
||||
const instrumentProgram = track ? track.instrumentProgram : undefined;
|
||||
const synthEngine = track ? track.synth_engine : undefined;
|
||||
var allTracks = activeTracksRef.current || [];
|
||||
var mainCh = 0;
|
||||
for (var i = 0; i < allTracks.length; i++) { if (allTracks[i].id === st.trackId) { mainCh = i >= 9 ? i + 1 : i; if (mainCh >= 16) mainCh = 0; break; } }
|
||||
midiNotes.forEach(note => {
|
||||
const noteOnBeat = note.start_beat || 0;
|
||||
const noteDurBeat = note.duration_beats || 1;
|
||||
@@ -10383,10 +10411,35 @@ const App = () => {
|
||||
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, undefined, synthEngine);
|
||||
window.SonicSF.playNote(note.pitch || 60, note.velocity || 0.8, durMs, scheduledTime, instrumentProgram, destNode, mainCh, synthEngine);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Play ghost notes from active tracks
|
||||
var ghostLayers = st.ghostPlayLayers || [];
|
||||
ghostLayers.forEach(function(layer) {
|
||||
var ghostTrack = allTracks.find(function(t) { return t.id === layer.trackId; });
|
||||
var ghostProg = layer.instrumentProgram !== undefined ? layer.instrumentProgram : (ghostTrack ? ghostTrack.instrumentProgram : undefined);
|
||||
var ghostSynth = layer.synthEngine || (ghostTrack ? ghostTrack.synth_engine : undefined);
|
||||
var ghostDest = getOrCreateTrackNode(ghostTrack, context);
|
||||
var ghostCh = 0;
|
||||
for (var i = 0; i < allTracks.length; i++) { if (allTracks[i].id === layer.trackId) { ghostCh = i >= 9 ? i + 1 : i; if (ghostCh >= 16) ghostCh = 0; break; } }
|
||||
layer.notes.forEach(function(note) {
|
||||
var beat = note.start_beat || 0;
|
||||
var dur = note.duration_beats || 1;
|
||||
var startSec = beat * secondsPerBeat;
|
||||
var durSec = dur * secondsPerBeat;
|
||||
if (startSec + durSec > offsetSeconds) {
|
||||
var effStart = Math.max(0, startSec - offsetSeconds);
|
||||
var effDur = durSec - Math.max(0, offsetSeconds - startSec);
|
||||
var schedTime = startWallTime + effStart;
|
||||
var durMs = effDur * 1000;
|
||||
if (window.SonicSF) {
|
||||
window.SonicSF.playNote(note.pitch || 60, note.velocity || 0.8, durMs, schedTime, ghostProg, ghostDest, ghostCh, ghostSynth);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const handlePlayPause = () => {
|
||||
if (activeTab !== 'main' && !activeTab.startsWith('session_')) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user