import React from 'react'; import { FaMousePointer, FaPencilAlt } from 'react-icons/fa'; import { TbArrowBarToUp } from 'react-icons/tb'; import { ColorPalettePopup, KGDropdown } from '../common'; import { KGPianoRollState } from '../../core/state/KGPianoRollState'; import { KGCore } from '../../core/KGCore'; import { getTranslatedAutomationOptions, type PianoRollAutomationType, } from './pianoRollAutomation'; import { useI18n } from '../../i18n/useI18n'; interface PianoRollToolbarProps { showAudioSpectrogramToggle?: boolean; audioSpectrogramEnabled?: boolean; onAudioSpectrogramToggle?: () => void; sheetMusicToggleDisabled?: boolean; sheetMusicViewEnabled?: boolean; onSheetMusicViewToggle?: () => void; sheetMusicTrackScopeEnabled?: boolean; onSheetMusicTrackScopeToggle?: () => void; sheetQuantization?: string; onSheetQuantizationChange?: (value: string) => void; sheetQuantizationOptions?: string[]; activeTool: 'pointer' | 'pencil'; onToolSelect: (tool: 'pointer' | 'pencil') => void; quantPosition: string; quantLength: string; onQuantSelect: (type: 'position' | 'length', value: string) => void; snapping: string; onSnappingSelect: (value: string) => void; selectedMode: string; onModeChange: (value: string) => void; chordGuide: 'N' | 'T' | 'S' | 'D'; onChordGuideChange: (value: 'N' | 'T' | 'S' | 'D') => void; blinkButton?: string | null; mode?: 'midi-edit' | 'audio-waveform' | 'spectrogram' | 'hybrid'; thresholdDb?: number; onThresholdChange?: (db: number) => void; power?: number; onPowerChange?: (power: number) => void; zoom: number; onZoomChange: (value: number) => void; showAutomationControls?: boolean; automationEnabled?: boolean; automationType?: PianoRollAutomationType; onAutomationToggle?: () => void; onAutomationTypeChange?: (value: PianoRollAutomationType) => void; onDetectChords?: () => void | Promise; detectingChords?: boolean; onDetectTempo?: () => void | Promise; detectingTempo?: boolean; selectedRegionColor?: string; onRegionColorSelect?: (color: string | null) => void; } const PianoRollToolbar: React.FC = ({ showAudioSpectrogramToggle = false, audioSpectrogramEnabled = false, onAudioSpectrogramToggle, sheetMusicToggleDisabled = false, sheetMusicViewEnabled = false, onSheetMusicViewToggle, sheetMusicTrackScopeEnabled = false, onSheetMusicTrackScopeToggle, sheetQuantization = '16,48', onSheetQuantizationChange, sheetQuantizationOptions = [], activeTool, onToolSelect, quantPosition, quantLength, onQuantSelect, snapping, onSnappingSelect, selectedMode, onModeChange, chordGuide, onChordGuideChange, blinkButton = null, mode = 'midi-edit', thresholdDb = -25, onThresholdChange, power = 0.5, onPowerChange, zoom, onZoomChange, showAutomationControls = false, automationEnabled = false, automationType = 'pitch-bend', onAutomationToggle, onAutomationTypeChange, onDetectChords, detectingChords = false, onDetectTempo, detectingTempo = false, selectedRegionColor, onRegionColorSelect, }) => { const { t } = useI18n(); const showMidiControls = mode !== 'spectrogram' && mode !== 'audio-waveform' && !sheetMusicViewEnabled; const showAudioOnlyControls = mode === 'audio-waveform' && !sheetMusicViewEnabled; const showSpectrogramOnlyControls = mode === 'spectrogram' && !sheetMusicViewEnabled; const showSpecControls = !sheetMusicViewEnabled && (mode === 'spectrogram' || mode === 'hybrid'); const showSpecMenu = !sheetMusicViewEnabled && (!!onDetectChords || !!onDetectTempo); const automationOptions = React.useMemo(() => getTranslatedAutomationOptions(t), [t]); const snapOptions = React.useMemo( () => KGPianoRollState.SNAP_OPTIONS.map(option => ({ label: t(option.labelKey), value: option.value })), [t], ); const quantPositionOptions = React.useMemo( () => KGPianoRollState.QUANT_POS_OPTIONS.map(option => ({ label: t(option.labelKey), value: option.value })), [t], ); const quantLengthOptions = React.useMemo( () => KGPianoRollState.QUANT_LEN_OPTIONS.map(option => ({ label: t(option.labelKey), value: option.value })), [t], ); const POWER_OPTIONS = [ { label: t('pianoRoll.power.linear'), value: '1.0' }, { label: t('pianoRoll.power.sqrtDefault'), value: '0.5' }, { label: t('pianoRoll.power.mild'), value: '0.4' }, { label: t('pianoRoll.power.strong'), value: '0.3' }, ]; const modeOptions = React.useMemo( () => Object.entries(KGCore.FUNCTIONAL_CHORDS_DATA).map(([id, data]) => { const translationKey = `pianoRoll.modeOption.${id}`; const translatedLabel = t(translationKey); return { label: translatedLabel === translationKey ? data.name : translatedLabel, value: id, }; }), [t], ); const CHORD_GUIDE_BUTTONS: Array<{ label: string; value: 'N' | 'T' | 'S' | 'D'; ariaLabel: string }> = [ { label: '⊘', value: 'N', ariaLabel: t('pianoRoll.chordGuide.off') }, { label: 'T', value: 'T', ariaLabel: t('pianoRoll.chordGuide.tonic') }, { label: 'S', value: 'S', ariaLabel: t('pianoRoll.chordGuide.subdominant') }, { label: 'D', value: 'D', ariaLabel: t('pianoRoll.chordGuide.dominant') }, ]; const [showZoomSlider, setShowZoomSlider] = React.useState(false); const zoomSliderRef = React.useRef(null); React.useEffect(() => { if (!showZoomSlider) return; const handleClickOutside = (e: MouseEvent) => { if (zoomSliderRef.current && !zoomSliderRef.current.contains(e.target as Node)) { setShowZoomSlider(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [showZoomSlider]); const [showMoreMenu, setShowMoreMenu] = React.useState(false); const [showRegionColorPalette, setShowRegionColorPalette] = React.useState(false); const specMenuRef = React.useRef(null); React.useEffect(() => { if (!showMoreMenu) return; const handleClickOutside = (e: MouseEvent) => { if (specMenuRef.current && !specMenuRef.current.contains(e.target as Node)) { setShowMoreMenu(false); setShowRegionColorPalette(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [showMoreMenu]); const spectrogramToggleButton = showAudioSpectrogramToggle ? ( ) : null; const sheetMusicToggleButton = ( ); return (
{showMidiControls && (
{spectrogramToggleButton} {sheetMusicToggleButton} {showAutomationControls && (
onAutomationTypeChange?.(value as PianoRollAutomationType)} label={t('pianoRoll.automation')} buttonClassName="automation-type-dropdown" showValueAsLabel={true} />
)} onModeChange(value)} label={t('pianoRoll.mode')} buttonClassName="mode-dropdown" showValueAsLabel={true} />
{CHORD_GUIDE_BUTTONS.map((button, index) => ( ))}
)} {showAudioOnlyControls && (
{spectrogramToggleButton}
)} {showSpectrogramOnlyControls && (
{spectrogramToggleButton}
)} {sheetMusicViewEnabled && (
{spectrogramToggleButton} {sheetMusicToggleButton} {mode !== 'spectrogram' && ( )}
)}
{sheetMusicViewEnabled && ( onSheetQuantizationChange?.(value)} label={t('pianoRoll.sheetQuantization')} buttonClassName="sheet-quantization" showValueAsLabel={true} /> )} {showMidiControls && ( <> onSnappingSelect(value)} label={t('pianoRoll.snap')} buttonClassName="snapping" showValueAsLabel={true} /> onQuantSelect('position', value)} label={t('pianoRoll.quantizePositionCompact')} buttonClassName={`quant-position ${blinkButton === 'quant-position' ? 'button-blink' : ''}`} /> onQuantSelect('length', value)} label={t('pianoRoll.quantizeLengthCompact')} buttonClassName={`quant-length ${blinkButton === 'quant-length' ? 'button-blink' : ''}`} /> )} {showSpecControls && (
{t('pianoRoll.floor')} onThresholdChange?.(parseInt(e.target.value))} title={`Noise floor: ${thresholdDb} dB`} /> {thresholdDb} dB onPowerChange?.(parseFloat(v))} label={t('pianoRoll.curve')} buttonClassName="curve-dropdown" showValueAsLabel={true} />
)} {!sheetMusicViewEnabled && (
{showZoomSlider && (
onZoomChange(parseInt(e.target.value))} /> {zoom}x
)}
)} {showSpecMenu && (
{showMoreMenu && (
{onRegionColorSelect && (
setShowRegionColorPalette(open => !open)} > {t('pianoRoll.regionColor')}
{showRegionColorPalette && (
{ onRegionColorSelect(color); setShowRegionColorPalette(false); setShowMoreMenu(false); }} />
)}
)} {onDetectChords && (
{ if (detectingChords) { return; } setShowRegionColorPalette(false); setShowMoreMenu(false); void onDetectChords(); }} aria-disabled={detectingChords} > {detectingChords ? t('pianoRoll.detectingChords') : t('pianoRoll.detectChords')}
)} {onDetectTempo && (
{ if (detectingTempo) { return; } setShowRegionColorPalette(false); setShowMoreMenu(false); void onDetectTempo(); }} aria-disabled={detectingTempo} > {detectingTempo ? t('pianoRoll.detectingTempo') : t('pianoRoll.detectTempo')}
)}
)}
)}
); }; export default PianoRollToolbar;