commit b939a5926255ccddcfe4157fac3cfc76f54f4354
parent bfb66e6cafb9d9e568154f6d43515fc04e61e061
Author: massi <mdsiboldi@gmail.com>
Date: Sun, 6 Aug 2023 04:35:22 -0700
make control more dumb and toil over types some more
Diffstat:
10 files changed, 158 insertions(+), 117 deletions(-)
diff --git a/src/lib/ConstUnit.svelte b/src/lib/ConstUnit.svelte
@@ -1,7 +1,8 @@
<script lang="ts">
import Control from '$lib/Control.svelte';
- import { unitStore } from '$lib/stores';
+ import { unitStore, unitToConnect } from '$lib/stores';
import { ensure, getUnit, type UnitId } from '$lib/types';
+ import { getControlProps } from './controlUtils';
export let id: UnitId;
@@ -10,7 +11,7 @@
<div>
<h1>just a const...</h1>
- <Control {id} controlName="value" />
+ <Control {...getControlProps(unit.kind, id, unit.controls.value, 'value', $unitToConnect)} />
</div>
<style>
diff --git a/src/lib/Control.svelte b/src/lib/Control.svelte
@@ -1,72 +1,26 @@
<script lang="ts">
import NumberSelector from '$lib/NumberSelector.svelte';
import { unitStore, unitToConnect } from '$lib/stores';
- import {
- getUnit,
- range,
- type Range,
- type OscUnit,
- type UnitId,
- rescale,
- type Unit,
- type Input,
- wrangle,
- inp
- } from './types';
+ import type { NumberRange } from '$lib/types';
import InputDragger from './InputDragger.svelte';
- export let id: UnitId;
export let controlName: string;
-
- $: unit = getUnit($unitStore, id);
- // @ts-ignore caller responsible for ensuring unit has the appropriate control name.
- $: controlRange = range[unit.kind][controlName] as range;
- // @ts-ignore caller responsible for ensuring unit has the appropriate control name.
- $: input = unit.controls[controlName] as Input;
- $: values =
- typeof input === 'number'
- ? {
- raw: input,
- control: wrangle(input, range.signal, controlRange)
- }
- : null;
-
- $: updateValue = (controlValue: number) => {
- const n = Math.round(rescale(controlValue, controlRange, range.signal));
- unitStore.setUnit(id, { ...unit, controls: { ...unit.controls, [controlName]: n } } as Unit);
- };
- $: onConnect = $unitToConnect
- ? () => {
- const _utc = $unitToConnect;
- if (!_utc) return;
- console.log('connecting', $unitToConnect, controlName);
- unitStore.setUnit(id, {
- ...unit,
- controls: { ...unit.controls, [controlName]: inp.toggle(input, _utc) }
- } as Unit);
- }
- : null;
- let connected: boolean;
- $: {
- const _unitToConnect = $unitToConnect;
- if (_unitToConnect !== false && Array.isArray(input)) {
- connected = Boolean(input.find(({ id }) => id === _unitToConnect.id));
- } else {
- connected = false;
- }
- }
+ export let controlRange: NumberRange;
+ export let value: number | null;
+ export let connection: { connected: boolean; onConnect: () => void } | null;
+ export let updateValue: (n: number) => void;
</script>
<div class="control-wrapper">
<h3>{controlName}</h3>
- <InputDragger {connected} {onConnect} />
- {#if values !== null}
+ <InputDragger {connection} />
+ {#if value !== null}
<input
type="range"
min={controlRange.min}
max={controlRange.max}
step={1}
- value={values.control}
+ {value}
on:input={(e) => updateValue(Number(e.currentTarget?.value))}
/>
{/if}
diff --git a/src/lib/InputDragger.svelte b/src/lib/InputDragger.svelte
@@ -1,14 +1,15 @@
<script lang="ts">
- export let onConnect: (() => void) | null | undefined;
- export let connected: boolean;
+ export let connection: { connected: Boolean; onConnect: () => void } | null;
</script>
<div class="input-dragger">
- {#if onConnect}
+ {#if connection}
<div
- class={['connect', connected && 'connected'].filter(Boolean).join(' ')}
- on:mouseup={onConnect}
- />
+ class={['connect', connection.connected && 'connected'].filter(Boolean).join(' ')}
+ on:mouseup={connection.onConnect}
+ >
+ hi
+ </div>
{/if}
</div>
diff --git a/src/lib/NoiseUnit.svelte b/src/lib/NoiseUnit.svelte
@@ -1,7 +1,8 @@
<script lang="ts">
import Control from '$lib/Control.svelte';
- import { unitStore } from '$lib/stores';
+ import { unitStore, unitToConnect } from '$lib/stores';
import { ensure, getUnit, type UnitId } from '$lib/types';
+ import { getControlProps } from '$lib/controlUtils';
export let id: UnitId;
@@ -10,7 +11,7 @@
<div>
<h1>noiseyboi</h1>
- <Control {id} controlName="amount" />
+ <Control {...getControlProps(unit.kind, id, unit.controls.amount, 'amount', $unitToConnect)} />
</div>
<style>
diff --git a/src/lib/OscUnit.svelte b/src/lib/OscUnit.svelte
@@ -1,6 +1,7 @@
<script lang="ts">
import Control from '$lib/Control.svelte';
- import { unitStore } from '$lib/stores';
+ import { getControlProps } from '$lib/controlUtils';
+ import { unitStore, unitToConnect } from '$lib/stores';
import { ensure, getUnit, type UnitId } from '$lib/types';
export let id: UnitId;
@@ -10,10 +11,10 @@
<div>
<h1>Os-KILL-8r</h1>
- <Control {id} controlName="coarse" />
- <Control {id} controlName="fine" />
- <Control {id} controlName="superfine" />
- <Control {id} controlName="amount" />
+ <Control {...getControlProps('osc', id, unit.controls.coarse, 'coarse', $unitToConnect)} />
+ <Control {...getControlProps('osc', id, unit.controls.fine, 'fine', $unitToConnect)} />
+ <Control {...getControlProps('osc', id, unit.controls.superfine, 'superfine', $unitToConnect)} />
+ <Control {...getControlProps('osc', id, unit.controls.amount, 'amount', $unitToConnect)} />
</div>
<style>
diff --git a/src/lib/controlUtils.ts b/src/lib/controlUtils.ts
@@ -0,0 +1,48 @@
+import {
+ RANGE,
+ type ControlName,
+ type Controls,
+ type UnitKind,
+ type UnitToConnect,
+ rescale,
+ range,
+ type UnitId,
+ inp,
+ wrangle,
+ type Input
+} from '$lib/types';
+import { unitStore } from './stores';
+
+export const getControlProps = <K extends UnitKind>(
+ kind: K,
+ id: UnitId,
+ input: Input,
+ controlName: ControlName<K>,
+ utc: UnitToConnect
+) => {
+ const controlRange = RANGE[kind][controlName];
+ const updateValue = (controlValue: number) => {
+ const n = Math.round(rescale(controlValue, controlRange, range.signal));
+ unitStore._setControl(id, controlName, n);
+ };
+
+ let connection: { connected: boolean; onConnect: () => void } | null = null;
+ if (utc) {
+ connection = {
+ connected: Array.isArray(input) && inp.connected(input, utc),
+ onConnect: () => {
+ if (!utc) return;
+ console.log('connecting', utc, controlName);
+ unitStore._setControl(id, controlName, inp.toggle(input, utc));
+ }
+ };
+ }
+
+ return {
+ controlName,
+ controlRange,
+ value: typeof input === 'number' ? wrangle(input, range.signal, controlRange) : null,
+ connection,
+ updateValue
+ };
+};
diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts
@@ -8,7 +8,7 @@ import type {
UnitStateMap
} from '$lib/types';
import { oklch } from '$lib/color';
-import { ROWS, COLS, getUnit, LOOP_CYCLES, range, rescale, wrangle } from '$lib/types';
+import { ROWS, COLS, getUnit, LOOP_CYCLES, range, rescale, wrangle, RANGE } from '$lib/types';
let config: SynthConfig | undefined = undefined;
@@ -128,9 +128,9 @@ 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, 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);
setUnitState(
id,
(position + (coarse * LOOP_CYCLES) / 100 + fine * 20000 + superfine * 10) % LOOP_CYCLES
diff --git a/src/lib/stores.ts b/src/lib/stores.ts
@@ -1,17 +1,29 @@
+import type { Controls, Input, Unit, UnitId, UnitKind, UnitToConnect, Units } from '$lib/types';
import { writable } from 'svelte/store';
-import type { Output, Unit, UnitId, UnitKind, Units } from '$lib/types';
-const mkUnitStore = <T = Units>() => {
- const { subscribe, set, update } = writable<T>({});
+const mkUnitStore = () => {
+ const { subscribe, set, update } = writable<Units>({});
+ const _setControl = (id: UnitId, controlName: string, input: Input) => {
+ update((units) => {
+ // @ts-ignore
+ if (units[id].controls[controlName] == null) {
+ throw new Error(`invalid control ${controlName} for unit kind ${units[id].kind}`);
+ }
+ // @ts-ignore
+ units[id].controls[controlName] = input;
+ return units;
+ });
+ };
return {
subscribe,
set,
- setUnit(id: UnitId, unit: Unit) {
+ setUnit<K extends UnitKind>(id: UnitId, unit: Unit<K>) {
update((units) => ({ ...units, [id]: unit }));
- }
+ },
+ _setControl
};
};
-export const unitToConnect = writable<Output | false>(false);
+export const unitToConnect = writable<UnitToConnect>(false);
export const unitStore = mkUnitStore();
diff --git a/src/lib/types.ts b/src/lib/types.ts
@@ -5,6 +5,7 @@ export const COLS = 100;
export const CELLS = ROWS * COLS;
export type WithTarget<E, T> = E & { currentTarget: T };
+export type Prettify<T> = { [k in keyof T]: T[k] } & {};
export type Color = {
r: number;
@@ -38,34 +39,42 @@ type Pos = {
y: number;
};
+const controlNames = {
+ osc: ['coarse', 'fine', 'superfine', 'amount'] as const,
+ noise: ['amount'] as const,
+ const: ['value'] as const
+};
+type ControlNames = typeof controlNames;
+export type ControlName<K extends UnitKind> = ControlNames[K][number];
+export type Controls<K extends UnitKind = UnitKind> = Prettify<{
+ [name in ControlNames[K][number]]: Input;
+}>;
+
export type ConstUnit = {
kind: 'const';
pos: Pos;
- controls: {
- value: Input;
- };
+ controls: Controls<'const'>;
};
export type OscUnit = {
kind: 'osc';
pos: Pos;
- controls: {
- coarse: Input;
- fine: Input;
- superfine: Input;
- amount: Input;
- };
+ controls: Controls<'osc'>;
};
export type NoiseUnit = {
kind: 'noise';
pos: Pos;
- controls: {
- amount: Input;
- };
+ controls: Controls<'noise'>;
};
-export type Unit = OscUnit | ConstUnit | NoiseUnit;
+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;
@@ -89,12 +98,16 @@ export type Sinks = {
h: Input;
};
-export type Range = {
+export type NumberRange = {
min: number;
max: number;
};
-export const range = {
+export const RANGE: {
+ [k in UnitKind]: {
+ [j in ControlName<k>]: NumberRange;
+ };
+} = {
osc: {
coarse: {
min: -50,
@@ -111,10 +124,6 @@ export const range = {
amount: {
min: -50,
max: 50
- },
- output: {
- min: -1,
- max: 1
}
},
const: {
@@ -128,7 +137,10 @@ export const range = {
min: -50,
max: 50
}
- },
+ }
+};
+
+export const range = {
smooth: {
frames: {
min: 0,
@@ -153,13 +165,13 @@ export const range = {
}
};
-export function clamp(n: number, range: Range) {
+export function clamp(n: number, range: NumberRange) {
return Math.max(range.min, Math.min(range.max, n));
}
-export function rescale(n: number, origin: Range, dest: Range) {
+export function rescale(n: number, origin: NumberRange, dest: NumberRange) {
return ((n - origin.min) / (origin.max - origin.min)) * (dest.max - dest.min) + dest.min;
}
-export function wrangle(n: number, origin: Range, dest: Range) {
+export function wrangle(n: number, origin: NumberRange, dest: NumberRange) {
return rescale(clamp(n, origin), origin, dest);
}
@@ -169,20 +181,29 @@ export function getUnit(units: Units, id: UnitId): Unit {
return result;
}
+const isUnit =
+ <K extends UnitKind>(k: K) =>
+ (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: (u: Unit): u is ConstUnit => {
- return u.kind === 'const';
- },
- osc: (u: Unit): u is OscUnit => {
- return u.kind === 'osc';
- },
- noise: (u: Unit): u is NoiseUnit => {
- return u.kind === 'noise';
- }
+ const: isUnit('const'),
+ osc: isUnit('osc'),
+ noise: isUnit('noise')
+ },
+ controlName: {
+ const: isControlName('const'),
+ osc: isControlName('osc'),
+ noise: isControlName('noise')
}
};
@@ -234,3 +255,5 @@ export const inp = {
return false;
}
};
+
+export type UnitToConnect = Output | false;
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
@@ -5,9 +5,9 @@
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 OscUnit from '$lib/OscUnit.svelte';
- import NoiseUnit from '$lib/NoiseUnit.svelte';
- import ConstUnit from '$lib/ConstUnit.svelte';
+ import OscUnitComponent from '$lib/OscUnit.svelte';
+ import NoiseUnitComponent from '$lib/NoiseUnit.svelte';
+ import ConstUnitComponent from '$lib/ConstUnit.svelte';
let cvs: HTMLCanvasElement | undefined;
@@ -244,11 +244,11 @@
<div class="unit" style={`top: ${unit.pos.y}px; left: ${unit.pos.x}px`}>
<h2 on:mousedown={handleMouseDown.bind(null, id)}>{id} {unit.kind}</h2>
{#if unit.kind === 'const'}
- <ConstUnit {id} />
+ <ConstUnitComponent {id} />
{:else if unit.kind === 'osc'}
- <OscUnit {id} />
+ <OscUnitComponent {id} />
{:else if unit.kind === 'noise'}
- <NoiseUnit {id} />
+ <NoiseUnitComponent {id} />
{/if}
<div
class="output-dragger"