From 4eba1f25c9da487a6386792f508691a3bdc92c37 Mon Sep 17 00:00:00 2001 From: BafS Date: Mon, 30 Dec 2024 22:30:57 +0100 Subject: [PATCH] Fine tune design details --- README.md | 11 -------- src/App.svelte | 34 +++++++++++++++--------- src/FareySequencePattern.svelte | 16 +++++++++-- src/WavePlot.svelte | 47 ++++++++++++++++++++------------- src/app.css | 11 +++++--- src/player.js | 4 +-- 6 files changed, 75 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 6badca2..cbe339b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ - `npm i` - `npm run dev` -- [localhost:8080](http://localhost:8080) ## Building and running in production mode @@ -17,13 +16,3 @@ To create an optimised version of the app: ```bash npm run build ``` - -## Single-page app mode - -By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere. - -If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json: - -```js -"start": "sirv public --single" -``` diff --git a/src/App.svelte b/src/App.svelte index fb66e17..58354e3 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -39,6 +39,12 @@ return best; }; + /** + * @param {number} number + * @param {number} precision + */ + const round = (number, precision) => Math.round(number * 10 ** precision) / 10 ** precision; + let fareySequenceOrder = $state(15); const fareySequence = $derived(getFareySequence(fareySequenceOrder)); @@ -50,7 +56,7 @@ let frequency = $derived((n) => 2 ** (n / semitones)); - let frequencies = $derived(scaleBinary.padEnd(semitones + 1, '0').split('').map((v, i) => [v === '1' || v === 'x', frequency(i)])); + let frequencies = $derived((scaleBinary.padEnd(semitones , '0') + (scaleBinary[0] ?? '0')).split('').map((v, i) => [v === '1' || v === 'x', frequency(i)])); let frequencyInfo = $derived(([selected, freq]) => { const base = { @@ -59,7 +65,7 @@ freq: freq * frequencyRoot, farey: getClosestFromFareySequence(fareySequence, freq), }; - base.fareyError = 1 - (1/ freq * base.farey[0] / base.farey[1]); + base.fareyError = 1 - (1 / freq * base.farey[0] / base.farey[1]); base.fareyErrorAbs = freq * frequencyRoot - (frequencyRoot * base.farey[0] / base.farey[1]); return base; @@ -84,12 +90,13 @@

Equal temperament playground

+source
- Scale: - Fundamental frequency (hz): + Scale: + Fundamental frequency (hz): Semi-tones: Farey sequence order:
@@ -117,7 +124,7 @@ {i} {freq ? Math.round(freq * 100) / 100 : ''} Hz - {farey.join('/')} + {farey.join(':')} {Math.round(fareyError * 10000) / 100}% ({Math.round(fareyErrorAbs * 100) / 100} Hz) {/each} @@ -142,12 +149,11 @@ 1000 ? (rowWidth / 2) : 280} height={rowWidth > 1000 ? (rowWidth / 3) : 280} - frequencyInfos={frequencyInfos} semitones={semitones} generator={toneCircleGenerator} frequencyRoot={frequencyRoot} />
- Generator: + Generator:
@@ -163,9 +169,9 @@

- Perfect fifth: {frequencyRoot * 3/2} Hz
- Perfect fourth: {frequencyRoot * 4/3} Hz
- Major third: {frequencyRoot * 5/4} Hz
+ Perfect fifth: {round(frequencyRoot * 3/2, 4)} Hz
+ Perfect fourth: {round(frequencyRoot * 4/3, 4)} Hz
+ Major third: {round(frequencyRoot * 5/4, 4)} Hz

@@ -190,7 +196,7 @@ background: #eee; } table td, table th { - padding: .5rem 1.5rem; + padding: .4rem 1.5rem; vertical-align: top; } table td { @@ -222,6 +228,10 @@ margin: 0; margin-bottom: 6px; } + .source { + font-size: 80%; + float: right; + } .main-inputs { margin-top: 4px; @@ -235,6 +245,6 @@ .btn-play { background-color: rgba(200, 200, 200, .075); font-size: 90%; - padding: 2px 10px; + padding: 3px 10px; } diff --git a/src/FareySequencePattern.svelte b/src/FareySequencePattern.svelte index a159375..bac6816 100644 --- a/src/FareySequencePattern.svelte +++ b/src/FareySequencePattern.svelte @@ -19,6 +19,17 @@ let cellHeight = 12; let height = $derived((fareySequenceOrder + 1.5) * cellHeight); + + /** + * @param {number} i + * @param {number} freq + * @return {number} + */ + const alignX = (i, freq) => { + const base = (freq - frequencyRoot) * (width - 5) / frequencyRoot; + const numWidth = 4 * (1 + Math.log10(i) | 0); + return Math.min(width - numWidth * 1.5, base - numWidth / 2); + }; @@ -29,14 +40,15 @@ {#each frequencyInfos as {selected, freq}, i} {Math.round(freq)} hz - - import {onMount, afterUpdate} from 'svelte'; - - let canvas; - export let scaleBinary; - export let frequencyRoot = 440; - export let semitones = 12; - export let width = 1000; - export let height = 300; + import {onMount} from 'svelte'; + + let canvas = $state(); + + /** + * @typedef {Object} Props + * @property {string} scaleBinary + * @property {number} [frequencyRoot] + * @property {number} [semitones] + * @property {number} [width] + * @property {number} [height] + */ + + /** @type {Props} */ + let { + scaleBinary, + frequencyRoot = 440, + semitones = 12, + width = 1000, + height = 290 + } = $props(); const colors = ['FF8A80', 'B388FF', '80D8FF', 'B9F6CA', 'FFFF8D', 'FF9E80', '8D6E63']; - $: frequency = (n) => 2 ** (n / semitones); + let frequency = $derived((n) => 2 ** (n / semitones)); - $: frequencies = scaleBinary.padEnd(semitones + 1, '0').split('').map((v, i) => [v === '1' || v === 'x', frequency(i)]); + let frequencies = $derived(scaleBinary.padEnd(semitones + 1, '0').split('').map((v, i) => [v === '1' || v === 'x', frequency(i)])); const frequencyFunctionCreator = (hz) => (t) => Math.sin(t * hz * Math.PI * 2); @@ -24,7 +37,7 @@ const y = fun(i / compression) * canvas.height / 2.2; let rel = canvas.height / 2; - ctx.lineWidth = "1.5"; + ctx.lineWidth = '1.5'; ctx.strokeStyle = color; // Green path ctx.moveTo(i, rel - previous); ctx.lineTo(i + 1, rel - y); @@ -33,7 +46,7 @@ ctx.stroke(); }; - $: canvasFn = (() => { + let canvasFn = () => { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -43,7 +56,7 @@ let x = 0; while (x < canvas.width) { ctx.beginPath(); - ctx.lineWidth = "2"; + ctx.lineWidth = '2'; ctx.strokeStyle = '#' + colors[idx % colors.length]; x = Math.round(i * compression / frequencyRoot / 2 * n); ctx.moveTo(x + .5, Math.round(canvas.height / 2) - 20); @@ -70,17 +83,15 @@ var t2 = new Date(); var dt = t2 - t1; - console.log('elapsed time = ' + dt + ' ms'); - }); + console.debug('elapsed time = ' + dt + ' ms'); + }; onMount(() => { canvas.width = width * 2; canvas.height = height * 2; }); - afterUpdate(() => { - canvasFn(); - }); + $effect(() => canvasFn()); any}|null} options + * @param {{gain: number, onended: () => void}|null} options */ export const playTone = (frequency, duration, options = null) => { const gainNode = audioCtx.createGain(); - gainNode.gain.value = options?.gain ?? 0.75; + gainNode.gain.value = options?.gain ?? 0.5; gainNode.connect(audioCtx.destination); const oscillator = audioCtx.createOscillator();