From a2edec45c32fcb3354a05b16479c9475d8e49fed Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Thu, 18 Apr 2024 15:09:23 +0300 Subject: [PATCH] Use only a single source of truth for base frequency Do the same with base MIDI note. ref #658 --- CHANGELOG.md | 2 +- src/App.vue | 6 +++--- src/components/ExporterButtons.vue | 8 -------- src/components/NewScale.vue | 2 +- src/components/ScaleControls.vue | 2 +- src/components/modals/export/KorgExport.vue | 4 ---- src/components/modals/export/MtsSysexExport.vue | 4 ---- src/components/modals/export/ReaperExport.vue | 4 ---- src/exporters/__tests__/test-data.ts | 2 -- src/exporters/ableton.ts | 4 ++-- src/exporters/anamark.ts | 3 +-- src/exporters/base.ts | 2 -- src/exporters/image-line.ts | 2 +- src/exporters/kontakt.ts | 4 ++-- src/exporters/korg.ts | 4 ++-- src/exporters/reaper.ts | 4 ++-- src/exporters/scala.ts | 7 ++++--- src/stores/scale.ts | 16 ++++++++++++---- src/views/AnalysisView.vue | 2 +- src/views/LatticeView.vue | 2 +- src/views/ScaleView.vue | 4 ++-- src/views/VirtualKeyboardView.vue | 6 +++--- src/views/VirtualQwerty.vue | 2 +- 23 files changed, 40 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c6175a7..1a0a4250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ * Feature: Implement multi-channel MIDI mode compatible with the Lumatone [#649](https://github.com/xenharmonic-devs/scale-workshop/pull/649) * Bug fix: Extreme ratios now only break parts of the tuning table that do not have IEEE floating point representation and format better when non-finite [#631](https://github.com/xenharmonic-devs/scale-workshop/issues/631), [#632](https://github.com/xenharmonic-devs/scale-workshop/issues/632) * Style fix: Make checkbox and radio button labels more consistent [#644](https://github.com/xenharmonic-devs/scale-workshop/issues/644) - * Beta cycle issues: [#643](https://github.com/xenharmonic-devs/scale-workshop/issues/643), [#640](https://github.com/xenharmonic-devs/scale-workshop/issues/640), [#577](https://github.com/xenharmonic-devs/scale-workshop/issues/577), [#513](https://github.com/xenharmonic-devs/scale-workshop/issues/513) + * Beta cycle issues: [#643](https://github.com/xenharmonic-devs/scale-workshop/issues/643), [#640](https://github.com/xenharmonic-devs/scale-workshop/issues/640), [#577](https://github.com/xenharmonic-devs/scale-workshop/issues/577), [#513](https://github.com/xenharmonic-devs/scale-workshop/issues/513), [#658](https://github.com/xenharmonic-devs/scale-workshop/issues/658) * Alpha cycle issues: [#574](https://github.com/xenharmonic-devs/scale-workshop/issues/574), [#579](https://github.com/xenharmonic-devs/scale-workshop/issues/579) ## 2.4.1 diff --git a/src/App.vue b/src/App.vue index 1d49371a..70e452e1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -308,7 +308,7 @@ function typingKeydown(event: CoordinateKeyboardEvent) { return emptyKeyup } - let index = scale.baseMidiNote + scale.scale.size * scale.equaveShift + scale.degreeShift + let index = scale.scale.baseMidiNote + scale.scale.size * scale.equaveShift + scale.degreeShift if (scale.keyboardMode === 'isomorphic') { index += x * state.isomorphicHorizontal + (2 - y) * state.isomorphicVertical @@ -351,7 +351,7 @@ onMounted(() => { const scaleWorkshopOneData = new ScaleWorkshopOneData() scale.name = scaleWorkshopOneData.name - scale.baseFrequency = scaleWorkshopOneData.freq + scale.userBaseFrequency = scaleWorkshopOneData.freq scale.autoFrequency = false scale.baseMidiNote = scaleWorkshopOneData.midi state.isomorphicHorizontal = scaleWorkshopOneData.horizontal @@ -389,7 +389,7 @@ onMounted(() => { } scale.name = decodedState.scaleName - scale.baseFrequency = decodedState.baseFrequency + scale.userBaseFrequency = decodedState.baseFrequency scale.autoFrequency = false scale.baseMidiNote = decodedState.baseMidiNote state.isomorphicHorizontal = decodedState.isomorphicHorizontal diff --git a/src/components/ExporterButtons.vue b/src/components/ExporterButtons.vue index 58868255..6ca7b569 100644 --- a/src/components/ExporterButtons.vue +++ b/src/components/ExporterButtons.vue @@ -46,8 +46,6 @@ function doExport(exporter: ExporterKey) { scaleUrl: window.location.href, filename: sanitizeFilename(scale.name), relativeIntervals: scale.relativeIntervals, - baseFrequency: scale.baseFrequency, - baseMidiNote: scale.baseMidiNote, scale: scale.scale, labels: scale.labels, midiOctaveOffset: -1, @@ -68,8 +66,6 @@ function doExport(exporter: ExporterKey) { @cancel="showKorgExportModal = false" :newline="state.newline" :scaleName="scale.name" - :baseMidiNote="scale.baseMidiNote" - :baseFrequency="scale.baseFrequency" :relativeIntervals="scale.relativeIntervals" :midiOctaveOffset="-1" :scale="scale.scale" @@ -82,8 +78,6 @@ function doExport(exporter: ExporterKey) { @cancel="showMtsSysexExportModal = false" :newline="state.newline" :scaleName="scale.name" - :baseMidiNote="scale.baseMidiNote" - :baseFrequency="scale.baseFrequency" :relativeIntervals="scale.relativeIntervals" :midiOctaveOffset="-1" :scale="scale.scale" @@ -96,8 +90,6 @@ function doExport(exporter: ExporterKey) { @cancel="showReaperExportModal = false" :newline="state.newline" :scaleName="scale.name" - :baseMidiNote="scale.baseMidiNote" - :baseFrequency="scale.baseFrequency" :relativeIntervals="scale.relativeIntervals" :midiOctaveOffset="-1" :scale="scale.scale" diff --git a/src/components/NewScale.vue b/src/components/NewScale.vue index a2a3b121..91dd3292 100644 --- a/src/components/NewScale.vue +++ b/src/components/NewScale.vue @@ -120,7 +120,7 @@ function blur() { } function updateBaseFrequency(value: number) { - scale.baseFrequency = value + scale.userBaseFrequency = value scale.autoFrequency = false } diff --git a/src/components/ScaleControls.vue b/src/components/ScaleControls.vue index 81cce1b6..a47194b3 100644 --- a/src/components/ScaleControls.vue +++ b/src/components/ScaleControls.vue @@ -64,7 +64,7 @@ defineExpose({ focus, clearPaletteInfo }) id="base-frequency" type="number" step="any" - v-model="scale.baseFrequency" + v-model="scale.baseFrequencyDisplay" :disabled="scale.autoFrequency" @input="updateScale" /> diff --git a/src/components/modals/export/KorgExport.vue b/src/components/modals/export/KorgExport.vue index 32d23bf3..273f632a 100644 --- a/src/components/modals/export/KorgExport.vue +++ b/src/components/modals/export/KorgExport.vue @@ -10,8 +10,6 @@ import type { Scale } from '@/scale' const props = defineProps<{ newline: string scaleName: string - baseMidiNote: number - baseFrequency: number midiOctaveOffset: number relativeIntervals: Interval[] labels: string[] @@ -50,9 +48,7 @@ async function doExport() { scale: props.scale, relativeIntervals: props.relativeIntervals, labels: props.labels, - baseFrequency: props.baseFrequency, filename: sanitizeFilename(props.scaleName), - baseMidiNote: props.baseMidiNote, midiOctaveOffset: props.midiOctaveOffset } diff --git a/src/components/modals/export/MtsSysexExport.vue b/src/components/modals/export/MtsSysexExport.vue index 4b345373..982f1f89 100644 --- a/src/components/modals/export/MtsSysexExport.vue +++ b/src/components/modals/export/MtsSysexExport.vue @@ -11,8 +11,6 @@ import type { Interval } from 'sonic-weave' const props = defineProps<{ newline: string scaleName: string - baseMidiNote: number - baseFrequency: number midiOctaveOffset: number relativeIntervals: Interval[] scale: Scale @@ -58,8 +56,6 @@ function doExport() { newline: props.newline, scale: props.scale, filename: sanitizeFilename(props.scaleName), - baseMidiNote: props.baseMidiNote, - baseFrequency: props.baseFrequency, relativeIntervals: props.relativeIntervals, midiOctaveOffset: props.midiOctaveOffset, labels: props.labels, diff --git a/src/components/modals/export/ReaperExport.vue b/src/components/modals/export/ReaperExport.vue index 3dea79f5..3eaa3f85 100644 --- a/src/components/modals/export/ReaperExport.vue +++ b/src/components/modals/export/ReaperExport.vue @@ -10,8 +10,6 @@ import type { Interval } from 'sonic-weave' const props = defineProps<{ newline: string scaleName: string - baseMidiNote: number - baseFrequency: number midiOctaveOffset: number relativeIntervals: Interval[] scale: Scale @@ -39,8 +37,6 @@ function doExport() { newline: props.newline, scale: props.scale, filename: sanitizeFilename(props.scaleName), - baseMidiNote: props.baseMidiNote, - baseFrequency: props.baseFrequency, midiOctaveOffset: props.midiOctaveOffset, relativeIntervals: props.relativeIntervals, labels: props.labels, diff --git a/src/exporters/__tests__/test-data.ts b/src/exporters/__tests__/test-data.ts index cf50ab24..57d69a95 100644 --- a/src/exporters/__tests__/test-data.ts +++ b/src/exporters/__tests__/test-data.ts @@ -78,8 +78,6 @@ export function getTestData(appTitle: string) { scale, appTitle, description: 'A scale for testing if the exporter works', - baseMidiNote: 69, - baseFrequency: 440, midiOctaveOffset: 0, sourceText: '100.\nC5_5\n4\\5\n5/3\n1,3591409142295225r\n3486784401/3276800000\n2/1', labels: ['100.', 'C5_5', '4\\5', '5/3', '1,3591409142295225r', '3486784401/3276800000', '2/1'], diff --git a/src/exporters/ableton.ts b/src/exporters/ableton.ts index 3f69926f..02cc5287 100644 --- a/src/exporters/ableton.ts +++ b/src/exporters/ableton.ts @@ -19,7 +19,7 @@ export default class AbletonAsclExporter extends BaseExporter { const newline = this.params.newline const intervals = this.params.relativeIntervals const labels = this.params.labels - const referenceFrequency = this.params.baseFrequency.toFixed(8) + const referenceFrequency = this.params.scale.baseFrequency.toFixed(8) // assemble the .ascl file contents let file = '! ' + this.params.filename + '.ascl' + newline file += '! Created using ' + this.appTitle + newline @@ -59,7 +59,7 @@ export default class AbletonAsclExporter extends BaseExporter { } // It's unclear what "octave number" means in the spec - const octave = Math.floor(ftom(this.params.baseFrequency)[0] / 12) - 1 + const octave = Math.floor(ftom(this.params.scale.baseFrequency)[0] / 12) - 1 file += '!' + newline file += '! @ABL NOTE_NAMES ' + names.join(' ') + newline diff --git a/src/exporters/anamark.ts b/src/exporters/anamark.ts index badbfaf9..3d3ee6c6 100644 --- a/src/exporters/anamark.ts +++ b/src/exporters/anamark.ts @@ -106,8 +106,7 @@ class AnaMarkExporter extends BaseExporter { newline + '; Set reference key to absolute frequency (not scale note but midi key)' + newline - file += - 'note ' + this.params.baseMidiNote + '="! ' + scale.baseFrequency.toFixed(6) + '"' + newline + file += 'note ' + scale.baseMidiNote + '="! ' + scale.baseFrequency.toFixed(6) + '"' + newline } file += newline + '[Scale End]' + newline diff --git a/src/exporters/base.ts b/src/exporters/base.ts index 5eb2e1a8..1507bc68 100644 --- a/src/exporters/base.ts +++ b/src/exporters/base.ts @@ -7,8 +7,6 @@ export type ExporterParams = { newline: string filename: string relativeIntervals: Interval[] - baseFrequency: number - baseMidiNote: number midiOctaveOffset: number scale: Scale labels: string[] diff --git a/src/exporters/image-line.ts b/src/exporters/image-line.ts index b94f0cf1..e894372b 100644 --- a/src/exporters/image-line.ts +++ b/src/exporters/image-line.ts @@ -20,7 +20,7 @@ class ImageLineExporter extends BaseExporter { getFileContents(range: number) { const scale = this.params.scale - const baseFreqOffset = Math.log2(this.params.baseFrequency / 440) // in number of octaves + const baseFreqOffset = Math.log2(this.params.scale.baseFrequency / 440) // in number of octaves // construct point data const points = new ArrayBuffer(121 * 24) diff --git a/src/exporters/kontakt.ts b/src/exporters/kontakt.ts index eac0a663..95ea6400 100644 --- a/src/exporters/kontakt.ts +++ b/src/exporters/kontakt.ts @@ -19,7 +19,7 @@ export default class KontaktExporter extends BaseExporter { getFileContents() { const newline = this.params.newline - const baseMidiNote = this.params.baseMidiNote + const baseMidiNote = this.params.scale.baseMidiNote // assemble the kontakt script contents let file = '{**************************************' + newline @@ -30,7 +30,7 @@ export default class KontaktExporter extends BaseExporter { ' (' + midiNoteNumberToName(baseMidiNote, this.params.midiOctaveOffset) + ') = ' + - this.params.baseFrequency.toString() + + this.params.scale.baseFrequency.toString() + ' Hz' + newline file += 'Created using ' + this.appTitle + newline + newline diff --git a/src/exporters/korg.ts b/src/exporters/korg.ts index c289b828..1ee8c161 100644 --- a/src/exporters/korg.ts +++ b/src/exporters/korg.ts @@ -158,12 +158,12 @@ export class KorgExporter extends BaseExporter { getFileContents(): [JSZip, string] { const scale = this.params.scale - const baseMidiNote = this.params.baseMidiNote + const baseMidiNote = scale.baseMidiNote let frequencies: number[] if (this.useOctaveFormat) { const rootFreq = mtof(0) - const transposeRatio = rootFreq / this.params.baseFrequency + const transposeRatio = rootFreq / scale.baseFrequency frequencies = scale .getFrequencyRange(baseMidiNote, baseMidiNote + OCTAVE_FORMAT_SIZE) .map((f: number) => f * transposeRatio) diff --git a/src/exporters/reaper.ts b/src/exporters/reaper.ts index a9c17de3..a329a190 100644 --- a/src/exporters/reaper.ts +++ b/src/exporters/reaper.ts @@ -10,7 +10,7 @@ export default class ReaperExporter extends BaseExporter { const digits = ReaperExporter.fractionDigits const scale = this.params.scale const labels = this.params.labels - const baseFrequency = this.params.baseFrequency + const baseFrequency = scale.baseFrequency const format = this.params.format const basePeriod = this.params.basePeriod || 0 const baseDegree = this.params.baseDegree || 0 @@ -27,7 +27,7 @@ export default class ReaperExporter extends BaseExporter { for (let i = ReaperExporter.tuningMaxSize - 1; i >= 0; i--) { file += i.toString() + ' ' - let index = i - this.params.baseMidiNote + let index = i - scale.baseMidiNote const period = basePeriod + Math.floor(index / scale.size) if (modBySize) { index = mmod(index, scale.size) diff --git a/src/exporters/scala.ts b/src/exporters/scala.ts index f4b88271..0aee96fb 100644 --- a/src/exporters/scala.ts +++ b/src/exporters/scala.ts @@ -63,7 +63,8 @@ export class ScalaKbmExporter extends BaseExporter { getFileContents() { const newline = this.params.newline const intervals = this.params.relativeIntervals - const baseFrequency = this.params.baseFrequency + const baseFrequency = this.params.scale.baseFrequency + const baseMidiNote = this.params.scale.baseMidiNote // assemble the .kbm file contents let file = '! Template for a keyboard mapping' + newline file += '!' + newline @@ -74,9 +75,9 @@ export class ScalaKbmExporter extends BaseExporter { file += '! Last MIDI note number to retune:' + newline file += '127' + newline file += '! Middle note where the first entry of the mapping is mapped to:' + newline - file += this.params.baseMidiNote.toString() + newline + file += baseMidiNote.toString() + newline file += '! Reference note for which frequency is given:' + newline - file += this.params.baseMidiNote.toString() + newline + file += baseMidiNote.toString() + newline file += '! Frequency to tune the above note to' + newline file += baseFrequency.toString() + newline file += '! Scale degree to consider as formal octave (determines difference in pitch' + newline diff --git a/src/stores/scale.ts b/src/stores/scale.ts index b953fa25..a6bf7e87 100644 --- a/src/stores/scale.ts +++ b/src/stores/scale.ts @@ -64,10 +64,17 @@ export const useScaleStore = defineStore('scale', () => { const gas = ref(parseInt(localStorage.getItem('gas') ?? '10000', 10)) const name = ref('') + // For the v-model. Consider stores.scale.scale.baseMidiNote the source of truth. const baseMidiNote = ref(60) + + // The concept of base frequency is a little confusing so here's an explanation: + // The user can either set the base frequency or have it be automatically calculated. + // baseFrequencyDisplay reflects the initial value passed to the SonicWeave runtime. + // However the runtime may assign a different unison frequency and that's what ends up in scale.value.baseFrequency. + // Threrefore stores.scale.scale.baseFrequency is the source of truth, while stores.scale.baseFrequencyDisplay is the v-model. const userBaseFrequency = ref(261.63) const autoFrequency = ref(true) - const baseFrequency = computed({ + const baseFrequencyDisplay = computed({ get() { return autoFrequency.value ? mtof(baseMidiNote.value) : userBaseFrequency.value }, @@ -75,11 +82,12 @@ export const useScaleStore = defineStore('scale', () => { userBaseFrequency.value = value } }) + // XXX: baseFrequencyDisplay is merely used for convenience here. This is the last time there's a direct connection. + const scale = ref(new Scale(TET12, baseFrequencyDisplay.value, baseMidiNote.value)) const autoColors = ref<'silver' | 'cents' | 'factors'>('silver') const sourceText = ref('') const relativeIntervals = ref(INTERVALS_12TET) const latticeIntervals = ref(INTERVALS_12TET) - const scale = ref(new Scale(TET12, baseFrequency.value, baseMidiNote.value)) const colors = ref(defaultColors(baseMidiNote.value)) const labels = ref(defaultLabels(baseMidiNote.value, accidentalPreference.value)) const error = ref('') @@ -302,7 +310,7 @@ export const useScaleStore = defineStore('scale', () => { // Inject global variables const _ = Interval.fromInteger(baseMidiNote.value) const baseFreq = new Interval( - TimeMonzo.fromFractionalFrequency(new Fraction(baseFrequency.value).simplify(1e-8)), + TimeMonzo.fromFractionalFrequency(new Fraction(baseFrequencyDisplay.value).simplify(1e-8)), 'linear' ) const extraBuiltins: Record = { @@ -433,7 +441,7 @@ export const useScaleStore = defineStore('scale', () => { userBaseFrequency, autoFrequency, autoColors, - baseFrequency, + baseFrequencyDisplay, sourcePrefix, sourceText, scale, diff --git a/src/views/AnalysisView.vue b/src/views/AnalysisView.vue index 9cda881d..8fe562e7 100644 --- a/src/views/AnalysisView.vue +++ b/src/views/AnalysisView.vue @@ -107,7 +107,7 @@ const heldScaleDegrees = computed(() => { const result: Set = new Set() for (const midiIndex of state.heldNotes.keys()) { if (state.heldNotes.get(midiIndex)! > 0) { - result.add(mmod(midiIndex - scale.baseMidiNote, scale.scale.size)) + result.add(mmod(midiIndex - scale.scale.baseMidiNote, scale.scale.size)) } } return result diff --git a/src/views/LatticeView.vue b/src/views/LatticeView.vue index 428df404..9bc82323 100644 --- a/src/views/LatticeView.vue +++ b/src/views/LatticeView.vue @@ -46,7 +46,7 @@ const heldNotes = computed(() => { for (const midiIndex of state.heldNotes.keys()) { if (state.heldNotes.get(midiIndex)! > 0) { // Offset by 1 to match relativeIntervals - result.add(perm[mmod(midiIndex - scale.baseMidiNote - 1, scale.scale.size)]) + result.add(perm[mmod(midiIndex - scale.scale.baseMidiNote - 1, scale.scale.size)]) } } return result diff --git a/src/views/ScaleView.vue b/src/views/ScaleView.vue index 89ce7caf..57054378 100644 --- a/src/views/ScaleView.vue +++ b/src/views/ScaleView.vue @@ -50,8 +50,8 @@ onMounted(() => { :heldNotes="state.heldNotes" :frequencies="scale.frequencies" :centss="scale.centss" - :baseFrequency="scale.baseFrequency" - :baseMidiNote="scale.baseMidiNote" + :baseFrequency="scale.scale.baseFrequency" + :baseMidiNote="scale.scale.baseMidiNote" :colors="scale.colors" :labels="scale.labels" /> diff --git a/src/views/VirtualKeyboardView.vue b/src/views/VirtualKeyboardView.vue index e34ee474..267f529e 100644 --- a/src/views/VirtualKeyboardView.vue +++ b/src/views/VirtualKeyboardView.vue @@ -13,7 +13,7 @@ const state = useStateStore() const scale = useScaleStore() const baseIndex = computed( - () => scale.baseMidiNote + scale.equaveShift * scale.scale.size + scale.degreeShift + () => scale.scale.baseMidiNote + scale.equaveShift * scale.scale.size + scale.degreeShift ) type NoteOff = () => void @@ -25,7 +25,7 @@ type NoteOnCallback = (index: number) => NoteOff NoteOff () const baseIndex = computed( - () => scale.baseMidiNote + scale.equaveShift * scale.scale.size + scale.degreeShift + () => scale.scale.baseMidiNote + scale.equaveShift * scale.scale.size + scale.degreeShift )