diff --git a/package-lock.json b/package-lock.json index 4fa86aaf..33909ceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "harmonic-entropy": "^0.2.0", "isomorphic-qwerty": "^0.0.2", - "ji-lattice": "^0.0.3", + "ji-lattice": "^0.1.0", "jszip": "^3.10.1", "moment-of-symmetry": "^0.8.0", "pinia": "^2.1.7", @@ -3802,9 +3802,9 @@ } }, "node_modules/ji-lattice": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/ji-lattice/-/ji-lattice-0.0.3.tgz", - "integrity": "sha512-LAO8u0aO4qpDr7WucJmWRpXocy1pnIYlvDNjXmS4/VUhMc4xxMjTzLf4TN0kL/bWTPIhAxRd+0amTEjN1Mnptg==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ji-lattice/-/ji-lattice-0.1.0.tgz", + "integrity": "sha512-Rk78Rp2hMahoS2ElBJAuLpFo1wzNQiCx9UkqNzmeGVuggVIq7G9wGrlj++QPiL0mBHaWyfYJZ9ZML3yDf2mgzA==", "dependencies": { "xen-dev-utils": "^0.2.8" }, diff --git a/package.json b/package.json index c3205ece..46beae78 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "harmonic-entropy": "^0.2.0", "isomorphic-qwerty": "^0.0.2", - "ji-lattice": "^0.0.3", + "ji-lattice": "^0.1.0", "jszip": "^3.10.1", "moment-of-symmetry": "^0.8.0", "pinia": "^2.1.7", diff --git a/src/components/EdoCycles.vue b/src/components/EdoCycles.vue index 45132a1d..aae015a5 100644 --- a/src/components/EdoCycles.vue +++ b/src/components/EdoCycles.vue @@ -108,7 +108,6 @@ const viewBox = computed( :y="v.y + store.size * store.labelOffset * labelY(j, v.indices.length)" :font-size="`${1.1 * store.size}px`" :stroke-width="store.size * 0.01" - dominant-baseline="middle" > {{ labels[idx] }} diff --git a/src/components/Faux3DLattice.vue b/src/components/Faux3DLattice.vue new file mode 100644 index 00000000..6d4faef9 --- /dev/null +++ b/src/components/Faux3DLattice.vue @@ -0,0 +1,194 @@ + + + diff --git a/src/components/GridLattice.vue b/src/components/GridLattice.vue index 7f050b44..b4062946 100644 --- a/src/components/GridLattice.vue +++ b/src/components/GridLattice.vue @@ -126,7 +126,6 @@ watch( :y="v.y + store.size * store.labelOffset * labelY(j, v.indices.length)" :font-size="`${2.5 * store.size}px`" :stroke-width="store.size * 0.05" - dominant-baseline="middle" > {{ labels[idx] }} diff --git a/src/components/JustIntonationLattice.vue b/src/components/JustIntonationLattice.vue index 8b4767bc..05750a7c 100644 --- a/src/components/JustIntonationLattice.vue +++ b/src/components/JustIntonationLattice.vue @@ -192,7 +192,6 @@ watch( v-for="(v, i) of lattice.vertices" :key="i" class="node-label" - dominant-baseline="middle" :x="v.x" :y="v.y - store.labelOffset * store.size" :font-size="`${3 * store.size}px`" diff --git a/src/stores/ji-lattice.ts b/src/stores/ji-lattice.ts index 34e9fab0..2e1cf9c4 100644 --- a/src/stores/ji-lattice.ts +++ b/src/stores/ji-lattice.ts @@ -1,6 +1,6 @@ import { computed, reactive, ref, watch } from 'vue' import { defineStore } from 'pinia' -import { kraigGrady9, type LatticeOptions, scottDakota24, primeRing72, align } from 'ji-lattice' +import { kraigGrady9, type LatticeOptions, scottDakota24, primeRing72, align, type LatticeOptions3D, WGP9, primeSphere } from 'ji-lattice' import { LOG_PRIMES, mmod } from 'xen-dev-utils' import { computedAndError } from '@/utils' import { TimeMonzo, parseChord } from 'sonic-weave' @@ -52,6 +52,48 @@ export const useJiLatticeStore = defineStore('ji-lattice', () => { ) } }) + const xs = computed({ + get() { + return xCoords.map(x => x.toFixed(2)).join(' ') + }, + set(value: string) { + xCoords.length = 0 + xCoords.push( + ...value.split(' ').map((v) => { + const c = parseFloat(v) + return isNaN(c) ? 0 : c + }) + ) + } + }) + const ys = computed({ + get() { + return yCoords.map(y => y.toFixed(2)).join(' ') + }, + set(value: string) { + yCoords.length = 0 + yCoords.push( + ...value.split(' ').map((v) => { + const c = parseFloat(v) + return isNaN(c) ? 0 : c + }) + ) + } + }) + const zs = computed({ + get() { + return zCoords.map(z => z.toFixed(2)).join(' ') + }, + set(value: string) { + zCoords.length = 0 + zCoords.push( + ...value.split(' ').map((v) => { + const c = parseFloat(v) + return isNaN(c) ? 0 : c + }) + ) + } + }) const edgeMonzos = computed(() => { const numComponents = horizontalCoordinates.length @@ -79,12 +121,31 @@ export const useJiLatticeStore = defineStore('ji-lattice', () => { } }) + const opts3D = WGP9(0); + const xCoords = reactive(opts3D.horizontalCoordinates); + const yCoords = reactive(opts3D.verticalCoordinates); + const zCoords = reactive(opts3D.depthwiseCoordinates); + const depth = ref(100) + + const latticeOptions3D = computed(() => { + return { + horizontalCoordinates: xCoords, + verticalCoordinates: yCoords, + depthwiseCoordinates: zCoords, + maxDistance: maxDistance.value, + edgeMonzos: edgeMonzos.value, + mergeEdges: false + } + }) + watch(rotation, (newValue) => { if (newValue < 0 || newValue >= 360) { rotation.value = mmod(newValue, 360) } }) + // 2D presets + function kraigGrady(equaveIndex = 0) { size.value = 2 const kg = kraigGrady9(equaveIndex) @@ -106,7 +167,11 @@ export const useJiLatticeStore = defineStore('ji-lattice', () => { horizontalCoordinates.push(...sd.horizontalCoordinates) verticalCoordinates.length = 0 verticalCoordinates.push(...sd.verticalCoordinates) - edgesString.value = '6/5' + if (equaveIndex === 0) { + edgesString.value = '6/5' + } else { + edgesString.value = '' + } } function pr72(equaveIndex = 0) { @@ -120,7 +185,11 @@ export const useJiLatticeStore = defineStore('ji-lattice', () => { horizontalCoordinates.push(...pr.horizontalCoordinates) verticalCoordinates.length = 0 verticalCoordinates.push(...pr.verticalCoordinates) - edgesString.value = '6/5' + if (equaveIndex === 0) { + edgesString.value = '6/5' + } else { + edgesString.value = '' + } } function pe72(equaveIndex = 0) { @@ -135,7 +204,82 @@ export const useJiLatticeStore = defineStore('ji-lattice', () => { horizontalCoordinates.push(...pr.horizontalCoordinates.map(Math.round)) verticalCoordinates.length = 0 verticalCoordinates.push(...pr.verticalCoordinates.map(Math.round)) - edgesString.value = '6/5' + if (equaveIndex === 0) { + edgesString.value = '6/5' + } else { + edgesString.value = '' + } + } + + // 3D presets + function WGP(equaveIndex = 0) { + size.value = 2 + const w = WGP9(equaveIndex) + xCoords.length = 0 + xCoords.push(...w.horizontalCoordinates) + yCoords.length = 0 + yCoords.push(...w.verticalCoordinates) + zCoords.length = 0 + zCoords.push(...w.depthwiseCoordinates) + edgesString.value = '' + } + + function sphere(equaveIndex = 0) { + size.value = 2 + depth.value = 300 + const logs = LOG_PRIMES.slice(0, 24) + if (equaveIndex !== 0) { + logs.unshift(logs.splice(equaveIndex, 1)[0]) + } + const ps = primeSphere(logs) + const scale = 3000 + xCoords.length = 0 + xCoords.push(...ps.horizontalCoordinates.map(x => Math.round(x * scale) / 100)) + yCoords.length = 0 + yCoords.push(...ps.verticalCoordinates.map(y => Math.round(y * scale) / 100)) + zCoords.length = 0 + zCoords.push(...ps.depthwiseCoordinates.map(z => Math.round(z * scale) / 100)) + if (equaveIndex === 0) { + edgesString.value = '6/5' + } else { + edgesString.value = '' + } + } + + function pitch(degrees: number) { + const theta = degrees / 180 * Math.PI + const c = Math.cos(theta) + const s = Math.sin(theta) + for (let i = 0; i < xCoords.length; ++i) { + const y = yCoords[i] + const z = zCoords[i] + yCoords[i] = c*y + s*z + zCoords[i] = c*z - s*y + } + } + + function yaw(degrees: number) { + const theta = degrees / 180 * Math.PI + const c = Math.cos(theta) + const s = Math.sin(theta) + for (let i = 0; i < xCoords.length; ++i) { + const x = xCoords[i] + const z = zCoords[i] + xCoords[i] = c*x + s*z + zCoords[i] = c*z - s*x + } + } + + function roll(degrees: number) { + const theta = degrees / 180 * Math.PI + const c = Math.cos(theta) + const s = Math.sin(theta) + for (let i = 0; i < xCoords.length; ++i) { + const x = xCoords[i] + const y = yCoords[i] + xCoords[i] = c*x + s*y + yCoords[i] = c*y - s*x + } } return { @@ -150,16 +294,29 @@ export const useJiLatticeStore = defineStore('ji-lattice', () => { rotation, drawArrows, grayExtras, + xCoords, + yCoords, + zCoords, + depth, // Computed state edgeMonzos, edgesError, horizontals, verticals, latticeOptions, + latticeOptions3D, + xs, + ys, + zs, // Methods kraigGrady, scott24, pr72, - pe72 + pe72, + WGP, + sphere, + pitch, + yaw, + roll, } }) diff --git a/src/stores/state.ts b/src/stores/state.ts index 45d2cb4b..30f0fc71 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' | 'cycles' | 'auto'>('auto') + const latticeType = ref<'ji' | 'et' | 'cycles' | '3d' | '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 f022b4ef..7fe56cdf 100644 --- a/src/views/LatticeView.vue +++ b/src/views/LatticeView.vue @@ -11,6 +11,7 @@ import { useJiLatticeStore } from '@/stores/ji-lattice' import { useGridStore } from '@/stores/grid' import { useCyclesStore } from '@/stores/edo-cycles' import { setAndReportValidity } from '@/utils' +import Faux3DLattice from '@/components/Faux3DLattice.vue' const state = useStateStore() const scale = useScaleStore() @@ -21,6 +22,7 @@ const cycles = useCyclesStore() const showConfig = ref(false) const jiPreset = ref<'nothing' | 'grady' | 'grady3' | 'dakota' | 'pr72' | 'pe72'>('nothing') const etPreset = ref<'nothing' | '12' | '53' | '311' | 'b13'>('nothing') +const preset3D = ref<'nothing' | 'WGP' | 'sphere'>('nothing') const extraEdgesElement = ref(null) watch(extraEdgesElement, (newElement) => setAndReportValidity(newElement, jiLattice.edgesError), { @@ -91,6 +93,16 @@ watch(etPreset, (newValue) => { } }) +watch(preset3D, (newValue) => { + switch (newValue) { + case 'WGP': + jiLattice.WGP() + return + case 'sphere': + jiLattice.sphere() + } +}) + function inferConfig() { // Default to 12-TET even if it looks bad state.latticeType = 'et' @@ -223,6 +235,13 @@ onMounted(() => { :relativeIntervals="scale.latticeIntervals" :heldNotes="heldNotes" /> + @@ -252,6 +271,10 @@ onMounted(() => { + + + + + @@ -486,6 +587,16 @@ line.edge.auxiliary { stroke-dasharray: 3 1; } +polygon.edge.primary { + fill: var(--color-accent); +} +polygon.edge.custom { + fill: var(--color-accent-deeper); +} +polygon.edge.auxiliary { + fill: var(--color-accent-mute); +} + marker#arrow { fill: var(--color-indicator); } @@ -512,5 +623,6 @@ text.node-label { fill: var(--color-text); text-anchor: middle; stroke: var(--color-background); + dominant-baseline: middle; }