import React, { useState, useEffect } from 'react'; import { ConfigManager } from '../../../core/config/ConfigManager'; import { KGAudioInterface } from '../../../core/audio-interface/KGAudioInterface'; import { normalizeSpectrogramHeightResolution, type SpectrogramHeightResolution, } from '../../../util/spectrogramUtil'; import { useI18n } from '../../../i18n/useI18n'; const BehaviorSettings: React.FC = () => { const { t } = useI18n(); const [playheadUpdateFrequency, setPlayheadUpdateFrequency] = useState(30); const [spectrogramHeightResolution, setSpectrogramHeightResolution] = useState(3); const [chatboxDefaultOpen, setChatboxDefaultOpen] = useState(true); const [audioLookaheadTime, setAudioLookaheadTime] = useState('50'); const [midiAutomationInterpolationIntervalMs, setMidiAutomationInterpolationIntervalMs] = useState(10); const [playbackDelay, setPlaybackDelay] = useState('200'); const [recordingOffset, setRecordingOffset] = useState('0'); const [bounceStartsFromBeat1, setBounceStartsFromBeat1] = useState(true); const [enableAudioCapture, setEnableAudioCapture] = useState(false); const [lookaheadValidationErrors, setLookaheadValidationErrors] = useState([]); const [playbackDelayValidationErrors, setPlaybackDelayValidationErrors] = useState([]); const [recordingOffsetValidationErrors, setRecordingOffsetValidationErrors] = useState([]); const configManager = ConfigManager.instance(); // Load configuration values on component mount useEffect(() => { const loadConfig = async () => { if (!configManager.getIsInitialized()) { await configManager.initialize(); } setPlayheadUpdateFrequency((configManager.get('editor.playhead_update_frequency') as number) ?? 30); setSpectrogramHeightResolution( normalizeSpectrogramHeightResolution(configManager.get('editor.spectrogram_height_resolution')) ); setChatboxDefaultOpen((configManager.get('chatbox.default_open') as boolean) ?? true); const lookaheadTimeSeconds = (configManager.get('audio.lookahead_time') as number) ?? 0.05; setAudioLookaheadTime(((lookaheadTimeSeconds * 1000).toFixed(0))); setMidiAutomationInterpolationIntervalMs( (configManager.get('audio.midi_automation_interpolation_interval_ms') as number) ?? 10 ); const playbackDelaySeconds = (configManager.get('audio.playback_delay') as number) ?? 0.2; setPlaybackDelay(((playbackDelaySeconds * 1000).toFixed(0))); const recordingOffsetSeconds = (configManager.get('audio.recording_offset') as number) ?? 0; setRecordingOffset(((recordingOffsetSeconds * 1000).toFixed(0))); setBounceStartsFromBeat1((configManager.get('audio.bounce_starts_from_beat_1') as boolean) ?? true); setEnableAudioCapture((configManager.get('audio.enable_audio_capture_for_screen_sharing') as boolean) ?? false); }; loadConfig(); }, [configManager]); // Save configuration when values change const handlePlayheadUpdateFrequencyChange = async (value: string) => { const numValue = parseInt(value, 10); setPlayheadUpdateFrequency(numValue); await configManager.set('editor.playhead_update_frequency', numValue); console.log(`Playhead update frequency changed to: ${numValue} fps`); }; const handleChatboxDefaultOpenChange = async (value: string) => { const boolValue = value === 'yes'; setChatboxDefaultOpen(boolValue); await configManager.set('chatbox.default_open', boolValue); }; const handleSpectrogramHeightResolutionChange = async (value: string) => { const nextValue = normalizeSpectrogramHeightResolution(parseInt(value, 10)); setSpectrogramHeightResolution(nextValue); await configManager.set('editor.spectrogram_height_resolution', nextValue); }; const handleAudioLookaheadTimeChange = async (value: string) => { // Allow empty string, treat as 0 ms const numValueMs = value === '' ? 0 : parseFloat(value); const numValueSeconds = numValueMs / 1000; const errors: string[] = []; // Validate the input if (isNaN(numValueMs)) { errors.push(t('settings.behavior.validation.lookahead.invalidNumber')); } else if (numValueSeconds < 0) { errors.push(t('settings.behavior.validation.lookahead.range')); } else if (numValueSeconds > 0.5) { errors.push(t('settings.behavior.validation.lookahead.range')); } setLookaheadValidationErrors(errors); // Only apply if valid if (errors.length === 0) { setAudioLookaheadTime(value); await configManager.set('audio.lookahead_time', numValueSeconds); // Apply the change immediately without restart const audioInterface = KGAudioInterface.instance(); audioInterface.setLookaheadTime(numValueSeconds); console.log(`Audio lookahead time changed to: ${numValueSeconds}s (${numValueMs}ms)`); } }; const handlePlaybackDelayChange = async (value: string) => { // Allow empty string, treat as 0 ms const numValueMs = value === '' ? 0 : parseFloat(value); const numValueSeconds = numValueMs / 1000; const errors: string[] = []; // Validate the input if (isNaN(numValueMs)) { errors.push(t('settings.behavior.validation.playbackDelay.invalidNumber')); } else if (numValueSeconds < 0) { errors.push(t('settings.behavior.validation.playbackDelay.range')); } else if (numValueSeconds > 0.5) { errors.push(t('settings.behavior.validation.playbackDelay.range')); } setPlaybackDelayValidationErrors(errors); // Only apply if valid if (errors.length === 0) { setPlaybackDelay(value); await configManager.set('audio.playback_delay', numValueSeconds); console.log(`Playback delay changed to: ${numValueSeconds}s (${numValueMs}ms)`); } }; const handleMidiAutomationInterpolationIntervalChange = async (value: string) => { const nextValue = parseInt(value, 10); setMidiAutomationInterpolationIntervalMs(nextValue); await configManager.set('audio.midi_automation_interpolation_interval_ms', nextValue); }; const handleRecordingOffsetChange = async (value: string) => { const numValueMs = value === '' ? 0 : parseFloat(value); const numValueSeconds = numValueMs / 1000; const errors: string[] = []; if (isNaN(numValueMs)) { errors.push(t('settings.behavior.validation.recordingOffset.invalidNumber')); } else if (numValueSeconds < 0) { errors.push(t('settings.behavior.validation.recordingOffset.range')); } else if (numValueSeconds > 0.5) { errors.push(t('settings.behavior.validation.recordingOffset.range')); } setRecordingOffsetValidationErrors(errors); if (errors.length === 0) { setRecordingOffset(value); await configManager.set('audio.recording_offset', numValueSeconds); console.log(`MIDI input latency changed to: ${numValueSeconds}s (${numValueMs}ms)`); } }; const handleEnableAudioCaptureChange = async (value: string) => { const boolValue = value === 'yes'; setEnableAudioCapture(boolValue); await configManager.set('audio.enable_audio_capture_for_screen_sharing', boolValue); }; const handleBounceStartsFromBeat1Change = async (value: string) => { const boolValue = value === 'yes'; setBounceStartsFromBeat1(boolValue); await configManager.set('audio.bounce_starts_from_beat_1', boolValue); }; return (

{t('settings.behavior.title')}

{t('settings.behavior.editor')}

{t('settings.behavior.playheadUpdateFrequencyHelp')}
{t('settings.behavior.spectrogramHeightResolutionHelp')}

{t('settings.behavior.chatBox')}

{t('settings.behavior.audio')}

handleAudioLookaheadTimeChange(e.target.value)} min="0" max="500" step="1" /> {lookaheadValidationErrors.length > 0 && (
{lookaheadValidationErrors.map((error, index) => (
{error}
))}
)}
{t('settings.behavior.lookaheadTimeHelp')}
handlePlaybackDelayChange(e.target.value)} min="0" max="500" step="1" /> {playbackDelayValidationErrors.length > 0 && (
{playbackDelayValidationErrors.map((error, index) => (
{error}
))}
)}
{t('settings.behavior.playbackDelayHelp')}
{t('settings.behavior.midiAutomationInterpolationHelp')}
handleRecordingOffsetChange(e.target.value)} min="0" max="500" step="1" /> {recordingOffsetValidationErrors.length > 0 && (
{recordingOffsetValidationErrors.map((error, index) => (
{error}
))}
)}
{t('settings.behavior.midiInputLatencyHelp')}
{t('settings.behavior.bounceStartsFromBeat1Help')}
{t('settings.behavior.captureAudioForScreenSharingHelp')}
{t('settings.behavior.captureAudioForScreenSharingWarning')}
); }; export default BehaviorSettings;