fix: wire positions on zoom, MIDI import, envelope release min
- Fix wires not recalculating positions on zoom until panning - Add MIDI file import button to Piano Roll (parses .mid files) - Allow envelope release to go to 0 (was clamped at 0.001) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import * as Tone from 'tone';
|
||||
import { state, updateModuleParam, emit } from '../engine/state.js';
|
||||
import { setSequencerSignals } from '../engine/audioEngine.js';
|
||||
import { parseMidi } from '../utils/midiParser.js';
|
||||
|
||||
const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
||||
const BLACK_KEYS = [1, 3, 6, 8, 10];
|
||||
@@ -65,6 +66,7 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
const [tool, setTool] = useState('draw'); // 'draw' | 'erase'
|
||||
const drawingRef = useRef(null);
|
||||
const rafRef = useRef(null);
|
||||
const midiInputRef = useRef(null);
|
||||
|
||||
const bpm = mod?.params?.bpm ?? 140;
|
||||
const bars = parseInt(mod?.params?.bars || '4');
|
||||
@@ -321,6 +323,37 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
}
|
||||
}, [tool, notes, beatW, totalBeats, mod]);
|
||||
|
||||
const handleMidiImport = useCallback(async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const midi = parseMidi(arrayBuffer);
|
||||
if (midi.notes.length === 0) return;
|
||||
|
||||
// Update BPM if detected
|
||||
if (midi.bpm && mod) {
|
||||
mod.params.bpm = midi.bpm;
|
||||
}
|
||||
|
||||
// Auto-fit bars to cover all notes
|
||||
const maxBeat = Math.max(...midi.notes.map(n => n.start + n.duration));
|
||||
const neededBars = Math.ceil(maxBeat / 4);
|
||||
const fitBars = [1, 2, 4, 8].find(b => b >= neededBars) || 8;
|
||||
if (mod) mod.params.bars = String(fitBars);
|
||||
|
||||
// Set notes
|
||||
if (mod) {
|
||||
mod.params._notes = midi.notes;
|
||||
notesRef.current = midi.notes;
|
||||
emit();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[PianoRoll] MIDI import failed:', err);
|
||||
}
|
||||
e.target.value = '';
|
||||
}, [mod]);
|
||||
|
||||
return (
|
||||
<div style={{ width: ROLL_W }}>
|
||||
{/* Mini toolbar */}
|
||||
@@ -345,6 +378,15 @@ export default function PianoRollWidget({ moduleId }) {
|
||||
}}
|
||||
onClick={() => setTool('erase')}
|
||||
>✕ Erase</button>
|
||||
<button
|
||||
style={{
|
||||
padding: '1px 6px', fontSize: 9, border: '1px solid #333',
|
||||
background: '#111', color: '#888',
|
||||
borderRadius: 3, cursor: 'pointer', fontFamily: 'inherit',
|
||||
}}
|
||||
onClick={() => midiInputRef.current?.click()}
|
||||
>🎵 MIDI</button>
|
||||
<input ref={midiInputRef} type="file" accept=".mid,.midi" style={{ display: 'none' }} onChange={handleMidiImport} />
|
||||
<span style={{ fontSize: 8, color: '#555', marginLeft: 'auto', alignSelf: 'center' }}>
|
||||
{notes.length} notes · {bars} bars
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user