fix: Transport lifecycle, scope zoom, clear button, and freq routing
- Fix pianoroll/sequencer Transport not resetting on stop/restart (notes were scheduled in the past and never fired) - Stop and cancel Transport in stopAudio() to prevent stale events - Add zoom +/- buttons to scope widget (6 levels, 64–2048 samples) - Increase scope analyser buffer from 256 to 2048 for wider time view - Add vertical grid lines to scope display - Add "Limpiar" clear canvas button to PuzzleView - Skip audio-graph connection for keyboard/seq/pianoroll freq→osc freq (direct frequency setting prevents inaudible ultrasonic values) - Auto-trigger envelopes without gate connections for noise/ambient levels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -235,12 +235,23 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
// Playback
|
||||
useEffect(() => {
|
||||
if (!state.isRunning) {
|
||||
if (partRef.current) { partRef.current.stop(); partRef.current.dispose(); partRef.current = null; }
|
||||
if (partRef.current) {
|
||||
try { partRef.current.stop(); } catch {}
|
||||
try { partRef.current.dispose(); } catch {}
|
||||
partRef.current = null;
|
||||
}
|
||||
setPlayPos(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
Tone.getTransport().bpm.value = bpm;
|
||||
const transport = Tone.getTransport();
|
||||
transport.bpm.value = bpm;
|
||||
|
||||
// Ensure Transport is at position 0 before scheduling
|
||||
if (transport.state === 'started') {
|
||||
transport.stop();
|
||||
}
|
||||
transport.position = 0;
|
||||
|
||||
// Build Tone.Part from notes using musical time (bars:quarters:sixteenths)
|
||||
// This lets the Transport BPM control actual playback speed
|
||||
@@ -263,7 +274,7 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
// 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(() => {
|
||||
transport.scheduleOnce(() => {
|
||||
setSequencerSignals(moduleId, midiToFreq(ev.note), false);
|
||||
}, noteOffTime);
|
||||
}, events.map(ev => [ev.time, { note: ev.note, dur: ev.dur }]));
|
||||
@@ -271,16 +282,15 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
part.loop = loop;
|
||||
part.loopEnd = `${bars}m`;
|
||||
part.start(0);
|
||||
|
||||
if (Tone.getTransport().state !== 'started') {
|
||||
Tone.getTransport().start();
|
||||
}
|
||||
partRef.current = part;
|
||||
|
||||
// Start Transport fresh from position 0
|
||||
transport.start();
|
||||
|
||||
// Track playhead position
|
||||
const posInterval = setInterval(() => {
|
||||
if (Tone.getTransport().state === 'started') {
|
||||
const pos = Tone.getTransport().seconds;
|
||||
if (transport.state === 'started') {
|
||||
const pos = transport.seconds;
|
||||
const beatDuration = 60 / bpm;
|
||||
const currentBeat = (pos / beatDuration) % totalBeats;
|
||||
setPlayPos(currentBeat);
|
||||
@@ -289,7 +299,11 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
|
||||
return () => {
|
||||
clearInterval(posInterval);
|
||||
if (partRef.current) { partRef.current.stop(); partRef.current.dispose(); partRef.current = null; }
|
||||
if (partRef.current) {
|
||||
try { partRef.current.stop(); } catch {}
|
||||
try { partRef.current.dispose(); } catch {}
|
||||
partRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [state.isRunning, moduleId, bpm, bars, loop]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user