fix: piano roll playback now uses musical time instead of absolute seconds

The Part events were pre-converted to seconds via Tone.Time().toSeconds(),
making the BPM setting ineffective. Now uses bars:quarters:sixteenths
notation so the Transport BPM actually controls playback speed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 01:51:43 +01:00
parent 9d61adb064
commit d0755413f3

View File

@@ -242,20 +242,31 @@ export default function PianoRollWidget({ moduleId }) {
Tone.getTransport().bpm.value = bpm; Tone.getTransport().bpm.value = bpm;
// Build Tone.Part from notes // Build Tone.Part from notes using musical time (bars:quarters:sixteenths)
const events = notesRef.current.map(n => ({ // This lets the Transport BPM control actual playback speed
time: `0:0:${n.start * 4}`, // convert beats to 16th notes for Tone const events = notesRef.current.map(n => {
note: n.note, // Convert beats to bars:quarters:sixteenths notation
dur: n.duration, const totalSixteenths = Math.round(n.start * 4);
})); const barNum = Math.floor(totalSixteenths / 16);
const remainder = totalSixteenths % 16;
const quarterNum = Math.floor(remainder / 4);
const sixteenthNum = remainder % 4;
return {
time: `${barNum}:${quarterNum}:${sixteenthNum}`,
note: n.note,
dur: n.duration,
};
});
// Use a simpler approach: schedule directly
const part = new Tone.Part((time, ev) => { const part = new Tone.Part((time, ev) => {
setSequencerSignals(moduleId, midiToFreq(ev.note), true); setSequencerSignals(moduleId, midiToFreq(ev.note), true);
// Note-off: convert duration beats to musical time for proper BPM-relative timing
const durSixteenths = Math.round(ev.dur * 4);
const noteOffTime = time + (durSixteenths * (60 / (bpm * 4))) * 0.9;
Tone.getTransport().scheduleOnce(() => { Tone.getTransport().scheduleOnce(() => {
setSequencerSignals(moduleId, midiToFreq(ev.note), false); setSequencerSignals(moduleId, midiToFreq(ev.note), false);
}, time + Tone.Time(`0:0:${ev.dur * 4}`).toSeconds() * 0.9); }, noteOffTime);
}, events.map(ev => [Tone.Time(ev.time).toSeconds(), { note: ev.note, dur: ev.dur }])); }, events.map(ev => [ev.time, { note: ev.note, dur: ev.dur }]));
part.loop = loop; part.loop = loop;
part.loopEnd = `${bars}m`; part.loopEnd = `${bars}m`;