Skip to content

Commit

Permalink
Merge pull request #3 from BafS/fine-tune-design-1
Browse files Browse the repository at this point in the history
Fine tune design details
  • Loading branch information
BafS authored Dec 30, 2024
2 parents 78b3f00 + 4eba1f2 commit 875c0aa
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 48 deletions.
11 changes: 0 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

- `npm i`
- `npm run dev`
- [localhost:8080](http://localhost:8080)

## Building and running in production mode

Expand All @@ -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"
```
34 changes: 22 additions & 12 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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 = {
Expand All @@ -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;
Expand All @@ -84,12 +90,13 @@
</script>
<h1>Equal temperament playground</h1>
<span class="source"><a href="https://github.com/BafS/equal-temperament-playground">source</a></span>
<WavePlot frequencyRoot={frequencyRoot} semitones={semitones} scaleBinary={scaleBinary} width={rowWidth} />
<div class="row main-inputs">
<span>Scale: <input type="text" maxlength={semitones} bind:value={scaleBinary}></span>
<span>Fundamental frequency (<em>hz</em>): <input type="number" bind:value={frequencyRoot}></span>
<span title="Use 0/1 to define which semi-tones are part of the scale">Scale: <input type="text" maxlength={semitones} bind:value={scaleBinary}></span>
<span><a href="https://en.wikipedia.org/wiki/Fundamental_frequency">Fundamental frequency</a> (<em>hz</em>): <input type="number" bind:value={frequencyRoot}></span>
<span>Semi-tones: <input type="number" bind:value={semitones}></span>
<span>Farey sequence order: <input type="number" min=2 max=200 bind:value={fareySequenceOrder}></span>
</div>
Expand Down Expand Up @@ -117,7 +124,7 @@
<td>{i}</td>
<td>{freq ? Math.round(freq * 100) / 100 : ''} <small>Hz</small> <button class="btn-play" onclick={() => playTone(freq, 1)}></button></td>
<!-- <td></td> -->
<td>{farey.join('/')}</td>
<td>{farey.join(':')}</td>
<td>{Math.round(fareyError * 10000) / 100}<small>%</small> <small>({Math.round(fareyErrorAbs * 100) / 100} Hz)</small></td>
</tr>
{/each}
Expand All @@ -142,12 +149,11 @@
<ToneCircle
width={rowWidth > 1000 ? (rowWidth / 2) : 280}
height={rowWidth > 1000 ? (rowWidth / 3) : 280}
frequencyInfos={frequencyInfos}
semitones={semitones}
generator={toneCircleGenerator}
frequencyRoot={frequencyRoot}
/><br>
Generator: <input class="input-small" type="number" max="199" bind:value={toneCircleGenerator}>
Generator: <input class="input-small" type="number" min="1" max="199" bind:value={toneCircleGenerator}>
</div>
<div id="farey-sequence-pattern">
Expand All @@ -163,9 +169,9 @@
<div>
<p>
Perfect fifth: {frequencyRoot * 3/2} Hz<br>
Perfect fourth: {frequencyRoot * 4/3} Hz<br>
Major third: {frequencyRoot * 5/4} Hz<br>
<a href="https://en.wikipedia.org/wiki/Perfect_fifth">Perfect fifth</a>: {round(frequencyRoot * 3/2, 4)} Hz<br>
<a href="https://en.wikipedia.org/wiki/Perfect_fourth">Perfect fourth</a>: {round(frequencyRoot * 4/3, 4)} Hz<br>
<a href="https://en.wikipedia.org/wiki/Major_third">Major third</a>: {round(frequencyRoot * 5/4, 4)} Hz<br>
</p>
</div>
</div>
Expand All @@ -190,7 +196,7 @@
background: #eee;
}
table td, table th {
padding: .5rem 1.5rem;
padding: .4rem 1.5rem;
vertical-align: top;
}
table td {
Expand Down Expand Up @@ -222,6 +228,10 @@
margin: 0;
margin-bottom: 6px;
}
.source {
font-size: 80%;
float: right;
}
.main-inputs {
margin-top: 4px;
Expand All @@ -235,6 +245,6 @@
.btn-play {
background-color: rgba(200, 200, 200, .075);
font-size: 90%;
padding: 2px 10px;
padding: 3px 10px;
}
</style>
16 changes: 14 additions & 2 deletions src/FareySequencePattern.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
</script>

<svg width={width} height={height}>
Expand All @@ -29,14 +40,15 @@
{#each frequencyInfos as {selected, freq}, i}
<rect opacity="{selected ? .75 : .25}" fill="#FF5722" width="2" height="{(fareySequenceOrder) * cellHeight}" y="0" x={((freq - frequencyRoot) * (width - 5) / frequencyRoot) + 1} tabindex="-1"></rect>
<text x=0
y="-9"
y={i === frequencyInfos.length - 1 ? 9 : -9}
font-family="monospace"
transform="translate({((freq - frequencyRoot) * (width - 5) / frequencyRoot) - 1}, 0) rotate(90)"
font-size="10">
{Math.round(freq)} hz
</text>

<text x={((freq - frequencyRoot) * (width - 5) / frequencyRoot) - 1} y={((fareySequenceOrder + 1.2) * cellHeight)}
<text x={alignX(i, freq)}
y={(fareySequenceOrder + 1.2) * cellHeight}
opacity="{selected ? 1 : 0.5}"
transform="translate(, 0) rotate(90)"
font-family="monospace"
Expand Down
47 changes: 29 additions & 18 deletions src/WavePlot.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
<script>
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);
Expand All @@ -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);
Expand All @@ -33,7 +46,7 @@
ctx.stroke();
};
$: canvasFn = (() => {
let canvasFn = () => {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
Expand All @@ -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);
Expand All @@ -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());
</script>

<canvas
Expand Down
11 changes: 8 additions & 3 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ body {
}

a {
color: rgb(0,100,200);
color: rgb(0,100,210);
text-decoration: none;
}

Expand All @@ -38,8 +38,8 @@ label {
}

input, button, select, textarea {
font-family: inherit;
font-size: inherit;
font-family: monospace;
font-size: 110%;
-webkit-padding: 0.4em 0;
padding: 0.4em;
margin: 0 0 0.5em 0;
Expand All @@ -56,6 +56,7 @@ button {
color: #333;
background-color: #f4f4f4;
outline: none;
margin: 0 4px;
}

button:disabled {
Expand All @@ -69,3 +70,7 @@ button:not(:disabled):active {
button:focus {
border-color: #666;
}

button:hover {
border-color: #88c;
}
4 changes: 2 additions & 2 deletions src/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
/**
* @param {number} frequency
* @param {number} duration
* @param {{gain: number, onended: () => 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();
Expand Down

0 comments on commit 875c0aa

Please sign in to comment.