color-synth

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

commit ad431becdd17df5a1f5afd0405e431be35131591
parent a1608ff5dd04a4e657e7a0ddb3b0df371b7e7d6e
Author: massi <mdsiboldi@gmail.com>
Date:   Thu,  6 Jul 2023 10:04:41 -0700

units can eventually have multiple outputs, so wrap input in obj

Diffstat:
Msrc/lib/Slider.svelte | 32++++++++++++++++----------------
Msrc/lib/engine.worker.ts | 23+++++++++++++----------
Msrc/lib/types.ts | 38++++++++++++++++++++++++++------------
Msrc/routes/+page.svelte | 62++++++++++++++++++++++----------------------------------------
4 files changed, 77 insertions(+), 78 deletions(-)

diff --git a/src/lib/Slider.svelte b/src/lib/Slider.svelte @@ -1,22 +1,22 @@ <script lang="ts"> import type { Unit, UnitId, UnitMap } from '$lib/types'; - import { INPUT_RANGE, getUnit as _getUnit } from '$lib/types'; + import { INPUT_RANGE, getUnit as _getUnit, is } from '$lib/types'; export let id: UnitId; export let units: UnitMap; export let handleInput: (id: string, coarseVal: number | null, fineVal: number | null) => void; export let handleChange: () => void; $: props = { units, handleInput, handleChange }; + + $: getUnit = _getUnit.bind(null, units); $: unit = getUnit(id); + $: coarseUnit = !is.unit.const(unit) ? 0 : Math.floor(unit.value / 100_000) * 100_000; + $: fineUnit = !is.unit.const(unit) ? 0 : unit.value % 100_000; const _handleInput = (...args) => { handleInput(...args); unit = unit; }; - - $: getUnit = _getUnit.bind(null, units); - $: coarseUnit = Math.floor(unit.value / 100_000) * 100_000; - $: fineUnit = unit.value % 100_000; </script> <div class="unit-container"> @@ -28,40 +28,40 @@ <input name={id} type="range" - min="0" - max={INPUT_RANGE} + min={(-1 * INPUT_RANGE) / 2} + max={INPUT_RANGE / 2} step={100_000} value={coarseUnit} - on:input={(e) => _handleInput(id, Number(e.target.value), null)} + on:input={(e) => _handleInput(id, Number(e.target?.value), null)} on:change={handleChange} /> <input type="range" min={0} - max={100_000} + max={100_000 - 1} step={1} value={fineUnit} - on:input={(e) => _handleInput(id, null, Number(e.target.value))} + on:input={(e) => _handleInput(id, null, Number(e.target?.value))} on:change={handleChange} /> {:else if unit.kind === 'combinator'} <h3>{unit.kind}</h3> <div class="combinator-container"> - {#each unit.sources as sourceId} - <svelte:self id={sourceId} {...props} /> + {#each unit.sources as input} + <svelte:self id={input.id} {...props} /> {/each} </div> {:else if unit.kind === 'rescale'} - <svelte:self id={unit.input} {...props} /> + <svelte:self id={unit.input.id} {...props} /> {:else if unit.kind === 'noise'} <h3>{unit.kind}</h3> - <svelte:self id={unit.amount} {...props} /> + <svelte:self id={unit.amount.id} {...props} /> {:else if unit.kind === 'osc'} <h3>{unit.kind}</h3> <h4>rate</h4> - <svelte:self id={unit.rate} {...props} /> + <svelte:self id={unit.rate.id} {...props} /> <h4>amount</h4> - <svelte:self id={unit.amount} {...props} /> + <svelte:self id={unit.amount.id} {...props} /> {:else} <h3>{unit.kind} nyi</h3> {/if} diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts @@ -1,5 +1,6 @@ import type { EngineMessage, + Input, SynthConfig, Unit, UnitId, @@ -60,13 +61,14 @@ function setUnitState(id: UnitId, state: UnitState) { unitState.set(id, state); } -function v(id: UnitId): number { +function v(input: Input): number { + const { id } = input; const unit: Unit = getUnit(config.units, id); switch (unit.kind) { case "osc": { const position = getUnitState(id); - return (Math.sin(2 * Math.PI * (position / LOOP_CYCLES)) - 0.5) * - v(unit.amount); + return Math.sin(2 * Math.PI * (position / LOOP_CYCLES)) * + v(unit.amount) - 0.5; } case "const": { return unit.value; @@ -109,13 +111,13 @@ function update() { function drawSquares() { if (config && canvas) { const ctx = canvas.getContext("2d"); - const cols = 300; - const rows = 100; - let color = [0, 0, 0]; - let width = canvas.width; - let height = canvas.height; - let paneWidth = Math.ceil(width / cols); - let paneHeight = Math.ceil(height / rows); + const cols = 100; + const rows = 50; + const color = [0, 0, 0]; + const width = canvas.width; + const height = canvas.height; + const paneWidth = Math.ceil(width / cols); + const paneHeight = Math.ceil(height / rows); for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { @@ -132,6 +134,7 @@ function drawSquares() { } requestAnimationFrame(drawSquares); } + drawSquares(); export {}; diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -1,3 +1,6 @@ +export const LOOP_CYCLES = 100_000_000; +export const INPUT_RANGE = 100_000_000; + export type SynthConfig = { sinks: Sinks; units: UnitMap; @@ -25,23 +28,26 @@ export type Unit = export type UnitId = string; +// To support units with multiple outputs. +export type Input = { id: UnitId }; + export type RescaleUnit = { kind: "rescale"; - input: UnitId; - min: UnitId; - max: UnitId; + input: Input; + min: Input; + max: Input; }; // uses rate and amount to output a sine wave going that fast and loud. TBD: what units the values are, etc. export type OscUnit = { kind: "osc"; - rate: UnitId; - amount: UnitId; + rate: Input; + amount: Input; }; export type NoiseUnit = { kind: "noise"; - amount: UnitId; + amount: Input; }; // outputs number as it is @@ -52,16 +58,16 @@ export type ConstUnit = { export type CombinatorUnit = { kind: "combinator"; - sources: UnitId[]; + sources: Input[]; }; export type UnitMap = Map<UnitId, Unit>; export type UnitStateMap = Map<UnitId, UnitState>; export type UnitState = any; export type Sinks = { - red?: UnitId; - green?: UnitId; - blue?: UnitId; + red?: Input; + green?: Input; + blue?: Input; }; export function getUnit(units: UnitMap, id: UnitId): Unit { @@ -70,5 +76,13 @@ export function getUnit(units: UnitMap, id: UnitId): Unit { return result; } -export const LOOP_CYCLES = 100_000_000; -export const INPUT_RANGE = 100_000_000; +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"; + }, + }, +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -2,21 +2,8 @@ import { onMount } from 'svelte'; import { debounce } from 'lodash'; import Slider from '$lib/Slider.svelte'; - import { getUnit as _getUnit, LOOP_CYCLES, INPUT_RANGE } from '$lib/types'; - import type { - Unit, - UnitId, - RescaleUnit, - OscUnit, - NoiseUnit, - ConstUnit, - CombinatorUnit, - PageMessage, - UnitMap, - UnitStateMap, - UnitState, - Sinks - } from '$lib/types'; + import { is, getUnit as _getUnit, INPUT_RANGE } from '$lib/types'; + import type { Input, Unit, UnitId, ConstUnit, UnitMap, Sinks } from '$lib/types'; let cvs: HTMLCanvasElement | undefined; let offscreenCanvas: OffscreenCanvas | undefined; @@ -60,7 +47,7 @@ }); $: getUnit = _getUnit.bind(null, units); - let id = 0; + let uid = 0; let units: UnitMap = new Map(); let sinks: Sinks = {}; @@ -101,22 +88,22 @@ console.log(fromUrl()); } - function addUnit(unit: Unit) { - const _id = String(id++); - units.set(_id, unit); + function addUnit(unit: Unit): Input { + const id = String(uid++); + units.set(id, unit); units = units; - return _id; + return { id }; } - type EzUnit = UnitId | number | undefined; + type EzUnit = Input | number | undefined; // make a const unit or use the supplied one - function ez(input: EzUnit): UnitId { - return typeof input === 'string' ? input : mk.c(input || 0); + function ez(input: EzUnit): Input { + return is.input(input) ? input : mk.c(input || 0); } - function gatherControls(accum: Set<UnitId>, ...ids: UnitId[]) { - for (let id of ids) { - const unit = getUnit(id); + function gatherControls(accum: Set<Input>, ...ins: Input[]) { + for (let input of ins) { + const unit = getUnit(input.id); switch (unit.kind) { case 'osc': { gatherControls(accum, unit.rate, unit.amount); @@ -131,7 +118,7 @@ break; } case 'const': { - accum.add(id); + accum.add(input); break; } case 'combinator': { @@ -145,13 +132,13 @@ $: unitsToControl = gatherControls(new Set(), ...Object.values(sinks)); const mk = { - c: function mkConst(value: number): UnitId { + c: function mkConst(value?: number): Input { return addUnit({ kind: 'const', - value + value: value || 0 }); }, - osc: function mkOsc(rate?: EzUnit, amount?: EzUnit): UnitId { + osc: function mkOsc(rate?: EzUnit, amount?: EzUnit): Input { return addUnit({ kind: 'osc', rate: mk.re(0, INPUT_RANGE / 10, ez(rate)), @@ -188,16 +175,11 @@ } }; - const CELLS = 5000; - const red = mk.re_color( mk.add( mk.c(INPUT_RANGE / 2), // - mk.n(INPUT_RANGE / 2), - mk.osc( - mk.add(mk.c(0), mk.osc(CELLS * 4 * 10), mk.osc(CELLS * 2 * 10, INPUT_RANGE / 2)), - INPUT_RANGE / 2 - ) // + mk.n(0), + mk.osc(mk.add(mk.c(0), mk.osc(), mk.osc(mk.c(), mk.osc())), INPUT_RANGE / 2) // ) ); const green = mk.re_color(); @@ -215,7 +197,7 @@ if (vCoarse !== null) { unit.value = (oldVal % 100_000) + vCoarse; } else if (vFine !== null) { - unit.value = Math.floor(oldVal / 100_000) * 100_000 + vFine; + unit.value = Math.floor(oldVal / 100_000) * 100_000 + Math.abs(vFine); } console.log({ value: unit.value }); units = units; @@ -225,10 +207,10 @@ <canvas bind:this={cvs} /> <div id="sliders"> - {#each [...Object.entries(sinks)] as [label, unitId]} + {#each [...Object.entries(sinks)] as [label, input]} <div> <h2>{label}</h2> - <Slider id={unitId} {units} handleInput={updateUnit} handleChange={updateUrl} /> + <Slider id={input.id} {units} handleInput={updateUnit} handleChange={updateUrl} /> </div> {/each} </div>