import EtudeError from "../error/EtudeError"; import CircularIterator from "../util/CircularIterator"; import CircularStream from "../util/CircularStream"; import Exceptional from "../util/Exceptional"; import MathUtil from "../util/MathUtil"; import Stream from "../util/Stream"; import Direction from "./Direction"; export default class Letter { public static size = 0; private static _values = []; /** * Values derived from General MIDI's program numbers * (https://en.wikipedia.org/wiki/General_MIDI) * and the octave convention for scientific pitch notation * (https://en.wikipedia.org/wiki/Scientific_pitch_notation) * Ex. The E (E4) above middle C (C4): * - new octaves start on C * - C4's program number is 48 * - increment that value by Letter.E's offset value (4) * - E4: 48 + 4 == 52 == E4's program number */ public static A = new Letter(9); public static B = new Letter(11); public static C = new Letter(0); public static D = new Letter(2); public static E = new Letter(4); public static F = new Letter(5); public static G = new Letter(7); private constructor(private offset: number) { ++Letter.size; Letter._values.push(this); } public static values(): Letter[] { return Letter._values.slice(); } public ordinal(): number { return Letter._values.indexOf(this); } public static valueOf(letterString: string): Letter { const letter: Letter = Letter[letterString]; if (letter instanceof Letter) { return letter; } throw EtudeError.forInvalid(Letter, letterString); } public getOffset(): number { return this.offset; } public static getLetters(direction: Direction | Letter = Direction.DEFAULT, startingLetter: Letter = Letter.A): Letter[] { if (direction instanceof Letter) { if (arguments.length !== 1) { throw EtudeError.forIllegalArgument(Letter, direction, "Invalid number of arguments"); } startingLetter = direction as Letter; direction = Direction.DEFAULT; } const letters = Letter._values.slice(); if (direction === Direction.DESCENDING) { letters.reverse(); } MathUtil.rotate(letters, letters.indexOf(startingLetter)); return letters; } public static stream(direction: Direction | Letter = Direction.DEFAULT, startingLetter: Letter = Letter.A): Stream { if (direction instanceof Letter) { if (arguments.length !== 1) { throw EtudeError.forIllegalArgument(Letter, direction, "Invalid number of arguments"); } startingLetter = direction as Letter; direction = Direction.DEFAULT; } return CircularStream.of(Letter.getLetters(direction, startingLetter)); } public static iterator(direction: Direction | Letter = Direction.DEFAULT, startingLetter: Letter = Letter.A): Iterator { if (direction instanceof Letter) { if (arguments.length !== 1) { throw EtudeError.forIllegalArgument(Letter, direction, "Invalid number of arguments"); } startingLetter = direction as Letter; direction = Direction.DEFAULT; } return CircularIterator.of(Letter.getLetters(direction, startingLetter)); } public static isValid(letterChar: string): boolean { const value = letterChar.toUpperCase().charCodeAt(0); return value >= 'A'.charCodeAt(0) && value <= 'G'.charCodeAt(0); } public static fromChar(letterChar: string): Exceptional { return Exceptional .of(letterChar) .filter(Letter.isValid, EtudeError.forInvalid(Letter, letterChar)) .map(c => Letter._values[c.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0)]); } public toString(): string { return Object.keys(Letter).find(l => Letter[l] === this); } }