feat: added global chord region to MIDI region conversion feature
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { Chord, Note } from 'tonal';
|
||||
import { KGChordRegion } from '../core/region/KGChordRegion';
|
||||
import { getChordMidiPitches, parseChordSymbol } from './chordUtil';
|
||||
|
||||
export const CHORD_REGION_IMPORT_MIME_TYPE = 'application/kgstudio-chord-region';
|
||||
export const CHORD_REGION_IMPORT_VELOCITY = 127;
|
||||
export const CHORD_REGION_IMPORT_REGION_NAME = 'Chord Progression';
|
||||
|
||||
export interface ChordRegionImportPayload {
|
||||
draggedRegionId: string;
|
||||
selectedRegionIds: string[];
|
||||
}
|
||||
|
||||
export interface ImportedChordMidiNoteData {
|
||||
startBeat: number;
|
||||
endBeat: number;
|
||||
pitch: number;
|
||||
velocity: number;
|
||||
}
|
||||
|
||||
export interface ChordRegionImportPlan {
|
||||
sourceRegionIds: string[];
|
||||
startBeat: number;
|
||||
lengthInBeats: number;
|
||||
notes: ImportedChordMidiNoteData[];
|
||||
}
|
||||
|
||||
export interface ChordRegionImportError {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type ChordRegionImportPlanResult =
|
||||
| { ok: true; plan: ChordRegionImportPlan }
|
||||
| { ok: false; error: ChordRegionImportError };
|
||||
|
||||
export function resolveChordRegionImportSelection(
|
||||
draggedRegionId: string,
|
||||
selectedRegionIds: string[],
|
||||
): string[] {
|
||||
return selectedRegionIds.includes(draggedRegionId)
|
||||
? selectedRegionIds
|
||||
: [draggedRegionId];
|
||||
}
|
||||
|
||||
function getBaseRootMidi(symbol: string): number | null {
|
||||
const descriptor = parseChordSymbol(symbol);
|
||||
if (!descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootLetter = descriptor.root.charAt(0).toUpperCase();
|
||||
const octave = ['F', 'G', 'A', 'B'].includes(rootLetter) ? 3 : 4;
|
||||
return Note.midi(`${descriptor.root}${octave}`);
|
||||
}
|
||||
|
||||
export function convertChordSymbolToMidiPitches(symbol: string): number[] | null {
|
||||
const descriptor = parseChordSymbol(symbol);
|
||||
if (!descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tonalChord = Chord.get(descriptor.symbol);
|
||||
if (tonalChord.empty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootMidi = getBaseRootMidi(descriptor.symbol);
|
||||
if (rootMidi === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const midiPitches = getChordMidiPitches(descriptor.symbol, rootMidi);
|
||||
if (midiPitches.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [rootMidi - 12, ...midiPitches];
|
||||
}
|
||||
|
||||
export function buildChordRegionImportPlan(
|
||||
chordRegions: KGChordRegion[],
|
||||
): ChordRegionImportPlanResult {
|
||||
if (chordRegions.length === 0) {
|
||||
return {
|
||||
ok: false,
|
||||
error: { message: 'No chord regions were available to import.' },
|
||||
};
|
||||
}
|
||||
|
||||
const sortedRegions = [...chordRegions].sort((left, right) => (
|
||||
left.getStartFromBeat() - right.getStartFromBeat()
|
||||
));
|
||||
const firstRegion = sortedRegions[0];
|
||||
const lastRegion = sortedRegions[sortedRegions.length - 1];
|
||||
const startBeat = firstRegion.getStartFromBeat();
|
||||
const endBeat = lastRegion.getStartFromBeat() + lastRegion.getLength();
|
||||
const notes: ImportedChordMidiNoteData[] = [];
|
||||
|
||||
for (const region of sortedRegions) {
|
||||
const midiPitches = convertChordSymbolToMidiPitches(region.getSymbol());
|
||||
if (!midiPitches) {
|
||||
return {
|
||||
ok: false,
|
||||
error: { message: `Unable to import chord "${region.getSymbol()}". Please update the chord symbol and try again.` },
|
||||
};
|
||||
}
|
||||
|
||||
const noteStartBeat = region.getStartFromBeat() - startBeat;
|
||||
const noteEndBeat = noteStartBeat + region.getLength();
|
||||
|
||||
midiPitches.forEach(pitch => {
|
||||
notes.push({
|
||||
startBeat: noteStartBeat,
|
||||
endBeat: noteEndBeat,
|
||||
pitch,
|
||||
velocity: CHORD_REGION_IMPORT_VELOCITY,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
plan: {
|
||||
sourceRegionIds: sortedRegions.map(region => region.getId()),
|
||||
startBeat,
|
||||
lengthInBeats: endBeat - startBeat,
|
||||
notes,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user