From 490cd19f687b7d7ff0e9d2ed3a7e19d0a7a857c2 Mon Sep 17 00:00:00 2001
From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com>
Date: Sat, 9 May 2026 15:57:46 -0700
Subject: [PATCH 1/4] feat: added audio recording feature
---
README.md | 17 +-
public/config.json | 2 +
src/components/Toolbar.tsx | 12 +-
src/components/settings/SettingsPanel.tsx | 7 +-
src/components/settings/SettingsSidebar.tsx | 3 +-
src/components/settings/index.ts | 3 +-
.../sections/AudioIOSettings.test.tsx | 92 ++++++
.../settings/sections/AudioIOSettings.tsx | 243 +++++++++++++++
src/components/track/Region.css | 5 +
src/components/track/RegionItem.test.tsx | 40 +++
src/components/track/RegionItem.tsx | 72 ++++-
src/components/track/TrackGridItem.test.tsx | 81 +++++
src/components/track/TrackGridItem.tsx | 32 +-
src/core/KGCore.ts | 13 +
src/core/audio-interface/KGAudioInterface.ts | 50 +++
src/core/audio-interface/KGAudioRecorder.ts | 289 ++++++++++++++++++
src/core/config/ConfigManager.ts | 4 +
src/hooks/useGlobalKeyboardHandler.ts | 9 +-
src/main.tsx | 33 ++
src/stores/projectStore.test.ts | 79 ++++-
src/stores/projectStore.ts | 280 ++++++++++++++++-
src/test/mocks/audio-interface.ts | 4 +
src/util/audioDeviceUtil.test.ts | 48 +++
src/util/audioDeviceUtil.ts | 160 ++++++++++
24 files changed, 1540 insertions(+), 38 deletions(-)
create mode 100644 src/components/settings/sections/AudioIOSettings.test.tsx
create mode 100644 src/components/settings/sections/AudioIOSettings.tsx
create mode 100644 src/components/track/TrackGridItem.test.tsx
create mode 100644 src/core/audio-interface/KGAudioRecorder.ts
create mode 100644 src/util/audioDeviceUtil.test.ts
create mode 100644 src/util/audioDeviceUtil.ts
diff --git a/README.md b/README.md
index f9c2180..a702683 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,8 @@ K.G.Studio is a lightweight, modern DAW that runs entirely in the browser with *
## Latest Updates
+- **2026.05.09**: Added **audio recording** — record directly from your microphone into an audio track. A live waveform preview grows in real time as you record, and the region is committed to the timeline as a standard audio region when you stop. Added **audio I/O device selection** in Settings so you can choose your preferred microphone input and audio output device.
+
- **2026.05.08**: Added **MIDI automation** — draw and edit pitch bend and MIDI CC curves (CC1 Modulation, CC2 Breath, CC7 Volume, CC11 Expression, CC64 Sustain) in an editable automation lane below the piano grid. Added **track-level automation**: each track now has a dedicated automation panel where you can view and edit the same curves directly on the timeline. Real-time MIDI controller input (pitch wheel, CC pedals) is recorded and played back with per-lane interpolation. Added the **List Event Panel** — a tabbed sidebar (Notes / Pitch Bend / Controller) for inspecting and inline-editing all events in the active MIDI region. Added **region multi-select** with lasso and bulk move/resize, and **merge MIDI regions**.
+ Choose the devices KGStudio should use for recording and playback. Input changes apply to the next recording session. Output changes require a page refresh in v1.
+
+
+
+
+
+
+ Changes apply to the next audio recording. If the selected device is removed, KGStudio will fall back to System Default.
+
+
+
+
+
+
+ {supportsOutputPrompt && (
+
+
+
+ )}
+
+ Output-device changes require refresh in v1. Browser support for non-default output routing is limited, so KGStudio will continue on System Default when unsupported.
+
+ {!supportsOutputSink && (
+
+ This browser does not expose reliable live Web Audio sink switching. Non-default output selection is best-effort and may remain on System Default.
+
+ >
+ )}
+ >
+ );
+};
+
+export default TrackListEventTab;
diff --git a/src/stores/projectStore.test.ts b/src/stores/projectStore.test.ts
index 2cfa240..5457b24 100644
--- a/src/stores/projectStore.test.ts
+++ b/src/stores/projectStore.test.ts
@@ -41,6 +41,8 @@ const mockCore = {
getRedoDescription: () => '',
setOnCommandHistoryChanged: vi.fn(),
executeCommand: vi.fn(),
+ undo: vi.fn(() => true),
+ redo: vi.fn(() => true),
clearSelectedItems: vi.fn(),
getStatus: () => 'Ready',
getPlayheadPosition: () => 0,
@@ -83,6 +85,10 @@ describe('projectStore piano roll state', () => {
mockCore.startPlaying.mockResolvedValue(undefined);
mockCore.stopPlaying.mockReset();
mockCore.stopPlaying.mockResolvedValue(undefined);
+ mockCore.undo.mockReset();
+ mockCore.undo.mockReturnValue(true);
+ mockCore.redo.mockReset();
+ mockCore.redo.mockReturnValue(true);
mockCore.setLoopBoundaryReachedCallback.mockReset();
mockAudioInterface.startAudioRecording.mockReset();
mockAudioInterface.startAudioRecording.mockResolvedValue({ usedDeviceId: 'default', fellBackToDefault: false });
@@ -226,4 +232,22 @@ describe('projectStore piano roll state', () => {
expect(useProjectStore.getState().recordingMode).toBeNull();
expect(useProjectStore.getState().playheadPosition).toBe(8);
});
+
+ it('bumps track automation redraw version on undo and redo', async () => {
+ const { useProjectStore } = await import('./projectStore');
+
+ const initialVersion = useProjectStore.getState().trackAutomationRedrawVersion;
+
+ act(() => {
+ useProjectStore.getState().undo();
+ });
+
+ expect(useProjectStore.getState().trackAutomationRedrawVersion).toBe(initialVersion + 1);
+
+ act(() => {
+ useProjectStore.getState().redo();
+ });
+
+ expect(useProjectStore.getState().trackAutomationRedrawVersion).toBe(initialVersion + 2);
+ });
});
diff --git a/src/stores/projectStore.ts b/src/stores/projectStore.ts
index fbf5f1e..9540cdc 100644
--- a/src/stores/projectStore.ts
+++ b/src/stores/projectStore.ts
@@ -1676,6 +1676,7 @@ export const useProjectStore = create((set, get) => {
if (core.undo()) {
// Use centralized refresh method
get().refreshProjectState();
+ get().bumpTrackAutomationRedrawVersion();
console.log('Undo completed');
}
},
@@ -1685,6 +1686,7 @@ export const useProjectStore = create((set, get) => {
if (core.redo()) {
// Use centralized refresh method
get().refreshProjectState();
+ get().bumpTrackAutomationRedrawVersion();
console.log('Redo completed');
}
},
From 894c3b116f0eb4842569dc557f67e73b2496ac19 Mon Sep 17 00:00:00 2001
From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com>
Date: Sat, 9 May 2026 17:10:03 -0700
Subject: [PATCH 3/4] feat: added track level event list tab; rename `List
Event` to `Event List`.
---
README.md | 4 +-
src/App.test.tsx | 2 +-
src/App.tsx | 14 +-
...{ListEventPanel.css => EventListPanel.css} | 78 +--
...Panel.test.tsx => EventListPanel.test.tsx} | 40 +-
...{ListEventPanel.tsx => EventListPanel.tsx} | 30 +-
src/components/Toolbar.tsx | 514 +++++++++---------
.../RegionEventListTab.tsx} | 44 +-
.../TrackEventListTab.tsx} | 38 +-
src/components/piano-roll/PianoRoll.tsx | 224 ++++----
src/stores/projectStore.ts | 214 ++++----
11 files changed, 601 insertions(+), 601 deletions(-)
rename src/components/{ListEventPanel.css => EventListPanel.css} (77%)
rename src/components/{ListEventPanel.test.tsx => EventListPanel.test.tsx} (93%)
rename src/components/{ListEventPanel.tsx => EventListPanel.tsx} (69%)
rename src/components/{list-event-panel/RegionListEventTab.tsx => event-list-panel/RegionEventListTab.tsx} (97%)
rename src/components/{list-event-panel/TrackListEventTab.tsx => event-list-panel/TrackEventListTab.tsx} (96%)
diff --git a/README.md b/README.md
index a702683..240b0c5 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ K.G.Studio is a lightweight, modern DAW that runs entirely in the browser with *
- **2026.05.09**: Added **audio recording** — record directly from your microphone into an audio track. A live waveform preview grows in real time as you record, and the region is committed to the timeline as a standard audio region when you stop. Added **audio I/O device selection** in Settings so you can choose your preferred microphone input and audio output device.
-- **2026.05.08**: Added **MIDI automation** — draw and edit pitch bend and MIDI CC curves (CC1 Modulation, CC2 Breath, CC7 Volume, CC11 Expression, CC64 Sustain) in an editable automation lane below the piano grid. Added **track-level automation**: each track now has a dedicated automation panel where you can view and edit the same curves directly on the timeline. Real-time MIDI controller input (pitch wheel, CC pedals) is recorded and played back with per-lane interpolation. Added the **List Event Panel** — a tabbed sidebar (Notes / Pitch Bend / Controller) for inspecting and inline-editing all events in the active MIDI region. Added **region multi-select** with lasso and bulk move/resize, and **merge MIDI regions**.
+- **2026.05.08**: Added **MIDI automation** — draw and edit pitch bend and MIDI CC curves (CC1 Modulation, CC2 Breath, CC7 Volume, CC11 Expression, CC64 Sustain) in an editable automation lane below the piano grid. Added **track-level automation**: each track now has a dedicated automation panel where you can view and edit the same curves directly on the timeline. Real-time MIDI controller input (pitch wheel, CC pedals) is recorded and played back with per-lane interpolation. Added the **Event List Panel** — a tabbed sidebar (Notes / Pitch Bend / Controller) for inspecting and inline-editing all events in the active MIDI region. Added **region multi-select** with lasso and bulk move/resize, and **merge MIDI regions**.
@@ -325,7 +325,7 @@ Feature priorities might change.
- [X] Support MIDI control events (e.g. CC, pitch bend, etc.)
- [X] Support WAV audio tracks
- [X] Recording
-- [ ] List Event + List Region
+- [X] Event List
- [X] Add support for OpenAI's open source models (`gpt-oss-20b` and `gpt-oss-120b`)
### Post 1.0
diff --git a/src/App.test.tsx b/src/App.test.tsx
index a0f4d40..4bd3d52 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -19,7 +19,7 @@ vi.mock('./components/MainContent', () => ({ default: () => null }));
vi.mock('./components/InstrumentSelection', () => ({ default: () => null }));
vi.mock('./components/ChatBox', () => ({ default: () => null }));
vi.mock('./components/KGOnePanel', () => ({ default: () => null }));
-vi.mock('./components/ListEventPanel', () => ({ default: () => null }));
+vi.mock('./components/EventListPanel', () => ({ default: () => null }));
vi.mock('./components/settings', () => ({ SettingsPanel: () => null }));
vi.mock('./core/audio-interface/KGToneBuffersPool', () => ({
KGToneBuffersPool: {
diff --git a/src/App.tsx b/src/App.tsx
index c066b6d..c31919b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -11,7 +11,7 @@ import ChatBox from './components/ChatBox';
import { SettingsPanel } from './components/settings';
import LoadingOverlay from './components/common/LoadingOverlay';
import KGOnePanel from './components/KGOnePanel';
-import ListEventPanel from './components/ListEventPanel';
+import EventListPanel from './components/EventListPanel';
import { useEffect as useEffectReact, useState, useRef } from 'react';
import { KGToneBuffersPool } from './core/audio-interface/KGToneBuffersPool';
import { KGOfflineRenderer } from './core/audio-interface/KGOfflineRenderer';
@@ -26,12 +26,12 @@ import { RESERVED_PROJECT_NAME } from './util/projectNameUtil';
function App() {
// Enable global keyboard handler for copy/paste and undo/redo
useGlobalKeyboardHandler();
-
+
// Use project store instead of local state for project name and tracks
const {
refreshStatus,
loadProject, showChatBox, showSettings, setShowSettings, initializeFromConfig,
- showInstrumentSelection, showKGOnePanel, showListEventPanel
+ showInstrumentSelection, showKGOnePanel, showEventListPanel
} = useProjectStore();
// Track if app has been initialized to prevent multiple initializations
@@ -144,12 +144,12 @@ function App() {
useEffect(() => {
// Initial refresh
refreshStatus();
-
+
// Set up interval to refresh status every second
const intervalId = setInterval(() => {
refreshStatus();
}, 1000);
-
+
// Clean up interval on unmount
return () => clearInterval(intervalId);
}, [refreshStatus]);
@@ -169,7 +169,7 @@ function App() {
>
)}
-
+