diff --git a/src/components/PianoRollWidget.jsx b/src/components/PianoRollWidget.jsx index adebc94..8cf5747 100644 --- a/src/components/PianoRollWidget.jsx +++ b/src/components/PianoRollWidget.jsx @@ -242,20 +242,31 @@ export default function PianoRollWidget({ moduleId }) { Tone.getTransport().bpm.value = bpm; - // Build Tone.Part from notes - const events = notesRef.current.map(n => ({ - time: `0:0:${n.start * 4}`, // convert beats to 16th notes for Tone - note: n.note, - dur: n.duration, - })); + // Build Tone.Part from notes using musical time (bars:quarters:sixteenths) + // This lets the Transport BPM control actual playback speed + const events = notesRef.current.map(n => { + // Convert beats to bars:quarters:sixteenths notation + 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) => { 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(() => { setSequencerSignals(moduleId, midiToFreq(ev.note), false); - }, time + Tone.Time(`0:0:${ev.dur * 4}`).toSeconds() * 0.9); - }, events.map(ev => [Tone.Time(ev.time).toSeconds(), { note: ev.note, dur: ev.dur }])); + }, noteOffTime); + }, events.map(ev => [ev.time, { note: ev.note, dur: ev.dur }])); part.loop = loop; part.loopEnd = `${bars}m`;