color-synth

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

commit b1a7aa45fd457a8f8efcf6e94cb112a7b4f939b8
parent bc65021938b86b284cf07a62f69cdeccc401f005
Author: massi <mdsiboldi@gmail.com>
Date:   Tue, 18 Jul 2023 23:28:02 -0700

Starting drag n drop units

Diffstat:
Asrc/lib/Sink.svelte | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/engine.worker.ts | 9++++++---
Msrc/lib/types.ts | 18+++++++++++++-----
Msrc/routes/+page.svelte | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
4 files changed, 174 insertions(+), 31 deletions(-)

diff --git a/src/lib/Sink.svelte b/src/lib/Sink.svelte @@ -0,0 +1,44 @@ +<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); + + let _onSinkConnect: null | (() => void) = null; + $: _onSinkConnect = onSinkConnect ? () => onSinkConnect && onSinkConnect(channel) : null; +</script> + +<div class="sink"> + {#if onSinkConnect} + <div class="connect" on:mouseup={_onSinkConnect} /> + {/if} + <button class={Boolean(_onSinkConnect) ? 'hl' : ''}>{channel}</button> +</div> + +<style> + .hl { + background: lightgreen; + } + .sink { + position: relative; + } + .connect { + position: absolute; + top: -5px; + left: -5px; + bottom: -5px; + right: -5px; + opacity: 0.5; + } + .connect:hover { + background: green; + } + button { + margin: 5px; + width: 40px; + height: 40px; + padding: 10px 15px; + font-size: 16px; + border-radius: 100%; + } +</style> diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts @@ -136,15 +136,18 @@ function drawSquares() { for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { - color[0] = wrangle(v(config.sinks.red), range.signal, { + 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 }); + color[0] = wrangle(l, range.signal, { min: 0, max: 100, }); - color[1] = wrangle(v(config.sinks.green), range.signal, { + color[1] = wrangle(c, range.signal, { min: 0, max: 0.5, }); - color[2] = wrangle(v(config.sinks.blue), range.signal, { + color[2] = wrangle(h, range.signal, { min: 0, max: 360, }); diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -35,9 +35,10 @@ export type Unit = export type UnitId = string; -// To support units with multiple outputs. export type Input = { id: UnitId }; +export type Pos = { x: number; y: number }; + export type RescaleUnit = { kind: "rescale"; input: Input; @@ -45,7 +46,6 @@ export type RescaleUnit = { 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"; coarse: Input; @@ -63,6 +63,7 @@ export type NoiseUnit = { export type ConstUnit = { kind: "const"; value: number; + pos: Pos; }; export type CombinatorUnit = { @@ -74,9 +75,9 @@ export type UnitMap = Map<UnitId, Unit>; export type UnitStateMap = Map<UnitId, UnitState>; export type UnitState = any; export type Sinks = { - red?: Input; - green?: Input; - blue?: Input; + l: Input | null; + c: Input | null; + h: Input | null; }; type Range = { @@ -209,6 +210,13 @@ export const is = { 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; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -1,8 +1,9 @@ <script lang="ts"> import { onMount } from 'svelte'; import { debounce } from 'lodash'; + import Sink from '$lib/Sink.svelte'; import Slider from '$lib/Slider.svelte'; - import { rescale, range, is, getUnit as _getUnit, INPUT_RANGE } from '$lib/types'; + import { ensure, rescale, range, is, getUnit as _getUnit, INPUT_RANGE } from '$lib/types'; import type { Input, Unit, UnitId, ConstUnit, UnitMap, Sinks } from '$lib/types'; let cvs: HTMLCanvasElement | undefined; @@ -153,42 +154,129 @@ } }; - const shared = new Array(10).fill(0).map(() => mk.c()); - - const red = mk.add( - mk.c(), // - mk.osc(mk.add(mk.c(), mk.osc())) - ); - const green = mk.add( - mk.c(), // - mk.osc(mk.add(mk.c(), mk.osc())) - ); - const blue = mk.add( - mk.c(), // - mk.osc(mk.add(mk.c(), mk.osc())) - ); - - sinks = { red, green, blue }; + sinks = { l: null, c: null, h: null }; function updateUnit(k: UnitId, value: number) { const unit = get.constUnit(k); unit.value = value; units = units; } + + const addConst = () => { + addUnit({ kind: 'const', value: randConst(), pos: { x: 5, y: 5 } }); + units = units; + }; + + let dragging: false | UnitId = false; + + const handleMouseMove = (e) => { + if (!dragging) return; + ensure.unit.const(getUnit(dragging)).pos = { + x: e.clientX, + y: e.clientY + }; + units = units; + }; + + const handleMouseUp = () => { + dragging = false; + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + const handleMouseDown = (id: UnitId) => { + console.log('mouse down', id); + dragging = id; + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + let signalDragging: false | { id: UnitId } = false; + + const handleSignalStart = (obj: { id: UnitId }) => { + console.log('signal start', obj); + const unit = getUnit(obj.id); + switch (unit.kind) { + case 'const': { + signalDragging = obj; + break; + } + default: + throw new Error('handleSignalStart nyi'); + } + }; + + const _onSinkConnect = (ch: 'l' | 'c' | 'h'): void => { + if (!signalDragging) { + throw new Error('cant connect sink to nonexistant signal'); + } + sinks[ch] = signalDragging.id; + engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } }); + console.log(sinks); + signalDragging = false; + }; + + $: onSinkConnect = signalDragging ? _onSinkConnect : null; + + $: unitEntries = units.entries(); + $: sinkEntries = Object.entries(sinks); </script> <canvas bind:this={cvs} /> -<div id="sliders"> - {#each [...Object.entries(sinks)] as [label, input]} - <div> - <h2>{label}</h2> - <Slider id={input.id} {units} handleInput={updateUnit} handleChange={updateUrl} /> +<div id="buttons"> + <button on:click={(e) => addConst()}>add const</button> +</div> + +<h1>HI {unitEntries}</h1> +<div id="sinks"> + {#each [...sinkEntries] as [channel, id]} + <Sink {id} {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> + <Slider {id} {units} handleInput={updateUnit} handleChange={updateUrl} /> + <div + class="output-dragger" + on:mousedown={(e) => { + e.preventDefault(); + handleSignalStart({ id }); + }} + /> </div> {/each} </div> <style> + .output-dragger { + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 20px; + background: purple; + } + #sinks { + position: absolute; + top: 0; + right: 0; + display: flex; + flex-direction: column; + } + .unit { + position: absolute; + z-index: 1; + background: black; + } + #buttons { + z-index: 1; + position: fixed; + bottom: 5px; + right: 5px; + } h2 { margin: 0 5px; background: white; @@ -198,7 +286,7 @@ height: 100vh; background: salmon; } - #sliders { + #units { position: absolute; left: 10px; top: 10px;