fix: dynamic sizing for sequencer and piano roll modules

Module width now adapts to step/bar count so extra steps are never
hidden. Sequencer width scales with numSteps, piano roll width scales
with bar count using a fixed BEAT_PX density.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 04:48:27 +01:00
parent 64280874ea
commit fce0bcdace
2 changed files with 22 additions and 7 deletions

View File

@@ -8,6 +8,20 @@ import KeyboardWidget from './KeyboardWidget.jsx';
import SequencerWidget from './SequencerWidget.jsx'; import SequencerWidget from './SequencerWidget.jsx';
import PianoRollWidget from './PianoRollWidget.jsx'; import PianoRollWidget from './PianoRollWidget.jsx';
// Dynamic module widths for sequencer/pianoroll based on step/bar count
function getModuleWidth(mod, type) {
if (type === 'sequencer') {
const numSteps = parseInt(mod?.params?.steps || '16');
return Math.max(200, numSteps * 18 + 20); // CELL_W=18 + padding
}
if (type === 'pianoroll') {
const bars = parseInt(mod?.params?.bars || '4');
const totalBeats = bars * 4;
return 24 + totalBeats * 30 + 20; // KEY_W + beats*BEAT_PX + padding
}
return undefined;
}
// Map input port names → the param name they modulate (for visual feedback) // Map input port names → the param name they modulate (for visual feedback)
const PORT_TO_PARAM = { const PORT_TO_PARAM = {
filter: { cutoff: 'frequency' }, filter: { cutoff: 'frequency' },
@@ -150,7 +164,7 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
style={{ style={{
left: mod.x * zoom, top: mod.y * zoom, left: mod.x * zoom, top: mod.y * zoom,
transform: `scale(${zoom})`, transformOrigin: 'top left', transform: `scale(${zoom})`, transformOrigin: 'top left',
...(mod.type === 'pianoroll' ? { width: 520 } : mod.type === 'sequencer' ? { width: 310 } : {}), ...(mod.type === 'pianoroll' ? { width: getModuleWidth(mod, 'pianoroll') } : mod.type === 'sequencer' ? { width: getModuleWidth(mod, 'sequencer') } : {}),
}} }}
data-module-id={mod.id} data-module-id={mod.id}
onPointerDown={(e) => { onPointerDown={(e) => {

View File

@@ -79,7 +79,7 @@ const MARIO_MELODY = [
{ note: 71, start: 70*s, duration: 2*s }, // B4 { note: 71, start: 70*s, duration: 2*s }, // B4
]; ];
const ROLL_W = 500; const BEAT_PX = 30; // pixels per beat — constant density regardless of bar count
const ROLL_H = 200; const ROLL_H = 200;
const KEY_W = 24; const KEY_W = 24;
const MIN_NOTE = 48; // C3 const MIN_NOTE = 48; // C3
@@ -110,7 +110,8 @@ export default function PianoRollWidget({ moduleId }) {
const notesRef = useRef(notes); const notesRef = useRef(notes);
notesRef.current = notes; notesRef.current = notes;
const beatW = (ROLL_W - KEY_W) / totalBeats; const rollW = KEY_W + totalBeats * BEAT_PX;
const beatW = BEAT_PX;
// Draw the piano roll // Draw the piano roll
const draw = useCallback(() => { const draw = useCallback(() => {
@@ -220,7 +221,7 @@ export default function PianoRollWidget({ moduleId }) {
ctx.fillStyle = 'rgba(0,229,255,0.3)'; ctx.fillStyle = 'rgba(0,229,255,0.3)';
ctx.fillRect(x, row * ROW_H, Math.max(nw, 2), ROW_H); ctx.fillRect(x, row * ROW_H, Math.max(nw, 2), ROW_H);
} }
}, [totalBeats, beatW, playPos]); }, [totalBeats, beatW, playPos, rollW]);
// Animation loop // Animation loop
useEffect(() => { useEffect(() => {
@@ -415,7 +416,7 @@ export default function PianoRollWidget({ moduleId }) {
}, [mod]); }, [mod]);
return ( return (
<div style={{ width: ROLL_W }}> <div style={{ width: rollW }}>
{/* Mini toolbar */} {/* Mini toolbar */}
<div style={{ display: 'flex', gap: 4, marginBottom: 3 }}> <div style={{ display: 'flex', gap: 4, marginBottom: 3 }}>
<button <button
@@ -453,9 +454,9 @@ export default function PianoRollWidget({ moduleId }) {
</div> </div>
<canvas <canvas
ref={canvasRef} ref={canvasRef}
width={ROLL_W} width={rollW}
height={ROLL_H} height={ROLL_H}
style={{ width: ROLL_W, height: ROLL_H, borderRadius: 4, cursor: tool === 'draw' ? 'crosshair' : 'pointer' }} style={{ width: rollW, height: ROLL_H, borderRadius: 4, cursor: tool === 'draw' ? 'crosshair' : 'pointer' }}
onPointerDown={handleMouseDown} onPointerDown={handleMouseDown}
/> />
</div> </div>