diff --git a/package-lock.json b/package-lock.json index fbd0207e..93d0f8c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "moment-of-symmetry": "^0.8.1", - "xen-dev-utils": "^0.9.0" + "xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#fix-decimal-fracs" }, "bin": { "sonic-weave": "bin/sonic-weave.js" @@ -4493,8 +4493,8 @@ }, "node_modules/xen-dev-utils": { "version": "0.9.0", - "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.9.0.tgz", - "integrity": "sha512-JsbXSg1zXaBoiKI19p2jC8Ka22YADQsTBD7fc2FkVxLWSdCO5BCS5KcRquDP5vP6J9v8t3B14G/7GZ5DC73rzg==", + "resolved": "git+ssh://git@github.com/xenharmonic-devs/xen-dev-utils.git#ee7b651f8fe4477ca003251243b508fec4210131", + "license": "MIT", "engines": { "node": ">=10.6.0" }, diff --git a/package.json b/package.json index 40a670dd..a5bf525d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "moment-of-symmetry": "^0.8.1", - "xen-dev-utils": "^0.9.0" + "xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#fix-decimal-fracs" }, "engines": { "node": ">=12.0.0" diff --git a/src/monzo.ts b/src/monzo.ts index d650ba05..e7ac7b4d 100644 --- a/src/monzo.ts +++ b/src/monzo.ts @@ -1827,7 +1827,14 @@ export class TimeMonzo { } const vector = []; for (let i = 0; i < other.primeExponents.length; ++i) { - vector.push(this.primeExponents[i].add(other.primeExponents[i])); + try { + vector.push(this.primeExponents[i].add(other.primeExponents[i])); + } catch { + return new TimeReal( + this.timeExponent.valueOf() + other.timeExponent.valueOf(), + this.valueOf() * other.valueOf() + ); + } } try { const residual = this.residual.mul(other.residual); diff --git a/src/parser/__tests__/expression.spec.ts b/src/parser/__tests__/expression.spec.ts index 838a8b84..f14da1da 100644 --- a/src/parser/__tests__/expression.spec.ts +++ b/src/parser/__tests__/expression.spec.ts @@ -2409,6 +2409,18 @@ describe('SonicWeave expression evaluator', () => { const {interval} = parseSingle(String.raw`\\\P1 tmpr 12@`); expect(interval.totalCents()).toBe(-1500); }); + + it('coerces too accurate cents too real', () => { + const interval = evaluate('1234.56789012345678901') as Interval; + expect(interval.value).toBeInstanceOf(TimeReal); + expect(interval.valueOf()).toBeCloseTo(2.04); + }); + + it('coerces too accurate monzos to real', () => { + const ronzo = evaluate('[1.234567890123456780901>') as Interval; + expect(ronzo.value).toBeInstanceOf(TimeReal); + expect(ronzo.valueOf()).toBeCloseTo(2.3531); + }); }); describe('Poor grammar / Fun with "<"', () => { diff --git a/src/parser/__tests__/source.spec.ts b/src/parser/__tests__/source.spec.ts index 12e8fb7f..c860ce09 100644 --- a/src/parser/__tests__/source.spec.ts +++ b/src/parser/__tests__/source.spec.ts @@ -1991,4 +1991,34 @@ describe('SonicWeave parser', () => { 'Index out of range.' ); }); + + it('can handle too complex multiplication resulting from too accurate prime mappings', () => { + const scale = expand(` + 14/13 + 8/7 + 44/35 + 4/3 + 99/70 + 99/65 + 396/245 + 2178/1225 + 66/35 + 9801/4900 + (* Commas = 1716/1715, 2080/2079 *) + PrimeMapping(1200., 1902.0236738027506, 2786.2942222449124, 3369.11433503606, 4151.361209675464, 4440.252343874884) + cents(£, 3) + `); + expect(scale).toEqual([ + '128.862', + '230.886', + '395.953', + '497.976', + '600.', + '728.862', + '830.886', + '995.953', + '1097.976', + '1200.', + ]); + }); }); diff --git a/src/parser/expression.ts b/src/parser/expression.ts index 9a7c633f..bcee1825 100644 --- a/src/parser/expression.ts +++ b/src/parser/expression.ts @@ -1,4 +1,4 @@ -import {Fraction, gcd} from 'xen-dev-utils'; +import {Fraction, PRIMES, gcd} from 'xen-dev-utils'; import { NedjiLiteral, IntegerLiteral, @@ -662,7 +662,18 @@ export class ExpressionVisitor { protected visitComponent(component: VectorComponent) { // XXX: This is so backwards... - return new Fraction(formatComponent(component)); + const str = formatComponent(component); + try { + return new Fraction(str); + } catch { + if (component.separator === '/') { + return ( + (component.left / parseInt(component.right)) * + 10 ** (component.exponent ?? 0) + ); + } + return parseFloat(str); + } } protected upLift( @@ -703,7 +714,21 @@ export class ExpressionVisitor { } } } else { - value = new TimeMonzo(ZERO, exponents); + let valid = true; + for (const exponent of exponents) { + if (typeof exponent === 'number') { + valid = false; + } + } + if (valid) { + value = new TimeMonzo(ZERO, exponents as Fraction[]); + } else { + let num = 1; + for (let i = 0; i < exponents.length; ++i) { + num *= PRIMES[i] ** exponents[i].valueOf(); + } + value = new TimeReal(0, num); + } } const result = this.upLift(value, node); if (steps.d !== 1) { @@ -719,6 +744,11 @@ export class ExpressionVisitor { protected visitValLiteral(node: ValLiteral) { const val = node.components.map(this.visitComponent); + for (const component of val) { + if (typeof component === 'number') { + throw new Error('Invalid val literal.'); + } + } let value: TimeMonzo; let equave = TWO_MONZO; if (node.basis.length) { @@ -729,7 +759,7 @@ export class ExpressionVisitor { value = valToTimeMonzo(val, subgroup); equave = subgroup[0]; } else { - value = new TimeMonzo(ZERO, val); + value = new TimeMonzo(ZERO, val as Fraction[]); } return new Val(value, equave, node); }