import EtudeError from "../error/EtudeError"; import EtudeParser from "../util/EtudeParser"; import Exceptional from "../util/Exceptional"; class Value { constructor(private type: Value.Type, private dotCount: number = 0, private duration: number = type.getBaseDuration() * Math.pow(1.5, dotCount)) { } public dotted(): Value { return new Value(this.type, this.dotCount + 1, this.duration * 1.5); } public undotted(): Value { return new Value(this.type, this.dotCount - 1, this.duration / 1.5); } public tuplet(n: number): Value { if (this.type === Value.Type.TWO_HUNDRED_FIFTY_SIXTH) { throw EtudeError.forIllegalState(Value, "etude does not support value types smaller than " + this.type); } return new Value(Value.Type.values()[this.type.ordinal() + 1], 0, this.duration / n); } public triplet(): Value { return this.tuplet(3); } public static fromString(valueString: string): Exceptional { return EtudeParser .of(valueString) .filter(o => o != null, EtudeError.forNull(Value)) .parse(s => { // longest prefix that contains only letters or . const prefix: string = valueString.match("^[a-zA-Z.]+")[0]; const dotIndex: number = prefix.indexOf('.'); let typeString: string; let dotCount: number; if (dotIndex === -1) { typeString = prefix; dotCount = 0; } else { typeString = prefix.substring(0, dotIndex); dotCount = prefix.length - dotIndex; } const type: Exceptional = Value.Type.fromString(typeString); if (!type.isPresent()) { return Exceptional.empty(); } // a number that has an open parentheses somewhere before it const durationString: string = valueString.match(/\([0-9.]+/g)[0]; const duration: number = durationString != null ? parseFloat(durationString.substring(1)) : type.get().getBaseDuration() * Math.pow(1.5, dotCount); return Exceptional.of(new Value(type.get(), dotCount, duration)); }) .get(a => a[0] as Value); } public toString(): string { return `${this.type}${".".repeat(this.dotCount)}(${this.duration})`; } public getType(): Value.Type { return this.type; } public getDotCount(): number { return this.dotCount; } public getDuration(): number { return this.duration; } } module Value { export class Type { public static size = 0; private static _values = []; public static DOUBLE_WHOLE = new Type(); public static WHOLE = new Type(); public static HALF = new Type(); public static QUARTER = new Type(); public static EIGHTH = new Type(); public static SIXTEENTH = new Type(); public static THIRTY_SECOND = new Type(); public static SIXTY_FOURTH = new Type(); public static HUNDRED_TWENTY_EIGHTH = new Type(); public static TWO_HUNDRED_FIFTY_SIXTH = new Type(); private constructor() { ++Type.size; Type._values.push(this); } public static values(): Type[] { return Type._values.slice(); } public ordinal(): number { return Type._values.indexOf(this); } public static valueOf(typeString: string): Type { const type: Type = Type[typeString]; if (type instanceof Type) { return type; } throw EtudeError.forInvalid(Type, typeString); } public toString(): string { return Object.keys(Type).find(t => Type[t] === this); } public static fromBaseDuration(baseDuration: number): Type { const ordinal: number = 1 - Math.log(baseDuration) / Math.log(2); if (ordinal % 1 !== 0) { throw EtudeError.forInvalid(Type, baseDuration, "should be a power of 2"); } // n | 0 truncates to int return Type.values()[ordinal | 0]; } public static fromString(typeString: string): Exceptional { return EtudeParser .of(typeString) .filter(o => o != null, EtudeError.forNull(Type)) .parse(s => { const value = Type .values() .find(t => s.localeCompare(t.toString()) === 0); return Exceptional .ofNullable(value) .withException(EtudeError.forInvalid(Type, s)); }) .get(a => a[0] as Type); } public getBaseDuration(): number { return Math.pow(2, 1 - this.ordinal()); } } export const DOUBLE_WHOLE = new Value(Type.DOUBLE_WHOLE); export const WHOLE = new Value(Type.WHOLE); export const HALF = new Value(Type.HALF); export const QUARTER = new Value(Type.QUARTER); export const EIGHTH = new Value(Type.EIGHTH); export const SIXTEENTH = new Value(Type.SIXTEENTH); export const THIRTY_SECOND = new Value(Type.THIRTY_SECOND); export const SIXTY_FOURTH = new Value(Type.SIXTY_FOURTH); export const HUNDRED_TWENTY_EIGHTH = new Value(Type.HUNDRED_TWENTY_EIGHTH); export const TWO_HUNDRED_FIFTY_SIXTH = new Value(Type.TWO_HUNDRED_FIFTY_SIXTH); } export default Value;