UNPKG

mosfez-xen-types

Version:

Basic xen number types for microtonal scale representation and manipulation with TypeScript / JavaScript.

163 lines (161 loc) 4.07 kB
const isNumber = (input) => typeof input === "number"; const isString = (input) => typeof input === "string"; const stringToNumber = (str) => { const result = Number(str); if (isNaN(result) || str === "") { throw new Error(`"${str}" is not a number`); } return result; }; const toDecimal = (value, places) => { const m = 10 ** places; return Math.round(value * m) / m; }; const toDecimalString = (value, places) => { return `${toDecimal(value, places)}`.replace(/(?:\.0+|(\.\d+?)0+)$/, "$1"); }; class Cents { value = 0; constructor(input) { if (isNumber(input)) { this.value = input; } else if (isString(input)) { this.value = Cents.parse(input); } else if (input instanceof Ed2) { this.value = Cents.fromEd2(input); } else if (input instanceof Ratio) { this.value = Cents.fromRatio(input); } } static parse(str) { const trimmed = str.trim().replace(/c$/g, ""); return stringToNumber(trimmed); } static fromEd2(ed22) { return Math.log2(ed22.toMultiplier()) * 1200; } static fromRatio(ratio2) { return Math.log2(ratio2.numerator / ratio2.denominator) * 1200; } toString() { return `${this.value}c`; } toDecimal(places) { return toDecimal(this.value, places); } toDecimalString(places) { return `${toDecimalString(this.value, places)}c`; } toScl(places) { const str = toDecimalString(this.value, places); return str.indexOf(".") === -1 ? `${str}.` : str; } toMultiplier() { return 2 ** (this.value / 1200); } } class Ratio { numerator = 1; denominator = 1; constructor(a, b) { if (isNumber(a) || b !== void 0) { this.numerator = a; this.denominator = b; } else { const [n, d] = Ratio.parse(a); this.numerator = n; this.denominator = d; } } static parse(str) { const split = str.trim().split("/"); if (split.length > 2) throw new Error(`ratio "${str}" must contain a single slash (/)`); const n = stringToNumber(split[0]); const d = split.length > 1 ? stringToNumber(split[1]) : 1; return [n, d]; } get value() { return [this.numerator, this.denominator]; } toMultiplier() { return this.numerator / this.denominator; } toString() { return `${this.numerator}/${this.denominator}`; } toScl() { return this.toString(); } } class Ed2 { steps = 1; divisions = 1; constructor(a, b) { if (isNumber(a) || b !== void 0) { this.steps = a; this.divisions = b; } else { const [s, d] = Ed2.parse(a); this.steps = s; this.divisions = d; } } static parse(str) { const split = str.trim().split("\\"); if (split.length !== 2) throw new Error(`ed2 "${str}" must contain a single backslash (\\)`); const s = stringToNumber(split[0]); const d = stringToNumber(split[1]); return [s, d]; } get value() { return [this.steps, this.divisions]; } toString() { return `${this.steps}\\${this.divisions}`; } toScl(places) { return cents(this).toScl(places); } toMultiplier() { return 2 ** (this.steps / this.divisions); } } function cents(a) { if (a instanceof Cents) return a; return new Cents(a); } function ratio(a, b) { if (a instanceof Ratio) return a; return new Ratio(a, b); } function ed2(a, b) { if (a instanceof Ed2) return a; return new Ed2(a, b); } const parse = (input) => { input = input.trim(); const catchAndError = (cb) => { try { return cb(); } catch (e) { return "invalid"; } }; if (input === "" || "!#".indexOf(input[0]) !== -1) { return "ignore"; } if (input.indexOf("\\") !== -1) { return catchAndError(() => ed2(input)); } if (input.indexOf(".") !== -1 || input.slice(-1) === "c") { return catchAndError(() => cents(input)); } return catchAndError(() => ratio(input)); }; export { Cents, Ed2, Ratio, cents, ed2, isNumber, isString, parse, ratio, stringToNumber, toDecimal, toDecimalString }; //# sourceMappingURL=xen-types.mjs.map