moment-of-symmetry
Version:
Moment of Symmetry (MOS) musical scale generation and analysis for Javascript
126 lines • 3.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateNotation = exports.nthNominal = exports.DIAMOND_MOS_ALPHABET = void 0;
const xen_dev_utils_1 = require("xen-dev-utils");
const helpers_1 = require("./helpers");
/** Single characters of valid nominals. */
exports.DIAMOND_MOS_ALPHABET = [
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
];
/**
* Obtain the 0-indexed nth generalized Diamond-mos nominal.
* @param n Index of the nominal.
* @returns A single character from J through Z or a multi-character string like 'JJ' starting from n = 17.
*/
function nthNominal(n) {
if (n < 0 || !Number.isInteger(n)) {
throw new Error('Invalid nominal index');
}
if (n >= exports.DIAMOND_MOS_ALPHABET.length) {
return (nthNominal(Math.floor(n / exports.DIAMOND_MOS_ALPHABET.length) - 1) +
exports.DIAMOND_MOS_ALPHABET[n % exports.DIAMOND_MOS_ALPHABET.length]);
}
return exports.DIAMOND_MOS_ALPHABET[n];
}
exports.nthNominal = nthNominal;
/**
* Generate configuration for [Diamond mos notation](https://en.xen.wiki/w/Diamond-mos_notation).
*
* Always based on J and 0-indexed even if scale is diatonic.
* @param mode Mode of a MOS scale such as 'LLsLLLs'.
* @returns Configuration for notation software.
*/
function generateNotation(mode) {
const scale = new Map();
let i = 0;
const monzo = [0, 0];
let hasLarge = false;
let hasSmall = false;
for (const character of mode) {
scale.set(nthNominal(i++), [...monzo]);
if (character === 'L') {
monzo[0]++;
hasLarge = true;
}
else if (character === 's') {
monzo[1]++;
hasSmall = true;
}
else {
throw new Error(`Invalid abstract step '${character}'.`);
}
}
if (!hasLarge || !hasSmall) {
throw new Error("The scale must contain both 'L' and 's' steps.");
}
const equave = [...monzo];
const numPeriods = (0, xen_dev_utils_1.gcd)(equave[0], equave[1]);
const period = [equave[0] / numPeriods, equave[1] / numPeriods];
const gen = (0, helpers_1.mosGeneratorMonzo)(period[0], period[1]);
const numUnique = period[0] + period[1];
const edoperiod = 2 * period[0] + period[1];
const basic = [
[0, [0, 0], true, undefined],
];
// Exception for nL ns
if (numUnique === 2) {
basic.push([2, [gen[0] - 0.5, gen[1] + 0.5], false, undefined]);
}
else {
// Dark mid
basic.push([
edoperiod - 2 * gen[0] - gen[1],
[period[0] - gen[0], period[1] - gen[1]],
true,
[period[0] - gen[0] + 0.5, period[1] - gen[1] - 0.5],
]);
let edostep = 2 * gen[0] + gen[1];
// Bright mid
basic.push([edostep, [...gen], true, [gen[0] - 0.5, gen[1] + 0.5]]);
monzo[0] = gen[0];
monzo[1] = gen[1];
for (let i = 2; i < numUnique - 1; ++i) {
edostep += 2 * gen[0] + gen[1];
monzo[0] += gen[0];
monzo[1] += gen[1];
while (edostep >= edoperiod) {
edostep -= edoperiod;
monzo[0] -= period[0];
monzo[1] -= period[1];
}
// Imperfect central
basic.push([edostep, [monzo[0] - 0.5, monzo[1] + 0.5], false, undefined]);
}
}
basic.sort((a, b) => a[0] - b[0]);
const degrees = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_, center, perfect, mid] of basic) {
degrees.push({ center, perfect, mid });
}
return {
scale,
degrees,
equave,
period,
brightGenerator: gen,
};
}
exports.generateNotation = generateNotation;
//# sourceMappingURL=notation.js.map