import React, { useMemo } from 'react'; import { KGMidiRegion } from '../../core/region/KGMidiRegion'; import { MIDI_PITCH_BEND_MAX, MIDI_PITCH_BEND_MIN, midiPitchBendToSignedValue, } from '../../util/midiUtil'; import { getControllerNumberForAutomationType, PIANO_ROLL_AUTOMATION_OPTIONS, type PianoRollAutomationType, } from './pianoRollAutomation'; interface AutomationPoint { id: string; absoluteBeat: number; value: number; label: string; } interface PianoRollAutomationLaneProps { activeRegion: KGMidiRegion | null; automationType: PianoRollAutomationType; maxBars: number; timeSignature: { numerator: number; denominator: number }; bpm?: number; redrawVersion?: number; } const AUTOMATION_COLOR = '#87CEFA'; const LANE_HEIGHT = 160; const LANE_PADDING_Y = 16; const PianoRollAutomationLane: React.FC = ({ activeRegion, automationType, maxBars, timeSignature, redrawVersion = 0, }) => { const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40; const keyWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-width')) || 60; const points = useMemo(() => { if (!activeRegion) { return []; } const regionStartBeat = activeRegion.getStartFromBeat(); if (automationType === 'pitch-bend') { return activeRegion.getPitchBends().map((pitchBend) => ({ id: pitchBend.getId(), absoluteBeat: regionStartBeat + pitchBend.getBeat(), value: pitchBend.getValue(), label: `${midiPitchBendToSignedValue(pitchBend.getValue())}`, })); } const controller = getControllerNumberForAutomationType(automationType); if (controller === null) { return []; } return activeRegion.getControllerEvents(controller).map((event) => ({ id: event.getId(), absoluteBeat: regionStartBeat + event.getBeat(), value: event.getValue(), label: `${event.getValue()}`, })); }, [activeRegion, automationType, redrawVersion]); const selectedOption = PIANO_ROLL_AUTOMATION_OPTIONS.find(option => option.value === automationType); const laneLabel = selectedOption?.label ?? automationType; const totalBeats = maxBars * timeSignature.numerator; const totalWidth = 'calc(var(--max-number-of-bars) * var(--region-grid-bar-width) + var(--region-piano-key-width))'; const toY = (value: number): number => { const minValue = automationType === 'pitch-bend' ? MIDI_PITCH_BEND_MIN : 0; const maxValue = automationType === 'pitch-bend' ? MIDI_PITCH_BEND_MAX : 127; const usableHeight = LANE_HEIGHT - LANE_PADDING_Y * 2; const normalized = (value - minValue) / (maxValue - minValue); return LANE_HEIGHT - LANE_PADDING_Y - normalized * usableHeight; }; const svgPoints = points.map(point => { return { ...point, x: point.absoluteBeat * beatWidth + keyWidth, y: toY(point.value), }; }); const polylinePoints = (() => { if (svgPoints.length === 0) { return ''; } const renderedPoints = [...svgPoints]; const lastPoint = renderedPoints[renderedPoints.length - 1]; renderedPoints.push({ ...lastPoint, id: `${lastPoint.id}-tail`, x: beatWidth * totalBeats + keyWidth, }); return renderedPoints .map(point => `${point.x},${point.y}`) .join(' '); })(); return (
{laneLabel}
{points.length > 0 && ( )} {svgPoints.map(point => { const labelY = Math.max(14, Math.min(LANE_HEIGHT - 6, point.y - 10)); return ( {point.label} ); })} {points.length === 0 && (
No {laneLabel} events in this region
)}
); }; export default PianoRollAutomationLane;