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:
@@ -1,9 +1,19 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import { getAnalyserData } from '../engine/audioEngine.js';
|
||||
|
||||
// Zoom levels: how many samples to display (from a 2048-sample buffer)
|
||||
// Fewer samples = zoomed in (more detail), more samples = zoomed out (more time visible)
|
||||
const ZOOM_LEVELS = [64, 128, 256, 512, 1024, 2048];
|
||||
const DEFAULT_ZOOM = 2; // index → 256 samples
|
||||
|
||||
export default function ScopeDisplay({ moduleId }) {
|
||||
const canvasRef = useRef(null);
|
||||
const rafRef = useRef(null);
|
||||
const [zoomIdx, setZoomIdx] = useState(DEFAULT_ZOOM);
|
||||
const zoomRef = useRef(ZOOM_LEVELS[DEFAULT_ZOOM]);
|
||||
|
||||
// Keep ref in sync so the draw loop picks it up without re-creating the effect
|
||||
useEffect(() => { zoomRef.current = ZOOM_LEVELS[zoomIdx]; }, [zoomIdx]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
@@ -23,16 +33,25 @@ export default function ScopeDisplay({ moduleId }) {
|
||||
ctx.moveTo(0, h / 2); ctx.lineTo(w, h / 2);
|
||||
ctx.moveTo(0, h / 4); ctx.lineTo(w, h / 4);
|
||||
ctx.moveTo(0, h * 3 / 4); ctx.lineTo(w, h * 3 / 4);
|
||||
for (let x = w / 4; x < w; x += w / 4) {
|
||||
ctx.moveTo(x, 0); ctx.lineTo(x, h);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
const data = getAnalyserData(moduleId);
|
||||
if (data && data.length > 0) {
|
||||
const samplesToShow = zoomRef.current;
|
||||
// Center the window in the buffer
|
||||
const offset = Math.max(0, Math.floor((data.length - samplesToShow) / 2));
|
||||
const end = Math.min(data.length, offset + samplesToShow);
|
||||
|
||||
ctx.strokeStyle = '#00e5ff';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
const step = w / data.length;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const y = h / 2 + data[i] * h / 2 * -1;
|
||||
const count = end - offset;
|
||||
const step = w / count;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const y = h / 2 + data[offset + i] * h / 2 * -1;
|
||||
if (i === 0) ctx.moveTo(0, y);
|
||||
else ctx.lineTo(i * step, y);
|
||||
}
|
||||
@@ -46,5 +65,43 @@ export default function ScopeDisplay({ moduleId }) {
|
||||
return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); };
|
||||
}, [moduleId]);
|
||||
|
||||
return <canvas ref={canvasRef} className="scope-canvas" />;
|
||||
const canZoomIn = zoomIdx > 0;
|
||||
const canZoomOut = zoomIdx < ZOOM_LEVELS.length - 1;
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<canvas ref={canvasRef} className="scope-canvas" />
|
||||
<div style={{
|
||||
position: 'absolute', bottom: 2, right: 2,
|
||||
display: 'flex', gap: 2,
|
||||
}}>
|
||||
<button
|
||||
onClick={() => canZoomOut && setZoomIdx(i => i + 1)}
|
||||
disabled={!canZoomOut}
|
||||
title="Zoom out (más tiempo)"
|
||||
style={{
|
||||
width: 18, height: 18, padding: 0,
|
||||
background: canZoomOut ? '#1a1a3a' : '#0a0a15',
|
||||
border: '1px solid #333', borderRadius: 3,
|
||||
color: canZoomOut ? '#00e5ff' : '#333',
|
||||
cursor: canZoomOut ? 'pointer' : 'default',
|
||||
fontSize: 12, lineHeight: '16px', fontWeight: 'bold',
|
||||
}}
|
||||
>−</button>
|
||||
<button
|
||||
onClick={() => canZoomIn && setZoomIdx(i => i - 1)}
|
||||
disabled={!canZoomIn}
|
||||
title="Zoom in (más detalle)"
|
||||
style={{
|
||||
width: 18, height: 18, padding: 0,
|
||||
background: canZoomIn ? '#1a1a3a' : '#0a0a15',
|
||||
border: '1px solid #333', borderRadius: 3,
|
||||
color: canZoomIn ? '#00e5ff' : '#333',
|
||||
cursor: canZoomIn ? 'pointer' : 'default',
|
||||
fontSize: 12, lineHeight: '16px', fontWeight: 'bold',
|
||||
}}
|
||||
>+</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user