diff --git a/package-lock.json b/package-lock.json index ad75e6b2..4e5a099a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.1", "dependencies": { "jszip": "^3.10.1", + "mathjs": "^11.5.0", "moment-of-symmetry": "github:xenharmonic-devs/moment-of-symmetry#v0.2.1", "qs": "^6.11.0", "scale-workshop-core": "github:xenharmonic-devs/scale-workshop-core#v0.0.1", @@ -53,11 +54,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", - "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" @@ -1374,6 +1375,18 @@ "node": ">=4.0.0" } }, + "node_modules/complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1566,10 +1579,9 @@ } }, "node_modules/decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==", - "dev": true + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, "node_modules/deep-eql": { "version": "3.0.1", @@ -2041,6 +2053,11 @@ "node": ">=12" } }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3262,6 +3279,11 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "node_modules/jazz-midi": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/jazz-midi/-/jazz-midi-1.7.6.tgz", @@ -3666,6 +3688,28 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "node_modules/mathjs": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.5.0.tgz", + "integrity": "sha512-vJ/+SqWtxjW6/aeDRt8xL3TlOVKqwN15BIyTGVqGbIWuiqgY4SxZ0yLuna82YH9CB757iFP7uJ4m3KvVBX7Qcg==", + "dependencies": { + "@babel/runtime": "^7.20.6", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^4.2.0", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.1.0" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4222,9 +4266,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regexpp": { "version": "3.2.0", @@ -4416,6 +4460,11 @@ "xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#v0.1.1" } }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -4770,6 +4819,11 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tinypool": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.3.tgz", @@ -4920,6 +4974,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-function": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", + "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/typescript": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", @@ -5397,11 +5459,11 @@ "integrity": "sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==" }, "@babel/runtime": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", - "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" } }, "@colors/colors": { @@ -6388,6 +6450,11 @@ "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true }, + "complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6550,10 +6617,9 @@ } }, "decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==", - "dev": true + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, "deep-eql": { "version": "3.0.1", @@ -6820,6 +6886,11 @@ "dev": true, "optional": true }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -7707,6 +7778,11 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "jazz-midi": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/jazz-midi/-/jazz-midi-1.7.6.tgz", @@ -8031,6 +8107,22 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "mathjs": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.5.0.tgz", + "integrity": "sha512-vJ/+SqWtxjW6/aeDRt8xL3TlOVKqwN15BIyTGVqGbIWuiqgY4SxZ0yLuna82YH9CB757iFP7uJ4m3KvVBX7Qcg==", + "requires": { + "@babel/runtime": "^7.20.6", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^4.2.0", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.1.0" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8432,9 +8524,9 @@ } }, "regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regexpp": { "version": "3.2.0", @@ -8560,6 +8652,11 @@ "xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#v0.1.1" } }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -8821,6 +8918,11 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tinypool": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.3.tgz", @@ -8934,6 +9036,11 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typed-function": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", + "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==" + }, "typescript": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", diff --git a/package.json b/package.json index f4f602de..dd738e45 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,15 @@ }, "dependencies": { "jszip": "^3.10.1", + "mathjs": "^11.5.0", "moment-of-symmetry": "github:xenharmonic-devs/moment-of-symmetry#v0.2.1", "qs": "^6.11.0", + "scale-workshop-core": "github:xenharmonic-devs/scale-workshop-core#v0.0.1", "temperaments": "github:xenharmonic-devs/temperaments#v0.3.0", "vue": "^3.2.33", "vue-router": "^4.1.5", "webmidi": "^3.0.21", - "xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#v0.1.0", - "scale-workshop-core": "github:xenharmonic-devs/scale-workshop-core#v0.0.1" + "xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#v0.1.0" }, "devDependencies": { "@rushstack/eslint-patch": "^1.2.0", diff --git a/src/__tests__/tempering.spec.ts b/src/__tests__/tempering.spec.ts index 9e600a0f..d82b220d 100644 --- a/src/__tests__/tempering.spec.ts +++ b/src/__tests__/tempering.spec.ts @@ -7,9 +7,125 @@ import { makeRank2FromVals, mosPatternsRank2FromCommas, mosPatternsRank2FromVals, + vanishCommas, + tenneyVals, + toPrimeMapping, } from "../tempering"; -import { arraysEqual, Fraction, valueToCents } from "xen-dev-utils"; +import { + arraysEqual, + dot, + Fraction, + PRIMES, + PRIME_CENTS, + toMonzoAndResidual, + valueToCents, +} from "xen-dev-utils"; import { ExtendedMonzo, Interval, Scale } from "scale-workshop-core"; +import { Subgroup } from "temperaments"; +import { DEFAULT_NUMBER_OF_COMPONENTS } from "../constants"; + +describe("Comma vanisher", () => { + it("can make the syntonic comma disappear (tempered octaves)", () => { + const inital = PRIME_CENTS.slice(0, 3); + const syntonic = [-4, 4, -1]; + const mapping = vanishCommas(inital, [syntonic]); + expect(dot(mapping, syntonic)).toBeCloseTo(0); + + const octave = mapping[0]; + expect(octave).not.toBe(1200); + expect(octave).toBeLessThan(1202); + expect(octave).toBeGreaterThan(1198); + }); + + it("can make the syntonic comma disappear (tempered octaves with weights)", () => { + const inital = PRIME_CENTS.slice(0, 3); + const syntonic = [-4, 4, -1]; + const mapping = vanishCommas(inital, [syntonic], [100, 3, 2]); + expect(dot(mapping, syntonic)).toBeCloseTo(0); + + const octave = mapping[0]; + expect(octave).not.toBe(1200); + expect(octave).toBeLessThan(1201); + expect(octave).toBeGreaterThan(1199); + }); + + it("can make the syntonic comma disappear (pure octaves)", () => { + const inital = PRIME_CENTS.slice(0, 3); + const syntonic = [-4, 4, -1]; + const mapping = vanishCommas(inital, [syntonic], undefined, false); + expect(dot(mapping, syntonic)).toBeCloseTo(0); + + const octave = mapping[0]; + expect(octave).toBe(1200); + + const twelfth = mapping[1]; + expect(twelfth).not.toBe(PRIME_CENTS[1]); + expect(twelfth).toBeLessThan(1905); + expect(twelfth).toBeGreaterThan(1896); + + const five = mapping[2]; + expect(five).not.toBe(PRIME_CENTS[2]); + expect(five).toBeLessThan(2789); + expect(five).toBeGreaterThan(2783); + }); + + it("can handle big subgroups (ratio units)", () => { + const initial = PRIMES.slice(0, 25); + const commas = [ + toMonzoAndResidual(new Fraction(621, 620), 25)[0], + toMonzoAndResidual(new Fraction(87, 86), 25)[0], + toMonzoAndResidual(new Fraction(98, 97), 25)[0], + ]; + const mapping = vanishCommas(initial, commas, undefined, false, "ratio"); + + for (const comma of commas) { + let interval = 1; + for (let i = 0; i < 25; ++i) { + interval *= mapping[i] ** comma[i]; + } + expect(interval).toBeCloseTo(1); + } + }); +}); + +describe("Tenney-Euclid optimal val combiner", () => { + it("calculates POTE meantone", () => { + const jip = PRIME_CENTS.slice(0, 3); + const twelve = [12, 19, 28]; + const nineteen = [19, 30, 44]; + + const mapping = tenneyVals(jip, [twelve, nineteen]); + + const syntonic = [-4, 4, -1]; + expect(dot(mapping, syntonic)).toBeCloseTo(0); + + const pote = mapping.map((m) => (1200 * m) / mapping[0]); + expect(dot(pote, [-1, 1, 0])).toBeCloseTo(696.239); + }); +}); + +describe("Prime map converter", () => { + it("does (almost) nothing in 5-limit JI", () => { + const fiveLimit = PRIME_CENTS.slice(0, 3); + const mapping = toPrimeMapping(fiveLimit, new Subgroup(5)); + expect(mapping[0]).toBe(fiveLimit[0]); + expect(mapping[1]).toBe(fiveLimit[1]); + expect(mapping[2]).toBe(fiveLimit[2]); + expect(mapping.length).toBe(DEFAULT_NUMBER_OF_COMPONENTS); + expect(mapping[3]).toBe(PRIME_CENTS[3]); + }); + + it("converts a mapping in 2.3.13/5 to 2.3.5.7.11.13...", () => { + const original = [1200, 1901, 1654]; + const mapping = toPrimeMapping(original, new Subgroup("2.3.13/5")); + expect(mapping[0]).toBeCloseTo(1200); + expect(mapping[1]).toBeCloseTo(1901); + expect(mapping[3]).toBeCloseTo(PRIME_CENTS[3]); + expect(mapping[4]).toBeCloseTo(PRIME_CENTS[4]); + expect(mapping[5] - mapping[2]).toBeCloseTo(1654); + expect(mapping[6]).toBeCloseTo(PRIME_CENTS[6]); + }); +}); describe("Temperament Mapping", () => { it("calculates POTE meantone", () => { diff --git a/src/components/modals/generation/RankTwo.vue b/src/components/modals/generation/RankTwo.vue index 1e0b36a4..0058e97f 100644 --- a/src/components/modals/generation/RankTwo.vue +++ b/src/components/modals/generation/RankTwo.vue @@ -1,5 +1,8 @@