From 231d62fc123c9204546213ea581a1a7bbdd1ece8 Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Thu, 6 Jun 2024 15:24:45 +0300 Subject: [PATCH] WIP: (Edo) cycle option on Lattice tab ref #678 --- package-lock.json | 32 +++++++- package.json | 2 +- src/components/EdoCycles.vue | 134 +++++++++++++++++++++++++++++++++ src/components/GridLattice.vue | 27 +------ src/stores/grid.ts | 82 +++++++++++++++++++- src/stores/state.ts | 2 +- src/views/LatticeView.vue | 38 ++++++++++ 7 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 src/components/EdoCycles.vue diff --git a/package-lock.json b/package-lock.json index 9dad91e8..89cc2fc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "vue": "^3.3.4", "vue-router": "^4.3.0", "webmidi": "^3.1.8", - "xen-dev-utils": "^0.8.0", + "xen-dev-utils": "^0.9.0", "xen-midi": "^0.2.0" }, "devDependencies": { @@ -4427,6 +4427,18 @@ "url": "https://github.com/sponsors/frostburn" } }, + "node_modules/moment-of-symmetry/node_modules/xen-dev-utils": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.8.0.tgz", + "integrity": "sha512-na9WWF1JlFhGjzvQDk/VHYWmTTvR1TD6nKHHy7GXC4f41u9CsLKxB7jphwioz1pR/5dBsbiSKBKTkWR4Uyactg==", + "engines": { + "node": ">=10.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/frostburn" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5520,6 +5532,18 @@ "node": ">=18" } }, + "node_modules/sonic-weave/node_modules/xen-dev-utils": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.8.0.tgz", + "integrity": "sha512-na9WWF1JlFhGjzvQDk/VHYWmTTvR1TD6nKHHy7GXC4f41u9CsLKxB7jphwioz1pR/5dBsbiSKBKTkWR4Uyactg==", + "engines": { + "node": ">=10.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/frostburn" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -6639,9 +6663,9 @@ } }, "node_modules/xen-dev-utils": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.8.0.tgz", - "integrity": "sha512-na9WWF1JlFhGjzvQDk/VHYWmTTvR1TD6nKHHy7GXC4f41u9CsLKxB7jphwioz1pR/5dBsbiSKBKTkWR4Uyactg==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/xen-dev-utils/-/xen-dev-utils-0.9.0.tgz", + "integrity": "sha512-JsbXSg1zXaBoiKI19p2jC8Ka22YADQsTBD7fc2FkVxLWSdCO5BCS5KcRquDP5vP6J9v8t3B14G/7GZ5DC73rzg==", "engines": { "node": ">=10.6.0" }, diff --git a/package.json b/package.json index 6e0f847b..fb2259bc 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "vue": "^3.3.4", "vue-router": "^4.3.0", "webmidi": "^3.1.8", - "xen-dev-utils": "^0.8.0", + "xen-dev-utils": "^0.9.0", "xen-midi": "^0.2.0" }, "devDependencies": { diff --git a/src/components/EdoCycles.vue b/src/components/EdoCycles.vue new file mode 100644 index 00000000..0003efc8 --- /dev/null +++ b/src/components/EdoCycles.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/components/GridLattice.vue b/src/components/GridLattice.vue index cc04cbd7..b5cc7dbb 100644 --- a/src/components/GridLattice.vue +++ b/src/components/GridLattice.vue @@ -24,29 +24,6 @@ const steps = computed(() => { return result }) -// Multi-label offsets -function lx(n: number, num: number) { - if (num < 3) { - return 0 - } - if (num & 1) { - // Odd counts exploit a different starting angle. - return store.labelOffset * store.size * Math.cos((2 * Math.PI * n) / num) - } - // Text tends to extend horizontally so we draw an ellipse. - return 1.5 * store.labelOffset * store.size * Math.sin((2 * Math.PI * n) / num) -} -function ly(n: number, num: number) { - if (num === 1) { - return -store.labelOffset * store.size - } - if (num & 1) { - // Odd counts exploit a different starting angle. - return store.labelOffset * store.size * Math.sin((2 * Math.PI * n) / num) - } - return -store.labelOffset * store.size * Math.cos((2 * Math.PI * n) / num) -} - const grid = computed(() => { const result = spanGrid(steps.value, store.gridOptions) return result @@ -145,8 +122,8 @@ watch( v-for="(idx, j) of v.indices" :key="idx" class="node-label" - :x="v.x + lx(j, v.indices.length)" - :y="v.y + ly(j, v.indices.length)" + :x="v.x + store.lx(j, v.indices.length)" + :y="v.y + store.ly(j, v.indices.length)" :font-size="`${2.5 * store.size}px`" :stroke-width="store.size * 0.05" dominant-baseline="middle" diff --git a/src/stores/grid.ts b/src/stores/grid.ts index 1560b772..7b5d5680 100644 --- a/src/stores/grid.ts +++ b/src/stores/grid.ts @@ -1,7 +1,7 @@ import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { shortestEdge, type GridOptions } from 'ji-lattice' -import { LOG_PRIMES, mmod } from 'xen-dev-utils' +import { LOG_PRIMES, mmod, modInv } from 'xen-dev-utils' import { Val, evaluateExpression, parseChord } from 'sonic-weave' import { computedAndError } from '@/utils' import { FIFTH, THIRD } from '@/constants' @@ -36,6 +36,33 @@ export const useGridStore = defineStore('grid', () => { const diagonals1 = ref(false) const diagonals2 = ref(false) + // Edo-cycles + const generator = ref(7) + + const labelOffsetCacheX = computed(() => { + const result: number[][] = [] + for (let num = 1; num < 6; ++num) { + const xs: number[] = [] + for (let n = 0; n < num; ++n) { + xs.push(labelX(n, num)) + } + result.push(xs) + } + return result + }) + + const labelOffsetCacheY = computed(() => { + const result: number[][] = [] + for (let num = 1; num < 6; ++num) { + const ys: number[] = [] + for (let n = 0; n < num; ++n) { + ys.push(labelY(n, num)) + } + result.push(ys) + } + return result + }) + const val = computed(() => { try { const val = evaluateExpression(valString.value) @@ -114,6 +141,53 @@ export const useGridStore = defineStore('grid', () => { } }) + // Edo-cycles + const generatorPseudoInverse = computed(() => modInv(generator.value, modulus.value, false)) + const numEdoCycles = computed(() => + mmod(generator.value * generatorPseudoInverse.value, modulus.value) + ) + const cycleLength = computed(() => modulus.value / numEdoCycles.value) + + // Multi-label offsets + function labelX(n: number, num: number) { + if (num < 3) { + return 0 + } + const r1 = labelOffset.value * size.value + if (num & 1) { + // Odd counts exploit a different starting angle. + return r1 * Math.cos((2 * Math.PI * n) / num) + } + // Text tends to extend horizontally so we draw an ellipse. + return 1.5 * r1 * Math.sin((2 * Math.PI * n) / num) + } + + function labelY(n: number, num: number) { + const r = labelOffset.value * size.value + if (num === 1) { + return -r + } + if (num & 1) { + // Odd counts exploit a different starting angle. + return r * Math.sin((2 * Math.PI * n) / num) + } + return -r * Math.cos((2 * Math.PI * n) / num) + } + + function lx(n: number, num: number) { + if (num < 6) { + return labelOffsetCacheX.value[num][n] + } + return labelX(n, num) + } + + function ly(n: number, num: number) { + if (num < 6) { + return labelOffsetCacheY.value[num][n] + } + return labelY(n, num) + } + function resetBounds() { minX.value = -3.1 maxX.value = 3.1 @@ -287,13 +361,19 @@ export const useGridStore = defineStore('grid', () => { gridlines2, diagonals1, diagonals2, + generator, // Computed state + lx, + ly, edges, edgesError, edgeVectors, val, modulus, gridOptions, + generatorPseudoInverse, + numEdoCycles, + cycleLength, // Methods (presets) square, squareBP, diff --git a/src/stores/state.ts b/src/stores/state.ts index 3c9fe9a4..45d2cb4b 100644 --- a/src/stores/state.ts +++ b/src/stores/state.ts @@ -8,7 +8,7 @@ export const useStateStore = defineStore('state', () => { const heldNotes = reactive(new Map()) const typingActive = ref(true) - const latticeType = ref<'ji' | 'et' | 'auto'>('auto') + const latticeType = ref<'ji' | 'et' | 'cycles' | 'auto'>('auto') // These user preferences are fetched from local storage. const storage = window.localStorage diff --git a/src/views/LatticeView.vue b/src/views/LatticeView.vue index ff1a79ba..b52be956 100644 --- a/src/views/LatticeView.vue +++ b/src/views/LatticeView.vue @@ -2,6 +2,7 @@ import { computed, onMounted, ref, watch } from 'vue' import { Fraction, isPrime, mmod, nthPrime, primeLimit } from 'xen-dev-utils' import GridLattice from '@/components/GridLattice.vue' +import EdoCycles from '@/components/EdoCycles.vue' import JustIntonationLattice from '@/components/JustIntonationLattice.vue' import Modal from '@/components/ModalDialog.vue' import { useStateStore } from '@/stores/state' @@ -210,6 +211,13 @@ onMounted(() => { :relativeIntervals="scale.latticeIntervals" :heldNotes="heldNotes" /> + @@ -235,6 +243,10 @@ onMounted(() => { + + + + +