From 70898d20ad49f7a34c9c7e9ab7354c2245585f68 Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Tue, 4 Jun 2024 13:07:29 +0300 Subject: [PATCH] Add support for real values in MOS declaration Fix automos infragility issues. ref #328 --- documentation/intermediate-dsl.md | 2 +- src/diamond-mos.ts | 50 +++++++++++++---------------- src/fjs.ts | 2 +- src/parser/__tests__/source.spec.ts | 20 ++++++++++++ src/parser/mos.ts | 38 ++++++++-------------- src/stdlib/builtin.ts | 4 ++- 6 files changed, 61 insertions(+), 55 deletions(-) diff --git a/documentation/intermediate-dsl.md b/documentation/intermediate-dsl.md index 40aa783b..c37f05a3 100644 --- a/documentation/intermediate-dsl.md +++ b/documentation/intermediate-dsl.md @@ -945,7 +945,7 @@ MOS { ### MOS declaration syntax The syntax for MOS declarations withing the curly brackets after `MOS` is as follows: -Below `n` is an integer, `x` and `y` are integers in the range from 1 to 9, and `expr` is any SonicWeave expression that evaluates to a radical interval. Syntax followed by a question mark is optional. +Below `n` is an integer, `x` and `y` are integers in the range from 1 to 9, and `expr` is any SonicWeave expression that evaluates to an interval. Syntax followed by a question mark is optional. | Name | Pattern | | -------------------- | ------------------------------------ | | Counts with UDP | `nL ns up\|down(period)? ?` | diff --git a/src/diamond-mos.ts b/src/diamond-mos.ts index 6b5fda9b..2bf98418 100644 --- a/src/diamond-mos.ts +++ b/src/diamond-mos.ts @@ -11,6 +11,8 @@ import { } from './pythagorean'; import {ZERO} from './utils'; +type Monzo = TimeMonzo | TimeReal; + /** * Base degree for a mosstep in a 0-indexed array. */ @@ -18,7 +20,7 @@ export type MosDegree = { /** * The perfect or neutral central interval. */ - center: TimeMonzo; + center: Monzo; /** * Flag to indicate if the degree has minor and major variants. */ @@ -26,7 +28,7 @@ export type MosDegree = { /** * The lopsided neutral variant of a bright or dark generator. */ - mid?: TimeMonzo; + mid?: Monzo; }; /** @@ -37,23 +39,23 @@ export type MosConfig = { /** * Interval of equivalence. The distance between J4 and J5. */ - equave: TimeMonzo; + equave: Monzo; /** * Period of repetition. */ - period: TimeMonzo; + period: Monzo; /** * Current value of the '&' accidental. */ - am: TimeMonzo; + am: Monzo; /** * Current value of the 'e' accidental. */ - semiam: TimeMonzo; + semiam: Monzo; /** * Relative scale from J onwards. Echelon depends on J. Use equave to reach higher octave numbers. */ - scale: Map; + scale: Map; /** * Intervals for relative notation. Use period to reach larger intervals. */ @@ -65,11 +67,11 @@ export type MosConfig = { /** * Value of the large step. */ - large: TimeMonzo; + large: Monzo; /** * Value of the small step. */ - small: TimeMonzo; + small: Monzo; }; /** @@ -125,7 +127,7 @@ export function scaleMonzos(config: MosConfig) { : a[0].localeCompare(b[0]) ); const monzos = entries.map(entry => entry[1]); - monzos.push(monzos.shift()!.mul(config.equave) as TimeMonzo); + monzos.push(monzos.shift()!.mul(config.equave)); return monzos; } @@ -135,12 +137,12 @@ export function scaleMonzos(config: MosConfig) { * @param config Result of a MOS declaration. * @returns A relative time monzo. */ -export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo { +export function mosMonzo(node: MosStep, config: MosConfig): Monzo { const baseDegree = mmod(Math.abs(node.degree), config.degrees.length); const periods = (node.degree - baseDegree) / config.degrees.length; const mosDegree = config.degrees[baseDegree]; const quality = node.quality.quality; - let inflection = new TimeMonzo(ZERO, []); + let inflection: Monzo = new TimeMonzo(ZERO, []); if ( quality === 'a' || quality === 'Â' || @@ -158,17 +160,14 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo { if (node.quality.fraction !== '') { const fraction = VULGAR_FRACTIONS.get(node.quality.fraction)!; const fractionalInflection = inflection.pow(fraction); - if (fractionalInflection instanceof TimeReal) { - throw new Error('Failed to fractionally inflect mosstep.'); - } inflection = fractionalInflection; } for (const augmentation of node.augmentations ?? []) { if (augmentation === 'd' || augmentation === 'dim') { - inflection = inflection.div(config.am) as TimeMonzo; + inflection = inflection.div(config.am); } else { - inflection = inflection.mul(config.am) as TimeMonzo; + inflection = inflection.mul(config.am); } } @@ -180,9 +179,9 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo { quality === 'aug' || quality === 'Aug' ) { - inflection = inflection.mul(config.semiam) as TimeMonzo; + inflection = inflection.mul(config.semiam); } else if (quality === 'd' || quality === 'dim') { - inflection = inflection.div(config.semiam) as TimeMonzo; + inflection = inflection.div(config.semiam); } if (quality === 'P') { throw new Error( @@ -199,9 +198,7 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo { `The mosstep ${baseDegree} does not have minor or major variants.` ); } - return mosDegree.center - .mul(inflection) - .mul(config.period.pow(periods)) as TimeMonzo; + return mosDegree.center.mul(inflection).mul(config.period.pow(periods)); } function mosInflection( @@ -234,7 +231,7 @@ function mosInflection( export function absoluteMosMonzo( node: AbsoluteMosPitch, config: MosConfig -): TimeMonzo { +): Monzo { if (!config.scale.has(node.nominal)) { throw new Error(`Nominal ${node.nominal} is unassigned.`); } @@ -244,10 +241,7 @@ export function absoluteMosMonzo( const fraction = VULGAR_FRACTIONS.get(accidental.fraction)!; const fractionalInflection = inflection.pow(fraction); - if (fractionalInflection instanceof TimeReal) { - throw new Error('Failed to fracture mos accidental.'); - } - result = result.mul(fractionalInflection) as TimeMonzo; + result = result.mul(fractionalInflection); } - return result.mul(config.equave.pow(node.octave - 4)) as TimeMonzo; + return result.mul(config.equave.pow(node.octave - 4)); } diff --git a/src/fjs.ts b/src/fjs.ts index 3fed2da0..d0c7552f 100644 --- a/src/fjs.ts +++ b/src/fjs.ts @@ -325,7 +325,7 @@ export function getInflection( } export function inflect( - pythagorean: TimeMonzo, + pythagorean: TimeMonzo | TimeReal, superscripts: FJSInflection[], subscripts: FJSInflection[] ) { diff --git a/src/parser/__tests__/source.spec.ts b/src/parser/__tests__/source.spec.ts index e9fe4563..12e8fb7f 100644 --- a/src/parser/__tests__/source.spec.ts +++ b/src/parser/__tests__/source.spec.ts @@ -1622,6 +1622,26 @@ describe('SonicWeave parser', () => { ]); }); + it('supports irrational hardness in MOS declaration', () => { + const scale = expand(` + MOS { + 5L 2s + hardness = PI + } + automos() + MOS niente + `); + expect(scale).toEqual([ + '212.8935511816436r¢', + '425.7871023632872r¢', + '638.6806535449307r¢', + '706.446775590823r¢', + '919.3403267724666r¢', + '1132.23387795411r¢', + '1200.0000000000025r¢', + ]); + }); + it('supports radicals in SOV', () => { const scale = expand(` 9/8 diff --git a/src/parser/mos.ts b/src/parser/mos.ts index add6f340..844f7801 100644 --- a/src/parser/mos.ts +++ b/src/parser/mos.ts @@ -22,10 +22,12 @@ import {Interval} from '../interval'; import {MosConfig, MosDegree} from '../diamond-mos'; import {ONE, TWO, ZERO} from '../utils'; +type Monzo = TimeMonzo | TimeReal; + const TWO_MONZO = new TimeMonzo(ZERO, [ONE]); -function realize(mosMonzo: MosMonzo, large: TimeMonzo, small: TimeMonzo) { - return large.pow(mosMonzo[0]).mul(small.pow(mosMonzo[1])) as TimeMonzo; +function realize(mosMonzo: MosMonzo, large: Monzo, small: Monzo) { + return large.pow(mosMonzo[0]).mul(small.pow(mosMonzo[1])); } /** @@ -33,10 +35,10 @@ function realize(mosMonzo: MosMonzo, large: TimeMonzo, small: TimeMonzo) { */ export class Tardigrade { subVisitor: ExpressionVisitor; - equave?: TimeMonzo; - hardness?: TimeMonzo | TimeReal; - large?: TimeMonzo; - small?: TimeMonzo; + equave?: Monzo; + hardness?: Monzo; + large?: Monzo; + small?: Monzo; pattern?: string; constructor(subVisitor: ExpressionVisitor) { @@ -56,8 +58,8 @@ export class Tardigrade { const N = new Fraction(this.pattern.length); const countL = new Fraction((this.pattern.match(/L/g) ?? []).length); const countS = N.sub(countL); - let small: TimeMonzo | TimeReal; - let large: TimeMonzo | TimeReal; + let small: Monzo; + let large: Monzo; if (this.equave) { if (this.hardness) { if (this.hardness.valueOf() === Infinity) { @@ -78,14 +80,10 @@ export class Tardigrade { } } else if (this.large) { large = this.large; - small = this.equave - .div(large.pow(countL)) - .pow(countS.inverse()) as TimeMonzo; + small = this.equave.div(large.pow(countL)).pow(countS.inverse()); } else if (this.small) { small = this.small; - large = this.equave - .div(small.pow(countS)) - .pow(countL.inverse()) as TimeMonzo; + large = this.equave.div(small.pow(countS)).pow(countL.inverse()); } else { // Assume basic small = this.equave.pow(countL.mul(TWO).add(countS).inverse()); @@ -150,15 +148,10 @@ export class Tardigrade { throw new Error('Inconsistent MOS declaration.'); } - if (large instanceof TimeReal || small instanceof TimeReal) { - throw new Error('MOS declaration must remain radical.'); - } - const am = large.div(small) as TimeMonzo; const semiam = am.sqrt() as TimeMonzo; - const r = (m: MosMonzo) => - realize(m, large as TimeMonzo, small as TimeMonzo); - const scale = notation.scale as unknown as Map; + const r = (m: MosMonzo) => realize(m, large, small); + const scale = notation.scale as unknown as Map; for (const [key, value] of notation.scale) { scale.set(key, r(value)); } @@ -263,9 +256,6 @@ export class Tardigrade { if (node.type === 'HardnessDeclaration' && value.valueOf() === Infinity) { return (this.hardness = value.value); } - if (!(value.value instanceof TimeMonzo)) { - throw new Error(`${node.type} must evaluate to a radical.`); - } switch (node.type) { case 'HardnessDeclaration': return (this.hardness = value.value); diff --git a/src/stdlib/builtin.ts b/src/stdlib/builtin.ts index d03cb996..74a4fb8f 100644 --- a/src/stdlib/builtin.ts +++ b/src/stdlib/builtin.ts @@ -1971,7 +1971,7 @@ function automos(this: ExpressionVisitor) { this.spendGas(this.rootContext.mosConfig.scale.size); const J4 = this.rootContext.C4; const monzos = scaleMonzos(this.rootContext.mosConfig).map(m => J4.mul(m)); - return monzos.map( + const result = monzos.map( (m, i) => new Interval(m, 'logarithmic', 0, { type: 'AbsoluteFJS', @@ -1987,6 +1987,8 @@ function automos(this: ExpressionVisitor) { subscripts: [], }) ); + this.rootContext.fragiles.push(...result); + return result; } automos.__doc__ = 'If the current scale is empty, generate absolute Diamond-mos notation based on the current config.';