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:
@@ -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`;
|
||||||
|
|||||||
Reference in New Issue
Block a user