93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { KEY_SIGNATURE_MAP } from '../constants/coreConstants';
|
|
import type { KeySignature } from '../core/KGProject';
|
|
|
|
type KeyQuality = 'major' | 'minor';
|
|
|
|
interface KeySignatureSlotDefinition {
|
|
id: string;
|
|
majorKeys: KeySignature[];
|
|
minorKeys: KeySignature[];
|
|
}
|
|
|
|
export interface KeySignatureCircleItem {
|
|
keySignature: KeySignature;
|
|
label: string;
|
|
quality: KeyQuality;
|
|
}
|
|
|
|
export interface KeySignatureCircleSlot {
|
|
id: string;
|
|
angleDeg: number;
|
|
outerItems: KeySignatureCircleItem[];
|
|
innerItems: KeySignatureCircleItem[];
|
|
accidentalLabel: string;
|
|
}
|
|
|
|
const CIRCLE_SLOT_DEFINITIONS: KeySignatureSlotDefinition[] = [
|
|
{ id: 'c', majorKeys: ['C major'], minorKeys: ['A minor'] },
|
|
{ id: 'g', majorKeys: ['G major'], minorKeys: ['E minor'] },
|
|
{ id: 'd', majorKeys: ['D major'], minorKeys: ['B minor'] },
|
|
{ id: 'a', majorKeys: ['A major'], minorKeys: ['F# minor'] },
|
|
{ id: 'e', majorKeys: ['E major'], minorKeys: ['C# minor'] },
|
|
{ id: 'b-cb', majorKeys: ['B major', 'Cb major'], minorKeys: ['G# minor', 'Ab minor'] },
|
|
{ id: 'gb-fsharp', majorKeys: ['Gb major', 'F# major'], minorKeys: ['Eb minor', 'D# minor'] },
|
|
{ id: 'db-csharp', majorKeys: ['Db major', 'C# major'], minorKeys: ['Bb minor', 'A# minor'] },
|
|
{ id: 'ab', majorKeys: ['Ab major'], minorKeys: ['F minor'] },
|
|
{ id: 'eb', majorKeys: ['Eb major'], minorKeys: ['C minor'] },
|
|
{ id: 'bb', majorKeys: ['Bb major'], minorKeys: ['G minor'] },
|
|
{ id: 'f', majorKeys: ['F major'], minorKeys: ['D minor'] },
|
|
];
|
|
|
|
function assertKnownKeySignature(keySignature: KeySignature): KeySignature {
|
|
if (!(keySignature in KEY_SIGNATURE_MAP)) {
|
|
throw new Error(`Unknown key signature in circle layout: ${keySignature}`);
|
|
}
|
|
|
|
return keySignature;
|
|
}
|
|
|
|
function getLabelForKeySignature(keySignature: KeySignature): string {
|
|
const [tonic, quality] = keySignature.split(' ');
|
|
return quality === 'major' ? tonic : tonic.toLowerCase();
|
|
}
|
|
|
|
function formatAccidentalCount(keySignatures: KeySignature[]): string {
|
|
const segments = keySignatures.map((keySignature) => {
|
|
const entry = KEY_SIGNATURE_MAP[keySignature];
|
|
if (entry.sharps > 0) {
|
|
return `${entry.sharps}♯`;
|
|
}
|
|
if (entry.flats > 0) {
|
|
return `${entry.flats}♭`;
|
|
}
|
|
return 'natural';
|
|
});
|
|
|
|
return [...new Set(segments)].join(' / ');
|
|
}
|
|
|
|
export function buildKeySignatureCircleSlots(): KeySignatureCircleSlot[] {
|
|
const totalSlots = CIRCLE_SLOT_DEFINITIONS.length;
|
|
|
|
return CIRCLE_SLOT_DEFINITIONS.map((slot, index) => {
|
|
const outerItems = slot.majorKeys.map((keySignature) => ({
|
|
keySignature: assertKnownKeySignature(keySignature),
|
|
label: getLabelForKeySignature(keySignature),
|
|
quality: 'major' as const,
|
|
}));
|
|
const innerItems = slot.minorKeys.map((keySignature) => ({
|
|
keySignature: assertKnownKeySignature(keySignature),
|
|
label: getLabelForKeySignature(keySignature),
|
|
quality: 'minor' as const,
|
|
}));
|
|
|
|
return {
|
|
id: slot.id,
|
|
angleDeg: -90 + (360 / totalSlots) * index,
|
|
outerItems,
|
|
innerItems,
|
|
accidentalLabel: formatAccidentalCount(slot.majorKeys),
|
|
};
|
|
});
|
|
}
|