color-synth

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

commit 5c9284a6084c232c1c47be8539b15e81f2c2afc2
parent 9a1882ee9f108571b74e38371612467f7492a3f7
Author: massi <mdsiboldi@gmail.com>
Date:   Mon, 15 Jul 2024 11:43:07 -0700

initial image support, modify build config

Diffstat:
Msrc/lib/ImgUnit.svelte | 1+
Msrc/lib/engine.worker.ts | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/lib/stores.ts | 9+++++++++
Msrc/lib/types.ts | 37+++++++++++++++++++++++++------------
Msrc/routes/+page.svelte | 3+++
Msvelte.config.js | 29++++++++++++++++-------------
6 files changed, 130 insertions(+), 30 deletions(-)

diff --git a/src/lib/ImgUnit.svelte b/src/lib/ImgUnit.svelte @@ -12,6 +12,7 @@ <div> <h1>image of a turtle</h1> + <UnitControl {...common} controlName="speed" /> </div> <style> diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts @@ -18,7 +18,8 @@ import { wrangle, unitRange, oscShapes, - mathOps + mathOps, + CELLS } from '$lib/types'; import _ from 'lodash'; @@ -62,6 +63,44 @@ function getUnitState(id: UnitId): UnitState { case 'lag': { return theState === undefined ? [] : theState; } + case 'img': { + if (theState === undefined) { + // NOW: make img sample here. should also store position + console.log('fetching...'); + const fetcher = async () => { + try { + const img = await fetch( + new Request( + 'https://t4.ftcdn.net/jpg/01/36/70/67/360_F_136706734_KWhNBhLvY5XTlZVocpxFQK1FfKNOYbMj.jpg', + { + mode: 'cors' + } + ) + ); + const bmp = await createImageBitmap(await img.blob(), { + resizeHeight: ROWS, + resizeWidth: COLS, + resizeQuality: 'pixelated' + }); + const canvas = new OffscreenCanvas(bmp.width, bmp.height); + const ctx = canvas.getContext('2d'); + ctx?.drawImage(bmp, 0, 0, COLS, ROWS); + const imgData = ctx?.getImageData(0, 0, COLS, ROWS); + const newState = { + position: 0, + data: imgData!.data + }; + console.log(newState); + setUnitState(id, newState); + } catch (e) { + console.error(e); + } + }; + fetcher(); + return 'loading'; + } + return theState; + } default: { throw new Error('state for this invalid or NYI'); } @@ -79,11 +118,18 @@ function vUnit(x: { id: UnitId }): number { const unit: Unit = getUnit(null, config!.units, id); switch (unit.kind) { case 'osc': { - const position = getUnitState(id); + const position = getUnitState(id) / LOOP_CYCLES; const shapeIdx = Math.round( wrangle(v(unit.controls.waveshape), RANGE.signal, unitRange.osc.waveshape) ); - return oscShapes[shapeIdx](position / LOOP_CYCLES, v(unit.controls.amount)); + // to calculate amplitude at position: + // take the position as % progress through the wave. + // find the osc shape's "value" at that point, range being pmone. + // multiply that by the amount control, scaled to pmone, + // finally, scale this value to a signal. + const scalar = wrangle(v(unit.controls.amount), RANGE.signal, RANGE.pmone); + const waveAmp = oscShapes[shapeIdx](position); + return wrangle(waveAmp * scalar, RANGE.pmone, RANGE.signal); } case 'const': { return v(unit.controls.value); @@ -109,6 +155,22 @@ function vUnit(x: { id: UnitId }): number { case 'lag': { return getUnitState(id)[0]; } + case 'img': { + // NOW: make offscreen canvas of the same resolution as the screen + // take color measurements of each pixel + // store position and increase by one mod cells each frame + const state = getUnitState(id); + logMap.img = state; + if (state instanceof Object) { + logMap.test = state.data[0]; + return rescale( + state.data[Math.abs(Math.round(state.position)) * 4], + { min: 0, max: 255 }, + RANGE.signal + ); + } + return 0; + } } } @@ -168,7 +230,7 @@ function update() { } else { setUnitState( id, - (position + (coarse * LOOP_CYCLES) / 100 + fine * 20000 + superfine) % LOOP_CYCLES + (position + (coarse * LOOP_CYCLES) / 100 + fine * 10000 + superfine) % LOOP_CYCLES ); } break; @@ -192,7 +254,7 @@ function update() { let arr = getUnitState(id) as number[]; arr.push(v(unit.controls.signal)); if (arr.length > amt) { - if (arr.length - 1 === amt) { + if (arr.length - 2 === amt) { arr.pop(); } else { arr = arr.slice(arr.length - amt); @@ -203,6 +265,15 @@ function update() { } break; } + case 'img': { + const state = getUnitState(id); + if (state instanceof Object) { + const speed = wrangle(v(unit.controls.speed), RANGE.signal, unitRange.img.speed); + state.position = (state.position + speed / 100000) % CELLS; + } else { + setUnitState(id, state); + } + } } } } diff --git a/src/lib/stores.ts b/src/lib/stores.ts @@ -115,6 +115,15 @@ const mkUnitStore = () => { }; break; } + case 'img': { + unit = { + kind: 'img', + controls: { + speed: mkInput(rescale(100000, unitRange.img.speed, RANGE.signal)) + }, + pos: { x: 0, y: 0 } + }; + } } const uuid = uuidv4(); diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -40,7 +40,7 @@ export type Pos = { y: number; }; -export const unitKinds = ['const', 'osc', 'noise', 'smooth', 'math', 'lag'] as const; +export const unitKinds = ['const', 'osc', 'noise', 'smooth', 'math', 'lag', 'img'] as const; export type UnitKind = (typeof unitKinds)[number]; type Corr<X, K extends keyof X> = { [P in K]: X[P] }[K]; export type Unit<K extends UnitKind = UnitKind> = Corr<UnitMap, K>; @@ -52,6 +52,7 @@ type UnitMap = { smooth: SmoothUnit; math: MathUnit; lag: LagUnit; + img: ImgUnit; }; // TODO: can we error when keys don't exhaust UnitKind? Record can't infer the tuples correctly. @@ -61,7 +62,8 @@ const controlNames = { const: ['value'] as const, smooth: ['signal', 'frames'] as const, math: ['a', 'op', 'b'] as const, - lag: ['signal', 'amount'] as const + lag: ['signal', 'amount'] as const, + img: ['speed'] as const }; type ControlNames = typeof controlNames; export type ControlName<K extends UnitKind> = ControlNames[K][number]; @@ -80,6 +82,7 @@ export type NoiseUnit = GenericUnit<'noise'>; export type SmoothUnit = GenericUnit<'smooth'>; export type MathUnit = GenericUnit<'math'>; export type LagUnit = GenericUnit<'lag'>; +export type ImgUnit = GenericUnit<'img'>; export type UnitId = string; @@ -120,16 +123,18 @@ export const RANGE = { } } as const; -type OscShapes = Array<(p: number, a: number) => number>; +type OscShapes = Array<(p: number) => number>; +// position from 0.0 to 1.0, from start to end of shape. +// returns value within the input range. export const oscShapes: OscShapes = [ - (position: number, amount: number) => { - return Math.sin(2 * Math.PI * position) * amount; + (p) => { + return Math.sin(2 * Math.PI * p); }, - (p, a) => { - return (p < 0.5 ? -1 : 1) * a; + (p) => { + return p < 0.5 ? -1 : 1; }, - (p, a) => p * a + (p) => p ]; type MathOp = (a: number, b: number) => number; @@ -157,8 +162,8 @@ export const unitRange: { max: 10_000 }, amount: { - min: -50, - max: 50 + min: -100_000, + max: 100_000 }, waveshape: { min: 0, @@ -195,6 +200,12 @@ export const unitRange: { min: 0, max: 10_000 } + }, + img: { + speed: { + min: -10_000_000, + max: 10_000_000 + } } } as const; @@ -235,7 +246,8 @@ export const is = { noise: isUnit('noise'), smooth: isUnit('smooth'), math: isUnit('math'), - lag: isUnit('lag') + lag: isUnit('lag'), + img: isUnit('img') } }; @@ -255,7 +267,8 @@ export const ensure = { noise: ensureUnit('noise'), smooth: ensureUnit('smooth'), math: ensureUnit('math'), - lag: ensureUnit('lag') + lag: ensureUnit('lag'), + img: ensureUnit('img') } }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -21,6 +21,7 @@ import SmoothUnitComponent from '$lib/SmoothUnit.svelte'; import MathUnitComponent from '$lib/MathUnit.svelte'; import LagUnitComponent from '$lib/LagUnit.svelte'; + import ImgUnitComponent from '$lib/ImgUnit.svelte'; import UnitDrag from '$lib/UnitDrag.svelte'; import Wires from '$lib/Wires.svelte'; @@ -229,6 +230,8 @@ <MathUnitComponent {id} /> {:else if unit.kind === 'lag'} <LagUnitComponent {id} /> + {:else if unit.kind === 'img'} + <ImgUnitComponent {id} /> {/if} </div> </div> diff --git a/svelte.config.js b/svelte.config.js @@ -1,20 +1,23 @@ -import adapter from "@sveltejs/adapter-static"; -import { vitePreprocess } from "@sveltejs/kit/vite"; +import adapter from '@sveltejs/adapter-static'; +import { vitePreprocess } from '@sveltejs/kit/vite'; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: vitePreprocess(), - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter({ - fallback: "index.html", // may differ from host to host - }), - }, + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter({ + fallback: 'index.html' // may differ from host to host + }), + paths: { + base: '/color-synth' + } + } }; export default config;