feat: misc behavior changes

This commit is contained in:
Xiaohan-Tian
2026-07-06 23:28:56 -07:00
parent 5aa74e61a8
commit 93450aebec
7 changed files with 297 additions and 12 deletions
@@ -843,7 +843,9 @@ const RegionEventListTab: React.FC<RegionEventListTabProps> = ({ activeMidiRegio
? lastSelectedNote.getEndBeat() - lastSelectedNote.getStartBeat() ? lastSelectedNote.getEndBeat() - lastSelectedNote.getStartBeat()
: KGPianoRollState.instance().getLastEditedNoteLength(); : KGPianoRollState.instance().getLastEditedNoteLength();
const defaultPitch = lastSelectedNote ? lastSelectedNote.getPitch() : noteNameToPitch('C4'); const defaultPitch = lastSelectedNote ? lastSelectedNote.getPitch() : noteNameToPitch('C4');
const defaultVelocity = lastSelectedNote ? lastSelectedNote.getVelocity() : 127; const defaultVelocity = lastSelectedNote
? lastSelectedNote.getVelocity()
: KGPianoRollState.instance().getLastEditedNoteVelocity();
const command = new CreateNoteCommand( const command = new CreateNoteCommand(
activeMidiRegion.getId(), activeMidiRegion.getId(),
@@ -855,6 +857,7 @@ const RegionEventListTab: React.FC<RegionEventListTabProps> = ({ activeMidiRegio
KGCore.instance().executeCommand(command); KGCore.instance().executeCommand(command);
KGPianoRollState.instance().setLastEditedNoteLength(defaultLength); KGPianoRollState.instance().setLastEditedNoteLength(defaultLength);
KGPianoRollState.instance().setLastEditedNoteVelocity(defaultVelocity);
const createdNote = command.getCreatedNote(); const createdNote = command.getCreatedNote();
if (createdNote) { if (createdNote) {
createdNote.select(); createdNote.select();
+9
View File
@@ -55,6 +55,7 @@ export class KGPianoRollState {
private activeTool: string = "pointer"; private activeTool: string = "pointer";
private currentSnap: PianoRollSnapValue = PIANO_ROLL_NO_SNAP; private currentSnap: PianoRollSnapValue = PIANO_ROLL_NO_SNAP;
private lastEditedNoteLength: number = 1; // Default to 1 beat private lastEditedNoteLength: number = 1; // Default to 1 beat
private lastEditedNoteVelocity: number = 127;
private currentMode: string = "ionian"; // Default mode private currentMode: string = "ionian"; // Default mode
private automationViewEnabled: boolean = false; private automationViewEnabled: boolean = false;
private currentAutomationType: string = "pitch-bend"; private currentAutomationType: string = "pitch-bend";
@@ -108,6 +109,14 @@ export class KGPianoRollState {
this.lastEditedNoteLength = length; this.lastEditedNoteLength = length;
} }
public getLastEditedNoteVelocity(): number {
return this.lastEditedNoteVelocity;
}
public setLastEditedNoteVelocity(velocity: number): void {
this.lastEditedNoteVelocity = velocity;
}
public getCurrentMode(): string { public getCurrentMode(): string {
return this.currentMode; return this.currentMode;
} }
+153 -4
View File
@@ -59,10 +59,22 @@ vi.mock('../stores/projectStore', () => ({
}), }),
})); }));
vi.mock('../core/audio-interface/KGAudioInterface', () => ({
KGAudioInterface: {
instance: () => ({
getIsInitialized: () => false,
getIsAudioContextStarted: () => false,
startAudioContext: vi.fn(),
triggerNote: vi.fn(),
}),
},
}));
import { useNoteOperations } from './useNoteOperations'; import { useNoteOperations } from './useNoteOperations';
import { KGCore } from '../core/KGCore'; import { KGCore } from '../core/KGCore';
import { KGPianoRollState } from '../core/state/KGPianoRollState'; import { KGPianoRollState } from '../core/state/KGPianoRollState';
import { MoveNotesCommand, ResizeNotesCommand } from '../core/commands'; import { CreateNoteCommand, MoveNotesCommand, ResizeNotesCommand } from '../core/commands';
import { CreateNotesCommand } from '../core/commands/note/CreateNotesCommand';
import { useProjectStore } from '../stores/projectStore'; import { useProjectStore } from '../stores/projectStore';
import { createMockMidiNote, createMockMidiRegion, createMockMidiTrack } from '../test/utils/mock-data'; import { createMockMidiNote, createMockMidiRegion, createMockMidiTrack } from '../test/utils/mock-data';
@@ -91,20 +103,157 @@ describe('useNoteOperations', () => {
KGPianoRollState.instance().setActiveTool('pointer'); KGPianoRollState.instance().setActiveTool('pointer');
KGPianoRollState.instance().setCurrentSnap('1/4'); KGPianoRollState.instance().setCurrentSnap('1/4');
KGPianoRollState.instance().setLastEditedNoteLength(1);
KGPianoRollState.instance().setLastEditedNoteVelocity(127);
KGPianoRollState.instance().setCurrentMatchingChords([]);
KGPianoRollState.instance().setCurrentSelectedChordIndex(0);
KGPianoRollState.instance().setCurrentChordCursorPitch(null);
}); });
const renderNoteOperations = (activeRegion: ReturnType<typeof createMockMidiRegion>, track = createMockMidiTrack({ id: 1, regions: [activeRegion] }), updateTrack = vi.fn()) => { const createPianoGridRef = () => ({
current: {
getBoundingClientRect: () => ({
left: 0,
top: 0,
right: 800,
bottom: 600,
width: 800,
height: 600,
x: 0,
y: 0,
toJSON: () => ({}),
}),
} as HTMLDivElement,
});
const createGridClickEvent = (overrides: Partial<React.MouseEvent> = {}): React.MouseEvent => ({
clientX: 80,
clientY: 120,
ctrlKey: false,
metaKey: false,
shiftKey: false,
altKey: false,
...overrides,
} as React.MouseEvent);
const renderNoteOperations = (
activeRegion: ReturnType<typeof createMockMidiRegion>,
track = createMockMidiTrack({ id: 1, regions: [activeRegion] }),
updateTrack = vi.fn(),
pianoGridRef = createPianoGridRef(),
) => {
const hook = renderHook(() => useNoteOperations({ const hook = renderHook(() => useNoteOperations({
activeRegion, activeRegion,
timeSignature: { numerator: 4, denominator: 4 }, timeSignature: { numerator: 4, denominator: 4 },
updateTrack, updateTrack,
tracks: [track], tracks: [track],
pianoGridRef: { current: null }, pianoGridRef,
})); }));
return { ...hook, track, updateTrack }; return { ...hook, track, updateTrack, pianoGridRef };
}; };
it('uses the last selected note velocity when creating after deselecting', () => {
const selectedNote = createMockMidiNote({ id: 'note-a', velocity: 91, pitch: 60 });
const activeRegion = createMockMidiRegion({
id: 'region-1',
trackId: '1',
notes: [selectedNote],
});
selectedNote.select();
KGCore.instance().addSelectedItem(selectedNote);
KGPianoRollState.instance().setLastEditedNoteVelocity(selectedNote.getVelocity());
KGCore.instance().clearSelectedItems();
const { result } = renderNoteOperations(activeRegion);
act(() => {
result.current.handleGridDoubleClick(createGridClickEvent());
});
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNoteCommand & { velocity: number };
expect(createCommand).toBeInstanceOf(CreateNoteCommand);
expect(createCommand.velocity).toBe(91);
});
it('uses the most recently selected note velocity after deselecting a multi-selection', () => {
const noteA = createMockMidiNote({ id: 'note-a', velocity: 40, pitch: 60 });
const noteB = createMockMidiNote({ id: 'note-b', velocity: 105, pitch: 64 });
const activeRegion = createMockMidiRegion({
id: 'region-1',
trackId: '1',
notes: [noteA, noteB],
});
noteA.select();
noteB.select();
KGCore.instance().addSelectedItem(noteA);
KGCore.instance().addSelectedItem(noteB);
KGPianoRollState.instance().setLastEditedNoteVelocity(noteB.getVelocity());
KGCore.instance().clearSelectedItems();
const { result } = renderNoteOperations(activeRegion);
act(() => {
result.current.handleGridDoubleClick(createGridClickEvent());
});
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNoteCommand & { velocity: number };
expect(createCommand).toBeInstanceOf(CreateNoteCommand);
expect(createCommand.velocity).toBe(105);
});
it('falls back to velocity 127 when creating a manual note with no selection', () => {
const activeRegion = createMockMidiRegion({
id: 'region-1',
trackId: '1',
notes: [],
});
const { result } = renderNoteOperations(activeRegion);
act(() => {
result.current.handleGridDoubleClick(createGridClickEvent());
});
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNoteCommand & { velocity: number };
expect(createCommand).toBeInstanceOf(CreateNoteCommand);
expect(createCommand.velocity).toBe(127);
});
it('applies the cached velocity to every note in manual chord creation after deselecting', () => {
const selectedNote = createMockMidiNote({ id: 'note-a', velocity: 73, pitch: 60 });
const activeRegion = createMockMidiRegion({
id: 'region-1',
trackId: '1',
notes: [selectedNote],
});
selectedNote.select();
KGCore.instance().addSelectedItem(selectedNote);
KGPianoRollState.instance().setLastEditedNoteVelocity(selectedNote.getVelocity());
KGCore.instance().clearSelectedItems();
KGPianoRollState.instance().setCurrentMatchingChords([[0, 4, 7]]);
KGPianoRollState.instance().setCurrentSelectedChordIndex(0);
KGPianoRollState.instance().setCurrentChordCursorPitch(72);
const { result } = renderNoteOperations(activeRegion);
act(() => {
result.current.handleGridDoubleClick(createGridClickEvent({ clientY: 706 }));
});
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNotesCommand;
expect(createCommand).toBeInstanceOf(CreateNotesCommand);
expect(createCommand.getNoteCreationData()).toHaveLength(3);
expect(createCommand.getNoteCreationData().every(note => note.velocity === 73)).toBe(true);
});
it('selects the grabbed note before resizing when it was not part of the current selection', () => { it('selects the grabbed note before resizing when it was not part of the current selection', () => {
const noteA = createMockMidiNote({ id: 'note-a', startBeat: 0, endBeat: 1, pitch: 60 }); const noteA = createMockMidiNote({ id: 'note-a', startBeat: 0, endBeat: 1, pitch: 60 });
const noteB = createMockMidiNote({ id: 'note-b', startBeat: 1, endBeat: 2, pitch: 62 }); const noteB = createMockMidiNote({ id: 'note-b', startBeat: 1, endBeat: 2, pitch: 62 });
+4 -3
View File
@@ -192,10 +192,10 @@ export const useNoteOperations = ({
// Calculate note timing relative to the region // Calculate note timing relative to the region
const regionStartBeat = activeRegion.getStartFromBeat(); const regionStartBeat = activeRegion.getStartFromBeat();
const noteStartBeat = beatNumber - regionStartBeat; // relative beat position const noteStartBeat = beatNumber - regionStartBeat; // relative beat position
const lastEditedLength = KGPianoRollState.instance().getLastEditedNoteLength();
const noteEndBeat = noteStartBeat + lastEditedLength; // Use last edited note length
const velocity = 127; // Maximum velocity
const pianoRollState = KGPianoRollState.instance(); const pianoRollState = KGPianoRollState.instance();
const lastEditedLength = pianoRollState.getLastEditedNoteLength();
const noteEndBeat = noteStartBeat + lastEditedLength; // Use last edited note length
const velocity = pianoRollState.getLastEditedNoteVelocity();
const matchingChordPitches = pianoRollState.getCurrentMatchingChords(); const matchingChordPitches = pianoRollState.getCurrentMatchingChords();
const selectedChordIndex = pianoRollState.getCurrentSelectedChordIndex(); const selectedChordIndex = pianoRollState.getCurrentSelectedChordIndex();
const cursorChordPitch = pianoRollState.getCurrentChordCursorPitch(); const cursorChordPitch = pianoRollState.getCurrentChordCursorPitch();
@@ -333,6 +333,7 @@ export const useNoteOperations = ({
note.select(); note.select();
core.addSelectedItem(note); core.addSelectedItem(note);
KGPianoRollState.instance().setLastEditedNoteLength(note.getEndBeat() - note.getStartBeat()); KGPianoRollState.instance().setLastEditedNoteLength(note.getEndBeat() - note.getStartBeat());
KGPianoRollState.instance().setLastEditedNoteVelocity(note.getVelocity());
const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId()); const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
if (track) { if (track) {
+3
View File
@@ -108,6 +108,7 @@ export const useNoteSelection = ({
// Update last edited note length // Update last edited note length
const noteLength = note.getEndBeat() - note.getStartBeat(); const noteLength = note.getEndBeat() - note.getStartBeat();
KGPianoRollState.instance().setLastEditedNoteLength(noteLength); KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
KGPianoRollState.instance().setLastEditedNoteVelocity(note.getVelocity());
if (DEBUG_MODE.PIANO_ROLL) { if (DEBUG_MODE.PIANO_ROLL) {
console.log(`Added note to selection: ${noteId}`); console.log(`Added note to selection: ${noteId}`);
@@ -139,6 +140,7 @@ export const useNoteSelection = ({
// Update last edited note length // Update last edited note length
const noteLength = note.getEndBeat() - note.getStartBeat(); const noteLength = note.getEndBeat() - note.getStartBeat();
KGPianoRollState.instance().setLastEditedNoteLength(noteLength); KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
KGPianoRollState.instance().setLastEditedNoteVelocity(note.getVelocity());
if (DEBUG_MODE.PIANO_ROLL) { if (DEBUG_MODE.PIANO_ROLL) {
console.log(`Selected note (replacing previous selection): ${noteId}`); console.log(`Selected note (replacing previous selection): ${noteId}`);
@@ -416,6 +418,7 @@ export const useNoteSelection = ({
const noteLength = closestNote.getEndBeat() - closestNote.getStartBeat(); const noteLength = closestNote.getEndBeat() - closestNote.getStartBeat();
KGPianoRollState.instance().setLastEditedNoteLength(noteLength); KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
KGPianoRollState.instance().setLastEditedNoteVelocity(closestNote.getVelocity());
if (DEBUG_MODE.PIANO_ROLL) { if (DEBUG_MODE.PIANO_ROLL) {
console.log(`Updated last edited note length to ${noteLength} from closest note: ${closestNote.getId()}`); console.log(`Updated last edited note length to ${noteLength} from closest note: ${closestNote.getId()}`);
+103 -3
View File
@@ -4,6 +4,8 @@ import { KGTrack } from '../core/track/KGTrack';
import { KGMidiTrack } from '../core/track/KGMidiTrack'; import { KGMidiTrack } from '../core/track/KGMidiTrack';
import { KGAudioTrack } from '../core/track/KGAudioTrack'; import { KGAudioTrack } from '../core/track/KGAudioTrack';
import { KGAudioRegion } from '../core/region/KGAudioRegion'; import { KGAudioRegion } from '../core/region/KGAudioRegion';
import { KGMidiRegion } from '../core/region/KGMidiRegion';
import { KGMidiNote } from '../core/midi/KGMidiNote';
import { createDefaultGlobalTracks } from '../core/global-track'; import { createDefaultGlobalTracks } from '../core/global-track';
import { getAudioRegionDisplayLengthBeats } from '../util/globalTrackUtil'; import { getAudioRegionDisplayLengthBeats } from '../util/globalTrackUtil';
@@ -35,6 +37,9 @@ let mockTracks: KGTrack[] = [new KGMidiTrack('Track 1', 0, 'acoustic_grand_piano
let mockIsMetronomeEnabled = false; let mockIsMetronomeEnabled = false;
let mockShowGlobalTracks = false; let mockShowGlobalTracks = false;
let mockPlayheadPosition = 0; let mockPlayheadPosition = 0;
let mockSelectedItems: Array<{ getId: () => string; select: () => void; deselect: () => void; isSelected: () => boolean }> = [];
let mockCopiedItems: Array<{ getId: () => string }> = [];
const selectionChangedCallbacks: Array<() => void> = [];
const mockProject = { const mockProject = {
getTimeSignature: () => ({ numerator: 4, denominator: 4 }), getTimeSignature: () => ({ numerator: 4, denominator: 4 }),
getMaxBars: () => 32, getMaxBars: () => 32,
@@ -90,8 +95,11 @@ const mockCore = {
setPlayheadUpdateCallback: vi.fn(), setPlayheadUpdateCallback: vi.fn(),
setPlaybackStateChangeCallback: vi.fn(), setPlaybackStateChangeCallback: vi.fn(),
setLoopBoundaryReachedCallback: vi.fn(), setLoopBoundaryReachedCallback: vi.fn(),
getSelectedItems: () => [], getSelectedItems: () => mockSelectedItems,
onSelectionChanged: vi.fn(), getCopiedItems: () => mockCopiedItems,
onSelectionChanged: vi.fn((callback: () => void) => {
selectionChangedCallbacks.push(callback);
}),
canUndo: () => false, canUndo: () => false,
canRedo: () => false, canRedo: () => false,
getUndoDescription: () => '', getUndoDescription: () => '',
@@ -100,7 +108,16 @@ const mockCore = {
executeCommand: vi.fn(), executeCommand: vi.fn(),
undo: vi.fn(() => true), undo: vi.fn(() => true),
redo: vi.fn(() => true), redo: vi.fn(() => true),
clearSelectedItems: vi.fn(), clearSelectedItems: vi.fn(() => {
mockSelectedItems = [];
selectionChangedCallbacks.forEach(callback => callback());
}),
addSelectedItems: vi.fn((items: Array<{ getId: () => string; select: () => void; deselect: () => void; isSelected: () => boolean }>) => {
const incomingIds = new Set(items.map(item => item.getId()));
mockSelectedItems = mockSelectedItems.filter(item => !incomingIds.has(item.getId()));
mockSelectedItems.push(...items);
selectionChangedCallbacks.forEach(callback => callback());
}),
getStatus: () => 'Ready', getStatus: () => 'Ready',
setStatus: vi.fn(), setStatus: vi.fn(),
getPlayheadPosition: () => mockPlayheadPosition, getPlayheadPosition: () => mockPlayheadPosition,
@@ -197,6 +214,12 @@ describe('projectStore piano roll state', () => {
audioStorageMocks.loadAudioFile.mockReset(); audioStorageMocks.loadAudioFile.mockReset();
toneMocks.decodeAudioData.mockReset(); toneMocks.decodeAudioData.mockReset();
toneMocks.toneBufferSet.mockReset(); toneMocks.toneBufferSet.mockReset();
mockSelectedItems = [];
mockCopiedItems = [];
selectionChangedCallbacks.length = 0;
mockCore.onSelectionChanged.mockClear();
mockCore.clearSelectedItems.mockClear();
mockCore.addSelectedItems.mockClear();
mockIsMetronomeEnabled = false; mockIsMetronomeEnabled = false;
mockShowGlobalTracks = false; mockShowGlobalTracks = false;
mockProject.setIsMetronomeEnabled.mockClear(); mockProject.setIsMetronomeEnabled.mockClear();
@@ -737,4 +760,81 @@ describe('projectStore piano roll state', () => {
expect(state.maxBars).toBe(16); expect(state.maxBars).toBe(16);
expect(state.playheadPosition).toBe(64); expect(state.playheadPosition).toBe(64);
}); });
it('selects only the newly pasted notes after pasting into the active MIDI region', async () => {
const { KGMidiTrack: TestMidiTrack } = await import('../core/track/KGMidiTrack');
const { KGMidiRegion: TestMidiRegion } = await import('../core/region/KGMidiRegion');
const { KGMidiNote: TestMidiNote } = await import('../core/midi/KGMidiNote');
const track = new TestMidiTrack('Track 1', 1, 'acoustic_grand_piano');
track.setTrackIndex(0);
const region = new TestMidiRegion('region-1', '1', 0, 'Region 1', 0, 16);
const existingSelectedNote = new TestMidiNote('existing-note', 0, 1, 60, 100);
existingSelectedNote.select();
region.addNote(existingSelectedNote);
track.setRegions([region]);
mockTracks = [track];
currentProject = {
...mockProject,
getTracks: () => mockTracks,
} as typeof mockProject;
mockSelectedItems = [existingSelectedNote];
mockCopiedItems = [
new TestMidiNote('copied-a', 2, 3, 64, 110),
new TestMidiNote('copied-b', 3, 4, 67, 120),
];
mockCore.executeCommand.mockImplementation((command: { execute: () => void }) => command.execute());
const { useProjectStore } = await import('./projectStore');
act(() => {
useProjectStore.getState().pasteNotesToActiveRegion(region.getId(), 8);
});
const pastedNotes = region.getNotes().filter(note => note.getId() !== existingSelectedNote.getId());
const pastedNoteIds = pastedNotes.map(note => note.getId());
const state = useProjectStore.getState();
expect(mockCore.executeCommand).toHaveBeenCalledTimes(1);
expect(existingSelectedNote.isSelected()).toBe(false);
expect(pastedNotes).toHaveLength(2);
expect(pastedNotes.every(note => note.isSelected())).toBe(true);
expect(mockCore.clearSelectedItems).toHaveBeenCalledTimes(1);
expect(mockCore.addSelectedItems).toHaveBeenCalledWith(pastedNotes);
expect(state.selectedNoteIds).toEqual(pastedNoteIds);
});
it('keeps selection unchanged when note paste has no clipboard notes', async () => {
const { KGMidiTrack: TestMidiTrack } = await import('../core/track/KGMidiTrack');
const { KGMidiRegion: TestMidiRegion } = await import('../core/region/KGMidiRegion');
const { KGMidiNote: TestMidiNote } = await import('../core/midi/KGMidiNote');
const track = new TestMidiTrack('Track 1', 1, 'acoustic_grand_piano');
track.setTrackIndex(0);
const region = new TestMidiRegion('region-1', '1', 0, 'Region 1', 0, 16);
const existingSelectedNote = new TestMidiNote('existing-note', 0, 1, 60, 100);
existingSelectedNote.select();
region.addNote(existingSelectedNote);
track.setRegions([region]);
mockTracks = [track];
currentProject = {
...mockProject,
getTracks: () => mockTracks,
} as typeof mockProject;
mockSelectedItems = [existingSelectedNote];
mockCopiedItems = [];
const { useProjectStore } = await import('./projectStore');
act(() => {
useProjectStore.getState().pasteNotesToActiveRegion(region.getId(), 8);
});
expect(mockCore.executeCommand).not.toHaveBeenCalled();
expect(mockCore.clearSelectedItems).not.toHaveBeenCalled();
expect(mockCore.addSelectedItems).not.toHaveBeenCalled();
expect(existingSelectedNote.isSelected()).toBe(true);
});
}); });
+21 -1
View File
@@ -1052,6 +1052,7 @@ export const useProjectStore = create<ProjectState>((set, get) => {
// Reset piano roll state for new/loaded project // Reset piano roll state for new/loaded project
KGPianoRollState.instance().setLastEditedNoteLength(1); KGPianoRollState.instance().setLastEditedNoteLength(1);
KGPianoRollState.instance().setLastEditedNoteVelocity(127);
KGPianoRollState.instance().setPianoRollZoom(projectToLoad.getPianoRollZoom()); KGPianoRollState.instance().setPianoRollZoom(projectToLoad.getPianoRollZoom());
// Add a status message // Add a status message
@@ -1904,7 +1905,26 @@ export const useProjectStore = create<ProjectState>((set, get) => {
} }
try { try {
KGCore.instance().executeCommand(command); const core = KGCore.instance();
core.executeCommand(command);
const createdNoteIds = new Set(command.getCreatedNotes().map(note => note.noteId));
const targetRegion = command.getTargetRegion();
const createdNotes = targetRegion
? targetRegion.getNotes().filter(note => createdNoteIds.has(note.getId()))
: [];
if (createdNotes.length > 0) {
core.getSelectedItems().forEach(item => {
item.deselect();
});
core.clearSelectedItems();
createdNotes.forEach(note => {
note.select();
});
core.addSelectedItems(createdNotes);
}
// Update the store to trigger re-render // Update the store to trigger re-render
const { tracks } = get(); const { tracks } = get();