Skip to content

Commit

Permalink
Generalize Diamond-mos notation to absurd scales
Browse files Browse the repository at this point in the history
ref #27
  • Loading branch information
frostburn committed May 21, 2024
1 parent b465ac2 commit f6ebbd3
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 12 deletions.
29 changes: 25 additions & 4 deletions src/__tests__/notation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import {describe, expect, it} from 'vitest';
import {generateNotation} from '../notation';
import {generateNotation, nthNominal} from '../notation';
import {dot} from 'xen-dev-utils';

describe('Diamond mos notation generator', () => {
describe('Generalized Diamond-mos nominals', () => {
it('has 17 standard nominals', () => {
expect([...Array(17).keys()].map(nthNominal).join('')).toBe(
'JKLMNOPQRSTUVWXYZ'
);
});
it('has 289 two-character nominals', () => {
for (let i = 0; i < 289; ++i) {
expect(nthNominal(17 + i)).toHaveLength(2);
}
});
it('has JJJ as the first three-character nominal', () => {
expect(nthNominal(17 + 17 * 17)).toBe('JJJ');
});
});

describe('Diamond-mos notation generator', () => {
it('generates the config for diatonic major', () => {
const notation = generateNotation('LLsLLLs');
const basic = [2, 1];
Expand Down Expand Up @@ -141,8 +157,13 @@ describe('Diamond mos notation generator', () => {
expect(scale.has('Z')).toBe(true);
});

it('rejects above nominal Z', () => {
expect(() => generateNotation('LsLsLsLsLsLsLsLsLs')).toThrow();
it('accepts above nominal Z', () => {
const {scale} = generateNotation('LsLsLsLsLsLsLsLsLs');
expect(scale.has('J')).toBe(true);
expect(scale.has('Z')).toBe(true);
expect(scale.has('JJ')).toBe(true);
expect(scale.has('JK')).toBe(false);
expect(scale.has('KJ')).toBe(false);
});

it('it rejects all L', () => {
Expand Down
52 changes: 44 additions & 8 deletions src/notation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {mosGeneratorMonzo} from './helpers';
/**
* Valid nominals for absolute pitches in [Diamond mos notation](https://en.xen.wiki/w/Diamond-mos_notation).
*/
export type DiamondMosNominal =
export type DiamondMosAlphabet =
| 'J'
| 'K'
| 'L'
Expand Down Expand Up @@ -53,7 +53,7 @@ export type DiamondMosNotation = {
/**
* Counts of [L, s] steps for every available nominal with J at unison. Add equaves to reach other octaves.
*/
scale: Map<DiamondMosNominal, MosMonzo>;
scale: Map<string, MosMonzo>;
/**
* Interval of equivalence / octave.
*/
Expand All @@ -72,6 +72,45 @@ export type DiamondMosNotation = {
brightGenerator: MosMonzo;
};

/** Single characters of valid nominals. */
export const DIAMOND_MOS_ALPHABET: DiamondMosAlphabet[] = [
'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.
*/
export function nthNominal(n: number): string {
if (n < 0 || !Number.isInteger(n)) {
throw new Error('Invalid nominal index');
}
if (n >= DIAMOND_MOS_ALPHABET.length) {
return (
nthNominal(Math.floor(n / DIAMOND_MOS_ALPHABET.length) - 1) +
DIAMOND_MOS_ALPHABET[n % DIAMOND_MOS_ALPHABET.length]
);
}
return DIAMOND_MOS_ALPHABET[n];
}

/**
* Generate configuration for [Diamond mos notation](https://en.xen.wiki/w/Diamond-mos_notation).
*
Expand All @@ -80,13 +119,13 @@ export type DiamondMosNotation = {
* @returns Configuration for notation software.
*/
export function generateNotation(mode: string): DiamondMosNotation {
const scale = new Map<DiamondMosNominal, MosMonzo>();
let code = 'J'.charCodeAt(0);
const scale = new Map<string, MosMonzo>();
let i = 0;
const monzo: MosMonzo = [0, 0];
let hasLarge = false;
let hasSmall = false;
for (const character of mode) {
scale.set(String.fromCharCode(code++) as DiamondMosNominal, [...monzo]);
scale.set(nthNominal(i++), [...monzo]);
if (character === 'L') {
monzo[0]++;
hasLarge = true;
Expand All @@ -100,9 +139,6 @@ export function generateNotation(mode: string): DiamondMosNotation {
if (!hasLarge || !hasSmall) {
throw new Error("The scale must contain both 'L' and 's' steps.");
}
if (code > 'Z'.charCodeAt(0) + 1) {
throw new Error('Out of Diamond mos nominals.');
}
const equave: MosMonzo = [...monzo];
const numPeriods = gcd(equave[0], equave[1]);
const period: MosMonzo = [equave[0] / numPeriods, equave[1] / numPeriods];
Expand Down

0 comments on commit f6ebbd3

Please sign in to comment.