Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dropdown for Pythagorean enharmonic #641

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scale-workshop",
"version": "3.0.0-beta.9",
"version": "3.0.0-beta.10",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
Expand All @@ -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.10",
"sonic-weave": "github:xenharmonic-devs/sonic-weave#v0.0.11",
"sw-synth": "^0.1.0",
"temperaments": "^0.5.3",
"vue": "^3.3.4",
Expand Down
2 changes: 2 additions & 0 deletions src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
--color-accent-dimmed: rgba(0, 20, 20, 0.7);

--color-error: red;
--color-warning: orangered;
--color-indicator: #c35;

/* Mimic Bootstrap alert with 'danger' variant */
Expand Down Expand Up @@ -59,6 +60,7 @@
--color-accent-dimmed: rgba(0, 20, 20, 0.7);

--color-error: red;
--color-warning: orangered;
--color-indicator: #b25;
}

Expand Down
3 changes: 3 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ button {
.error {
color: var(--color-error);
}
.warning {
color: var(--color-warning);
}
code {
display: inline-block;
background-color: var(--color-background-soft);
Expand Down
9 changes: 2 additions & 7 deletions src/components/ScaleControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ defineExpose({ focus, clearPaletteInfo })
@input="updateScale"
/>
</div>
<div class="control">
<label for="enharmonic">Pythagorean enharmonic</label>
<select id="enharmonic" v-model="scale.enharmonic" @input="updateScale">
<option v-for="e of scale.enharmonics" :key="e" :value="e">{{ e }}</option>
</select>
</div>

<div class="control">
<label for="base-frequency">Base frequency</label>
Expand Down Expand Up @@ -133,7 +127,8 @@ defineExpose({ focus, clearPaletteInfo })
></textarea>
</div>
<ScaleRule :scale="scale.scale" />
<p class="error">{{ scale.error }}</p>
<p v-if="scale.error" class="error">{{ scale.error }}</p>
<p v-else-if="scale.warning" class="warning">{{ scale.warning }}</p>
<h3>Character palette</h3>
<div class="control">
<button
Expand Down
57 changes: 39 additions & 18 deletions src/stores/scale.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Scale } from '@/scale'
import { midiNoteNumberToEnharmonics, type AccidentalStyle, syncValues } from '@/utils'
import {
midiNoteNumberToEnharmonics,
type AccidentalStyle,
syncValues,
isBlackMidiNote,
midiNoteNumberToName
} from '@/utils'
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
import { mmod, mtof } from 'xen-dev-utils'
Expand All @@ -15,7 +21,8 @@ import {
type SonicWeaveValue,
StatementVisitor,
ExpressionVisitor,
builtinNode
builtinNode,
repr
} from 'sonic-weave'
import {
DEFAULT_NUMBER_OF_COMPONENTS,
Expand Down Expand Up @@ -57,10 +64,6 @@ export const useScaleStore = defineStore('scale', () => {

const name = ref('')
const baseMidiNote = ref(60)
const enharmonics = computed(() =>
midiNoteNumberToEnharmonics(baseMidiNote.value, accidentalPreference.value)
)
const enharmonic = ref(enharmonics.value[0])
const userBaseFrequency = ref(261.63)
const autoFrequency = ref(true)
const baseFrequency = computed({
Expand All @@ -79,6 +82,7 @@ export const useScaleStore = defineStore('scale', () => {
const colors = ref(defaultColors(baseMidiNote.value))
const labels = ref(defaultLabels(baseMidiNote.value, accidentalPreference.value))
const error = ref('')
const warning = ref('')

// Keyboard mode affects both physical qwerty and virtual keyboards
const keyboardMode = ref<'isomorphic' | 'piano'>('isomorphic')
Expand All @@ -98,10 +102,11 @@ export const useScaleStore = defineStore('scale', () => {
// === Computed state ===
const sourcePrefix = computed(() => {
const base = `numComponents(${DEFAULT_NUMBER_OF_COMPONENTS})\n`
const rootPitch = midiNoteNumberToName(baseMidiNote.value)
if (autoFrequency.value) {
return `${base}${enharmonic.value} = mtof(_) = 1/1`
return `${base}${rootPitch} = mtof(_) = 1/1`
}
return `${base}${enharmonic.value} = baseFrequency = 1/1`
return `${base}${rootPitch} = baseFrequency = 1/1`
})

const frequencies = computed(() => scale.value.getFrequencyRange(0, NUMBER_OF_NOTES))
Expand Down Expand Up @@ -257,11 +262,6 @@ export const useScaleStore = defineStore('scale', () => {
return baseMidiNote.value - baseInfo.whiteNumber
})

// State synchronization
watch([baseMidiNote, accidentalPreference], () => {
enharmonic.value = enharmonics.value[0]
})

// Sanity watchers
watch(baseMidiNote, (newValue) => {
if (isNaN(newValue)) {
Expand All @@ -274,6 +274,7 @@ export const useScaleStore = defineStore('scale', () => {
// Local storage watchers
syncValues({ accidentalPreference, hasLeftOfZ, gas })

// Extra builtins
function latticeView(this: ExpressionVisitor) {
const scale = this.getCurrentScale()
for (let i = 0; i < scale.length; ++i) {
Expand All @@ -287,6 +288,14 @@ export const useScaleStore = defineStore('scale', () => {
latticeView.__doc__ = 'Store the current scale to be displayed in the lattice tab.'
latticeView.__node__ = builtinNode(latticeView)

function warn(this: ExpressionVisitor, ...args: any[]) {
const s = repr.bind(this);
const message = args.map(a => (typeof a === 'string' ? a : s(a))).join(', ');
warning.value = message
}
warn.__doc__ = 'Issue a warning to the user and continue execution.'
warn.__node__ = builtinNode(warn)

// Local helpers
function getGlobalVisitor() {
// Inject global variables
Expand All @@ -297,7 +306,8 @@ export const useScaleStore = defineStore('scale', () => {
_,
baseMidiNote: _,
baseFrequency: baseFreq,
latticeView
latticeView,
warn
}
const visitor = getSourceVisitor(true, extraBuiltins)
visitor.rootContext.gas = gas.value
Expand Down Expand Up @@ -333,11 +343,17 @@ export const useScaleStore = defineStore('scale', () => {

function computeScale() {
try {
error.value = ''
warning.value = ''
latticeIntervals.value = []
const globalVisitor = getGlobalVisitor()
const visitor = new StatementVisitor(globalVisitor.rootContext, globalVisitor)
const ast = parseAST(sourceText.value)
let userDeclaredPitch = false
for (const statement of ast.body) {
if (statement.type === 'PitchDeclaration') {
userDeclaredPitch = true
}
const interupt = visitor.visit(statement)
if (interupt) {
throw new Error('Illegal statement.')
Expand Down Expand Up @@ -374,12 +390,18 @@ export const useScaleStore = defineStore('scale', () => {
)
}
labels.value = intervals.map((interval) => interval.label || name(interval))
error.value = ''
} else {
scale.value = new Scale(TET12, visitorBaseFrequency, baseMidiNote.value)
colors.value = defaultColors(baseMidiNote.value)
labels.value = defaultLabels(baseMidiNote.value, accidentalPreference.value)
error.value = 'Empty scale defaults to 12-tone equal temperament.'
warning.value = 'Empty scale defaults to 12-tone equal temperament.'
}
const noteNumber = baseMidiNote.value
if (!warning.value && isBlackMidiNote(noteNumber) && !userDeclaredPitch) {
const midiName = midiNoteNumberToName(noteNumber)
const enharmonics = midiNoteNumberToEnharmonics(noteNumber)
const rootFrequency = autoFrequency.value ? 'mtof(_)' : 'baseFrequency'
warning.value = `Base MIDI note ${noteNumber} defaults to ${midiName}. Use an explicit ${enharmonics[0]} = ${rootFrequency} or ${enharmonics[1]} = ${rootFrequency} to get rid of this warning.`
}
} catch (e) {
if (e instanceof Error) {
Expand All @@ -403,8 +425,6 @@ export const useScaleStore = defineStore('scale', () => {
// Live state
name,
baseMidiNote,
enharmonics,
enharmonic,
userBaseFrequency,
autoFrequency,
autoColors,
Expand All @@ -421,6 +441,7 @@ export const useScaleStore = defineStore('scale', () => {
latticeColors,
latticeLabels,
error,
warning,
keyboardMode,
equaveShift,
degreeShift,
Expand Down
19 changes: 19 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,25 @@ export function midiNoteNumberToEnharmonics(
return result
}

const IS_BLACK_MIDI_NOTE = [
false, // C
true, // C# / Db
false, // D
true, // D# / Eb
false, // E
false, // F
true, // F# / Gb
false, // G
true, // G# / Ab
false, // A
true, // A# / Bb
false // B
]

export function isBlackMidiNote(noteNumber: number) {
return IS_BLACK_MIDI_NOTE[mmod(noteNumber, 12)]
}

export function annotateColors(sourceLines: string[], keyColors: string[]) {
if (!keyColors.length) {
return
Expand Down
Loading