From 77885c8e2e3f0fa426773801e1b72084f3f0436e Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Fri, 1 May 2026 10:59:36 -0700 Subject: [PATCH] feat: added recording MIDI device feature. --- public/config.json | 3 +- src/components/Toolbar.tsx | 58 ++++++++- src/components/piano-roll/PianoRoll.css | 8 ++ .../piano-roll/PianoRollContent.tsx | 28 ++++- .../settings/sections/BehaviorSettings.tsx | 53 ++++++++ src/core/config/ConfigManager.ts | 4 +- src/core/midi-input/KGMidiInput.ts | 16 +++ src/stores/projectStore.ts | 114 +++++++++++++++++- 8 files changed, 277 insertions(+), 7 deletions(-) diff --git a/public/config.json b/public/config.json index e63d612..9b0619f 100644 --- a/public/config.json +++ b/public/config.json @@ -73,7 +73,8 @@ "audio": { "enable_audio_capture_for_screen_sharing": false, "lookahead_time": 0.05, - "playback_delay": 0.2 + "playback_delay": 0.2, + "recording_offset": 0 }, "templates": { "custom_instructions": "" diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index b41a068..b4b0a44 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -12,9 +12,11 @@ import { FaUndo, FaRedo, FaMousePointer, FaStepBackward, FaPlay, FaPause, FaComments, FaSync, FaFolderOpen, FaSave, FaDownload, FaUpload, FaPlus, - FaCog, FaMagnet, FaCut + FaCog, FaMagnet, FaCut, FaCircle } from 'react-icons/fa'; import { KGProject, type KeySignature } from '../core/KGProject'; +import { KGMidiInput } from '../core/midi-input/KGMidiInput'; +import { KGMidiRegion } from '../core/region/KGMidiRegion'; import { plainToInstance } from 'class-transformer'; import { FaPencil, FaCopy, FaPaste, FaTrash, FaWandMagicSparkles } from 'react-icons/fa6'; import { KGMainContentState } from '../core/state/KGMainContentState'; @@ -45,6 +47,7 @@ const Toolbar: React.FC = () => { isLooping, toggleLoop, canUndo, canRedo, undoDescription, redoDescription, undo, redo, toggleChatBox, toggleSettings, toggleKGOnePanel, showKGOnePanel, cleanupProjectState, toggleMetronome, isMetronomeEnabled, + isRecording, startRecording, stopRecording, // Piano roll state/actions showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId, // Selection state @@ -515,6 +518,11 @@ const Toolbar: React.FC = () => { console.log("Pause button clicked"); } try { + if (isRecording) { + await stopRecording(); + setStatus("Recording stopped — notes committed"); + return; + } await stopPlaying(); } catch (error) { console.error("Failed to stop playback:", error); @@ -860,6 +868,46 @@ const Toolbar: React.FC = () => { } }; + const handleRecordClick = async () => { + if (isRecording) { + await stopRecording(); + setStatus("Recording stopped — notes committed"); + return; + } + + // Require an active or selected MIDI region + const candidateId = activeRegionId ?? (selectedRegionIds[0] ?? null); + if (!candidateId) { + await showAlert("Please open a MIDI region in the Piano Roll before starting recording."); + return; + } + const tracks = KGCore.instance().getCurrentProject().getTracks(); + let isMidi = false; + for (const track of tracks) { + const region = track.getRegions().find(r => r.getId() === candidateId); + if (region) { isMidi = region instanceof KGMidiRegion; break; } + } + if (!isMidi) { + await showAlert("Please select a MIDI region before starting recording."); + return; + } + + // Require at least one connected MIDI device + if (KGMidiInput.instance().getConnectedInputCount() === 0) { + await showAlert("No MIDI device detected. Please connect a MIDI keyboard and try again."); + return; + } + + // Ensure the piano roll is open showing the target region + if (!activeRegionId) { + setActiveRegionId(candidateId); + setShowPianoRoll(true); + } + + await startRecording(); + setStatus("Recording started..."); + }; + return ( <>
@@ -943,6 +991,14 @@ const Toolbar: React.FC = () => { ) : ( )} +
+
+ + handleRecordingOffsetChange(e.target.value)} + min="0" + max="500" + step="1" + /> + {recordingOffsetValidationErrors.length > 0 && ( +
+ {recordingOffsetValidationErrors.map((error, index) => ( +
+ {error} +
+ ))} +
+ )} +
+ Timing correction for MIDI recording (0-500ms). If recorded notes appear slightly late compared to where you intended to play them, increase this value to match your MIDI device's input latency. Each note's position is shifted back by this amount when committed. +
+
+