color-synth

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

commit f4a5f72284046c308acb7aad7c5109d8a05e8677
parent e8cdcb3c25d66e3a0744509c6d36d14e275a6012
Author: massi <mdsiboldi@gmail.com>
Date:   Wed, 21 Jun 2023 17:49:35 -0700

first stab at typing

Diffstat:
Msrc/routes/+page.svelte | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 167 insertions(+), 35 deletions(-)

diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -20,62 +20,187 @@ let t = 0; + const DELTA = 0.00001; function step() { - t = (t + 1) % 1000000000000; // lol + t = (t + DELTA) % 100; // lol } - type Binding = () => number; + enum BindingKind { + OscBinding + } + + type UnitMap = Map<UnitId, Unit>; + type UnitStateMap = Map<UnitId, UnitState>; + type UnitState = any; + + type UnitId = string; + + // uses rate and amount to output a sine wave going that fast and loud. TBD: what units the values are, etc. + type OscUnit = { + kind: 'osc'; + rate: Binding[]; + amount: Binding[]; + }; + + // outputs number as it is + type ConstUnit = { + kind: 'const'; + value: number; + }; + + type Unit = OscUnit | ConstUnit; + + type Binding = { + scale: number; + source: UnitId; + }; - function mkOsc(inputs: { rate: Binding; amount: Binding }): Binding { - let { rate, amount } = inputs; - return () => (Math.sin(2 * Math.PI * t * rate()) - 0.5) * amount(); + type Grid = { + rows: Binding[]; + cols: Binding[]; + }; + + type OldBinding = () => number; + + let id = 1; + let units: UnitMap = new Map(); + let unitState: UnitStateMap = new Map(); + function addUnit(unit: Unit) { + const _id = String(id++); + units.set(_id, unit); + switch (unit.kind) { + case 'osc': { + unitState.set(_id, 0); + } + } + return _id; } - function combinatorBinding(...bindings: Binding[]): Binding { - return () => bindings.reduce((a, b) => a + b(), 0); + function getUnit(id: UnitId): Unit { + let goal = units.get(id); + if (!goal) throw new Error('invalid id for unit: ' + id); + return goal; + } + + function getUnitState(id: UnitId): UnitState { + let unit = getUnit(id); + let goal = unitState.get(id); + switch (unit.kind) { + case 'osc': { + if (typeof goal !== 'number') { + throw new Error('invalid state for osc unit: ' + id); + } + break; + } + default: { + throw new Error('state for this invalid or NYI'); + } + } + return goal; + } + + function setUnitState(id: UnitId, state: UnitState) { + // TODO: type safety + unitState.set(id, state); + } + + function v(id: UnitId): number { + const unit: Unit = getUnit(id); + switch (unit.kind) { + case 'osc': { + const position = getUnitState(id); + return (Math.sin(2 * Math.PI * position) - 0.5) * r(unit.amount); + } + case 'const': { + return unit.value; + } + } + } + + function r(bindings: Binding[]): number { + return bindings.reduce((a, b) => a + b.scale * v(b.source), 0); } - function constBinding(value: number): Binding { - return () => value; + function update(...ids: UnitId[]) { + // update all unit states + ids = ids || units.keys(); + for (let id of ids) { + const unit = getUnit(id); + switch (unit.kind) { + case 'osc': + const position = getUnitState(id); + const rate = r(unit.rate); + setUnitState(id, position + rate); + } + } + } + + function mkConst(value: number): UnitId { + return addUnit({ + kind: 'const', + value + }); + } + + function mkOsc(rate: Binding[], amount: Binding[]): UnitId { + return addUnit({ + kind: 'osc', + rate, + amount + }); + } + + function c(value: number): Binding { + return { scale: 1, source: mkConst(value) }; + } + + function mkBinding(source: UnitId): Binding { + return { + scale: 1, + source + }; + } + + function o(rate: Binding[], amount: Binding[]): Binding { + return mkBinding(mkOsc(rate, amount)); + } + + //const red = mkOsc([c(0.1), o([c(0.1)], [c(0.01)])], [c(100)]); + const red = mkOsc([c(189)], [c(50)]); + const green = mkConst(20); + const blue = mkOsc([c(0.01)], [c(100)]); + + console.log(units.get(red)); + + //function mkOsc(inputs: { rate: OldBinding; amount: OldBinding }): OldBinding { + // let { rate, amount } = inputs; + // return () => (Math.sin(2 * Math.PI * t * rate()) - 0.5 + Math.random() / 20) * amount(); + //} + + function combinatorBinding(...bindings: OldBinding[]): OldBinding { + return () => bindings.reduce((a, b) => a + b(), 0); } + let nextT = null; + function drawSquares(ctx: CanvasRenderingContext2D) { if (!cvs) { return; } - const cols = 100; - const rows = 200; - let color = [128, 128, 128]; + const cols = 50; + const rows = 50; + let color = [50, 0, 100]; let width = cvs.width; let height = cvs.height; let paneWidth = Math.ceil(width / cols); let paneHeight = Math.ceil(height / rows); - let osc1 = mkOsc({ - rate: combinatorBinding( - constBinding(0.761243), - mkOsc({ rate: () => 0.0001, amount: () => 0.01 }) - ), - amount: constBinding(30) - }); - let osc2 = mkOsc({ - rate: combinatorBinding( - mkOsc({ rate: () => 0.00001, amount: () => 1 }), - constBinding(0.881242) - ), - amount: constBinding(55) - }); - let osc3 = mkOsc({ rate: constBinding(0.000001), amount: constBinding(255) }); - - const red = combinatorBinding(constBinding(128), osc1); - const green = combinatorBinding(constBinding(128), osc2); - const blue = combinatorBinding(constBinding(128), osc3); for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { - step(); - color[0] = red(); - color[1] = green(); - color[2] = blue(); + color[0] = v(red); + color[1] = v(green); + color[2] = v(blue); + update(red, green, blue); ctx.fillStyle = `rgb(${color.join(', ')})`; let x = col * paneWidth; let y = row * paneHeight; @@ -97,10 +222,17 @@ <canvas bind:this={cvs} /> +<button on:click={run}>step </button> + <style> canvas { width: 100vw; height: 100vh; background: salmon; } + button { + position: absolute; + left: 10px; + top: 10px; + } </style>