diff --git a/documentation/BUILTIN.md b/documentation/BUILTIN.md index 657f9245..852be8de 100644 --- a/documentation/BUILTIN.md +++ b/documentation/BUILTIN.md @@ -366,6 +366,9 @@ Prepend an interval at the beginning of the current/given scale. ### warn(*...args*) Print the arguments to the console with "warning" emphasis. +### wilsonHeight(*interval*) +Calculate the Wilson height of the interval. Sum of prime absolute factors with repetition.. + ### withEquave(*val*, *equave*) Change the equave of the val. @@ -420,10 +423,10 @@ Calculate the geometric difference of two intervals on a circle. Calculate the geometric distance of two intervals on a circle. ### coalesce(*tolerance = 3.5*, *action = "simplest"*, *preserveBoundary = false*, *scale = $$*) -Coalesce intervals in the current/given scale separated by `tolerance` (default 3.5 cents) into one. `action` is one of 'simplest', 'lowest', 'highest', 'avg', 'havg' or 'geoavg' defaulting to 'simplest'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. +Coalesce intervals in the current/given scale separated by `tolerance` (default 3.5 cents) into one. `action` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg' defaulting to 'simplest'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. ### coalesced(*tolerance = 3.5*, *action = "simplest"*, *preserveBoundary = false*, *scale = $$*) -Obtain a copy of the current/given scale where groups of intervals separated by `tolerance` are coalesced into one. `action` is one of 'simplest', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. +Obtain a copy of the current/given scale where groups of intervals separated by `tolerance` are coalesced into one. `action` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. ### colorsOf(*scale = $$*) Obtain an array of colors of the current/given scale. diff --git a/package-lock.json b/package-lock.json index 90a796b6..95e94217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "moment-of-symmetry": "^0.4.2", - "xen-dev-utils": "^0.4.0" + "xen-dev-utils": "^0.5.0" }, "bin": { "sonic-weave": "bin/sonic-weave.js" @@ -4444,9 +4444,9 @@ } }, "node_modules/xen-dev-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.4.0.tgz", - "integrity": "sha512-wABrZPEIx09lk3bDf40gB8G1YrUNo9luqQmDViCkxjmzvkyjgwm5pw0Kl/QWyI1f/tAA+sBZxEIjJvWPI2VUbw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.5.0.tgz", + "integrity": "sha512-6dRxq8xfVzDOqhitBJPQr2iVHtn4aXEJ8twGGkMCaR25S/MRqB8R/7xOMIH3VqN/cGmgRitibHEYOwnV0o78cg==", "engines": { "node": ">=10.6.0" }, diff --git a/package.json b/package.json index be47ba1f..215a1710 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "moment-of-symmetry": "^0.4.2", - "xen-dev-utils": "^0.4.0" + "xen-dev-utils": "^0.5.0" }, "engines": { "node": ">=12.0.0" diff --git a/scripts/builtin-docs.ts b/scripts/builtin-docs.ts index f4062546..faa43cbd 100644 --- a/scripts/builtin-docs.ts +++ b/scripts/builtin-docs.ts @@ -1,9 +1,9 @@ import {stdout} from 'process'; -import {getSourceVisitor} from '../src/parser'; +import {getGlobalVisitor} from '../src/parser'; import {SonicWeaveFunction} from '../src/stdlib'; import {expressionToString} from '../src/ast'; -const visitor = getSourceVisitor(); +const visitor = getGlobalVisitor(); stdout.write('# SonicWeave standard library\n'); stdout.write( diff --git a/src/parser/__tests__/expression.spec.ts b/src/parser/__tests__/expression.spec.ts index fc62ae4a..6df4ae19 100644 --- a/src/parser/__tests__/expression.spec.ts +++ b/src/parser/__tests__/expression.spec.ts @@ -2262,4 +2262,14 @@ describe('Poor grammar / Fun with "<"', () => { expect(oof[0].valueOf()).toBe(1); expect(oof[1].a.valueOf()).toBe(2); }); + + it('can measure the Wilson complexity of 11/8', () => { + const {fraction} = parseSingle('wilsonHeight(11/8)'); + expect(fraction).toBe('17'); + }); + + it('can measure the Wilson complexity of 1073741820', () => { + const {fraction} = parseSingle('wilsonHeight(1073741820)'); + expect(fraction).toBe((2 + 2 + 3 + 5 + 29 + 43 + 113 + 127).toString()); + }); }); diff --git a/src/parser/__tests__/stdlib.spec.ts b/src/parser/__tests__/stdlib.spec.ts index 79fb1350..f6afae19 100644 --- a/src/parser/__tests__/stdlib.spec.ts +++ b/src/parser/__tests__/stdlib.spec.ts @@ -1308,6 +1308,11 @@ describe('SonicWeave standard library', () => { expect(scale).toEqual(['4096/4095', '3/2', '4095/2048', '2/1']); }); + it('coalesces based on wilson height', () => { + const scale = expand('70/69;37/36;P5;2;coalesce(23., "wilson")'); + expect(scale).toEqual(['70/69', 'P5', '2']); + }); + it('calculates Euler-Fokker genus 444', () => { const scale = expand('eulerGenus(444)'); expect(scale).toEqual(['37/32', '3/2', '111/64', '2']); diff --git a/src/parser/__tests__/vector-broadcasting.spec.ts b/src/parser/__tests__/vector-broadcasting.spec.ts index 03dd966b..20a836f6 100644 --- a/src/parser/__tests__/vector-broadcasting.spec.ts +++ b/src/parser/__tests__/vector-broadcasting.spec.ts @@ -320,6 +320,7 @@ describe('SonicWeave vector broadcasting', () => { 'JIP', 'PrimeMapping(1200., 1902.)', 'tenneyHeight', + 'wilsonHeight', 'floor', 'round', 'trunc', diff --git a/src/stdlib/builtin.ts b/src/stdlib/builtin.ts index 1604251b..e7e1c89d 100644 --- a/src/stdlib/builtin.ts +++ b/src/stdlib/builtin.ts @@ -53,6 +53,7 @@ import { absolute as pubAbsolute, relative as pubRelative, tenneyHeight as pubTenney, + wilsonHeight as pubWilson, track as pubTrack, sort as pubSort, repr as pubRepr, @@ -1372,6 +1373,21 @@ tenneyHeight.__doc__ = 'Calculate the Tenney height of the interval. Natural logarithm of numerator times denominator.'; tenneyHeight.__node__ = builtinNode(tenneyHeight); +// TODO: Wilson in cosJIP, nthPrime, primeRange, primeMonzo +function wilsonHeight( + this: ExpressionVisitor, + interval: SonicWeaveValue +): SonicWeaveValue { + requireParameters({interval}); + if (typeof interval === 'boolean' || interval instanceof Interval) { + return pubWilson.bind(this)(interval); + } + return unaryBroadcast.bind(this)(interval, wilsonHeight.bind(this)); +} +wilsonHeight.__doc__ = + 'Calculate the Wilson height of the interval. Sum of prime absolute factors with repetition..'; +wilsonHeight.__node__ = builtinNode(wilsonHeight); + function gcd( this: ExpressionVisitor, x: SonicWeaveValue, @@ -2277,6 +2293,7 @@ export const BUILTIN_CONTEXT: Record = { JIP, PrimeMapping, tenneyHeight, + wilsonHeight, gcd, lcm, hasConstantStructure: hasConstantStructure_, diff --git a/src/stdlib/prelude.ts b/src/stdlib/prelude.ts index ae06e035..dab5df00 100644 --- a/src/stdlib/prelude.ts +++ b/src/stdlib/prelude.ts @@ -939,7 +939,7 @@ riff randomVaried(amount, varyEquave = false, scale = $$) { riff coalesced(tolerance = 3.5, action = 'simplest', preserveBoundary = false, scale = $$) { "Obtain a copy of the current/given scale where groups of intervals separated by \`tolerance\` are coalesced into one.\\ - \`action\` is one of 'simplest', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'.\\ + \`action\` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'.\\ If \`preserveBoundary\` is \`true\` intervals close to unison and the equave are not eliminated."; if (not scale) return []; @@ -957,6 +957,9 @@ riff coalesced(tolerance = 3.5, action = 'simplest', preserveBoundary = false, s havg(...group); } else if (action === 'geoavg') { geoavg(...group); + } else if (action === 'wilson') { + sort(group, (a, b) => wilsonHeight(a) - wilsonHeight(b)); + group[0]; } else { sort(group, (a, b) => tenneyHeight(a) - tenneyHeight(b)); group[0]; @@ -980,7 +983,7 @@ riff coalesced(tolerance = 3.5, action = 'simplest', preserveBoundary = false, s riff coalesce(tolerance = 3.5, action = 'simplest', preserveBoundary = false, scale = $$) { "Coalesce intervals in the current/given scale separated by \`tolerance\` (default 3.5 cents) into one. \\ - \`action\` is one of 'simplest', 'lowest', 'highest', 'avg', 'havg' or 'geoavg' defaulting to 'simplest'.\\ + \`action\` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg' defaulting to 'simplest'.\\ If \`preserveBoundary\` is \`true\` intervals close to unison and the equave are not eliminated."; $ = scale; scale = $[..]; diff --git a/src/stdlib/public.ts b/src/stdlib/public.ts index b963d7b5..9d83635e 100644 --- a/src/stdlib/public.ts +++ b/src/stdlib/public.ts @@ -1,10 +1,14 @@ /** * Exported builtins without vectorization complications. */ -import {Fraction, LOG_PRIMES} from 'xen-dev-utils'; +import { + Fraction, + tenneyHeight as xduTenney, + wilsonHeight as xduWilson, +} from 'xen-dev-utils'; import {Color, Interval, Val} from '../interval'; import {type ExpressionVisitor} from '../parser/expression'; -import {NEGATIVE_ONE, TWO} from '../utils'; +import {FRACTION_PRIMES, NEGATIVE_ONE, TWO} from '../utils'; import {SonicWeavePrimitive, SonicWeaveValue, upcastBool} from './runtime'; import {TimeMonzo, TimeReal} from '../monzo'; @@ -213,14 +217,38 @@ export function tenneyHeight( return new Interval(TimeReal.fromValue(Infinity), 'linear'); } const height = - Math.log(monzo.residual.n * monzo.residual.d) + - monzo.primeExponents.reduce( - (total, pe, i) => total + Math.abs(pe.valueOf()) * LOG_PRIMES[i], - 0 - ); + xduTenney(monzo.residual) + + xduTenney(monzo.primeExponents.map(pe => pe.valueOf())); return new Interval(TimeReal.fromValue(height), 'linear'); } +/** + * Calculate the Wilson height of the interval. Sum of prime absolute factors with repetition. + * @param this {@link ExpressionVisitor} instance providing context for the height of absolute intervals. + * @param interval Interval to measure. + * @returns Relative linear interval representing the Wilson height. + */ +export function wilsonHeight( + this: ExpressionVisitor, + interval: Interval | boolean +): Interval { + const monzo = relative.bind(this)(upcastBool(interval)).value; + if (monzo instanceof TimeReal) { + return new Interval(TimeReal.fromValue(Infinity), 'linear'); + } + const resHeight = xduWilson(monzo.residual); + if (resHeight === Infinity) { + return new Interval(TimeReal.fromValue(Infinity), 'linear'); + } + let result = new Fraction(resHeight); + const pe = monzo.primeExponents; + for (let i = 0; i < pe.length; ++i) { + result = result.add(pe[i].abs().mul(FRACTION_PRIMES[i])); + } + + return new Interval(TimeMonzo.fromFraction(result), 'linear'); +} + /** * Attach a tracking ID to the interval. * @param this {@link ExpressionVisitor} instance providing context for the next tracking ID.