commit bd4581677a1717e16d44d76e5bd297265aeb1477
parent 28d2f8ed5222d05552d6e0bb2cb111325163cfdf
Author: massi <mdsiboldi@gmail.com>
Date: Tue, 8 Aug 2023 17:35:55 -0700
smooth added and types reworked to facilitate more
Diffstat:
6 files changed, 177 insertions(+), 131 deletions(-)
diff --git a/src/lib/Control.svelte b/src/lib/Control.svelte
@@ -1,10 +1,10 @@
<script lang="ts" generics="TKind extends UnitKind">
import { unitStore, unitToConnect } from '$lib/stores';
import {
- RANGE,
+ unitRange,
getUnit,
inp,
- range,
+ RANGE,
rescale,
type ControlName,
type Controls,
@@ -21,10 +21,10 @@
$: unit = getUnit(kind, $unitStore, id);
- const controlRange = RANGE[kind][controlName];
+ const controlRange = unitRange[kind][controlName];
$: input = (unit.controls as Controls<TKind>)[controlName];
const updateValue = (controlValue: number) => {
- const n = Math.round(rescale(controlValue, controlRange, range.signal));
+ const n = Math.round(rescale(controlValue, controlRange, RANGE.signal));
unitStore.setControl(kind, id, controlName, n);
};
let connection: { connected: boolean; onConnect: () => void } | null;
@@ -42,7 +42,7 @@
}
}
let value: number | null;
- $: value = typeof input === 'number' ? wrangle(input, range.signal, controlRange) : null;
+ $: value = typeof input === 'number' ? wrangle(input, RANGE.signal, controlRange) : null;
$: slotProps = { range: controlRange, value: value || 0, update: updateValue };
</script>
diff --git a/src/lib/SmoothUnit.svelte b/src/lib/SmoothUnit.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import Control from '$lib/Control.svelte';
+ import type { UnitId } from '$lib/types';
+
+ export let id: UnitId;
+
+ const kind = 'smooth';
+ const KControl = Control<typeof kind>;
+
+ $: common = { kind, id } as const;
+</script>
+
+<div>
+ <h1>smoother</h1>
+ <KControl {...common} controlName="signal"><div>cant control this one</div></KControl>
+ <KControl {...common} controlName="frames" />
+</div>
+
+<style>
+ h1 {
+ color: salmon;
+ }
+</style>
diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts
@@ -8,12 +8,10 @@ import type {
UnitStateMap
} from '$lib/types';
import { oklch } from '$lib/color';
-import { ROWS, COLS, getUnit, LOOP_CYCLES, range, rescale, wrangle, RANGE } from '$lib/types';
+import { ROWS, COLS, getUnit, LOOP_CYCLES, RANGE, rescale, wrangle, unitRange } from '$lib/types';
let config: SynthConfig | undefined = undefined;
-
let unitState: UnitStateMap = new Map();
-
let canvas: OffscreenCanvas = new OffscreenCanvas(1, 1);
onmessage = (message: { data: EngineMessage }) => {
@@ -45,6 +43,10 @@ function getUnitState(id: UnitId): UnitState {
}
break;
}
+ case 'smooth': {
+ return theState === undefined ? [] : theState;
+ break;
+ }
default: {
throw new Error('state for this invalid or NYI');
}
@@ -84,6 +86,10 @@ function vUnit(x: { id: UnitId }): number {
case 'noise': {
return Math.random() * v(unit.controls.amount);
}
+ case 'smooth': {
+ const frames: number[] = getUnitState(id);
+ return frames.reduce((acc, item) => item + acc, 0) / frames.length;
+ }
}
}
@@ -129,15 +135,29 @@ function update() {
switch (unit.kind) {
case 'osc': {
const position = getUnitState(id);
- const coarse = rescale(v(unit.controls.coarse), range.signal, RANGE.osc.coarse);
- const fine = rescale(v(unit.controls.fine), range.signal, RANGE.osc.fine);
- const superfine = rescale(v(unit.controls.superfine), range.signal, RANGE.osc.superfine);
+ const coarse = rescale(v(unit.controls.coarse), RANGE.signal, unitRange.osc.coarse);
+ const fine = rescale(v(unit.controls.fine), RANGE.signal, unitRange.osc.fine);
+ const superfine = rescale(
+ v(unit.controls.superfine),
+ RANGE.signal,
+ unitRange.osc.superfine
+ );
setUnitState(
id,
(position + (coarse * LOOP_CYCLES) / 100 + fine * 20000 + superfine * 10) % LOOP_CYCLES
);
break;
}
+ case 'smooth': {
+ // wrangle frames since <0 means nothing
+ const n = wrangle(v(unit.controls.frames), RANGE.signal, unitRange.smooth.frames);
+ // keep n frames
+ let frames = [v(unit.controls.signal), ...getUnitState(id)];
+ if (frames.length > n) {
+ frames = frames.slice(0, n);
+ }
+ setUnitState(id, frames);
+ }
}
}
}
@@ -163,15 +183,15 @@ function drawSquares() {
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, {
+ wrangle(l, RANGE.signal, {
min: 0,
max: 1
}),
- wrangle(c, range.signal, {
+ wrangle(c, RANGE.signal, {
min: 0,
max: 0.5
}),
- wrangle(h, range.signal, {
+ wrangle(h, RANGE.signal, {
min: 0,
max: 360
})
diff --git a/src/lib/stores.ts b/src/lib/stores.ts
@@ -1,15 +1,26 @@
-import type {
- ControlName,
- Controls,
- Input,
- Unit,
- UnitId,
- UnitKind,
- UnitToConnect,
- Units
+import {
+ rescale,
+ type ControlName,
+ type Controls,
+ type Input,
+ type Unit,
+ type UnitId,
+ type UnitKind,
+ type UnitToConnect,
+ type Units,
+ RANGE,
+ wrangle,
+ unitRange
} from '$lib/types';
import { writable } from 'svelte/store';
+let uuid = 0;
+
+const randConst = () => {
+ const sliderVal = Math.round(rescale(Math.random(), { min: 0, max: 1 }, RANGE.slider));
+ return rescale(sliderVal, RANGE.slider, RANGE.signal);
+};
+
const mkUnitStore = () => {
const { subscribe, set, update } = writable<Units>({});
const setControl = <K extends UnitKind>(
@@ -30,7 +41,39 @@ const mkUnitStore = () => {
setUnit<K extends UnitKind>(id: UnitId, unit: Unit<K>) {
update((units) => ({ ...units, [id]: unit }));
},
- setControl
+ setControl,
+ addUnit<K extends UnitKind>(kind: K): UnitId {
+ const id = uuid++;
+ let unit: Unit;
+ switch (kind) {
+ case 'const': {
+ unit = { kind: 'const', controls: { value: 0 }, pos: { x: 0, y: 0 } };
+ break;
+ }
+ case 'osc': {
+ unit = {
+ kind: 'osc',
+ controls: { coarse: 0, fine: 0, superfine: 0, amount: RANGE.signal.max },
+ pos: { x: 0, y: 0 }
+ };
+ break;
+ }
+ case 'noise': {
+ unit = { kind: 'noise', controls: { amount: RANGE.signal.max / 2 }, pos: { x: 0, y: 0 } };
+ break;
+ }
+ case 'smooth': {
+ unit = {
+ kind: 'smooth',
+ controls: { frames: wrangle(3, unitRange.smooth.frames, RANGE.signal), signal: 0 },
+ pos: { x: 0, y: 0 }
+ };
+ break;
+ }
+ }
+ update((units) => ({ ...units, [id]: unit }));
+ return String(id);
+ }
};
};
diff --git a/src/lib/types.ts b/src/lib/types.ts
@@ -39,10 +39,23 @@ type Pos = {
y: number;
};
+export const unitKinds = ['const', 'osc', 'noise', 'smooth'] as const;
+export type UnitKind = (typeof unitKinds)[number];
+export type Unit<K extends UnitKind = UnitKind> = { [P in K]: UnitMap[P] }[K];
+
+type UnitMap = {
+ const: ConstUnit;
+ osc: OscUnit;
+ noise: NoiseUnit;
+ smooth: SmoothUnit;
+};
+
+// TODO: can we error when keys don't exhaust UnitKind? Record can't infer the tuples correctly.
const controlNames = {
osc: ['coarse', 'fine', 'superfine', 'amount'] as const,
noise: ['amount'] as const,
- const: ['value'] as const
+ const: ['value'] as const,
+ smooth: ['signal', 'frames'] as const
};
type ControlNames = typeof controlNames;
export type ControlName<K extends UnitKind> = ControlNames[K][number];
@@ -68,20 +81,10 @@ export type NoiseUnit = {
controls: Controls<'noise'>;
};
-export type Unit<K extends UnitKind = UnitKind> = K extends 'osc'
- ? OscUnit
- : K extends 'noise'
- ? NoiseUnit
- : K extends 'const'
- ? ConstUnit
- : never;
-
-export type UnitKind = keyof UnitMap;
-
-type UnitMap = {
- const: ConstUnit;
- osc: OscUnit;
- noise: NoiseUnit;
+export type SmoothUnit = {
+ kind: 'smooth';
+ pos: Pos;
+ controls: Controls<'smooth'>;
};
export type UnitId = string;
@@ -99,10 +102,27 @@ export type NumberRange = {
max: number;
};
-export const RANGE: {
- [k in UnitKind]: {
- [j in ControlName<k>]: NumberRange;
- };
+export const RANGE = {
+ color: {
+ min: 0,
+ max: 255
+ },
+ pmone: {
+ min: -1,
+ max: 1
+ },
+ slider: {
+ min: -50,
+ max: 50
+ },
+ signal: {
+ min: -5_000_000,
+ max: 5_000_000
+ }
+};
+
+export const unitRange: {
+ [k in UnitKind]: Record<ControlName<k>, NumberRange>;
} = {
osc: {
coarse: {
@@ -133,31 +153,13 @@ export const RANGE: {
min: -50,
max: 50
}
- }
-};
-
-export const range = {
+ },
smooth: {
frames: {
- min: 0,
- max: 50
- }
- },
- color: {
- min: 0,
- max: 255
- },
- pmone: {
- min: -1,
- max: 1
- },
- slider: {
- min: -50,
- max: 50
- },
- signal: {
- min: -5_000_000,
- max: 5_000_000
+ min: 1,
+ max: 25
+ },
+ signal: RANGE.signal
}
};
@@ -189,50 +191,30 @@ const isUnit =
(u: Unit): u is Unit<K> =>
u.kind === k;
-const isControlName =
- <K extends UnitKind>(k: K) =>
- (controlName: string): controlName is ControlName<K> =>
- !!controlNames.const.find((n) => n === controlName);
-
export const is = {
- input: (i: any): i is Input => {
- return i === Object(i) && i.id;
- },
unit: {
const: isUnit('const'),
osc: isUnit('osc'),
- noise: isUnit('noise')
- },
- controlName: {
- const: isControlName('const'),
- osc: isControlName('osc'),
- noise: isControlName('noise')
+ noise: isUnit('noise'),
+ smooth: isUnit('smooth')
}
};
+const ensureUnit = <K extends UnitKind>(k: K) => {
+ return (u: Unit) => {
+ if (is.unit[k](u)) {
+ return u as Unit<K>;
+ } else {
+ throw new Error('ensure check failed');
+ }
+ };
+};
export const ensure = {
unit: {
- const: (u: Unit): ConstUnit => {
- if (is.unit.const(u)) {
- return u;
- } else {
- throw new Error('this is not a const');
- }
- },
- osc: (u: Unit): OscUnit => {
- if (is.unit.osc(u)) {
- return u;
- } else {
- throw new Error('this is not an osc');
- }
- },
- noise: (u: Unit): NoiseUnit => {
- if (is.unit.noise(u)) {
- return u;
- } else {
- throw new Error('this is not a noise unit');
- }
- }
+ const: ensureUnit('const'),
+ osc: ensureUnit('osc'),
+ noise: ensureUnit('noise'),
+ smooth: ensureUnit('smooth')
}
};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
@@ -3,11 +3,12 @@
import { onMount } from 'svelte';
import { debounce } from 'lodash';
import Sink from '$lib/Sink.svelte';
- import { COLS, ROWS, inp, rescale, range, is, getUnit as _getUnit } from '$lib/types';
- import type { Output, Input, Unit, UnitId, ConstUnit, Units, Sinks } from '$lib/types';
+ import { COLS, ROWS, inp, rescale, RANGE, is, getUnit as _getUnit, unitKinds } from '$lib/types';
+ import type { Output, Input, Unit, UnitId, ConstUnit, Units, Sinks, UnitKind } from '$lib/types';
import OscUnitComponent from '$lib/OscUnit.svelte';
import NoiseUnitComponent from '$lib/NoiseUnit.svelte';
import ConstUnitComponent from '$lib/ConstUnit.svelte';
+ import SmoothUnitComponent from '$lib/SmoothUnit.svelte';
let cvs: HTMLCanvasElement | undefined;
@@ -114,31 +115,6 @@
return id;
}
- const randConst = () => {
- const sliderVal = Math.round(rescale(Math.random(), { min: 0, max: 1 }, range.slider));
- return rescale(sliderVal, range.slider, range.signal);
- };
-
- const mk = {
- c: function mkConst(): UnitId {
- return addUnit({
- kind: 'const',
- controls: { value: randConst() },
- pos: { x: 0, y: 0 }
- });
- },
- osc: function mkOsc(): UnitId {
- return addUnit({
- kind: 'osc',
- controls: { coarse: randConst(), fine: randConst(), superfine: 0, amount: randConst() },
- pos: { x: 0, y: 0 }
- });
- },
- noise: function noise(): UnitId {
- return addUnit({ kind: 'noise', controls: { amount: randConst() }, pos: { x: 0, y: 0 } });
- }
- };
-
let dragging: false | UnitId = false;
const handleMouseMove = (e: MouseEvent) => {
@@ -209,9 +185,9 @@
<canvas bind:this={cvs} />
<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>
+ {#each unitKinds as kind}
+ <button on:click={() => unitStore.addUnit(kind)}>add {kind}</button>
+ {/each}
</div>
<div id="sinks">
@@ -234,6 +210,8 @@
<OscUnitComponent {id} />
{:else if unit.kind === 'noise'}
<NoiseUnitComponent {id} />
+ {:else if unit.kind === 'smooth'}
+ <SmoothUnitComponent {id} />
{/if}
<div
class="output-dragger"