From 5141f00d8c119f9abf8510481486a4ae3f71e779 Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Thu, 2 May 2024 08:53:02 +0300 Subject: [PATCH] Implement conversion between arrays, monzos and vals ref #248 --- documentation/BUILTIN.md | 9 +++ src/parser/__tests__/expression.spec.ts | 23 ++++++++ src/stdlib/builtin.ts | 75 ++++++++++++++++++++++++- 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/documentation/BUILTIN.md b/documentation/BUILTIN.md index 9b7c40b7..9674763b 100644 --- a/documentation/BUILTIN.md +++ b/documentation/BUILTIN.md @@ -249,6 +249,9 @@ Obtain the argument with the minimum value. ### monzo(*interval*) Convert interval to a prime count vector a.k.a. monzo. +### monzoFromPrimeArray(*primeExponents*) +Convert an array of prime counts to a monzo. + ### mosSubset(*numberOfLargeSteps*, *numberOfSmallSteps*, *sizeOfLargeStep*, *sizeOfSmallStep*, *up*, *down*) Calculate a subset of equally tempered degrees with maximum variety two per scale degree. @@ -360,6 +363,9 @@ Access the nth template argument when using the `sw` tag inside JavaScript. ### tenneyHeight(*interval*) Calculate the Tenney height of the interval. Natural logarithm of numerator times denominator. +### toPrimeArray(*interval*) +Convert interval to an array of its prime counts. + ### track(*interval*) Attach a tracking ID to the interval. @@ -375,6 +381,9 @@ Obtain a copy of the current/given scale with only unique intervals kept. ### unshift(*interval*, *scale = $$*) Prepend an interval at the beginning of the current/given scale. +### valFromPrimeArray(*primeExponents*, *equave*) +Convert an array of prime mapping entries to a val. + ### warn(*...args*) Print the arguments to the console with "warning" emphasis. diff --git a/src/parser/__tests__/expression.spec.ts b/src/parser/__tests__/expression.spec.ts index ab17fa23..d879f890 100644 --- a/src/parser/__tests__/expression.spec.ts +++ b/src/parser/__tests__/expression.spec.ts @@ -2283,4 +2283,27 @@ describe('Poor grammar / Fun with "<"', () => { ); expect(interval.domain).toBe('logarithmic'); }); + + it('can convert 81/80 to a prime count array', () => { + const counts = evaluate('toPrimeArray(81/80)') as Interval[]; + expect(counts.map(i => i.toInteger())).toEqual([-4, 4, -1]); + }); + + it('can convert [-4, 4, -1] to a monzo', () => { + const {interval, fraction} = parseSingle( + 'monzoFromPrimeArray([-4, 4, -1])' + ); + expect(fraction).toBe('81/80'); + expect(interval.toString()).toBe('[-4 4 -1>'); + }); + + it('can convert 12@.5 to a prime mapping array', () => { + const map = evaluate('toPrimeArray(12@.5)') as Interval[]; + expect(map.map(i => i.toInteger())).toEqual([12, 19, 28]); + }); + + it('can convert [12, 19, 28] to a val', () => { + const val = evaluate('valFromPrimeArray([12, 19, 28])') as Val; + expect(val.toString()).toBe('<12 19 28]'); + }); }); diff --git a/src/stdlib/builtin.ts b/src/stdlib/builtin.ts index 29161c56..58e6b544 100644 --- a/src/stdlib/builtin.ts +++ b/src/stdlib/builtin.ts @@ -38,7 +38,7 @@ import { fractionToVectorComponent, integerToVectorComponent, } from '../expression'; -import {TWO, ZERO} from '../utils'; +import {ONE, TWO, ZERO} from '../utils'; import {stepString, stepSignature as wordsStepSignature} from '../words'; import {hasConstantStructure} from '../tools'; import { @@ -1561,6 +1561,76 @@ lcm.__doc__ = 'Obtain the smallest (linear) interval that shares both arguments as multiplicative factors. Applies to the current scale if not arguments are given.'; lcm.__node__ = builtinNode(lcm); +function toPrimeArray( + this: ExpressionVisitor, + interval: SonicWeaveValue +): SonicWeaveValue { + requireParameters({interval}); + if (typeof interval === 'boolean' || interval instanceof Interval) { + const monzo = upcastBool(interval).value; + if (monzo instanceof TimeReal) { + throw new Error('Unable to convert irrational value to vector.'); + } + if (!monzo.residual.isUnity()) { + throw new Error('Interval too complex to convert to vector.'); + } + const pe = [...monzo.primeExponents]; + while (pe.length && !pe[pe.length - 1].n) { + pe.pop(); + } + return pe.map(e => Interval.fromFraction(e)); + } else if (interval instanceof Val) { + return interval.value.primeExponents.map(pe => Interval.fromFraction(pe)); + } + return unaryBroadcast.bind(this)(interval, toPrimeArray.bind(this)); +} +toPrimeArray.__doc__ = 'Convert interval to an array of its prime counts.'; +toPrimeArray.__node__ = builtinNode(toPrimeArray); + +function monzoFromPrimeArray( + this: ExpressionVisitor, + primeExponents: Interval[] +) { + if (!Array.isArray(primeExponents)) { + throw new Error('An array is required.'); + } + const value = new TimeMonzo( + ZERO, + primeExponents.map(i => upcastBool(i).toFraction()) + ); + return new Interval(value, 'logarithmic', 0, value.asMonzoLiteral()); +} +monzoFromPrimeArray.__doc__ = 'Convert an array of prime counts to a monzo.'; +monzoFromPrimeArray.__node__ = builtinNode(monzoFromPrimeArray); + +function valFromPrimeArray( + this: ExpressionVisitor, + primeExponents: Interval[], + equave?: Interval +) { + if (!Array.isArray(primeExponents)) { + throw new Error('An array is required.'); + } + const value = new TimeMonzo( + ZERO, + primeExponents.map(i => upcastBool(i).toFraction()) + ); + let equaveMonzo: TimeMonzo; + if (equave === undefined) { + equaveMonzo = new TimeMonzo(ZERO, [ONE]); + } else { + equave = upcastBool(equave); + if (equave.value instanceof TimeReal) { + throw new Error('Irrational equaves not supported'); + } + equaveMonzo = equave.value.clone(); + } + return new Val(value, equaveMonzo); +} +valFromPrimeArray.__doc__ = + 'Convert an array of prime mapping entries to a val.'; +valFromPrimeArray.__node__ = builtinNode(valFromPrimeArray); + function hasConstantStructure_(this: ExpressionVisitor, scale?: Interval[]) { scale ??= this.currentScale; this.spendGas(scale.length * scale.length); @@ -2435,6 +2505,9 @@ export const BUILTIN_CONTEXT: Record = { wilsonHeight, gcd, lcm, + toPrimeArray, + monzoFromPrimeArray, + valFromPrimeArray, hasConstantStructure: hasConstantStructure_, stepString: stepString_, str,