From e34adf412cb48bc66846f5de1582c3ff5f6553dc Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Sun, 12 May 2024 10:04:47 +0300 Subject: [PATCH] Implement automos --- documentation/BUILTIN.md | 3 +++ documentation/intermediate-dsl.md | 11 ++++++++++ src/context.ts | 7 ++---- src/diamond-mos.ts | 8 +++++++ src/parser/__tests__/source.spec.ts | 22 +++++++++++++++++++ src/stdlib/builtin.ts | 33 +++++++++++++++++++++++++++++ 6 files changed, 79 insertions(+), 5 deletions(-) diff --git a/documentation/BUILTIN.md b/documentation/BUILTIN.md index 95cf98d4..65faebc2 100644 --- a/documentation/BUILTIN.md +++ b/documentation/BUILTIN.md @@ -30,6 +30,9 @@ Calculate atan2(y, x) which is the angle between (1, 0) and (x, y), chosen to li ### atanXY(*x*, *y*) Calculate atanXY(x, y) = atan2(y, x) which is the angle between (1, 0) and (x, y), chosen to lie in (−π; π], positive anticlockwise. +### automos() +If the current scale is empty, generate absolute Diamond-mos notation based on the current config. + ### bleach(*interval*) Get rid of interval coloring and label. diff --git a/documentation/intermediate-dsl.md b/documentation/intermediate-dsl.md index d46d04ab..dd02b55e 100644 --- a/documentation/intermediate-dsl.md +++ b/documentation/intermediate-dsl.md @@ -922,6 +922,17 @@ The accidental `&` (read "am") raises pitch by `L - s` while its opposite `@` (r The accidental `e` (read "semiam") raises pitch by a half *am* while `a` (read "semiat") corresponingly lowest pitch by a half *at*. +#### Auto-MOS +The easiest way to generate Diamond-mos notation for one equave is to call the `automos()` helper. +```c +"Specific mode (Anti-phrygian) of specific hardness of octave-equivalent Antidiatonic" +MOS 4333433 +J4 = 263 Hz +automos() +``` + +A tool such as Scale Workshop may do this automatically as `automos()` doesn't do anything if the scale isn't empty or if `MOS` is undeclared. (It's possible to use `MOS niente` to undeclare an existing `MOS`.) + ### TAMNAMS relative intervals You can also [name relative intervals](https://en.xen.wiki/w/TAMNAMS#Naming_mos_intervals) after declaring `MOS`. diff --git a/src/context.ts b/src/context.ts index 01727004..af469d6d 100644 --- a/src/context.ts +++ b/src/context.ts @@ -2,7 +2,7 @@ import {SonicWeaveValue} from './stdlib'; import {Interval} from './interval'; import {TimeMonzo} from './monzo'; import {ZERO} from './utils'; -import {MosConfig} from './diamond-mos'; +import {MosConfig, scaleMonzos} from './diamond-mos'; import {stepString} from './words'; /** @@ -168,10 +168,7 @@ export class RootContext { lines.push(`/ = ${this.lift.toString()}`); } if (this.mosConfig) { - const entries = Array.from(this.mosConfig.scale.entries()); - entries.sort((a, b) => a[0].localeCompare(b[0])); - const monzos = entries.map(entry => entry[1]); - monzos.push(monzos.shift()!.mul(this.mosConfig.equave) as TimeMonzo); + const monzos = scaleMonzos(this.mosConfig); const ss = stepString(monzos); let large: TimeMonzo; let small: TimeMonzo; diff --git a/src/diamond-mos.ts b/src/diamond-mos.ts index 0191254e..992a7556 100644 --- a/src/diamond-mos.ts +++ b/src/diamond-mos.ts @@ -91,6 +91,14 @@ export type AbsoluteMosPitch = { octave: number; }; +export function scaleMonzos(config: MosConfig) { + const entries = Array.from(config.scale); + entries.sort((a, b) => a[0].localeCompare(b[0])); + const monzos = entries.map(entry => entry[1]); + monzos.push(monzos.shift()!.mul(config.equave) as TimeMonzo); + return monzos; +} + export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo { const baseDegree = mmod(Math.abs(node.degree), config.degrees.length); const periods = (node.degree - baseDegree) / config.degrees.length; diff --git a/src/parser/__tests__/source.spec.ts b/src/parser/__tests__/source.spec.ts index b76f2a23..434da304 100644 --- a/src/parser/__tests__/source.spec.ts +++ b/src/parser/__tests__/source.spec.ts @@ -1630,4 +1630,26 @@ describe('SonicWeave parser', () => { `); expect(scale).toEqual(['1\\8<3>', '3\\8<3>', '1\\2<3>']); }); + + it('generates automos', () => { + const scale = parseSource(` + MOS 4L 3s 3|3 + J4 = 100z + automos() + map(str) + `); + expect(scale.map(i => i.label)).toEqual([ + 'K♮4', + 'L♮4', + 'M♮4', + 'N♮4', + 'O♮4', + 'P♮4', + 'J♮5', + ]); + expect(scale.map(i => i.valueOf())).toEqual([ + 113.4312522195463, 120.80894444044476, 137.03509847201238, + 145.94801056814464, 165.55065597696213, 176.31825099920434, 200, + ]); + }); }); diff --git a/src/stdlib/builtin.ts b/src/stdlib/builtin.ts index 09418441..2a23cd26 100644 --- a/src/stdlib/builtin.ts +++ b/src/stdlib/builtin.ts @@ -16,6 +16,7 @@ import { primeFactorize, nthPrime as xduNthPrime, primeRange as xduPrimeRange, + mmod, } from 'xen-dev-utils'; import {Color, Interval, Val} from '../interval'; import { @@ -71,6 +72,7 @@ import { factorColor as pubFactorColor, compare, } from './public'; +import {MosNominal, scaleMonzos} from '../diamond-mos'; const {version: VERSION} = require('../../package.json'); // === Library === @@ -1926,6 +1928,36 @@ function keepUnique(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { keepUnique.__doc__ = 'Only keep unique intervals in the current/given scale.'; keepUnique.__node__ = builtinNode(keepUnique); +function automos(this: ExpressionVisitor) { + const scale = this.currentScale; + if (!this.rootContext?.mosConfig || scale.length) { + return; + } + const J4 = this.rootContext.C4; + const monzos = scaleMonzos(this.rootContext.mosConfig).map(m => J4.mul(m)); + return monzos.map( + (m, i) => + new Interval(m, 'logarithmic', 0, { + type: 'AbsoluteFJS', + pitch: { + type: 'AbsolutePitch', + nominal: String.fromCharCode( + 'J'.charCodeAt(0) + mmod(i + 1, monzos.length) + ) as MosNominal, + accidentals: [{accidental: '♮', fraction: ''}], + octave: i === monzos.length - 1 ? 5 : 4, + }, + ups: 0, + lifts: 0, + superscripts: [], + subscripts: [], + }) + ); +} +automos.__doc__ = + 'If the current scale is empty, generate absolute Diamond-mos notation based on the current config.'; +automos.__node__ = builtinNode(automos); + function reverse(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { scale ??= this.currentScale; scale.reverse(); @@ -2551,6 +2583,7 @@ export const BUILTIN_CONTEXT: Record = { sorted, keepUnique, uniquesOf, + automos, reverse, reversed, pop,