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:
Jose Luis
2026-03-21 04:28:36 +01:00
parent 58d567c671
commit 36eb31a652
5 changed files with 173 additions and 24 deletions

View File

@@ -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]);