Files
KGStudio/src/util/chordRegionImportUtil.ts
T

131 lines
3.5 KiB
TypeScript

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,
},
};
}