color-synth

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

commit 5e6139f8e0eabb75dee9d65edf842d9939f5fd92
parent b1a7aa45fd457a8f8efcf6e94cb112a7b4f939b8
Author: massi <mdsiboldi@gmail.com>
Date:   Wed, 19 Jul 2023 00:43:19 -0700

get back in sync with ts

Diffstat:
Mpackage-lock.json | 21++++++++++++++++++---
Mpackage.json | 3++-
Msrc/lib/Sink.svelte | 4+---
Msrc/lib/Slider.svelte | 25+++++++++++--------------
Msrc/lib/engine.worker.ts | 45+++++++++++++++++++++++++++++----------------
Msrc/lib/types.ts | 15++++-----------
Msrc/routes/+page.svelte | 31+++++++++++++++++++------------
Mtsconfig.json | 3++-
8 files changed, 86 insertions(+), 61 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -9,12 +9,13 @@ "version": "0.0.1", "dependencies": { "@sveltejs/adapter-static": "^2.0.2", - "@types/offscreencanvas": "^2019.7.0", "lodash": "^4.17.21" }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.5.0", + "@types/lodash": "^4.14.195", + "@types/offscreencanvas": "^2019.7.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", @@ -616,10 +617,17 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, "node_modules/@types/offscreencanvas": { "version": "2019.7.0", "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==", + "dev": true }, "node_modules/@types/pug": { "version": "2.0.6", @@ -3305,10 +3313,17 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, "@types/offscreencanvas": { "version": "2019.7.0", "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==", + "dev": true }, "@types/pug": { "version": "2.0.6", diff --git a/package.json b/package.json @@ -14,6 +14,8 @@ "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.5.0", + "@types/lodash": "^4.14.195", + "@types/offscreencanvas": "^2019.7.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", @@ -30,7 +32,6 @@ "type": "module", "dependencies": { "@sveltejs/adapter-static": "^2.0.2", - "@types/offscreencanvas": "^2019.7.0", "lodash": "^4.17.21" } } diff --git a/src/lib/Sink.svelte b/src/lib/Sink.svelte @@ -1,8 +1,6 @@ <script lang="ts"> - import type { UnitId } from '$lib/types'; - export let id: UnitId; export let channel: 'l' | 'c' | 'h'; - export let onSinkConnect: null | ((ch: string) => void); + export let onSinkConnect: null | ((ch: 'l' | 'c' | 'h') => void); let _onSinkConnect: null | (() => void) = null; $: _onSinkConnect = onSinkConnect ? () => onSinkConnect && onSinkConnect(channel) : null; diff --git a/src/lib/Slider.svelte b/src/lib/Slider.svelte @@ -1,6 +1,6 @@ <script lang="ts"> - import type { Unit, UnitId, UnitMap } from '$lib/types'; - import { rescale, range, INPUT_RANGE, getUnit as _getUnit, is } from '$lib/types'; + import type { WithTarget, UnitId, UnitMap } from '$lib/types'; + import { rescale, range, getUnit as _getUnit } from '$lib/types'; import NumberSelector from '$lib/NumberSelector.svelte'; export let id: UnitId; @@ -12,12 +12,18 @@ $: getUnit = _getUnit.bind(null, units); $: unit = getUnit(id); - $: sliderValue = Math.round(rescale(unit.value, range.signal, range.slider)); + $: 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>) => { + console.log(e.currentTarget); + _handleInput(Number(e.currentTarget?.value)); + }; </script> <div class="unit-container"> @@ -31,7 +37,7 @@ max={range.slider.max} step={1} value={sliderValue} - on:input={(e) => _handleInput(Number(e.target?.value))} + on:input={onInput} on:change={handleChange} /> {:else if unit.kind === 'combinator'} @@ -41,8 +47,6 @@ <svelte:self id={input.id} {...props} /> {/each} </div> - {:else if unit.kind === 'rescale'} - <svelte:self id={unit.input.id} {...props} /> {:else if unit.kind === 'noise'} <h3>{unit.kind}</h3> <svelte:self id={unit.amount.id} {...props} /> @@ -50,15 +54,13 @@ <h3>{unit.kind}</h3> <h4>rate</h4> coarse - <svelte:self id={unit.coarse.id} {...props} /> + <svelte:self id={unit.coarse[0].id} {...props} /> fine <svelte:self id={unit.fine.id} {...props} /> superfine <svelte:self id={unit.superfine.id} {...props} /> <h4>amount</h4> <svelte:self id={unit.amount.id} {...props} /> - {:else} - <h3>{unit.kind} nyi</h3> {/if} </div> @@ -72,14 +74,9 @@ padding: 10px; display: flexbox; } - h1, - h2, h3, h4 { padding: 0; margin: 0; } - .val { - height: 16px; - } </style> diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts @@ -8,10 +8,7 @@ import type { UnitStateMap, } from "$lib/types"; import { - CELLS, - clamp, getUnit, - INPUT_RANGE, LOOP_CYCLES, range, rescale, @@ -22,23 +19,29 @@ let config: SynthConfig | undefined = undefined; let unitState: UnitStateMap = new Map(); +// @ts-ignore dunno how to get OffscreenCanvas working let canvas: OffscreenCanvas = new OffscreenCanvas(1, 1); +// @ts-ignore dunno how to get message working onmessage = (message: { data: EngineMessage }) => { - const { data, data: { kind, content } } = message; - switch (kind) { + const { data } = message; + switch (data.kind) { case "config": { - config = content; + config = data.content; break; } case "window": { - console.log(content); + console.log(data.content); + // @ts-ignore dunno how to get OffscreenCanvas working canvas = new OffscreenCanvas(content.width, 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) { @@ -63,8 +66,17 @@ function setUnitState(id: UnitId, state: UnitState) { unitState.set(id, state); } -function v(input: Input): number { - const { id } = input; +function v(inOrIns: Input | Input[]): number { + if (!config) { + throw new Error("no config, can't get unit value.") + } + + if (Array.isArray(inOrIns)) { + return inOrIns.reduce((a, b) => a + v(b), 0); + } + + + const { id } = inOrIns; const unit: Unit = getUnit(config.units, id); switch (unit.kind) { case "osc": { @@ -90,14 +102,14 @@ function v(input: Input): number { case "noise": { return Math.random() * v(unit.amount); } - default: { - throw new Error(`${unit.kind} unsupported`); - } } } function update() { // update all unit states + if (!config) { + throw new Error("no config, can't update unit state.") + } const ids = config.units.keys(); for (let id of ids) { const unit = getUnit(config.units, id); @@ -115,7 +127,7 @@ function update() { id, (position + coarse * LOOP_CYCLES / 100 + fine * 20000 + superfine * 10) % - LOOP_CYCLES, + LOOP_CYCLES, ); break; } @@ -126,6 +138,7 @@ function update() { function drawSquares() { if (config) { const ctx = canvas.getContext("2d"); + if (!ctx) throw new Error("could not get 2d rendering context...") const cols = 100; const rows = 50; const color = [0, 0, 0]; @@ -136,9 +149,9 @@ function drawSquares() { for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { - const l = config.sinks.l == null ? 0 : v({ id: config.sinks.l }); - const c = config.sinks.c == null ? 0 : v({ id: config.sinks.c }); - const h = config.sinks.h == null ? 0 : v({ id: config.sinks.h }); + 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); color[0] = wrangle(l, range.signal, { min: 0, max: 100, diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -1,10 +1,11 @@ -import type { OffscreenCavas } from "@types/offscreencanvas"; export const LOOP_CYCLES = 100_000_000; export const INPUT_RANGE = 100; export const ROWS = 100; export const COLS = 50; export const CELLS = ROWS * COLS; +export type WithTarget<E, T> = E & { currentTarget: T }; + export type SynthConfig = { sinks: Sinks; units: UnitMap; @@ -26,7 +27,6 @@ export type EngineMessage = { export type HigherUnit = | OscUnit | CombinatorUnit - | RescaleUnit | NoiseUnit; export type Unit = @@ -39,16 +39,9 @@ export type Input = { id: UnitId }; export type Pos = { x: number; y: number }; -export type RescaleUnit = { - kind: "rescale"; - input: Input; - min: Input; - max: Input; -}; - export type OscUnit = { kind: "osc"; - coarse: Input; + coarse: Input[]; fine: Input; superfine: Input; amount: Input; @@ -174,7 +167,7 @@ export function clamp(n: number, range: Range) { } export function rescale(n: number, origin: Range, dest: Range) { return ((n - origin.min) / (origin.max - origin.min)) * - (dest.max - dest.min) + dest.min; + (dest.max - dest.min) + dest.min; } export function wrangle(n: number, origin: Range, dest: Range) { return rescale(clamp(n, origin), origin, dest); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -3,7 +3,7 @@ import { debounce } from 'lodash'; import Sink from '$lib/Sink.svelte'; import Slider from '$lib/Slider.svelte'; - import { ensure, rescale, range, is, getUnit as _getUnit, INPUT_RANGE } from '$lib/types'; + import { ensure, rescale, range, is, getUnit as _getUnit } from '$lib/types'; import type { Input, Unit, UnitId, ConstUnit, UnitMap, Sinks } from '$lib/types'; let cvs: HTMLCanvasElement | undefined; @@ -61,7 +61,7 @@ $: getUnit = _getUnit.bind(null, units); let uid = 0; let units: UnitMap = new Map(); - let sinks: Sinks = {}; + let sinks: Sinks = { l: null, c: null, h: null }; onMount(() => { const doc: SerializedState = fromUrl(); @@ -118,7 +118,8 @@ c: function mkConst(value?: number): Input { return addUnit({ kind: 'const', - value: value == null ? randConst() : value + value: value == null ? randConst() : value, + pos: { x: 0, y: 0 } }); }, osc: function mkOsc( @@ -129,7 +130,7 @@ ): Input { return addUnit({ kind: 'osc', - coarse: ez(coarse), + coarse: [ez(coarse)], fine: ez(fine), superfine: superfine ? ez(superfine) : mk.c(0), amount: ez(amount) @@ -169,7 +170,7 @@ let dragging: false | UnitId = false; - const handleMouseMove = (e) => { + const handleMouseMove = (e: MouseEvent) => { if (!dragging) return; ensure.unit.const(getUnit(dragging)).pos = { x: e.clientX, @@ -210,7 +211,7 @@ if (!signalDragging) { throw new Error('cant connect sink to nonexistant signal'); } - sinks[ch] = signalDragging.id; + sinks[ch] = { id: signalDragging.id }; engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } }); console.log(sinks); signalDragging = false; @@ -219,25 +220,31 @@ $: onSinkConnect = signalDragging ? _onSinkConnect : null; $: unitEntries = units.entries(); - $: sinkEntries = Object.entries(sinks); + type SinkEntries = [keyof Sinks, Input | null][]; + // Object.entries COULD have extra stuff, so it doesn't assume keys are keyof Sinks exactly. + // see https://stackoverflow.com/questions/60141960/typescript-key-value-relation-preserving-object-entries-type + $: sinkEntries = [...Object.entries(sinks)] as SinkEntries; </script> <canvas bind:this={cvs} /> <div id="buttons"> - <button on:click={(e) => addConst()}>add const</button> + <button on:click={addConst}>add const</button> </div> <h1>HI {unitEntries}</h1> <div id="sinks"> - {#each [...sinkEntries] as [channel, id]} - <Sink {id} {channel} {onSinkConnect} /> + {#each sinkEntries as [channel]} + <Sink {channel} {onSinkConnect} /> {/each} </div> <div id="units"> {#each [...unitEntries] as [id, unit]} - <div class="unit" style={`top: ${unit.pos.y}px; left: ${unit.pos.x}px`}> - <h2 on:mousedown={(e) => handleMouseDown(id)}>{id} {unit.kind}</h2> + <div + class="unit" + style={unit.kind === 'const' ? `top: ${unit.pos.y}px; left: ${unit.pos.x}px` : ''} + > + <h2 on:mousedown={handleMouseDown.bind(null, id)}>{id} {unit.kind}</h2> <Slider {id} {units} handleInput={updateUnit} handleChange={updateUrl} /> <div class="output-dragger" diff --git a/tsconfig.json b/tsconfig.json @@ -8,7 +8,8 @@ "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, - "strict": true + "strict": true, + "lib": ["webworker", "es2019"] } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias //