171 lines
2.9 KiB
TypeScript
171 lines
2.9 KiB
TypeScript
export enum Note {
|
|
'C',
|
|
'C#',
|
|
'D',
|
|
'D#',
|
|
'E',
|
|
'F',
|
|
'F#',
|
|
'G',
|
|
'G#',
|
|
'A',
|
|
'A#',
|
|
'B',
|
|
}
|
|
|
|
export enum NoteNameBehavior {
|
|
'always',
|
|
'onpress',
|
|
'onhighlight',
|
|
'onhover',
|
|
'never',
|
|
}
|
|
export enum KeyPressStyle {
|
|
'subtle',
|
|
'vivid',
|
|
}
|
|
export type HighlightedKey = {
|
|
key: PianoKey;
|
|
// if not specified, the default color for highlighted notes will be used
|
|
bgColor?: string;
|
|
};
|
|
|
|
export class PianoKey {
|
|
public note: Note;
|
|
public octave?: number;
|
|
|
|
constructor(note: Note, octave?: number) {
|
|
this.note = note;
|
|
this.octave = octave;
|
|
}
|
|
|
|
public toString = () => {
|
|
return (this.note as unknown as string) + (this.octave || '');
|
|
};
|
|
}
|
|
|
|
export const strToKey = (str: string): PianoKey => {
|
|
let note: Note;
|
|
const isSimpleNote = str[1]! >= '0' && str[1]! <= '9';
|
|
// later we need to support different annotations
|
|
|
|
switch (isSimpleNote ? str[0] : str.substring(0, 2)) {
|
|
case 'E':
|
|
note = Note.E;
|
|
break;
|
|
case 'B':
|
|
note = Note.B;
|
|
break;
|
|
case 'C':
|
|
note = Note.C;
|
|
break;
|
|
case 'D':
|
|
note = Note.D;
|
|
break;
|
|
case 'F':
|
|
note = Note.F;
|
|
break;
|
|
case 'G':
|
|
note = Note.G;
|
|
break;
|
|
case 'A':
|
|
note = Note.A;
|
|
break;
|
|
case 'C#':
|
|
note = Note['C#'];
|
|
break;
|
|
case 'D#':
|
|
note = Note['D#'];
|
|
break;
|
|
case 'F#':
|
|
note = Note['F#'];
|
|
break;
|
|
case 'G#':
|
|
note = Note['G#'];
|
|
break;
|
|
case 'A#':
|
|
note = Note['A#'];
|
|
break;
|
|
default:
|
|
throw new Error('Invalid note name');
|
|
}
|
|
if ((isSimpleNote && !str[1]) || (!isSimpleNote && str.length < 3)) {
|
|
return new PianoKey(note);
|
|
}
|
|
const octave = parseInt(str.substring(isSimpleNote ? 1 : 2));
|
|
return new PianoKey(note, octave);
|
|
};
|
|
|
|
export const keyToStr = (key: PianoKey, showOctave = true): string => {
|
|
let s = '';
|
|
switch (key.note) {
|
|
case Note.C:
|
|
s += 'C';
|
|
break;
|
|
case Note.D:
|
|
s += 'D';
|
|
break;
|
|
case Note.E:
|
|
s += 'E';
|
|
break;
|
|
case Note.F:
|
|
s += 'F';
|
|
break;
|
|
case Note.G:
|
|
s += 'G';
|
|
break;
|
|
case Note.A:
|
|
s += 'A';
|
|
break;
|
|
case Note.B:
|
|
s += 'B';
|
|
break;
|
|
case Note['C#']:
|
|
s += 'C#';
|
|
break;
|
|
case Note['D#']:
|
|
s += 'D#';
|
|
break;
|
|
case Note['F#']:
|
|
s += 'F#';
|
|
break;
|
|
case Note['G#']:
|
|
s += 'G#';
|
|
break;
|
|
case Note['A#']:
|
|
s += 'A#';
|
|
break;
|
|
default:
|
|
throw new Error('Invalid note name');
|
|
}
|
|
if (showOctave && key.octave) {
|
|
s += key.octave;
|
|
}
|
|
return s;
|
|
};
|
|
|
|
export const isAccidental = (key: PianoKey): boolean => {
|
|
return (
|
|
key.note === Note['C#'] ||
|
|
key.note === Note['D#'] ||
|
|
key.note === Note['F#'] ||
|
|
key.note === Note['G#'] ||
|
|
key.note === Note['A#']
|
|
);
|
|
};
|
|
|
|
export const octaveKeys: Array<PianoKey> = [
|
|
new PianoKey(Note.C),
|
|
new PianoKey(Note['C#']),
|
|
new PianoKey(Note.D),
|
|
new PianoKey(Note['D#']),
|
|
new PianoKey(Note.E),
|
|
new PianoKey(Note.F),
|
|
new PianoKey(Note['F#']),
|
|
new PianoKey(Note.G),
|
|
new PianoKey(Note['G#']),
|
|
new PianoKey(Note.A),
|
|
new PianoKey(Note['A#']),
|
|
new PianoKey(Note.B),
|
|
];
|