Skip to content

Commit

Permalink
Implement a full-width MOS view
Browse files Browse the repository at this point in the history
ref #700
  • Loading branch information
frostburn committed May 20, 2024
1 parent 971a7da commit f51a7f8
Show file tree
Hide file tree
Showing 11 changed files with 418 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Feature: New "repeat" modifier [#406](https://github.com/xenharmonic-devs/scale-workshop/issues/406)
* Feature: Implement multi-channel MIDI mode compatible with the Lumatone [#649](https://github.com/xenharmonic-devs/scale-workshop/pull/649)
* Feature: Show labels, ratios, cents and frequencies on the tuning table [#534](https://github.com/xenharmonic-devs/scale-workshop/issues/534)
* Feature: Full width view dedicated to the MOS pyramid [#700](https://github.com/xenharmonic-devs/scale-workshop/issues/700)
* 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), [#658](https://github.com/xenharmonic-devs/scale-workshop/issues/658), [#664](https://github.com/xenharmonic-devs/scale-workshop/issues/664), [#666](https://github.com/xenharmonic-devs/scale-workshop/issues/666)
Expand Down
18 changes: 9 additions & 9 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
Expand Up @@ -18,10 +18,10 @@
"isomorphic-qwerty": "^0.0.2",
"ji-lattice": "^0.0.3",
"jszip": "^3.10.1",
"moment-of-symmetry": "^0.5.2",
"moment-of-symmetry": "^0.6.0",
"pinia": "^2.1.7",
"qs": "^6.12.0",
"sonic-weave": "^0.2.0",
"sonic-weave": "^0.3.1",
"sw-synth": "^0.1.0",
"temperaments": "^0.5.3",
"values.js": "^2.1.1",
Expand Down
4 changes: 4 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ function panic() {
<RouterLink to="/about"><strong>Sw</strong></RouterLink>
</li>
<li><RouterLink to="/">Build Scale</RouterLink></li>
<li v-if="state.showMosTab">
<RouterLink to="/mos">MOS</RouterLink>
</li>
<li><RouterLink to="/analysis">Analysis</RouterLink></li>
<li><RouterLink to="/lattice">Lattice</RouterLink></li>
<li><RouterLink to="/vk">Virtual Keyboard</RouterLink></li>
Expand Down Expand Up @@ -610,6 +613,7 @@ nav a:first-of-type {
line-height: 1;
padding-right: 1em;
text-align: right;
color: var(--color-text-mute);
}
#app-footer a {
color: var(--color-text-mute);
Expand Down
160 changes: 160 additions & 0 deletions src/components/MosPyramid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<script setup lang="ts">
import { mosScaleInfo } from 'moment-of-symmetry'
import { computed } from 'vue'
import { dot } from 'xen-dev-utils'
const props = defineProps<{
selected: string
rows: number
vertical: number
horizontal: number
hardness: { n: number; d: number }
}>()
const emit = defineEmits(['easter-egg', 'mos'])
const viewBox = computed(
() => `${props.horizontal - props.rows / 2} ${props.vertical - 0.5} ${props.rows} ${props.rows}`
)
type BasicInfo = {
x: number
y: number
name: string
abbreviation: string
pattern: string
udp: string
equaveMonzo: [number, number]
brightGeneratorMonzo: [number, number]
}
const basics = computed(() => {
const v = props.vertical
const rows: BasicInfo[][] = []
// Do two extra rows to compensate for potentially non-square container.
for (let size = Math.max(2, v); size < props.rows + v + 4; ++size) {
const row: BasicInfo[] = []
for (let numL = 1; numL < size; ++numL) {
const numS = size - numL
const info = mosScaleInfo(numL, numS)
const p = info.numberOfPeriods
// Brightest for woods
let udp = `${size - p}|0(${p})`
if (size > 2 * p) {
// Major-like for everything else
udp = `${size - p * 2}|${p}`
if (p > 1) {
udp += `(${p})`
}
}
row.push({
x: numL - size / 2,
y: size - 2,
name: info.name ?? info.mosPattern,
abbreviation: info.abbreviation ?? info.mosPattern,
pattern: info.mosPattern,
udp,
equaveMonzo: [numL, numS],
brightGeneratorMonzo: info.brightGeneratorMonzo
})
}
rows.push(row)
}
return rows
})
const generators = computed(() => {
const { n, d } = props.hardness
return basics.value.map((row) =>
row.map((i) => `${dot(i.brightGeneratorMonzo, [n, d])}\\${dot(i.equaveMonzo, [n, d])}`)
)
})
</script>

<template>
<svg width="100%" height="100%" :viewBox="viewBox">
<image
href="@/assets/img/spoob.png"
x="-1"
y="-5"
width="2"
height="2"
@click="emit('easter-egg')"
/>

<template v-for="(row, i) of basics" :key="i">
<g
v-for="(info, j) of row"
:key="j"
@click="emit('mos', info.name, info.pattern, info.udp)"
:class="{ selected: selected === info.pattern }"
>
<rect :x="info.x - 0.45" :y="info.y - 0.45" width="0.9" height="0.9" />
<text
:x="info.x - 0.4"
:y="info.y - 0.3"
font-size="0.11"
dominant-baseline="middle"
text-anchor="start"
>
{{ info.pattern }}
</text>
<text
:x="info.x + 0.4"
:y="info.y - 0.3"
font-size="0.11"
dominant-baseline="middle"
text-anchor="end"
>
{{ generators[i][j] }}
</text>
<text
:x="info.x"
:y="info.y"
font-size="0.25"
dominant-baseline="middle"
text-anchor="middle"
>
{{ info.abbreviation }}
</text>
<text
:x="info.x"
:y="info.y + 0.3"
font-size="0.09"
dominant-baseline="middle"
text-anchor="middle"
>
{{ info.name }}
</text>
</g>
</template>
</svg>
</template>

<style scoped>
image {
cursor: pointer;
}
g {
cursor: pointer;
}
rect {
fill: var(--color-accent-background);
stroke: var(--color-accent-text-btn);
stroke-width: 0.01;
}
g:hover > rect {
fill: var(--color-accent);
stroke: var(--color-accent);
}
.selected > rect {
fill: var(--color-background-mute);
stroke-width: 0.02;
}
text {
fill: var(--color-accent-text-btn);
}
g:hover > text {
fill: white;
}
</style>
24 changes: 20 additions & 4 deletions src/components/ScaleRule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import type { Scale } from '@/scale'
import { computed } from 'vue'
import { mmod, valueToCents } from 'xen-dev-utils'
const props = defineProps<{
scale: Scale
}>()
const props = withDefaults(
defineProps<{
scale: Scale
orientation: 'horizontal' | 'vertical'
}>(),
{ orientation: 'horizontal' }
)
const ticksAndColors = computed(() => {
const equaveCents = valueToCents(props.scale.equaveRatio)
Expand All @@ -32,7 +36,7 @@ const ticksAndColors = computed(() => {
</script>

<template>
<svg width="100%" height="10">
<svg width="100%" height="10" v-if="orientation === 'horizontal'">
<line x1="0.5%" y1="50%" x2="99.5%" y2="50%" style="stroke: var(--color-text)" />
<line
v-for="([tick, color], i) of ticksAndColors"
Expand All @@ -44,4 +48,16 @@ const ticksAndColors = computed(() => {
:style="'stroke:' + color + ';'"
/>
</svg>
<svg width="10" height="100%" v-else>
<line y1="0.5%" x1="50%" y2="99.5%" x2="50%" style="stroke: var(--color-text)" />
<line
v-for="([tick, color], i) of ticksAndColors"
:key="i"
:y1="tick"
x1="5%"
:y2="tick"
x2="95%"
:style="'stroke:' + color + ';'"
/>
</svg>
</template>
6 changes: 6 additions & 0 deletions src/components/modals/generation/MosScale.vue
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ function edoClick(info: MosScaleInfo) {
><i>{{ modal.hardness }}</i>
</template>
<i v-else>{{ modal.previewName }}</i>
<template v-if="modal.method === 'pyramid'">
<RouterLink class="right" to="/mos">Fullscreen view</RouterLink>
</template>
</div>
</template>
</Modal>
Expand All @@ -327,6 +330,9 @@ function edoClick(info: MosScaleInfo) {
.pyramid button {
font-size: small;
}
.right {
margin-left: auto;
}
@media only screen and (max-width: 38rem) {
.pyramid {
text-align: left;
Expand Down
5 changes: 5 additions & 0 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ const router = createRouter({
name: 'qwerty',
component: () => import('../views/VirtualQwerty.vue')
},
{
path: '/mos',
name: 'mos',
component: () => import('../views/MosView.vue')
},
// Root aliases mainly for compatibility with old SW1 URLs.
{
path: '/index.html',
Expand Down
3 changes: 3 additions & 0 deletions src/stores/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const useStateStore = defineStore('state', () => {
const centsFractionDigits = ref(parseInt(storage.getItem('centsFractionDigits') ?? '3', 10))
const decimalFractionDigits = ref(parseInt(storage.getItem('decimalFractionDigits') ?? '5', 10))
const showVirtualQwerty = ref(storage.getItem('showVirtualQwerty') === 'true')
const showMosTab = ref(storage.getItem('showMosTab') === 'true')
const showKeyboardLabel = ref(storage.getItem('showKeyboardLabel') !== 'false')
const showKeyboardCents = ref(storage.getItem('showKeyboardCents') !== 'false')
const showKeyboardRatio = ref(storage.getItem('showKeyboardRatio') !== 'false')
Expand Down Expand Up @@ -55,6 +56,7 @@ export const useStateStore = defineStore('state', () => {
centsFractionDigits,
decimalFractionDigits,
showVirtualQwerty,
showMosTab,
showKeyboardLabel,
showKeyboardCents,
showKeyboardRatio,
Expand Down Expand Up @@ -92,6 +94,7 @@ export const useStateStore = defineStore('state', () => {
centsFractionDigits,
decimalFractionDigits,
showVirtualQwerty,
showMosTab,
showKeyboardLabel,
showKeyboardCents,
showKeyboardRatio,
Expand Down
Loading

0 comments on commit f51a7f8

Please sign in to comment.