Skip to content

Commit

Permalink
Implement conversion between arrays, monzos and vals
Browse files Browse the repository at this point in the history
ref #248
  • Loading branch information
frostburn committed May 2, 2024
1 parent 6b69940 commit 5141f00
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 1 deletion.
9 changes: 9 additions & 0 deletions documentation/BUILTIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand Down
23 changes: 23 additions & 0 deletions src/parser/__tests__/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 [email protected] to a prime mapping array', () => {
const map = evaluate('toPrimeArray([email protected])') 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]');
});
});
75 changes: 74 additions & 1 deletion src/stdlib/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2435,6 +2505,9 @@ export const BUILTIN_CONTEXT: Record<string, Interval | SonicWeaveFunction> = {
wilsonHeight,
gcd,
lcm,
toPrimeArray,
monzoFromPrimeArray,
valFromPrimeArray,
hasConstantStructure: hasConstantStructure_,
stepString: stepString_,
str,
Expand Down

0 comments on commit 5141f00

Please sign in to comment.