mosfez-xen-types
Version:
Basic xen number types for microtonal scale representation and manipulation with TypeScript / JavaScript.
163 lines (161 loc) • 4.07 kB
JavaScript
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