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..527b0f74 --- /dev/null +++ b/src/components/EdoCycles.vue @@ -0,0 +1,108 @@ + + + + + + + + + {{ labels[idx] }} + + + + + diff --git a/src/stores/grid.ts b/src/stores/grid.ts index 1560b772..91686aba 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,9 @@ export const useGridStore = defineStore('grid', () => { const diagonals1 = ref(false) const diagonals2 = ref(false) + // Edo-cycles + const generator = ref(7) + const val = computed(() => { try { const val = evaluateExpression(valString.value) @@ -114,6 +117,10 @@ 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)) + function resetBounds() { minX.value = -3.1 maxX.value = 3.1 @@ -287,6 +294,7 @@ export const useGridStore = defineStore('grid', () => { gridlines2, diagonals1, diagonals2, + generator, // Computed state edges, edgesError, @@ -294,6 +302,8 @@ export const useGridStore = defineStore('grid', () => { val, modulus, gridOptions, + generatorPseudoInverse, + numEdoCycles, // 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..bf91e1d0 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" /> + Selecting lattice... @@ -235,6 +243,10 @@ onMounted(() => { Equal temperament + + + Cycles + @@ -400,6 +412,16 @@ onMounted(() => { + + + Val + + + + Generator + + + Selecting lattice...