moment-of-symmetry
Version:
Moment of Symmetry (MOS) musical scale generation and analysis for Javascript
664 lines • 27 kB
JavaScript
import { getHardness } from './hardness.js';
import { tamnamsInfo, modeName } from './names.js';
import { bjorklund, bjorklundStr, mosGeneratorMonzo } from './helpers.js';
import { fareyInterior } from 'xen-dev-utils/core';
import { Fraction, gcd, mmod } from 'xen-dev-utils/fraction';
import { dot } from 'xen-dev-utils/number-array';
export * from './hardness.js';
export * from './names.js';
export * from './generator-ratio.js';
export * from './info.js';
export * from './notation.js';
/**
* Produce an array of booleans that is mixed as evenly as possible.
* @param numberOfTrue Number of true elements
* @param numberOfFalse Number of false elements
* @returns The array of evenly mixed booleans
*/
export function euclid(numberOfTrue, numberOfFalse) {
return bjorklund(numberOfTrue, numberOfFalse, true, false);
}
/**
* Obtain the bright generator of the MOS scale expressed as multipliers of the size of the large and small steps.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @returns An array of [number of large steps in the bright generator, number of small steps in the bright generator].
*/
export function brightGeneratorMonzo(numberOfLargeSteps, numberOfSmallSteps) {
const numPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
return [
...mosGeneratorMonzo(numberOfLargeSteps / numPeriods, numberOfSmallSteps / numPeriods),
];
}
function getDown(options, period, numPeriods) {
let down = 0;
if (options.up !== undefined) {
down = period * numPeriods - numPeriods - options.up;
if (options.down !== undefined && down !== options.down) {
throw new Error('Incompatible up and down with the scale size');
}
}
else if (options.down !== undefined) {
down = options.down;
}
if (down < 0) {
throw new Error('Down must not be negative');
}
if (down >= period * numPeriods) {
throw new Error('Up must not be negative');
}
if (down % numPeriods !== 0) {
throw new Error('Up/down must be divisible by the number of periods');
}
return down;
}
function mergeParentMembership(existing, incoming) {
return (existing ?? incoming) ? true : incoming;
}
function mergeDaughterLabel(existing, incoming) {
if (existing === undefined || existing === incoming) {
return incoming;
}
return 'both';
}
/**
* Obtain an abstract string like 'LLsLLLs' corresponding to the mode specified.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @param options Options for brightness of the scale.
* @returns String with the given number of 'L' and 's' characters in the specified mode.
*/
export function stepString(numberOfLargeSteps, numberOfSmallSteps, options) {
if (!numberOfLargeSteps) {
return 's'.repeat(numberOfSmallSteps);
}
if (!numberOfSmallSteps) {
return 'L'.repeat(numberOfLargeSteps);
}
options ?? (options = {});
const brightest = bjorklundStr(numberOfLargeSteps, numberOfSmallSteps);
let mode = brightest;
const modes = [];
while (true) {
modes.push(mode);
mode = mode.slice(1) + mode[0];
if (mode === brightest) {
break;
}
}
// Lexicographic order corresponds to brightness.
modes.sort();
const numPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = (numberOfLargeSteps + numberOfSmallSteps) / numPeriods;
const brightGeneratorsDown = getDown(options, period, numPeriods);
return modes[brightGeneratorsDown / numPeriods];
}
/**
* Generate MOS pattern as a subset of an EDO.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @param options Options for sizes of the steps and brightness of the scale.
* @returns An array of integers representing the EDO subset. The 0 degree is not included, but the final degree representing the size of the EDO is.
*/
export function mos(numberOfLargeSteps, numberOfSmallSteps, options) {
const abstract = stepString(numberOfLargeSteps, numberOfSmallSteps, options);
const sizeOfLargeStep = options?.sizeOfLargeStep ?? 2;
const sizeOfSmallStep = options?.sizeOfSmallStep ?? 1;
let step = 0;
const result = [];
for (const character of abstract) {
if (character === 'L') {
step += sizeOfLargeStep;
}
else {
step += sizeOfSmallStep;
}
result.push(step);
}
return result;
}
/**
* Generate MOS pattern as a subset of an EDO with parent MOS relationship indicated.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @param options Options for sizes of the steps, brightness of the scale and flat/sharp relationship.
* @returns A map of integers representing the EDO subset to booleans indicating if the scale degree belongs to the parent MOS or not.
* The 0 degree is not included, but the final degree representing the size of the EDO is.
*/
export function mosWithParent(numberOfLargeSteps, numberOfSmallSteps, options) {
options ?? (options = {});
const numPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = (numberOfLargeSteps + numberOfSmallSteps) / numPeriods;
const sizeOfLargeStep = options.sizeOfLargeStep ?? 2;
const sizeOfSmallStep = options.sizeOfSmallStep ?? 1;
const brightGeneratorsDown = getDown(options, period, numPeriods);
const l = numberOfLargeSteps / numPeriods;
const s = numberOfSmallSteps / numPeriods;
const d = brightGeneratorsDown / numPeriods;
const p = l * sizeOfLargeStep + s * sizeOfSmallStep;
const gMonzo = mosGeneratorMonzo(l, s);
const g = gMonzo[0] * sizeOfLargeStep + gMonzo[1] * sizeOfSmallStep;
const parentPeriod = Math.max(l, s);
const base = new Map();
for (let i = 0; i < period; ++i) {
let isParent;
if (options.accidentals === 'flat') {
isParent = period - i <= parentPeriod;
}
else {
isParent = i < parentPeriod;
}
const degree = mmod((i - d) * g, p);
base.set(degree, mergeParentMembership(base.get(degree), isParent));
}
const edoDegrees = [...base.keys()].sort((a, b) => a - b);
let result = new Map();
for (let i = 0; i < numPeriods; ++i) {
edoDegrees.forEach(degree => {
const key = degree + i * p;
result = result.set(key, mergeParentMembership(result.get(key), base.get(degree)));
});
}
const rootIsParent = result.get(0);
result.delete(0);
result.set(numPeriods * p, rootIsParent);
return result;
}
/**
* Generate a daughter MOS as a subset of an EDO while labeling each degree by its relationship to the parent MOS.
* @param numberOfLargeSteps Number of large steps in the parent MOS.
* @param numberOfSmallSteps Number of small steps in the parent MOS.
* @param options Options for sizes of the steps, brightness of the scale, and daughter accidental labeling.
* @returns A map of EDO degrees to labels indicating whether a degree is in the parent MOS (`'parent'`) or belongs to the daughter as `'flat'`, `'sharp'`, or `'both'`.
* The 0 degree is not included, but the final degree representing the size of the EDO is.
*/
export function mosWithDaughter(numberOfLargeSteps, numberOfSmallSteps, options) {
options ?? (options = {});
const numPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = (numberOfLargeSteps + numberOfSmallSteps) / numPeriods;
const sizeOfLargeStep = options.sizeOfLargeStep ?? 2;
const sizeOfSmallStep = options.sizeOfSmallStep ?? 1;
const brightGeneratorsDown = getDown(options, period, numPeriods);
const l = numberOfLargeSteps / numPeriods;
const s = numberOfSmallSteps / numPeriods;
const d = brightGeneratorsDown / numPeriods;
const p = l * sizeOfLargeStep + s * sizeOfSmallStep;
const gMonzo = mosGeneratorMonzo(l, s);
const g = gMonzo[0] * sizeOfLargeStep + gMonzo[1] * sizeOfSmallStep;
const daughterPeriod = 2 * l + s;
const base = new Map();
for (let i = 0; i < period; ++i) {
const degree = mmod((i - d) * g, p);
base.set(degree, mergeDaughterLabel(base.get(degree), 'parent'));
}
const accs = options.accidentals ?? 'sharp';
if (accs === 'flat' || (accs === 'both' && sizeOfLargeStep > 2)) {
for (let i = period - daughterPeriod; i < 0; ++i) {
const degree = mmod((i - d) * g, p);
base.set(degree, mergeDaughterLabel(base.get(degree), 'flat'));
}
}
if (accs === 'sharp' || accs === 'both') {
const acc = sizeOfLargeStep === 2 ? 'both' : 'sharp';
for (let i = period; i < daughterPeriod; ++i) {
const degree = mmod((i - d) * g, p);
base.set(degree, mergeDaughterLabel(base.get(degree), acc));
}
}
const edoDegrees = [...base.keys()].sort((a, b) => a - b);
let result = new Map();
for (let i = 0; i < numPeriods; ++i) {
edoDegrees.forEach(degree => {
const key = degree + i * p;
result = result.set(key, mergeDaughterLabel(result.get(key), base.get(degree)));
});
}
const rootIsParent = result.get(0);
result.delete(0);
result.set(numPeriods * p, rootIsParent);
return result;
}
/**
* Information about the modes of a MOS scale.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @param extraNames If true adds extra mode names in parenthesis such as Ionian (Major).
* @returns An array of mode information, ordered from darkest to brightest.
*/
export function mosModes(numberOfLargeSteps, numberOfSmallSteps, extraNames = false) {
const numberOfPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = (numberOfLargeSteps + numberOfSmallSteps) / numberOfPeriods;
const l = numberOfLargeSteps / numberOfPeriods;
const s = numberOfSmallSteps / numberOfPeriods;
const p = l * 2 + s;
const gMonzo = mosGeneratorMonzo(l, s);
const g = gMonzo[0] * 2 + gMonzo[1];
const result = [];
for (let u = 0; u < period; ++u) {
const base = [];
for (let i = 0; i < period; ++i) {
base.push(mmod((u - i) * g, p));
}
base.sort((a, b) => a - b);
let scale = base;
for (let i = 1; i < numberOfPeriods; ++i) {
scale = scale.concat(base.map(s => s + i * p));
}
scale.push(numberOfPeriods * p);
let pattern = '';
for (let i = 1; i < scale.length; ++i) {
if (scale[i] - scale[i - 1] === 2) {
pattern += 'L';
}
else {
pattern += 's';
}
}
const modeName_ = modeName(pattern, extraNames);
let udp = `${u * numberOfPeriods}|${(period - 1 - u) * numberOfPeriods}`;
if (numberOfPeriods > 1) {
udp += `(${numberOfPeriods})`;
}
result.push({
period,
numberOfPeriods,
udp,
mode: pattern,
modeName: modeName_,
});
}
return result;
}
/**
* Information about a mode of a MOS scale.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @param options Options for brightness of the scale and for adding extra names like Ionian (Major).
* @returns Information about the selected mode.
*/
export function modeInfo(numberOfLargeSteps, numberOfSmallSteps, options) {
options ?? (options = {});
const numberOfPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = (numberOfLargeSteps + numberOfSmallSteps) / numberOfPeriods;
const brightGeneratorsDown = getDown(options, period, numberOfPeriods);
const scale = mos(numberOfLargeSteps, numberOfSmallSteps, options);
scale.unshift(0);
let pattern = '';
for (let i = 1; i < scale.length; ++i) {
if (scale[i] - scale[i - 1] === 2) {
pattern += 'L';
}
else {
pattern += 's';
}
}
const modeName_ = modeName(pattern, options.extraNames);
const brightGeneratorsUp = (period - 1) * numberOfPeriods - brightGeneratorsDown;
let udp = `${brightGeneratorsUp}|${brightGeneratorsDown}`;
if (numberOfPeriods > 1) {
udp += `(${numberOfPeriods})`;
}
return {
period,
numberOfPeriods,
udp,
mode: pattern,
modeName: modeName_,
};
}
/**
* Split a string like "5L 2s" into [5, 2].
* @param mosPattern MOS pattern such as "5L 2s".
* @returns A pair of integers representing the number of large and small steps.
*/
export function splitMosPattern(mosPattern) {
const [l, s] = mosPattern.split('L');
const numberOfLargeSteps = parseInt(l.trim(), 10);
const numberOfSmallSteps = parseInt(s.split('s')[0].trim(), 10);
return [numberOfLargeSteps, numberOfSmallSteps];
}
export function parentMos(patternOrLarge, numberOfSmallSteps) {
let numberOfLargeSteps;
if (typeof patternOrLarge === 'string') {
[numberOfLargeSteps, numberOfSmallSteps] = splitMosPattern(patternOrLarge);
}
else {
numberOfLargeSteps = patternOrLarge;
if (typeof numberOfSmallSteps !== 'number') {
throw new Error('Number of small steps must be given');
}
}
// Calculate the parent's size.
const size = Math.max(numberOfLargeSteps, numberOfSmallSteps);
numberOfLargeSteps = Math.min(numberOfLargeSteps, numberOfSmallSteps);
numberOfSmallSteps = size - numberOfLargeSteps;
const mosPattern = `${numberOfLargeSteps}L ${numberOfSmallSteps}s`;
const info = {
size,
numberOfLargeSteps,
numberOfSmallSteps,
mosPattern,
};
Object.assign(info, tamnamsInfo(mosPattern));
return info;
}
/**
* Obtain detailed information about a MOS scale embedded in an EDO.
* @param numberOfLargeSteps Number of large steps in the MOS pattern.
* @param numberOfSmallSteps Number of small steps in the MOS pattern.
* @param sizeOfLargeStep Size of the large step in EDO steps.
* @param sizeOfSmallStep Size of the small step in EDO steps.
* @returns Information about the MOS scale, including generators, period data, and hardness.
*/
export function mosScaleInfo(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep = 2, sizeOfSmallStep = 1) {
const mosPattern = `${numberOfLargeSteps}L ${numberOfSmallSteps}s`;
const numberOfPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const edo = numberOfLargeSteps * sizeOfLargeStep + numberOfSmallSteps * sizeOfSmallStep;
const period = edo / numberOfPeriods;
const periodMonzo = [
numberOfLargeSteps / numberOfPeriods,
numberOfSmallSteps / numberOfPeriods,
];
const brightGeneratorMonzo = mosGeneratorMonzo(...periodMonzo);
const brightGenerator = dot(brightGeneratorMonzo, [
sizeOfLargeStep,
sizeOfSmallStep,
]);
const info = {
numberOfLargeSteps,
numberOfSmallSteps,
sizeOfLargeStep,
sizeOfSmallStep,
edo,
numberOfPeriods,
period,
brightGenerator,
darkGenerator: period - brightGenerator,
periodMonzo,
brightGeneratorMonzo,
mosPattern,
hardness: getHardness(sizeOfLargeStep, sizeOfSmallStep),
};
Object.assign(info, tamnamsInfo(mosPattern));
return info;
}
/**
* Calculate the daughter MOS implied by a parent MOS and its step sizes.
* @param numberOfLargeSteps Number of large steps in the parent MOS.
* @param numberOfSmallSteps Number of small steps in the parent MOS.
* @param sizeOfLargeStep Size of the parent MOS large step in EDO steps.
* @param sizeOfSmallStep Size of the parent MOS small step in EDO steps.
* @returns Information about the daughter MOS scale.
*/
export function daughterMos(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep, sizeOfSmallStep) {
const size = numberOfLargeSteps + numberOfSmallSteps;
if (sizeOfLargeStep >= 2 * sizeOfSmallStep) {
numberOfSmallSteps = size;
sizeOfLargeStep -= sizeOfSmallStep;
}
else {
numberOfSmallSteps = numberOfLargeSteps;
numberOfLargeSteps = size;
const temp = sizeOfSmallStep;
sizeOfSmallStep = sizeOfLargeStep - sizeOfSmallStep;
sizeOfLargeStep = temp;
}
return mosScaleInfo(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep, sizeOfSmallStep);
}
// One entry in the EDO map for each hardness class
const STEP_SIZES = [
[2, 1], // basic
[3, 2], // soft
[3, 1], // hard
[4, 3], // supersoft
[4, 1], // superhard
[5, 3], // semisoft
[5, 2], // semihard
[5, 4], // ultrasoft
[5, 1], // ultrahard
[7, 5], // parasoft
[7, 4], // minisoft
[7, 3], // minihard
[7, 2], // parahard
[8, 5], // quasisoft
[8, 3], // quasihard
];
/**
* Construct a mapping from EDO size to supported MOS scales.
* @param maxSize Maximum size of the MOS patterns to include.
* @returns A mapping from EDO size to an array of information about the supported MOS scales.
*/
export function makeEdoMap(maxSize = 12) {
const result = new Map();
STEP_SIZES.forEach(([sizeOfLargeStep, sizeOfSmallStep]) => {
const hardness = getHardness(sizeOfLargeStep, sizeOfSmallStep);
for (let size = 2; size <= maxSize; ++size) {
for (let numberOfLargeSteps = 1; numberOfLargeSteps < size; ++numberOfLargeSteps) {
const numberOfSmallSteps = size - numberOfLargeSteps;
const mosPattern = `${numberOfLargeSteps}L ${numberOfSmallSteps}s`;
const edo = numberOfLargeSteps * sizeOfLargeStep +
numberOfSmallSteps * sizeOfSmallStep;
const info = {
mosPattern,
numberOfLargeSteps,
numberOfSmallSteps,
sizeOfLargeStep,
sizeOfSmallStep,
hardness,
};
Object.assign(info, tamnamsInfo(mosPattern));
const infos = result.get(edo) || [];
infos.push(info);
result.set(edo, infos);
}
}
});
return result;
}
const STEP_COUNTS = [
[5, 2], // diatonic
[4, 3], // smitonic
[3, 4], // mosh
[2, 5], // antidiatonic
[3, 5], // sensoid
[5, 3], // oneirotonic
[6, 2], // echinoid
[2, 6], // antiechinoid
[4, 2], // lemon
[2, 4], // antilemon
[5, 1], // machinoid
[2, 3], // pentic
[3, 2], // antipentic
[1, 4], // machinoid (subset)
[1, 3], // manic
[1, 2], // happy
[2, 1], // grumpy
[1, 1], // trivial
];
/**
* Find a MOS scale supported by the given EDO.
* @param edo Size of the EDO.
* @returns Information about the supported MOS scale.
*/
export function anyForEdo(edo) {
if (edo <= 1) {
throw new Error('Minimum size is 2');
}
if (edo === 2) {
return {
mosPattern: '1L 1s',
numberOfLargeSteps: 1,
numberOfSmallSteps: 1,
sizeOfLargeStep: 1,
sizeOfSmallStep: 1,
edo,
numberOfPeriods: 1,
period: edo,
brightGenerator: 1,
darkGenerator: 1,
periodMonzo: [1, 1],
brightGeneratorMonzo: [1, 0],
hardness: 'equalized',
name: 'trivial',
subset: false,
};
}
for (let i = 0; i < STEP_COUNTS.length; ++i) {
const [numberOfLargeSteps, numberOfSmallSteps] = STEP_COUNTS[i];
let sizeOfLargeStep = 2;
while (true) {
const largePart = sizeOfLargeStep * numberOfLargeSteps;
const smallPart = edo - largePart;
if (smallPart <= 0) {
break;
}
if (smallPart % numberOfSmallSteps === 0) {
const sizeOfSmallStep = smallPart / numberOfSmallSteps;
if (sizeOfLargeStep <= 3 * sizeOfSmallStep &&
3 * sizeOfSmallStep <= 2 * sizeOfLargeStep) {
const mosPattern = `${numberOfLargeSteps}L ${numberOfSmallSteps}s`;
const hardness = getHardness(sizeOfLargeStep, sizeOfSmallStep);
const numberOfPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = edo / numberOfPeriods;
const periodMonzo = [
numberOfLargeSteps / numberOfPeriods,
numberOfSmallSteps / numberOfPeriods,
];
const brightGeneratorMonzo = mosGeneratorMonzo(...periodMonzo);
const brightGenerator = dot(brightGeneratorMonzo, [
sizeOfLargeStep,
sizeOfSmallStep,
]);
const info = {
mosPattern,
numberOfLargeSteps,
numberOfSmallSteps,
sizeOfLargeStep,
sizeOfSmallStep,
edo,
numberOfPeriods,
period,
brightGenerator,
darkGenerator: period - brightGenerator,
periodMonzo,
brightGeneratorMonzo,
hardness,
};
Object.assign(info, tamnamsInfo(mosPattern));
return info;
}
}
sizeOfLargeStep++;
}
}
throw new Error(`Failed to find MOS pattern for ${edo}`);
}
/**
* Find all MOS scales supported by the given EDO within the given constraints.
* @param edo Size of the EDO.
* @param minSize Minimum size of a MOS scale in the result.
* @param maxSize Maximum size of a MOS scale in the result.
* @param maxHardness Maximum hardness of the step ratio L/s.
* @returns Array of information about the supported MOS scales.
*/
export function allForEdo(edo, minSize = 2, maxSize, maxHardness) {
if (maxSize === undefined) {
maxSize = edo;
}
if (minSize < 2) {
throw new Error('Minimum size must be at least 2');
}
if (maxSize > edo) {
throw new Error(`Maximum size must be smaller or equal to edo (${edo})`);
}
const result = [];
for (let numberOfLargeSteps = 1; numberOfLargeSteps < maxSize; ++numberOfLargeSteps) {
for (let numberOfSmallSteps = Math.max(1, minSize - numberOfLargeSteps); numberOfSmallSteps <= maxSize - numberOfLargeSteps; numberOfSmallSteps++) {
for (const hardness of fareyInterior(edo - numberOfSmallSteps)) {
const { n: sizeOfSmallStep, d: sizeOfLargeStep } = hardness;
if (maxHardness && sizeOfLargeStep > sizeOfSmallStep * maxHardness) {
continue;
}
if (numberOfLargeSteps * sizeOfLargeStep +
numberOfSmallSteps * sizeOfSmallStep ===
edo) {
const mosPattern = `${numberOfLargeSteps}L ${numberOfSmallSteps}s`;
const hardness = getHardness(sizeOfLargeStep, sizeOfSmallStep);
const numberOfPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
const period = edo / numberOfPeriods;
const periodMonzo = [
numberOfLargeSteps / numberOfPeriods,
numberOfSmallSteps / numberOfPeriods,
];
const brightGeneratorMonzo = mosGeneratorMonzo(...periodMonzo);
const brightGenerator = dot(brightGeneratorMonzo, [
sizeOfLargeStep,
sizeOfSmallStep,
]);
const info = {
mosPattern,
numberOfLargeSteps,
numberOfSmallSteps,
sizeOfLargeStep,
sizeOfSmallStep,
hardness,
edo,
numberOfPeriods,
period,
brightGenerator,
darkGenerator: period - brightGenerator,
periodMonzo,
brightGeneratorMonzo,
};
Object.assign(info, tamnamsInfo(mosPattern));
result.push(info);
}
}
}
}
return result;
}
/**
* Find the ranges of all (equally tempered) fractions of the equave that span MOS scales.
* @param size Size of the scales to consider.
* @param includeMultiPeriods Include scales that split the equave into multiple periods.
* @returns Information about the ranges of generator that span MOS. Ranges are grouped by period and otherwise sorted in ascending order.
*/
export function generatorRanges(size, includeMultiPeriods = false) {
const result = [];
for (let numberOfLargeSteps = 1; numberOfLargeSteps < size; numberOfLargeSteps++) {
const numberOfSmallSteps = size - numberOfLargeSteps;
const numPeriods = gcd(numberOfLargeSteps, numberOfSmallSteps);
if (!includeMultiPeriods && numPeriods !== 1) {
continue;
}
const period = new Fraction(1, numPeriods);
const monzo = mosGeneratorMonzo(numberOfLargeSteps / numPeriods, numberOfSmallSteps / numPeriods);
// Collapsed endpoint
let lowerBound = new Fraction(monzo[0], numberOfLargeSteps);
// Equalized endpoint
let upperBound = new Fraction(monzo[0] + monzo[1], size);
if (lowerBound.compare(upperBound) > 0) {
[lowerBound, upperBound] = [upperBound, lowerBound];
}
result.push({
period,
lowerBound,
upperBound,
numberOfLargeSteps,
numberOfSmallSteps,
bright: true,
});
result.push({
period,
lowerBound: period.sub(upperBound),
upperBound: period.sub(lowerBound),
numberOfLargeSteps,
numberOfSmallSteps,
bright: false,
});
}
result.sort((a, b) => a.period.compare(b.period) || a.lowerBound.compare(b.lowerBound));
return result;
}
//# sourceMappingURL=core.js.map