commit 4d097c501e82996ed24e00fc51b96d49afd6b9e5
parent bb77179c2d64ecda140cc065647ea5c610f55b68
Author: massi <mdsiboldi@gmail.com>
Date: Mon, 24 Jul 2023 01:37:48 -0700
add noise unit (and more)
Diffstat:
7 files changed, 183 insertions(+), 27 deletions(-)
diff --git a/src/lib/Noise.svelte b/src/lib/Noise.svelte
@@ -0,0 +1,76 @@
+<script lang="ts">
+ import type { NoiseUnit, NoiseUnitInputs, Output } from '$lib/types';
+ import { unitInputs, wrangle, rescale, range } from '$lib/types';
+ import NumberSelector from '$lib/NumberSelector.svelte';
+ import InputDragger from '$lib/InputDragger.svelte';
+
+ export let unit: NoiseUnit;
+ export let signalDragging: false | Output;
+ export let update: (n: NoiseUnit) => void;
+ export let onConnect: ((k: string) => void) | null;
+
+ $: vals = {
+ amount:
+ typeof unit.amount === 'number'
+ ? wrangle(unit.amount, range.signal, range.noise.amount)
+ : null
+ };
+
+ const updateValue = (k: keyof NoiseUnitInputs, n: number) => {
+ update({ ...unit, [k]: Math.round(rescale(n, range.noise[k], range.signal)) });
+ };
+
+ const isConnected = (k: keyof NoiseUnitInputs, signalDragging: false | Output) => {
+ const inputs = unit[k];
+ if (signalDragging && Array.isArray(inputs)) {
+ const { id: sigId } = signalDragging;
+ return Boolean(inputs.find(({ id }) => id === sigId));
+ }
+ return false;
+ };
+</script>
+
+<div class="unit-container">
+ <h3>{unit.kind}</h3>
+ {#each unitInputs.noise as k}
+ {@const v = vals[k]}
+ {@const inputs = unit[k]}
+ <div class="sect">
+ <InputDragger
+ connected={isConnected(k, signalDragging)}
+ onConnect={onConnect ? onConnect.bind(null, k) : null}
+ />
+ {#if v !== null}
+ <h3>{k}</h3>
+ <NumberSelector value={v} updateValue={updateValue.bind(undefined, k)} />
+ <input
+ type="range"
+ min={range.osc[k].min}
+ max={range.osc[k].max}
+ step={1}
+ value={v}
+ on:input={(e) => updateValue(k, Number(e.currentTarget?.value))}
+ />
+ {:else}
+ <div>{Array.isArray(inputs) ? inputs.map((o) => o.id).join(' + ') : ''}</div>
+ {/if}
+ </div>
+ {/each}
+</div>
+
+<style>
+ .sect {
+ position: relative;
+ width: 100%;
+ height: 80px;
+ }
+ .unit-container {
+ background: rgba(255, 255, 255, 0.3);
+ padding: 10px;
+ display: flexbox;
+ }
+ h3 {
+ padding: 0;
+ margin: 0;
+ }
+</style>
diff --git a/src/lib/Osc.svelte b/src/lib/Osc.svelte
@@ -57,7 +57,7 @@
on:input={(e) => updateValue(k, Number(e.currentTarget?.value))}
/>
{:else}
- <div>{Array.isArray(inputs) ? inputs.map((o) => o.id).join(' ') : ''}</div>
+ <div>{Array.isArray(inputs) ? inputs.map((o) => o.id).join(' + ') : ''}</div>
{/if}
</div>
{/each}
diff --git a/src/lib/Sink.svelte b/src/lib/Sink.svelte
@@ -17,7 +17,7 @@
<div class={classes} on:mouseup={_onSinkConnect} />
{/if}
<button class={Boolean(_onSinkConnect) ? 'hl' : ''}>
- {channel}: {Array.isArray(input) ? input.map((o) => o.id).join(' ') : 'none'}
+ {channel}: {Array.isArray(input) ? input.map((o) => o.id).join(' + ') : 'none'}
</button>
</div>
diff --git a/src/lib/color.ts b/src/lib/color.ts
@@ -0,0 +1,29 @@
+import type { Color } from '$lib/types';
+
+// from ttps://observablehq.com/@shan/oklab-color-wheel
+
+const gamma = (x: number) => (x >= 0.0031308 ? 1.055 * Math.pow(x, 1 / 2.4) - 0.055 : 12.92 * x)
+
+export const oklab = (L: number, a: number, b: number): Color => {
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
+ const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
+ const l = l_ * l_ * l_;
+ const m = m_ * m_ * m_;
+ const s = s_ * s_ * s_;
+
+ return {
+ r: 255 * gamma(+4.0767245293 * l - 3.3072168827 * m + 0.2307590544 * s),
+ g: 255 * gamma(-1.2681437731 * l + 2.6093323231 * m - 0.3411344290 * s),
+ b: 255 * gamma(-0.0041119885 * l - 0.7034763098 * m + 1.7068625689 * s)
+ };
+}
+
+export const oklch = (lightness: number, chroma: number, hue: number): Color => {
+ const h = 2 * Math.PI * (hue / 360);
+ const C = chroma;
+ const a = C * Math.cos(h);
+ const b = C * Math.sin(h);
+
+ return oklab(lightness, a, b)
+}
diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts
@@ -7,6 +7,7 @@ import type {
UnitState,
UnitStateMap,
} from "$lib/types";
+import { oklch } from '$lib/color';
import {
ROWS,
COLS,
@@ -64,14 +65,26 @@ function setUnitState(id: UnitId, state: UnitState) {
unitState.set(id, state);
}
+type OscShapes = {
+ [k: string]: (p: number, a: number) => number
+}
+const oscShapes: OscShapes = {
+ sine: (position: number, amount: number) => {
+ return Math.sin(2 * Math.PI * position) * amount;
+ },
+ square: (p, a) => {
+ return (p < 0.5 ? -1 : 1) * a;
+ },
+ triangle: (p, a) => p * a
+}
+
function vUnit(x: { id: UnitId }): number {
const { id } = x;
const unit: Unit = getUnit(config!.units, id);
switch (unit.kind) {
case "osc": {
const position = getUnitState(id);
- return Math.sin(2 * Math.PI * (position / LOOP_CYCLES)) *
- v(unit.amount);
+ return oscShapes.sine(position / LOOP_CYCLES, v(unit.amount))
}
case "const": {
// range is whatever the control for it is???
@@ -157,24 +170,31 @@ function drawSquares() {
const data = new Uint8ClampedArray(ROWS * COLS * 4);
let di = 0;
- for (let i = 0; i < ROWS * COLS; i++) {
- const l = config.sinks.l == null ? 0 : v(config.sinks.l);
- const c = config.sinks.c == null ? 0 : v(config.sinks.c);
- const h = config.sinks.h == null ? 0 : v(config.sinks.h);
- data[di++] = wrangle(l, range.signal, {
- min: 0,
- max: 255,
- });
- data[di++] = wrangle(c, range.signal, {
- min: 0,
- max: 255,
- });
- data[di++] = wrangle(h, range.signal, {
- min: 0,
- max: 255,
- });
- data[di++] = 255;
- update();
+ for (let row = 0; row < ROWS; row++) {
+ for (let col = 0; col < COLS; col++) {
+ const l = config.sinks.l == null ? 0 : v(config.sinks.l);
+ const c = config.sinks.c == null ? 0 : v(config.sinks.c);
+ const h = config.sinks.h == null ? 0 : v(config.sinks.h);
+ const rgb = oklch(
+ wrangle(l, range.signal, {
+ min: 0,
+ max: 1,
+ }),
+ wrangle(c, range.signal, {
+ min: 0,
+ max: 0.5,
+ }),
+ wrangle(h, range.signal, {
+ min: 0,
+ max: 360,
+ })
+ );
+ data[di++] = rgb.r;
+ data[di++] = rgb.g;
+ data[di++] = rgb.b;
+ data[di++] = 255;
+ update();
+ }
}
postMessage({ kind: "buf", content: data.buffer }, [data.buffer]);
p.end("drawSquares");
diff --git a/src/lib/types.ts b/src/lib/types.ts
@@ -6,6 +6,12 @@ export const CELLS = ROWS * COLS;
export type WithTarget<E, T> = E & { currentTarget: T };
+export type Color = {
+ r: number,
+ g: number,
+ b: number
+}
+
export type SynthConfig = {
sinks: Sinks;
units: UnitMap;
@@ -47,21 +53,30 @@ export type OscUnitInputs = {
export type NoiseUnitInputs = {
amount: Input;
}
+export type SmoothUnitInputs = {
+ frames: Input;
+}
+
type UnitInputs = {
osc: (keyof OscUnitInputs)[],
- noise: (keyof NoiseUnitInputs)[]
+ noise: (keyof NoiseUnitInputs)[],
+ smooth: (keyof SmoothUnitInputs)[]
}
export const unitInputs: UnitInputs = {
osc: ['coarse', 'fine', 'superfine', 'amount'],
- noise: ['amount']
+ noise: ['amount'],
+ smooth: ['frames']
+}
+
+export type SmoothUnit = WithPos & SmoothUnitInputs & {
+ kind: "smooth";
}
export type OscUnit = WithPos & OscUnitInputs & {
kind: "osc";
};
-export type NoiseUnit = WithPos & {
+export type NoiseUnit = WithPos & NoiseUnitInputs & {
kind: "noise";
- amount: Input;
};
// outputs number as it is
@@ -113,6 +128,12 @@ export const range = {
max: 50,
},
},
+ smooth: {
+ frames: {
+ min: 0,
+ max: 50
+ }
+ },
color: {
min: 0,
max: 255,
@@ -129,6 +150,7 @@ export const range = {
min: -5_000_000,
max: 5_000_000,
},
+
};
//export function rangeForInput<U extends Unit>(
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
@@ -3,6 +3,7 @@
import { debounce } from 'lodash';
import Sink from '$lib/Sink.svelte';
import Osc from '$lib/Osc.svelte';
+ import Noise from '$lib/Noise.svelte';
import Slider from '$lib/Slider.svelte';
import { COLS, ROWS, inp, rescale, range, is, getUnit as _getUnit } from '$lib/types';
import type { Output, Input, Unit, UnitId, ConstUnit, UnitMap, Sinks } from '$lib/types';
@@ -131,7 +132,7 @@
pos: { x: 0, y: 0 }
});
},
- n: function noise(): UnitId {
+ noise: function noise(): UnitId {
return addUnit({ kind: 'noise', amount: randConst(), pos: { x: 0, y: 0 } });
}
};
@@ -235,6 +236,7 @@
<div id="buttons">
<button on:click={mk.c}>add const</button>
<button on:click={mk.osc}>add osc</button>
+ <button on:click={mk.noise}>add noise</button>
</div>
<div id="sinks">
@@ -260,6 +262,13 @@
onConnect={getConnectHandler(signalDragging, id)}
updateOsc={updateEntireUnit.bind(null, id)}
/>
+ {:else if unit.kind === 'noise'}
+ <Noise
+ {unit}
+ {signalDragging}
+ onConnect={getConnectHandler(signalDragging, id)}
+ update={updateEntireUnit.bind(null, id)}
+ />
{/if}
<div
class="output-dragger"