From d405f88674e1b76bcb020353f9bd4884957623df Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Fri, 5 Apr 2024 10:33:01 +0300 Subject: [PATCH 1/3] Fix handling of negative intervals in Anamark and Reaper exporters Update sonic-weave dependency. --- package-lock.json | 10 +++++----- package.json | 4 ++-- src/exporters/anamark.ts | 4 ++-- src/exporters/reaper.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index cad300b1..c3ab2e49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scale-workshop", - "version": "3.0.0-beta.8", + "version": "3.0.0-beta.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scale-workshop", - "version": "3.0.0-beta.8", + "version": "3.0.0-beta.9", "dependencies": { "isomorphic-qwerty": "^0.0.2", "ji-lattice": "^0.0.3", @@ -14,7 +14,7 @@ "moment-of-symmetry": "^0.4.2", "pinia": "^2.1.7", "qs": "^6.12.0", - "sonic-weave": "github:xenharmonic-devs/sonic-weave#v0.0.8", + "sonic-weave": "github:xenharmonic-devs/sonic-weave#v0.0.9", "sw-synth": "^0.1.0", "temperaments": "^0.5.3", "vue": "^3.3.4", @@ -5414,8 +5414,8 @@ } }, "node_modules/sonic-weave": { - "version": "0.0.8", - "resolved": "git+ssh://git@github.com/xenharmonic-devs/sonic-weave.git#2c00c749cde9c4b06364963013b6f85652535950", + "version": "0.0.9", + "resolved": "git+ssh://git@github.com/xenharmonic-devs/sonic-weave.git#48d5cf0dd9026c978a8a0958957f73d036ef1001", "license": "MIT", "dependencies": { "moment-of-symmetry": "^0.4.2", diff --git a/package.json b/package.json index 06700fb7..2d383f16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scale-workshop", - "version": "3.0.0-beta.8", + "version": "3.0.0-beta.9", "scripts": { "dev": "vite", "build": "run-p type-check \"build-only {@}\" --", @@ -21,7 +21,7 @@ "moment-of-symmetry": "^0.4.2", "pinia": "^2.1.7", "qs": "^6.12.0", - "sonic-weave": "github:xenharmonic-devs/sonic-weave#v0.0.8", + "sonic-weave": "github:xenharmonic-devs/sonic-weave#v0.0.9", "sw-synth": "^0.1.0", "temperaments": "^0.5.3", "vue": "^3.3.4", diff --git a/src/exporters/anamark.ts b/src/exporters/anamark.ts index 65d9087a..badbfaf9 100644 --- a/src/exporters/anamark.ts +++ b/src/exporters/anamark.ts @@ -93,12 +93,12 @@ class AnaMarkExporter extends BaseExporter { '="#>-' + i + ' % ' + - intervals[i - 1].totalCents().toFixed(6) + + intervals[i - 1].totalCents(true).toFixed(6) + ' ~999"' + newline } else { file += - 'note ' + i + '="#=0 % ' + intervals[i - 1].totalCents().toFixed(6) + '"' + newline + 'note ' + i + '="#=0 % ' + intervals[i - 1].totalCents(true).toFixed(6) + '"' + newline } } diff --git a/src/exporters/reaper.ts b/src/exporters/reaper.ts index 442ffc12..64a44e0e 100644 --- a/src/exporters/reaper.ts +++ b/src/exporters/reaper.ts @@ -62,7 +62,7 @@ export default class ReaperExporter extends BaseExporter { if (interval.domain === 'linear') { file += value.valueOf().toFixed(digits).replace('.', ',') } else { - file += value.totalCents().toFixed(digits) + file += value.totalCents(true).toFixed(digits) } } else { file += new Interval( From 49cfc497fd8df49b81ebe5cb965b7f64ea1954d5 Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Fri, 5 Apr 2024 10:33:55 +0300 Subject: [PATCH 2/3] Fix how negative intervals interact with the chord wheels --- src/virtual-synth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual-synth.ts b/src/virtual-synth.ts index 5a399dcb..c53e3f3a 100644 --- a/src/virtual-synth.ts +++ b/src/virtual-synth.ts @@ -54,7 +54,7 @@ export class VirtualSynth { frequency, waveform }) - this.voices.sort((a, b) => a.frequency - b.frequency) + this.voices.sort((a, b) => Math.abs(a.frequency) - Math.abs(b.frequency)) const voiceOff = () => { for (let i = 0; i < this.voices.length; ++i) { From 4117f2d47b43f13d0c8395de3abc0f14ac258e12 Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Fri, 5 Apr 2024 10:36:02 +0300 Subject: [PATCH 3/3] Add an optional margin of equivalence to CS checks Mostly ignore signs of intervals on the analysis tab. ref #577 --- src/analysis.ts | 36 ++++++++++++++++++++++++++++--- src/stores/state.ts | 5 +++++ src/views/AnalysisView.vue | 43 ++++++++++++++++++++++++++++++-------- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/analysis.ts b/src/analysis.ts index 5578f6c2..cc2478d4 100644 --- a/src/analysis.ts +++ b/src/analysis.ts @@ -410,6 +410,36 @@ export function constantStructureViolations(matrix: Interval[][]) { return result } +/** + * Compute a matrix of constant structure violations up to a margin. + * @param centsMatrix Interval matrix converted to cents. + * @param margin Margin of violation in cents. + * @returns Boolean array of CS violations within the given margin. + */ +export function marginViolations(centsMatrix: number[][], margin: number) { + const result: boolean[][] = [] + for (let i = 0; i < centsMatrix.length; ++i) { + result.push(Array(centsMatrix[i].length).fill(false)) + } + for (let i = 0; i < centsMatrix[0].length; ++i) { + for (let j = 0; j < centsMatrix.length; ++j) { + if (result[j][i]) { + continue + } + const cents = centsMatrix[j][i] + for (let k = i + 1; k < centsMatrix[0].length; ++k) { + for (let l = 0; l < centsMatrix.length; ++l) { + if (Math.abs(centsMatrix[l][k] - cents) < margin) { + result[j][i] = true + result[l][k] = true + } + } + } + } + } + return result +} + export function varietySignature(matrix: Interval[][]) { const result: number[] = [] if (!matrix.length) { @@ -431,12 +461,12 @@ export function varietySignature(matrix: Interval[][]) { return result } -export function brightnessSignature(matrix: Interval[][]) { - const totals = matrix.map((row) => row.reduce((prev, cur) => prev + cur.totalCents(), 0)) +export function brightnessSignature(centsMatrix: number[][]) { + const totals = centsMatrix.map((row) => row.reduce((prev, cur) => prev + cur, 0)) const minimum = Math.min(...totals) const maximum = Math.max(...totals) if (minimum === maximum) { - return Array(matrix.length).fill(1) + return Array(centsMatrix.length).fill(1) } const normalizer = 1 / (maximum - minimum) return totals.map((t) => (t - minimum) * normalizer) diff --git a/src/stores/state.ts b/src/stores/state.ts index c56231ea..eb3b9a9c 100644 --- a/src/stores/state.ts +++ b/src/stores/state.ts @@ -33,6 +33,9 @@ export const useStateStore = defineStore('state', () => { ) const calculateVariety = ref(storage.getItem('calculateVariety') === 'true') const calculateBrightness = ref(storage.getItem('calculateBrightness') === 'true') + const constantStructureMargin = ref( + parseInt(storage.getItem('constantStructureMargin') ?? '0', 10) + ) // Special keyboard codes also from local storage. const deactivationCode = ref(storage.getItem('deactivationCode') ?? 'Backquote') @@ -52,6 +55,7 @@ export const useStateStore = defineStore('state', () => { calculateConstantStructureViolations, calculateVariety, calculateBrightness, + constantStructureMargin, deactivationCode, equaveUpCode, equaveDownCode, @@ -85,6 +89,7 @@ export const useStateStore = defineStore('state', () => { calculateConstantStructureViolations, calculateVariety, calculateBrightness, + constantStructureMargin, deactivationCode, equaveUpCode, equaveDownCode, diff --git a/src/views/AnalysisView.vue b/src/views/AnalysisView.vue index 20d84c61..69023288 100644 --- a/src/views/AnalysisView.vue +++ b/src/views/AnalysisView.vue @@ -4,6 +4,7 @@ import { constantStructureViolations, freeEquallyTemperedChord, intervalMatrix, + marginViolations, rootedEquallyTemperedChord, varietySignature } from '@/analysis' @@ -67,7 +68,7 @@ const strokeStyle = computed(() => { // we want more accurate results here. function formatMatrixCell(interval: Interval) { if (cellFormat.value === 'cents') { - return interval.totalCents().toFixed(1) + return interval.totalCents(true).toFixed(1) } if (cellFormat.value === 'decimal') { // Consistent with tuning table localization. @@ -83,13 +84,24 @@ const matrix = computed(() => intervalMatrix(scale.relativeIntervals.slice(0, state.maxMatrixWidth)) ) +const centsMatrix = computed(() => matrix.value.map((row) => row.map((i) => i.totalCents(true)))) + const matrixRows = computed(() => matrix.value.map((row) => row.map(formatMatrixCell))) -const violations = computed(() => constantStructureViolations(matrix.value)) +const violations = computed(() => { + const margin = state.constantStructureMargin + if (margin) { + return marginViolations(centsMatrix.value, margin) + } else { + return constantStructureViolations(matrix.value) + } +}) const variety = computed(() => varietySignature(matrix.value)) -const brightness = computed(() => brightnessSignature(matrix.value).map((b) => Math.round(100 * b))) +const brightness = computed(() => + brightnessSignature(centsMatrix.value).map((b) => Math.round(100 * b)) +) const heldScaleDegrees = computed(() => { const result: Set = new Set() @@ -109,8 +121,8 @@ const equallyTemperedChordData = computed(() => { divisions: 1 } } - const frequencies = audio.virtualSynth.voices.map((voice) => voice.frequency) - const equaveCents = equave.value.totalCents() + const frequencies = audio.virtualSynth.voices.map((voice) => Math.abs(voice.frequency)) + const equaveCents = equave.value.totalCents(true) if (errorModel.value === 'rooted') { return rootedEquallyTemperedChord(frequencies, maxDivisions.value, equaveCents) } @@ -128,6 +140,7 @@ function highlight(y?: number, x?: number) { if (!state.calculateConstantStructureViolations) { return } + const margin = state.constantStructureMargin if (highlights.length !== matrix.value.length) { highlights.length = 0 for (let i = 0; i < matrix.value.length; ++i) { @@ -136,11 +149,15 @@ function highlight(y?: number, x?: number) { } // Look at other violators if (y !== undefined && x !== undefined && violations.value[y][x]) { - const value = matrix.value[y][x].value + const value: any = margin ? centsMatrix.value[y][x] : matrix.value[y][x].value for (let i = 0; i < matrix.value.length; ++i) { for (let j = 0; j < matrix.value[i].length; ++j) { if (violations.value[i][j]) { - highlights[i][j] = matrix.value[i][j].value.strictEquals(value) + if (margin) { + highlights[i][j] = Math.abs(centsMatrix.value[i][j] - value) < margin + } else { + highlights[i][j] = matrix.value[i][j].value.strictEquals(value) + } } else { highlights[i][j] = false } @@ -160,9 +177,13 @@ function highlight(y?: number, x?: number) { } // Look at own column - const value = matrix.value[y][x].value + const value: any = margin ? centsMatrix.value[y][x] : matrix.value[y][x].value for (let i = 0; i < matrix.value.length; ++i) { - highlights[i][x] = matrix.value[i][x].value.strictEquals(value) + if (margin) { + highlights[i][x] = Math.abs(centsMatrix.value[i][x] - value) < margin + } else { + highlights[i][x] = matrix.value[i][x].value.strictEquals(value) + } } } @@ -257,6 +278,10 @@ function highlight(y?: number, x?: number) { /> +
+ + +