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) { /> +
+ + +