fix: session-absolute timing for playhead + MIDI scheduling

- st.currentTime is now session-absolute (0 = session bar 0)
- schedulePianoRollMidi adds sessionBeatOffset to note timing so
  notes at renderBeatOffset beats are scheduled with correct delay
- Playhead rendering: removed renderBeatOffset (uses st.currentTime
  directly as session-absolute beats)
- handleEditMidiInTab/handleSwitchMidiItem: currentTime = 0
- Ruler click: clickTime = clickBeat * beatSec (session-absolute)
This commit is contained in:
2026-07-27 19:55:58 +07:00
parent a60607c673
commit e14554a75a
2 changed files with 21 additions and 18 deletions
+14 -11
View File
@@ -4646,7 +4646,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
var trk = (activeTracks || []).find(function(t) { return t.id === match._trackId; });
var newBeatOff = (match.startTime / secondsPerBar) * timeSigNum;
var spb = 60.0 / (parseInt(bpm) || 120);
var newTime = -newBeatOff * spb;
var newTime = 0;
setSubTabs(function(prev) {
return prev.map(function(s) {
if (s.id !== st.id) return s;
@@ -4986,7 +4986,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
// Draw playhead
if (st.currentTime !== undefined && st.currentTime !== null) {
const phBeat = renderBeatOffset + st.currentTime / (60.0 / (parseInt(bpm) || 120));
const phBeat = st.currentTime / (60.0 / (parseInt(bpm) || 120));
const phX = phBeat * pixelsPerBeat;
if (phX >= 0 && phX <= viewWidth) {
ctx.strokeStyle = '#f59e0b';
@@ -6017,7 +6017,7 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left + e.currentTarget.scrollLeft;
const clickBeat = x / pixelsPerBeat;
const clickTime = (clickBeat - renderBeatOffset) * beatSec;
const clickTime = clickBeat * beatSec;
const clickInRange = loopStartBeat !== null && loopEndBeat !== null && clickBeat >= loopStartBeat && clickBeat <= loopEndBeat;
if (e.ctrlKey || e.metaKey) {
setLoopStartBeat(null);
@@ -8282,10 +8282,6 @@ const App = () => {
const tabLabel = `Piano Roll: ${midiItem.name || 'MIDI'}`;
const ctx = getAudioContext();
const silentBuffer = ctx.createBuffer(1, 128, ctx.sampleRate);
var bpmVal = parseInt(bpm) || 120;
var beatSec = 60.0 / bpmVal;
var secPerBar = beatSec * 4;
var beatOff = (midiItem.startTime / secPerBar) * 4;
const newTab = {
id: tabId,
label: tabLabel,
@@ -8298,7 +8294,7 @@ const App = () => {
buffer: silentBuffer,
instrumentProgram: track.instrumentProgram,
instrumentName: track.instrumentName,
currentTime: -beatOff * beatSec,
currentTime: 0,
isPlaying: false,
selectionStart: null,
selectionEnd: null,
@@ -10403,8 +10399,15 @@ const App = () => {
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; } }
// Compute session beat offset from target item
var sessionBeatOffset = 0;
var targetTrk = allTracks.find(function(t) { return t.id === st.trackId; });
if (targetTrk) {
var targetIt = (targetTrk.midiItems || []).find(function(m) { return m.id === st.target_id; });
if (targetIt) sessionBeatOffset = (targetIt.startTime / ((60.0 / bpmVal) * 4)) * 4;
}
midiNotes.forEach(note => {
const noteOnBeat = note.start_beat || 0;
const noteOnBeat = (note.start_beat || 0) + sessionBeatOffset;
const noteDurBeat = note.duration_beats || 1;
const noteStartSec = noteOnBeat * secondsPerBeat;
const noteDurSec = noteDurBeat * secondsPerBeat;
@@ -10424,12 +10427,12 @@ const App = () => {
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);
if (ghostProg === undefined && !ghostSynth) return; // skip tracks with no instrument
if (ghostProg === undefined && !ghostSynth) return;
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 beat = (note.start_beat || 0) + sessionBeatOffset;
var dur = note.duration_beats || 1;
var startSec = beat * secondsPerBeat;
var durSec = dur * secondsPerBeat;