color-synth

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

commit bfb66e6cafb9d9e568154f6d43515fc04e61e061
parent 83325dc37bf001abae5e73b61ad0eb3709071ee1
Author: massi <mdsiboldi@gmail.com>
Date:   Sat,  5 Aug 2023 15:29:08 -0700

just a few things ya know

Diffstat:
Asrc/lib/ConstUnit.svelte | 20++++++++++++++++++++
Asrc/lib/Control.svelte | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/Noise.svelte | 76----------------------------------------------------------------------------
Asrc/lib/NoiseUnit.svelte | 20++++++++++++++++++++
Dsrc/lib/Osc.svelte | 81-------------------------------------------------------------------------------
Asrc/lib/OscUnit.svelte | 23+++++++++++++++++++++++
Msrc/lib/engine.worker.ts | 320++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/lib/stores.ts | 23++++++++++++-----------
Msrc/lib/types.ts | 401++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/routes/+page.svelte | 69+++++++++++++++++++++++++--------------------------------------------
Mtsconfig.json | 9+++++++--
11 files changed, 529 insertions(+), 597 deletions(-)

diff --git a/src/lib/ConstUnit.svelte b/src/lib/ConstUnit.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import Control from '$lib/Control.svelte'; + import { unitStore } from '$lib/stores'; + import { ensure, getUnit, type UnitId } from '$lib/types'; + + export let id: UnitId; + + $: unit = ensure.unit.const(getUnit($unitStore, id)); +</script> + +<div> + <h1>just a const...</h1> + <Control {id} controlName="value" /> +</div> + +<style> + h1 { + color: white; + } +</style> diff --git a/src/lib/Control.svelte b/src/lib/Control.svelte @@ -0,0 +1,84 @@ +<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 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; + } + } +</script> + +<div class="control-wrapper"> + <h3>{controlName}</h3> + <InputDragger {connected} {onConnect} /> + {#if values !== null} + <input + type="range" + min={controlRange.min} + max={controlRange.max} + step={1} + value={values.control} + on:input={(e) => updateValue(Number(e.currentTarget?.value))} + /> + {/if} +</div> + +<style> + .control-wrapper { + position: relative; + width: 100%; + height: 80px; + background: rgba(255, 255, 255, 0.3); + padding: 10px; + display: flexbox; + } +</style> diff --git a/src/lib/Noise.svelte b/src/lib/Noise.svelte @@ -1,76 +0,0 @@ -<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/NoiseUnit.svelte b/src/lib/NoiseUnit.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import Control from '$lib/Control.svelte'; + import { unitStore } from '$lib/stores'; + import { ensure, getUnit, type UnitId } from '$lib/types'; + + export let id: UnitId; + + $: unit = ensure.unit.noise(getUnit($unitStore, id)); +</script> + +<div> + <h1>noiseyboi</h1> + <Control {id} controlName="amount" /> +</div> + +<style> + h1 { + color: green; + } +</style> diff --git a/src/lib/Osc.svelte b/src/lib/Osc.svelte @@ -1,81 +0,0 @@ -<script lang="ts"> - import type { OscUnit, OscUnitInputs, 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: OscUnit; - export let signalDragging: false | Output; - export let updateOsc: (osc: OscUnit) => void; - export let onConnect: ((k: string) => void) | null; - - $: vals = { - coarse: - typeof unit.coarse === 'number' ? wrangle(unit.coarse, range.signal, range.osc.coarse) : null, - fine: typeof unit.fine === 'number' ? wrangle(unit.fine, range.signal, range.osc.fine) : null, - superfine: - typeof unit.superfine === 'number' - ? wrangle(unit.superfine, range.signal, range.osc.superfine) - : null, - amount: - typeof unit.amount === 'number' ? wrangle(unit.amount, range.signal, range.osc.amount) : null - }; - - const updateValue = (k: keyof OscUnitInputs, n: number) => { - updateOsc({ ...unit, [k]: Math.round(rescale(n, range.osc[k], range.signal)) }); - }; - - const isConnected = (k: keyof OscUnitInputs, 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.osc 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/OscUnit.svelte b/src/lib/OscUnit.svelte @@ -0,0 +1,23 @@ +<script lang="ts"> + import Control from '$lib/Control.svelte'; + import { unitStore } from '$lib/stores'; + import { ensure, getUnit, type UnitId } from '$lib/types'; + + export let id: UnitId; + + $: unit = ensure.unit.osc(getUnit($unitStore, id)); +</script> + +<div> + <h1>Os-KILL-8r</h1> + <Control {id} controlName="coarse" /> + <Control {id} controlName="fine" /> + <Control {id} controlName="superfine" /> + <Control {id} controlName="amount" /> +</div> + +<style> + h1 { + color: red; + } +</style> diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts @@ -1,22 +1,14 @@ import type { - EngineMessage, - Input, - SynthConfig, - Unit, - UnitId, - UnitState, - UnitStateMap, -} from "$lib/types"; + EngineMessage, + Input, + SynthConfig, + Unit, + UnitId, + UnitState, + 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 } from '$lib/types'; let config: SynthConfig | undefined = undefined; @@ -25,181 +17,175 @@ let unitState: UnitStateMap = new Map(); let canvas: OffscreenCanvas = new OffscreenCanvas(1, 1); onmessage = (message: { data: EngineMessage }) => { - const { data } = message; - switch (data.kind) { - case "config": { - config = data.content; - break; - } - case "window": { - canvas = new OffscreenCanvas(data.content.width, data.content.height); - } - } + const { data } = message; + switch (data.kind) { + case 'config': { + config = data.content; + break; + } + case 'window': { + canvas = new OffscreenCanvas(data.content.width, data.content.height); + } + } }; function getUnitState(id: UnitId): UnitState { - if (!config) { - throw new Error("no config, can't get unit state.") - } - const unit = getUnit(config.units, id); - const theState = unitState.get(id); - switch (unit.kind) { - case "osc": { - if (theState === undefined) { - return 0; - } - if (typeof theState !== "number" || Number.isNaN(theState)) { - throw new Error(`invalid state for osc unit ${id}: ${theState}`); - } - break; - } - default: { - throw new Error("state for this invalid or NYI"); - } - } - return theState; + if (!config) { + throw new Error("no config, can't get unit state."); + } + const unit = getUnit(config.units, id); + const theState = unitState.get(id); + switch (unit.kind) { + case 'osc': { + if (theState === undefined) { + return 0; + } + if (typeof theState !== 'number' || Number.isNaN(theState)) { + throw new Error(`invalid state for osc unit ${id}: ${theState}`); + } + break; + } + default: { + throw new Error('state for this invalid or NYI'); + } + } + return theState; } function setUnitState(id: UnitId, state: UnitState) { - // TODO: type safety - unitState.set(id, state); + // TODO: type safety + unitState.set(id, state); } type OscShapes = { - [k: string]: (p: number, a: number) => number -} + [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 -} + 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 oscShapes.sine(position / LOOP_CYCLES, v(unit.amount)) - } - case "const": { - // range is whatever the control for it is??? - return unit.value; - } - case "noise": { - return Math.random() * v(unit.amount); - } - } + const { id } = x; + const unit: Unit = getUnit(config!.units, id); + switch (unit.kind) { + case 'osc': { + const position = getUnitState(id); + return oscShapes.sine(position / LOOP_CYCLES, v(unit.controls.amount)); + } + case 'const': { + return v(unit.controls.value); + } + case 'noise': { + return Math.random() * v(unit.controls.amount); + } + } } -const perf = {} +const perf = {}; const p = { - start: (tag: string) => { - if (!perf[tag]) perf[tag] = { - n: 0, - cum: 0, - avg: 0 - } - perf[tag]._t = performance.now(); - }, - end: (tag: string) => { - const r = perf[tag] - r.n++; - r.cum += performance.now() - r._t; - r.avg = r.cum / r.n; - } -} + start: (tag: string) => { + if (!perf[tag]) + perf[tag] = { + n: 0, + cum: 0, + avg: 0 + }; + perf[tag]._t = performance.now(); + }, + end: (tag: string) => { + const r = perf[tag]; + r.n++; + r.cum += performance.now() - r._t; + r.avg = r.cum / r.n; + } +}; -setInterval(() => console.log(perf), 5000) +setInterval(() => console.log(perf), 5000); function v(input: Input): number { - if (typeof input === "number") { - return input; - } - const result = input.reduce((a, b) => a + vUnit(b), 0); - return result; + if (typeof input === 'number') { + return input; + } + const result = input.reduce((a, b) => a + vUnit(b), 0); + return result; } function update() { - // update all unit states - if (!config) { - throw new Error("no config, can't update unit state.") - } - const ids = Object.keys(config.units); - for (let id of ids) { - const unit = getUnit(config.units, id); - switch (unit.kind) { - case "osc": { - const position = getUnitState(id); - const coarse = rescale(v(unit.coarse), range.signal, range.osc.coarse); - const fine = rescale(v(unit.fine), range.signal, range.osc.fine); - const superfine = rescale( - v(unit.superfine), - range.signal, - range.osc.superfine - ); - setUnitState( - id, - (position + coarse * LOOP_CYCLES / 100 + fine * 20000 + - superfine * 10) % - LOOP_CYCLES, - ); - break; - } - } - } + // update all unit states + if (!config) { + throw new Error("no config, can't update unit state."); + } + const ids = Object.keys(config.units); + for (let id of ids) { + const unit = getUnit(config.units, id); + 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); + setUnitState( + id, + (position + (coarse * LOOP_CYCLES) / 100 + fine * 20000 + superfine * 10) % LOOP_CYCLES + ); + break; + } + } + } } function drawSquares() { - if (config) { - p.start("drawSquares"); - const ctx = canvas.getContext("2d", { - antialias: false, - alpha: false, - }); - if (!ctx) { - throw new Error("couldnt get ctx"); - } - - // 4 = R G B A - const data = new Uint8ClampedArray(ROWS * COLS * 4); - let di = 0; - - 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"); - } - requestAnimationFrame(drawSquares); + if (config) { + p.start('drawSquares'); + const ctx = canvas.getContext('2d', { + antialias: false, + alpha: false + }); + if (!ctx) { + throw new Error('couldnt get ctx'); + } + + // 4 = R G B A + const data = new Uint8ClampedArray(ROWS * COLS * 4); + let di = 0; + + 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'); + } + requestAnimationFrame(drawSquares); } drawSquares(); diff --git a/src/lib/stores.ts b/src/lib/stores.ts @@ -1,16 +1,17 @@ import { writable } from 'svelte/store'; -import type { Unit, UnitId, Units } from '$lib/types'; +import type { Output, Unit, UnitId, UnitKind, Units } from '$lib/types'; -const mkUnitStore = () => { - const { subscribe, set, update } = writable<Units>({}); - return { - subscribe, - set, - setUnit(id: UnitId, unit: Unit) { - update(units => ({ ...units, [id]: unit })); - } - }; +const mkUnitStore = <T = Units>() => { + const { subscribe, set, update } = writable<T>({}); + return { + subscribe, + set, + setUnit(id: UnitId, unit: Unit) { + update((units) => ({ ...units, [id]: unit })); + } + }; +}; -} +export const unitToConnect = writable<Output | false>(false); export const unitStore = mkUnitStore(); diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -7,261 +7,230 @@ export const CELLS = ROWS * COLS; export type WithTarget<E, T> = E & { currentTarget: T }; export type Color = { - r: number, - g: number, - b: number -} + r: number; + g: number; + b: number; +}; export type SynthConfig = { - sinks: Sinks; - units: Units; + sinks: Sinks; + units: Units; }; -export type EngineMessage = { - kind: "config"; - content: SynthConfig; -} | { - kind: "window"; - content: { - height: number; - width: number; - }; -} | { - kind: "canvas"; - content: OffscreenCanvas; +export type EngineMessage = + | { + kind: 'config'; + content: SynthConfig; + } + | { + kind: 'window'; + content: { + height: number; + width: number; + }; + } + | { + kind: 'canvas'; + content: OffscreenCanvas; + }; + +type Pos = { + x: number; + y: number; }; -export type Unit = - | ConstUnit - | OscUnit - | NoiseUnit; - -export type UnitId = string; +export type ConstUnit = { + kind: 'const'; + pos: Pos; + controls: { + value: Input; + }; +}; -export type Output = { id: UnitId }; -export type Input = number | Output[]; +export type OscUnit = { + kind: 'osc'; + pos: Pos; + controls: { + coarse: Input; + fine: Input; + superfine: Input; + amount: Input; + }; +}; -export type Pos = { x: number; y: number }; +export type NoiseUnit = { + kind: 'noise'; + pos: Pos; + controls: { + amount: Input; + }; +}; -type WithPos = { pos: Pos }; +export type Unit = OscUnit | ConstUnit | NoiseUnit; -export type OscUnitInputs = { - coarse: Input; - fine: Input; - superfine: Input; - amount: Input; -} -export type NoiseUnitInputs = { - amount: Input; -} -export type SmoothUnitInputs = { - frames: Input; -} +export type UnitKind = keyof UnitMap; -type UnitInputs = { - osc: (keyof OscUnitInputs)[], - noise: (keyof NoiseUnitInputs)[], - smooth: (keyof SmoothUnitInputs)[] -} -export const unitInputs: UnitInputs = { - osc: ['coarse', 'fine', 'superfine', 'amount'], - noise: ['amount'], - smooth: ['frames'] -} - -export type SmoothUnit = WithPos & SmoothUnitInputs & { - kind: "smooth"; -} -export type OscUnit = WithPos & OscUnitInputs & { - kind: "osc"; +type UnitMap = { + const: ConstUnit; + osc: OscUnit; + noise: NoiseUnit; }; -export type NoiseUnit = WithPos & NoiseUnitInputs & { - kind: "noise"; -}; +export type UnitId = string; -// outputs number as it is -export type ConstUnit = WithPos & { - kind: "const"; - value: number; -}; +export type Output = { id: UnitId }; +export type Input = number | Output[]; export type Units = { [u: UnitId]: Unit }; -export type UnitMap = Map<UnitId, Unit>; export type UnitStateMap = Map<UnitId, UnitState>; export type UnitState = any; export type Sinks = { - l: Input; - c: Input; - h: Input; + l: Input; + c: Input; + h: Input; }; -type Range = { - min: number; - max: number; +export type Range = { + min: number; + max: number; }; export const range = { - osc: { - coarse: { - min: -50, - max: 50, - }, - fine: { - min: -50, - max: 50, - }, - superfine: { - min: -50, - max: 50, - }, - amount: { - min: -50, - max: 50, - }, - output: { - min: -1, - max: 1, - }, - }, - noise: { - amount: { - min: -50, - max: 50, - }, - }, - 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, - }, - + osc: { + coarse: { + min: -50, + max: 50 + }, + fine: { + min: -50, + max: 50 + }, + superfine: { + min: -50, + max: 50 + }, + amount: { + min: -50, + max: 50 + }, + output: { + min: -1, + max: 1 + } + }, + const: { + value: { + min: -5_000_000, + max: 5_000_000 + } + }, + noise: { + amount: { + min: -50, + max: 50 + } + }, + 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 + } }; -//export function rangeForInput<U extends Unit>( -// units: UnitMap, -// id: UnitId, -// k: keyof Omit<U, "kind">, -//): Range { -// let unit = getUnit(units, id); -// switch (unit.kind) { -// case "const": { -// return range.slider; -// } -// case "osc": { -// switch (k) { -// case "amount": { -// return range.osc.amount; -// } -// case "coarse": { -// return range.osc.coarse; -// } -// case "fine": { -// return range.osc.fine; -// } -// default: { -// throw new Error(`cannot find range for ${String(k)} in ${unit.kind}`) -// } -// } -// } -// case "combinator": { -// return unit.sources.reduce((accum, (src) => { -// console.log('hi'); -// return -// })) -// } -// default: -// throw new Error("NYI: range for " + unit.kind); -// } -//} - export function clamp(n: number, range: Range) { - return Math.max(range.min, Math.min(range.max, n)); + return Math.max(range.min, Math.min(range.max, n)); } export function rescale(n: number, origin: Range, dest: Range) { - return ((n - origin.min) / (origin.max - origin.min)) * - (dest.max - dest.min) + dest.min; + return ((n - origin.min) / (origin.max - origin.min)) * (dest.max - dest.min) + dest.min; } export function wrangle(n: number, origin: Range, dest: Range) { - return rescale(clamp(n, origin), origin, dest); + 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; + const result = units[id]; + if (!result) throw new Error('invalid id for unit: ' + id); + return result; } 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"; - }, - }, + 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'; + } + } }; 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"); - } - }, - }, + 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'); + } + } + } }; export const inp = { - toggle: (input: Input, output: Output): Input => { - if (typeof input === "number") { - return [output] - } - else { - const deduped = input.filter((o) => o.id !== output.id) - if (input.length === deduped.length) { - return [...input, output]; - } - else { - // it's there and we removed it - // if the last input signal was removed, we're back to a value. - return deduped.length === 0 ? 0 : deduped; - } - } - }, - connected: (input: Input, output: Output): boolean => { - if (Array.isArray(input)) { - return Boolean(input.find((o) => o.id === output.id)); - } - return false; - } -} + toggle: (input: Input, output: Output): Input => { + if (typeof input === 'number') { + return [output]; + } else { + const deduped = input.filter((o) => o.id !== output.id); + if (input.length === deduped.length) { + return [...input, output]; + } else { + // it's there and we removed it + // if the last input signal was removed, we're back to a value. + return deduped.length === 0 ? 0 : deduped; + } + } + }, + connected: (input: Input, output: Output): boolean => { + if (Array.isArray(input)) { + return Boolean(input.find((o) => o.id === output.id)); + } + return false; + } +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -1,13 +1,13 @@ <script lang="ts"> - import { unitStore } from '$lib/stores'; + import { unitStore, unitToConnect } from '$lib/stores'; import { onMount } from 'svelte'; 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, Units, Sinks } from '$lib/types'; + import OscUnit from '$lib/OscUnit.svelte'; + import NoiseUnit from '$lib/NoiseUnit.svelte'; + import ConstUnit from '$lib/ConstUnit.svelte'; let cvs: HTMLCanvasElement | undefined; @@ -20,7 +20,6 @@ }; onMount(loadWorker); - $: engineWorker && engineWorker.postMessage({ kind: 'config', content: { units, sinks } }); $: { if (engineWorker) { @@ -70,6 +69,13 @@ $: units = $unitStore; let sinks: Sinks = { l: 0, c: 0, h: 0 }; + // 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 } }); + } + } onMount(() => { const doc: SerializedState = fromUrl(); if (doc) { @@ -118,22 +124,19 @@ c: function mkConst(): UnitId { return addUnit({ kind: 'const', - value: randConst(), + controls: { value: randConst() }, pos: { x: 0, y: 0 } }); }, osc: function mkOsc(): UnitId { return addUnit({ kind: 'osc', - coarse: randConst(), - fine: randConst(), - superfine: 0, - amount: randConst(), + controls: { coarse: randConst(), fine: randConst(), superfine: 0, amount: randConst() }, pos: { x: 0, y: 0 } }); }, noise: function noise(): UnitId { - return addUnit({ kind: 'noise', amount: randConst(), pos: { x: 0, y: 0 } }); + return addUnit({ kind: 'noise', controls: { amount: randConst() }, pos: { x: 0, y: 0 } }); } }; @@ -145,16 +148,6 @@ } }; - function updateUnit(id: UnitId, value: number) { - const unit = get.constUnit(id); - unit.value = value; - unitStore.setUnit(id, unit); - } - - function updateEntireUnit(id: UnitId, unit: Unit) { - unitStore.setUnit(id, unit); - } - let dragging: false | UnitId = false; const handleMouseMove = (e: MouseEvent) => { @@ -181,12 +174,10 @@ document.addEventListener('mouseup', handleMouseUp); }; - let signalDragging: false | Output = false; - const handleSignalStart = (o: Output) => { - signalDragging = o; + $unitToConnect = o; const h = () => { - signalDragging = false; + $unitToConnect = false; document.removeEventListener('mouseup', h); }; setTimeout(() => { @@ -195,20 +186,20 @@ }; const _onSinkConnect = (ch: 'l' | 'c' | 'h'): void => { - if (!signalDragging) { + if (!$unitToConnect) { throw new Error('cant connect sink to nonexistant signal'); } - sinks[ch] = inp.toggle(sinks[ch], signalDragging); + sinks[ch] = inp.toggle(sinks[ch], $unitToConnect); engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } }); console.log(sinks); - signalDragging = false; + $unitToConnect = false; }; $: { console.log(units); } - $: onSinkConnect = signalDragging ? _onSinkConnect : null; + $: onSinkConnect = $unitToConnect ? _onSinkConnect : null; type SinkEntries = [keyof Sinks, Input | null][]; // Object.entries COULD have extra stuff, so it doesn't assume keys are keyof Sinks exactly. @@ -226,7 +217,7 @@ // @ts-ignore unit[k] is a channel but i don't wanna prove it. unitStore.setUnit(input, { ...unit, [k]: inp.toggle(unit[k], { id: output }) }); engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } }); - signalDragging = false; + $unitToConnect = false; }; </script> @@ -244,7 +235,7 @@ {input} {channel} {onSinkConnect} - connected={signalDragging && input ? inp.connected(input, signalDragging) : false} + connected={$unitToConnect && input ? inp.connected(input, $unitToConnect) : false} /> {/each} </div> @@ -253,21 +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'} - <Slider {id} {units} handleInput={updateUnit} handleChange={updateUrl} /> + <ConstUnit {id} /> {:else if unit.kind === 'osc'} - <Osc - {unit} - {signalDragging} - onConnect={getConnectHandler(signalDragging, id)} - updateOsc={updateEntireUnit.bind(null, id)} - /> + <OscUnit {id} /> {:else if unit.kind === 'noise'} - <Noise - {unit} - {signalDragging} - onConnect={getConnectHandler(signalDragging, id)} - update={updateEntireUnit.bind(null, id)} - /> + <NoiseUnit {id} /> {/if} <div class="output-dragger" diff --git a/tsconfig.json b/tsconfig.json @@ -9,10 +9,14 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "lib": ["webworker", "es2019"] + "noErrorTruncation": true, + "lib": [ + "webworker", + "es2019" + ] } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in -} +} +\ No newline at end of file