feat: added global chord region to MIDI region conversion feature

This commit is contained in:
Xiaohan-Tian
2026-05-25 16:25:43 -07:00
parent e49b675985
commit 8aa36d2368
12 changed files with 941 additions and 16 deletions
+1
View File
@@ -29,6 +29,7 @@ export { PasteRegionsCommand } from './region/PasteRegionsCommand';
export { UpdateRegionCommand, type RegionUpdateProperties } from './region/UpdateRegionCommand';
export { ImportAudioCommand } from './region/ImportAudioCommand';
export { ImportMidiClipCommand } from './region/ImportMidiClipCommand';
export { ImportChordRegionsCommand } from './region/ImportChordRegionsCommand';
export { ImportStemsCommand } from './region/ImportStemsCommand';
export type { StemImportEntry } from './region/ImportStemsCommand';
export { SplitRegionCommand } from './region/SplitRegionCommand';
@@ -0,0 +1,61 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ImportChordRegionsCommand } from './ImportChordRegionsCommand';
import { createMockMidiTrack } from '../../../test/utils/mock-data';
const track = createMockMidiTrack({ id: 1, regions: [] });
track.setTrackIndex(0);
vi.mock('../../KGCore', () => ({
KGCore: {
instance: () => ({
getCurrentProject: () => ({
getTracks: () => [track],
}),
}),
},
}));
describe('ImportChordRegionsCommand', () => {
beforeEach(() => {
track.setRegions([]);
});
it('creates one MIDI region with imported notes and supports undo', () => {
const command = new ImportChordRegionsCommand(
'1',
0,
8,
6,
[
{ startBeat: 0, endBeat: 4, pitch: 48, velocity: 127 },
{ startBeat: 0, endBeat: 4, pitch: 60, velocity: 127 },
{ startBeat: 4, endBeat: 6, pitch: 41, velocity: 127 },
{ startBeat: 4, endBeat: 6, pitch: 53, velocity: 127 },
],
'Chord Progression',
'imported-region',
);
command.execute();
expect(track.getRegions()).toHaveLength(1);
const region = command.getCreatedRegion();
expect(region?.getId()).toBe('imported-region');
expect(region?.getStartFromBeat()).toBe(8);
expect(region?.getLength()).toBe(6);
expect(region?.getNotes().map(note => ({
startBeat: note.getStartBeat(),
endBeat: note.getEndBeat(),
pitch: note.getPitch(),
velocity: note.getVelocity(),
}))).toEqual([
{ startBeat: 0, endBeat: 4, pitch: 48, velocity: 127 },
{ startBeat: 0, endBeat: 4, pitch: 60, velocity: 127 },
{ startBeat: 4, endBeat: 6, pitch: 41, velocity: 127 },
{ startBeat: 4, endBeat: 6, pitch: 53, velocity: 127 },
]);
command.undo();
expect(track.getRegions()).toHaveLength(0);
});
});
@@ -0,0 +1,88 @@
import { KGCommand } from '../KGCommand';
import { KGCore } from '../../KGCore';
import { KGMidiRegion } from '../../region/KGMidiRegion';
import { KGMidiNote } from '../../midi/KGMidiNote';
import { generateUniqueId } from '../../../util/miscUtil';
import {
CHORD_REGION_IMPORT_REGION_NAME,
type ImportedChordMidiNoteData,
} from '../../../util/chordRegionImportUtil';
export class ImportChordRegionsCommand extends KGCommand {
private trackId: string;
private trackIndex: number;
private regionId: string;
private regionName: string;
private startBeat: number;
private lengthInBeats: number;
private notes: ImportedChordMidiNoteData[];
private createdRegion: KGMidiRegion | null = null;
constructor(
trackId: string,
trackIndex: number,
startBeat: number,
lengthInBeats: number,
notes: ImportedChordMidiNoteData[],
regionName: string = CHORD_REGION_IMPORT_REGION_NAME,
regionId?: string,
) {
super();
this.trackId = trackId;
this.trackIndex = trackIndex;
this.startBeat = startBeat;
this.lengthInBeats = lengthInBeats;
this.notes = notes;
this.regionId = regionId ?? generateUniqueId('KGMidiRegion');
this.regionName = regionName;
}
execute(): void {
const track = KGCore.instance().getCurrentProject().getTracks().find(
candidate => candidate.getId().toString() === this.trackId,
);
if (!track) {
throw new Error(`Track ${this.trackId} not found`);
}
this.createdRegion = new KGMidiRegion(
this.regionId,
this.trackId,
this.trackIndex,
this.regionName,
this.startBeat,
this.lengthInBeats,
);
this.notes.forEach(noteData => {
this.createdRegion?.addNote(new KGMidiNote(
generateUniqueId('KGMidiNote'),
noteData.startBeat,
noteData.endBeat,
noteData.pitch,
noteData.velocity,
));
});
track.addRegion(this.createdRegion);
}
undo(): void {
const track = KGCore.instance().getCurrentProject().getTracks().find(
candidate => candidate.getId().toString() === this.trackId,
);
if (!track) {
throw new Error(`Track ${this.trackId} not found during undo`);
}
track.removeRegion(this.regionId);
}
getDescription(): string {
return `Import chord progression "${this.regionName}"`;
}
getCreatedRegion(): KGMidiRegion | null {
return this.createdRegion;
}
}