color-synth

a synth that generates colors instead of sounds
Log | Files | Refs | README

commit ca9d7fff7d9d41c36a419a2ad363882f090a4b42
parent b939a5926255ccddcfe4157fac3cfc76f54f4354
Author: massi <mdsiboldi@gmail.com>
Date:   Tue,  8 Aug 2023 15:41:12 -0700

generic unit control

Diffstat:
Msrc/lib/ConstUnit.svelte | 15++++++++-------
Msrc/lib/Control.svelte | 65+++++++++++++++++++++++++++++++++++++++++++++++++----------------
Asrc/lib/DumbSlider.svelte | 16++++++++++++++++
Msrc/lib/NoiseUnit.svelte | 11++++++-----
Msrc/lib/NumberSelector.svelte | 1-
Msrc/lib/OscUnit.svelte | 19++++++++++---------
Dsrc/lib/Slider.svelte | 53-----------------------------------------------------
Dsrc/lib/controlUtils.ts | 48------------------------------------------------
Msrc/lib/engine.worker.ts | 6+++---
Msrc/lib/stores.ts | 28+++++++++++++++++++---------
Msrc/lib/types.ts | 21++++++++++++---------
Msrc/routes/+page.svelte | 17+----------------
12 files changed, 124 insertions(+), 176 deletions(-)

diff --git a/src/lib/ConstUnit.svelte b/src/lib/ConstUnit.svelte @@ -1,21 +1,22 @@ <script lang="ts"> import Control from '$lib/Control.svelte'; - import { unitStore, unitToConnect } from '$lib/stores'; - import { ensure, getUnit, type UnitId } from '$lib/types'; - import { getControlProps } from './controlUtils'; + import type { UnitId } from '$lib/types'; export let id: UnitId; - $: unit = ensure.unit.const(getUnit($unitStore, id)); + const kind = 'const'; + const KControl = Control<typeof kind>; + + $: common = { kind, id } as const; </script> <div> - <h1>just a const...</h1> - <Control {...getControlProps(unit.kind, id, unit.controls.value, 'value', $unitToConnect)} /> + <h1>const</h1> + <KControl {...common} controlName="value" /> </div> <style> h1 { - color: white; + color: blue; } </style> diff --git a/src/lib/Control.svelte b/src/lib/Control.svelte @@ -1,28 +1,61 @@ -<script lang="ts"> - import NumberSelector from '$lib/NumberSelector.svelte'; +<script lang="ts" generics="TKind extends UnitKind"> import { unitStore, unitToConnect } from '$lib/stores'; - import type { NumberRange } from '$lib/types'; + import { + RANGE, + getUnit, + inp, + range, + rescale, + type ControlName, + type Controls, + type UnitId, + type UnitKind, + wrangle + } from '$lib/types'; + import DumbSlider from './DumbSlider.svelte'; import InputDragger from './InputDragger.svelte'; - export let controlName: string; - export let controlRange: NumberRange; - export let value: number | null; - export let connection: { connected: boolean; onConnect: () => void } | null; - export let updateValue: (n: number) => void; + export let id: UnitId; + export let kind: TKind; + export let controlName: ControlName<TKind>; + + $: unit = getUnit(kind, $unitStore, id); + + const controlRange = RANGE[kind][controlName]; + $: input = (unit.controls as Controls<TKind>)[controlName]; + const updateValue = (controlValue: number) => { + const n = Math.round(rescale(controlValue, controlRange, range.signal)); + unitStore.setControl(kind, id, controlName, n); + }; + let connection: { connected: boolean; onConnect: () => void } | null; + $: { + if ($unitToConnect) { + connection = { + connected: Array.isArray(input) && inp.connected(input, $unitToConnect), + onConnect: () => { + if (!$unitToConnect) return; + unitStore.setControl(kind, id, controlName, inp.toggle(input, $unitToConnect)); + } + }; + } else { + connection = null; + } + } + let value: number | null; + $: value = typeof input === 'number' ? wrangle(input, range.signal, controlRange) : null; + + $: slotProps = { range: controlRange, value: value || 0, update: updateValue }; </script> <div class="control-wrapper"> <h3>{controlName}</h3> <InputDragger {connection} /> {#if value !== null} - <input - type="range" - min={controlRange.min} - max={controlRange.max} - step={1} - {value} - on:input={(e) => updateValue(Number(e.currentTarget?.value))} - /> + {#if $$slots.default} + <slot props={{ range: controlRange, value: value || 0, update: updateValue }} /> + {:else} + <DumbSlider range={controlRange} value={value || 0} update={updateValue} /> + {/if} {/if} </div> diff --git a/src/lib/DumbSlider.svelte b/src/lib/DumbSlider.svelte @@ -0,0 +1,16 @@ +<script lang="ts"> + import type { NumberRange } from './types'; + + export let range: NumberRange; + export let value: number; + export let update: (v: number) => void; +</script> + +<input + type="range" + min={range.min} + max={range.max} + step={1} + {value} + on:input={(e) => update(Number(e.currentTarget?.value))} +/> diff --git a/src/lib/NoiseUnit.svelte b/src/lib/NoiseUnit.svelte @@ -1,17 +1,18 @@ <script lang="ts"> import Control from '$lib/Control.svelte'; - import { unitStore, unitToConnect } from '$lib/stores'; - import { ensure, getUnit, type UnitId } from '$lib/types'; - import { getControlProps } from '$lib/controlUtils'; + import type { UnitId } from '$lib/types'; export let id: UnitId; - $: unit = ensure.unit.noise(getUnit($unitStore, id)); + const kind = 'noise'; + const KControl = Control<typeof kind>; + + $: common = { kind, id } as const; </script> <div> <h1>noiseyboi</h1> - <Control {...getControlProps(unit.kind, id, unit.controls.amount, 'amount', $unitToConnect)} /> + <KControl {...common} controlName="amount" /> </div> <style> diff --git a/src/lib/NumberSelector.svelte b/src/lib/NumberSelector.svelte @@ -99,7 +99,6 @@ }; const handleDbl = () => { - console.log('dbl'); updateValue(0); }; diff --git a/src/lib/OscUnit.svelte b/src/lib/OscUnit.svelte @@ -1,20 +1,21 @@ <script lang="ts"> import Control from '$lib/Control.svelte'; - import { getControlProps } from '$lib/controlUtils'; - import { unitStore, unitToConnect } from '$lib/stores'; - import { ensure, getUnit, type UnitId } from '$lib/types'; + import type { UnitId } from '$lib/types'; export let id: UnitId; - $: unit = ensure.unit.osc(getUnit($unitStore, id)); + const kind = 'osc'; + const KControl = Control<typeof kind>; + + $: common = { kind, id } as const; </script> <div> - <h1>Os-KILL-8r</h1> - <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)} /> + <h1>os-kill-8r</h1> + <KControl {...common} controlName="coarse" /> + <KControl {...common} controlName="fine" /> + <KControl {...common} controlName="superfine" /> + <KControl {...common} controlName="amount" /> </div> <style> diff --git a/src/lib/Slider.svelte b/src/lib/Slider.svelte @@ -1,53 +0,0 @@ -<script lang="ts"> - import type { WithTarget, UnitId, Units } from '$lib/types'; - import { rescale, range, getUnit as _getUnit } from '$lib/types'; - import NumberSelector from '$lib/NumberSelector.svelte'; - - export let id: UnitId; - export let units: Units; - export let handleInput: (id: string, value: number) => void; - export let handleChange: () => void; - - $: getUnit = _getUnit.bind(null, units); - $: unit = getUnit(id); - $: sliderValue = - unit.kind === 'const' ? Math.round(rescale(unit.value, range.signal, range.slider)) : 0; - - const _handleInput = (val: number) => { - handleInput(id, Math.round(rescale(val, range.slider, range.signal))); - unit = unit; - }; - - const onInput = (e: WithTarget<Event, HTMLInputElement>) => { - _handleInput(Number(e.currentTarget?.value)); - }; -</script> - -<div class="unit-container"> - {#if unit.kind === 'const'} - <h3>{unit.kind}</h3> - <NumberSelector value={sliderValue} updateValue={_handleInput} /> - <input - name={id} - type="range" - min={range.slider.min} - max={range.slider.max} - step={1} - value={sliderValue} - on:input={onInput} - on:change={handleChange} - /> - {/if} -</div> - -<style> - .unit-container { - background: rgba(255, 255, 255, 0.3); - padding: 10px; - display: flexbox; - } - h3 { - padding: 0; - margin: 0; - } -</style> diff --git a/src/lib/controlUtils.ts b/src/lib/controlUtils.ts @@ -1,48 +0,0 @@ -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 @@ -33,7 +33,7 @@ function getUnitState(id: UnitId): UnitState { if (!config) { throw new Error("no config, can't get unit state."); } - const unit = getUnit(config.units, id); + const unit = getUnit(null, config.units, id); const theState = unitState.get(id); switch (unit.kind) { case 'osc': { @@ -72,7 +72,7 @@ const oscShapes: OscShapes = { function vUnit(x: { id: UnitId }): number { const { id } = x; - const unit: Unit = getUnit(config!.units, id); + const unit: Unit = getUnit(null, config!.units, id); switch (unit.kind) { case 'osc': { const position = getUnitState(id); @@ -124,7 +124,7 @@ function update() { } const ids = Object.keys(config.units); for (let id of ids) { - const unit = getUnit(config.units, id); + const unit = getUnit(null, config.units, id); switch (unit.kind) { case 'osc': { const position = getUnitState(id); diff --git a/src/lib/stores.ts b/src/lib/stores.ts @@ -1,16 +1,26 @@ -import type { Controls, Input, Unit, UnitId, UnitKind, UnitToConnect, Units } from '$lib/types'; +import type { + ControlName, + Controls, + Input, + Unit, + UnitId, + UnitKind, + UnitToConnect, + Units +} from '$lib/types'; import { writable } from 'svelte/store'; const mkUnitStore = () => { const { subscribe, set, update } = writable<Units>({}); - const _setControl = (id: UnitId, controlName: string, input: Input) => { + const setControl = <K extends UnitKind>( + kind: K, + id: UnitId, + controlName: ControlName<K>, + 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; + const controls = units[id].controls as Controls<K>; + controls[controlName] = input; return units; }); }; @@ -20,7 +30,7 @@ const mkUnitStore = () => { setUnit<K extends UnitKind>(id: UnitId, unit: Unit<K>) { update((units) => ({ ...units, [id]: unit })); }, - _setControl + setControl }; }; diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -92,11 +92,7 @@ export type Input = number | Output[]; export type Units = { [u: UnitId]: Unit }; export type UnitStateMap = Map<UnitId, UnitState>; export type UnitState = any; -export type Sinks = { - l: Input; - c: Input; - h: Input; -}; +export type Sinks = Record<'l' | 'c' | 'h', Input>; export type NumberRange = { min: number; @@ -175,10 +171,17 @@ export function wrangle(n: number, origin: NumberRange, dest: NumberRange) { return rescale(clamp(n, origin), origin, dest); } -export function getUnit(units: Units, id: UnitId): Unit { - const result = units[id]; - if (!result) throw new Error('invalid id for unit: ' + id); - return result; +export function getUnit(kind: null, units: Units, id: UnitId): Unit; +export function getUnit<K extends UnitKind = UnitKind>(kind: K, units: Units, id: UnitId): Unit<K>; +export function getUnit<K extends UnitKind = UnitKind>( + kind: K | null, + units: Units, + id: UnitId +): Unit<UnitKind> { + if (kind !== null) { + const r = ensure.unit[kind](units[id]); + return r as Unit<K>; + } else return units[id]; } const isUnit = diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -64,7 +64,7 @@ }; }); - const getUnit = (id: UnitId): Unit => _getUnit($unitStore, id); + const getUnit = (id: UnitId): Unit => _getUnit(null, $unitStore, id); let uid = 0; $: units = $unitStore; let sinks: Sinks = { l: 0, c: 0, h: 0 }; @@ -72,7 +72,6 @@ // let's update the engine whenever the config changes for now, even though we could ignore pos at least. $: { if (engineWorker) { - console.log('updating the engine'); engineWorker.postMessage({ kind: 'config', content: { units: $unitStore, sinks } }); } } @@ -140,14 +139,6 @@ } }; - const get = { - constUnit: (id: UnitId): ConstUnit => { - const unit = getUnit(id); - if (!is.unit.const(unit)) throw new Error('expecting a const here bro....'); - return unit; - } - }; - let dragging: false | UnitId = false; const handleMouseMove = (e: MouseEvent) => { @@ -168,7 +159,6 @@ }; const handleMouseDown = (id: UnitId) => { - console.log('mouse down', id); dragging = id; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); @@ -191,14 +181,9 @@ } sinks[ch] = inp.toggle(sinks[ch], $unitToConnect); engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } }); - console.log(sinks); $unitToConnect = false; }; - $: { - console.log(units); - } - $: onSinkConnect = $unitToConnect ? _onSinkConnect : null; type SinkEntries = [keyof Sinks, Input | null][];