diff --git a/documentation/intermediate-dsl.md b/documentation/intermediate-dsl.md index dc3c2b03..e6f64852 100644 --- a/documentation/intermediate-dsl.md +++ b/documentation/intermediate-dsl.md @@ -942,6 +942,8 @@ Below `n` is an integer, `x` and `y` are integers in the range from 1 to 9, and | Large declaration | `L = expr` | | Small declaration | `s = expr` | +It's also legal to set `hardness = inf` implying `s = 0.0c`. + ### Diamond-mos notation Once `MOS` has been declared [Diamond-mos notation](https://en.xen.wiki/w/Diamond-mos_notation) becomes available. diff --git a/src/grammars/mos.pegjs b/src/grammars/mos.pegjs index d840cc64..3c4ce8b5 100644 --- a/src/grammars/mos.pegjs +++ b/src/grammars/mos.pegjs @@ -58,7 +58,7 @@ AbstractStepPattern } SmallIntegerPattern - = pattern: [1-9]+ __ equave: RationalEquave? { + = pattern: [0-9]+ __ equave: RationalEquave? { return { type: 'IntegerPattern', pattern: pattern.map(d => parseInt(d, 10)), @@ -67,7 +67,7 @@ SmallIntegerPattern } LargeIntegerPattern - = pattern: PositiveBasicInteger|2.., _ ',' _| equave: (__ ','? __ @RationalEquave?) { + = pattern: BasicInteger|2.., _ ',' _| equave: (__ ','? __ @RationalEquave?) { return { type: 'IntegerPattern', pattern, diff --git a/src/parser/__tests__/source.spec.ts b/src/parser/__tests__/source.spec.ts index d9a671cc..b9e54f68 100644 --- a/src/parser/__tests__/source.spec.ts +++ b/src/parser/__tests__/source.spec.ts @@ -1720,4 +1720,60 @@ describe('SonicWeave parser', () => { 'P7ms', ]); }); + + it('supports infinite hardness (explicit)', () => { + const scale = expand(` + MOS { + 5L 2s + hardness = inf + } + J4 = 123 Hz + automos() + nedji + `); + expect(scale).toEqual([ + 'MOS {LLLsLLs;L=2^1/5;s=1}', + '1\\5', + '2\\5', + '3\\5', + '3\\5', + '4\\5', + '1\\1', + '1\\1', + ]); + }); + + it('supports infinite hardness (implicit)', () => { + const scale = expand(` + MOS 101010; + automos() + nedji + `); + expect(scale).toEqual([ + 'MOS {LsLsLs;L=2^1/3;s=1}', + '1\\3', + '1\\3', + '2\\3', + '2\\3', + '1\\1', + '1\\1', + ]); + }); + + it('supports infinite hardness (s=1)', () => { + const scale = expand(` + MOS {LsLsLs;L=2^1/3;s=1} + automos() + nedji + `); + expect(scale).toEqual([ + 'MOS {LsLsLs;L=2^1/3;s=1}', + '1\\3', + '1\\3', + '2\\3', + '2\\3', + '1\\1', + '1\\1', + ]); + }); }); diff --git a/src/parser/mos.ts b/src/parser/mos.ts index 0f15d2ac..61eb5b09 100644 --- a/src/parser/mos.ts +++ b/src/parser/mos.ts @@ -29,7 +29,7 @@ function realize(mosMonzo: MosMonzo, large: TimeMonzo, small: TimeMonzo) { export class Tardigrade { subVisitor: ExpressionVisitor; equave?: TimeMonzo; - hardness?: TimeMonzo; + hardness?: TimeMonzo | TimeReal; large?: TimeMonzo; small?: TimeMonzo; pattern?: string; @@ -50,17 +50,22 @@ export class Tardigrade { let large: TimeMonzo | TimeReal; if (this.equave) { if (this.hardness) { - // L /_ s = r - // L = s^r - // L^countL * s^countS = equave - // s^(countL * r + countS) = equave - small = this.equave.pow( - TimeMonzo.fromFraction(countL) - .mul(this.hardness) - .add(TimeMonzo.fromFraction(countS)) - .inverse() - ); - large = small.pow(this.hardness); + if (this.hardness.valueOf() === Infinity) { + small = this.equave.pow(0); + large = this.equave.pow(countL.inverse()); + } else { + // L /_ s = r + // L = s^r + // L^countL * s^countS = equave + // s^(countL * r + countS) = equave + small = this.equave.pow( + TimeMonzo.fromFraction(countL) + .mul(this.hardness) + .add(TimeMonzo.fromFraction(countS)) + .inverse() + ); + large = small.pow(this.hardness); + } } else if (this.large) { large = this.large; small = this.equave @@ -78,21 +83,35 @@ export class Tardigrade { } } else { if (this.hardness) { - if (this.large) { - large = this.large; - small = large.pow(this.hardness.inverse()); - } else if (this.small) { - small = this.small; - large = small.pow(this.hardness); + if (this.hardness.valueOf() === Infinity) { + if (this.large) { + large = this.large; + } else if (this.small) { + throw new Error( + 'Small step may not be given with infinite hardness.' + ); + } else { + // Assume octave + large = TWO_MONZO.pow(countL.inverse()); + } + small = large.pow(0); } else { - // Assume octave - small = TWO_MONZO.pow( - TimeMonzo.fromFraction(countL) - .mul(this.hardness) - .add(TimeMonzo.fromFraction(countS)) - .inverse() - ); - large = small.pow(this.hardness); + if (this.large) { + large = this.large; + small = large.pow(this.hardness.inverse()); + } else if (this.small) { + small = this.small; + large = small.pow(this.hardness); + } else { + // Assume octave + small = TWO_MONZO.pow( + TimeMonzo.fromFraction(countL) + .mul(this.hardness) + .add(TimeMonzo.fromFraction(countS)) + .inverse() + ); + large = small.pow(this.hardness); + } } } else if (this.large) { large = this.large; @@ -188,7 +207,11 @@ export class Tardigrade { const small = Math.min(...node.pattern); const large = Math.max(...node.pattern); this.pattern = node.pattern.map(i => (i === small ? 's' : 'L')).join(''); - this.hardness = TimeMonzo.fromFraction(new Fraction(large, small)); + if (small) { + this.hardness = TimeMonzo.fromFraction(new Fraction(large, small)); + } else { + this.hardness = TimeReal.fromValue(Infinity); + } if (node.equave) { this.equave = this.visitRationalEquave(node.equave); } @@ -235,6 +258,9 @@ export class Tardigrade { if (!(value instanceof Interval)) { throw new Error(`${node.type} must evaluate to an interval.`); } + 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.`); }