From 670f4b486202b9d3e8236fb9beeba559a98315df Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Wed, 24 Apr 2024 20:46:44 +0300 Subject: [PATCH] Implement a method for accurately measuring the sizes of small commas --- src/__tests__/monzo.spec.ts | 22 +++++++++ src/commas.ts | 35 ++++++++++++++ src/monzo.ts | 95 +++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 src/commas.ts diff --git a/src/__tests__/monzo.spec.ts b/src/__tests__/monzo.spec.ts index a6bad7b..6869da1 100644 --- a/src/__tests__/monzo.spec.ts +++ b/src/__tests__/monzo.spec.ts @@ -2,6 +2,7 @@ import {describe, it, expect} from 'vitest'; import {Fraction} from '../fraction'; import { monzoToBigInt, + monzoToCents, monzoToFraction, primeLimit, toMonzo, @@ -372,3 +373,24 @@ describe('Prime limit calculator', () => { expect(primeLimit(2n ** 1025n)).toEqual(2); }); }); + +describe('Monzo size measure', () => { + it('calculates the size of the perfect fourth accurately', () => { + expect(monzoToCents([2, -1])).toBeCloseTo(498.0449991346125, 12); + }); + + it('calculates the size of the neutrino accurately', () => { + // XXX: Produces 1.6358114862669026e-10. Three digits of accuracy, smh... + expect(monzoToCents([1889, -2145, 138, 424])).toBeCloseTo( + 1.6361187484440885e-10, + 13 + ); + }); + + it('calculates the size of the demiquartervice comma accurately', () => { + expect(monzoToCents([-3, 2, -1, -1, 0, 0, -1, 0, 2])).toBeCloseTo( + 0.3636664332386927, + 14 + ); + }); +}); diff --git a/src/commas.ts b/src/commas.ts new file mode 100644 index 0000000..fe25ad2 --- /dev/null +++ b/src/commas.ts @@ -0,0 +1,35 @@ +// Mapping commas for measuring the sizes of small intervals + +export const MAPPING_COMMA_MONZOS = [ + [1], // Octave + [-1, 1], // Pure fifth + [554, -351, 1], // Quectisma + [-55, 30, 2, 1], // Nommisma + [-30, 27, -7, 0, 1], // Negative syntonoschisma / syntonisma + [9, 0, -1, 0, -3, 1], // Jacobin comma + [-2, 2, -1, -5, 0, 3, 1], // Aksial comma + [9, -3, -3, -2, 0, 0, 1, 1], // Decimillisma + [-1, -1, 0, -1, -2, 1, 1, 0, 1], // Broadviewsma +]; + +export const MAPPING_COMMA_CENTS = [ + 1200, 701.9550008653874, 0.10841011385118912, 0.1033601604170961, + -0.09303132362673557, 0.2601208102056527, 0.005150328654440726, + 0.010468503793319029, 0.34062647410756758, +]; + +// Auxiliary commas for chipping away at the 3-limit +export const SATANIC_COMMA_MONZO = [-1054, 665]; +export const SATANIC_COMMA_CENTS = 0.07557548263280008; + +export const MERCATOR_COMMA_MONZO = [-84, 53]; +export const MERCATOR_COMMA_CENTS = 3.61504586553314; + +export const PYTH_COMMA_MONZO = [-19, 12]; +export const PYTH_COMMA_CENTS = 23.46001038464901; + +export const PYTH_LIMMA_MONZO = [8, -5]; +export const PYTH_LIMMA_CENTS = 90.22499567306291; + +export const PYTH_TONE_MONZO = [-3, 2]; +export const PYTH_TONE_CENTS = 203.9100017307748; diff --git a/src/monzo.ts b/src/monzo.ts index 3880a5d..fc053c2 100644 --- a/src/monzo.ts +++ b/src/monzo.ts @@ -1,5 +1,19 @@ import {Fraction, FractionValue} from './fraction'; import {BIG_INT_PRIMES, PRIMES} from './primes'; +import { + MAPPING_COMMA_CENTS, + MAPPING_COMMA_MONZOS, + MERCATOR_COMMA_CENTS, + MERCATOR_COMMA_MONZO, + PYTH_COMMA_CENTS, + PYTH_COMMA_MONZO, + PYTH_LIMMA_CENTS, + PYTH_LIMMA_MONZO, + PYTH_TONE_CENTS, + PYTH_TONE_MONZO, + SATANIC_COMMA_CENTS, + SATANIC_COMMA_MONZO, +} from './commas'; /** * Array of integers representing the exponents of prime numbers in the unique factorization of a rational number. @@ -593,3 +607,84 @@ function bigIntToMonzo7(n: bigint): [Monzo, bigint] { } return [result, n]; } + +export function monzoToCents(monzo: Monzo) { + monzo = [...monzo]; + while (monzo.length && !monzo[monzo.length - 1]) { + monzo.pop(); + } + let result = 0; + while (monzo.length > 2) { + const component = monzo.pop()!; + result += component * MAPPING_COMMA_CENTS[monzo.length]; + const comma = MAPPING_COMMA_MONZOS[monzo.length]; + for (let i = 0; i < monzo.length; ++i) { + monzo[i] -= component * comma[i]; + } + } + if (monzo.length === 2) { + while (monzo[1] >= 665) { + result += SATANIC_COMMA_CENTS; + monzo[0] -= SATANIC_COMMA_MONZO[0]; + monzo[1] -= SATANIC_COMMA_MONZO[1]; + } + while (monzo[1] <= -665) { + result -= SATANIC_COMMA_CENTS; + monzo[0] += SATANIC_COMMA_MONZO[0]; + monzo[1] += SATANIC_COMMA_MONZO[1]; + } + + while (monzo[1] >= 53) { + result += MERCATOR_COMMA_CENTS; + monzo[0] -= MERCATOR_COMMA_MONZO[0]; + monzo[1] -= MERCATOR_COMMA_MONZO[1]; + } + while (monzo[1] <= -53) { + result -= MERCATOR_COMMA_CENTS; + monzo[0] += MERCATOR_COMMA_MONZO[0]; + monzo[1] += MERCATOR_COMMA_MONZO[1]; + } + + while (monzo[1] >= 12) { + result += PYTH_COMMA_CENTS; + monzo[0] -= PYTH_COMMA_MONZO[0]; + monzo[1] -= PYTH_COMMA_MONZO[1]; + } + while (monzo[1] <= -12) { + result -= PYTH_COMMA_CENTS; + monzo[0] += PYTH_COMMA_MONZO[0]; + monzo[1] += PYTH_COMMA_MONZO[1]; + } + + while (monzo[1] >= 5) { + result -= PYTH_LIMMA_CENTS; + monzo[0] += PYTH_LIMMA_MONZO[0]; + monzo[1] += PYTH_LIMMA_MONZO[1]; + } + while (monzo[1] <= -5) { + result += PYTH_LIMMA_CENTS; + monzo[0] -= PYTH_LIMMA_MONZO[0]; + monzo[1] -= PYTH_LIMMA_MONZO[1]; + } + + while (monzo[1] >= 2) { + result += PYTH_TONE_CENTS; + monzo[0] -= PYTH_TONE_MONZO[0]; + monzo[1] -= PYTH_TONE_MONZO[1]; + } + while (monzo[1] <= -2) { + result -= PYTH_TONE_CENTS; + monzo[0] += PYTH_TONE_MONZO[0]; + monzo[1] += PYTH_TONE_MONZO[1]; + } + } + while (monzo.length) { + const component = monzo.pop()!; + result += component * MAPPING_COMMA_CENTS[monzo.length]; + const comma = MAPPING_COMMA_MONZOS[monzo.length]; + for (let i = 0; i < monzo.length; ++i) { + monzo[i] -= component * comma[i]; + } + } + return result; +}