color-synth

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

commit 305848b3f608989ba3289a49e5911aa375e2319f
parent 0b6136b99e5974b174908de9a3bd00a8948bca23
Author: massi <mdsiboldi@gmail.com>
Date:   Sat, 22 Jul 2023 00:13:04 -0700

drag n drop

Diffstat:
Asrc/lib/InputDragger.svelte | 36++++++++++++++++++++++++++++++++++++
Msrc/lib/NumberSelector.svelte | 8++------
Msrc/lib/Osc.svelte | 36+++++++++++++++++++++++++-----------
Msrc/lib/Sink.svelte | 24+++++++++++++++++++-----
Msrc/lib/engine.worker.ts | 8--------
Msrc/lib/types.ts | 33+++++++++++++++++++++++++--------
Msrc/routes/+page.svelte | 51+++++++++++++++++++++++++++++++++++++++++----------
7 files changed, 148 insertions(+), 48 deletions(-)

diff --git a/src/lib/InputDragger.svelte b/src/lib/InputDragger.svelte @@ -0,0 +1,36 @@ +<script lang="ts"> + export let onConnect: (() => void) | null | undefined; + export let connected: boolean; +</script> + +<div class="input-dragger"> + {#if onConnect} + <div + class={['connect', connected && 'connected'].filter(Boolean).join(' ')} + on:mouseup={onConnect} + /> + {/if} +</div> + +<style> + .connect { + position: absolute; + background: green; + opacity: 0.5; + top: -5px; + right: -5px; + bottom: -5px; + left: -5px; + } + .input-dragger { + background: lightgreen; + width: 5px; + position: absolute; + top: 2px; + bottom: 2px; + left: -10px; + } + .connect.connected { + background: red; + } +</style> diff --git a/src/lib/NumberSelector.svelte b/src/lib/NumberSelector.svelte @@ -1,11 +1,8 @@ <script lang="ts"> - import { range, clamp } from '$lib/types'; + import { clamp } from '$lib/types'; export let value: number; export let updateValue: (n: number) => void; - const MIN = range.slider.min; - const MAX = range.slider.max; - let pos: | { start: { x: number; y: number }; @@ -69,9 +66,8 @@ let ticker: Ticker | undefined; - $: distance = pos ? Math.sqrt(pos.delta.x * pos.delta.x + pos.delta.y * pos.delta.y) : 0; - const handleMouseDown = async (evt: MouseEvent) => { + //@ts-ignore TODO figure out if this is true cuz mdn says to do this. await el?.requestPointerLock(); pos = { start: { diff --git a/src/lib/Osc.svelte b/src/lib/Osc.svelte @@ -1,10 +1,13 @@ <script lang="ts"> - import type { OscUnit, WithTarget } from '$lib/types'; - import { wrangle, rescale, range } from '$lib/types'; + import type { OscUnit, OscUnitInputs, Output } from '$lib/types'; + import { unitInputs, wrangle, rescale, range } from '$lib/types'; import NumberSelector from '$lib/NumberSelector.svelte'; + import InputDragger from '$lib/InputDragger.svelte'; export let unit: OscUnit; + export let signalDragging: false | Output; export let updateOsc: (osc: OscUnit) => void; + export let onConnect: ((k: string) => void) | null; $: vals = { coarse: @@ -18,22 +21,34 @@ typeof unit.amount === 'number' ? wrangle(unit.amount, range.signal, range.osc.amount) : null }; - console.log({ vals }); - - const updateValue = (k: string, n: number) => { + const updateValue = (k: keyof OscUnitInputs, n: number) => { updateOsc({ ...unit, [k]: Math.round(rescale(n, range.osc[k], range.signal)) }); }; + + const isConnected = (k: keyof OscUnitInputs, signalDragging: false | Output) => { + const inputs = unit[k]; + if (signalDragging && Array.isArray(inputs)) { + const { id: sigId } = signalDragging; + return Boolean(inputs.find(({ id }) => id === sigId)); + } + return false; + }; </script> <div class="unit-container"> <h3>{unit.kind}</h3> - {#each [...Object.entries(vals)] as [k, v]} + {#each unitInputs.osc as k} + {@const v = vals[k]} + {@const inputs = unit[k]} <div class="sect"> + <InputDragger + connected={isConnected(k, signalDragging)} + onConnect={onConnect ? onConnect.bind(null, k) : null} + /> {#if v !== null} <h3>{k}</h3> <NumberSelector value={v} updateValue={updateValue.bind(undefined, k)} /> <input - name={unit.id} type="range" min={range.osc[k].min} max={range.osc[k].max} @@ -41,6 +56,8 @@ value={v} on:input={(e) => updateValue(k, Number(e.currentTarget?.value))} /> + {:else} + <div>{Array.isArray(inputs) ? inputs.map((o) => o.id).join(' ') : ''}</div> {/if} </div> {/each} @@ -48,13 +65,10 @@ <style> .sect { + position: relative; width: 100%; height: 80px; } - .disabled { - opacity: 0.5; - pointer-events: none; - } .unit-container { background: rgba(255, 255, 255, 0.3); padding: 10px; diff --git a/src/lib/Sink.svelte b/src/lib/Sink.svelte @@ -1,22 +1,33 @@ <script lang="ts"> + import type { Input } from '$lib/types'; export let channel: 'l' | 'c' | 'h'; export let onSinkConnect: null | ((ch: 'l' | 'c' | 'h') => void); + export let connected: boolean; + export let input: Input | null; let _onSinkConnect: null | (() => void) = null; - $: _onSinkConnect = onSinkConnect ? () => onSinkConnect && onSinkConnect(channel) : null; + $: _onSinkConnect = onSinkConnect ? onSinkConnect.bind(null, channel) : null; + $: classes = ['connect', _onSinkConnect && 'hl', connected && 'connected'] + .filter(Boolean) + .join(' '); </script> <div class="sink"> {#if onSinkConnect} - <div class="connect" on:mouseup={_onSinkConnect} /> + <div class={classes} on:mouseup={_onSinkConnect} /> {/if} - <button class={Boolean(_onSinkConnect) ? 'hl' : ''}>{channel}</button> + <button class={Boolean(_onSinkConnect) ? 'hl' : ''}> + {channel}: {Array.isArray(input) ? input.map((o) => o.id).join(' ') : 'none'} + </button> </div> <style> .hl { background: lightgreen; } + .hl.connected { + background: red; + } .sink { position: relative; } @@ -31,12 +42,15 @@ .connect:hover { background: green; } + .connect.connected:hover { + background: red; + } button { margin: 5px; - width: 40px; + width: 150px; height: 40px; padding: 10px 15px; font-size: 16px; - border-radius: 100%; + border-radius: 50%; } </style> diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts @@ -70,14 +70,6 @@ function vUnit(x: { id: UnitId }): number { const position = getUnitState(id); return Math.sin(2 * Math.PI * (position / LOOP_CYCLES)) * v(unit.amount); - //if (id % 7 === 0) { - // return (position < LOOP_CYCLES / 2 ? -1 : 1) * v(unit.amount); - //} else if (id % 5 === 0) { - // return (position / LOOP_CYCLES) * v(unit.amount); - //} else { - // return Math.sin(2 * Math.PI * (position / LOOP_CYCLES)) * - // v(unit.amount); - //} } case "const": { // range is whatever the control for it is??? diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -24,13 +24,10 @@ export type EngineMessage = { content: OffscreenCanvas; }; -export type HigherUnit = - | OscUnit - | NoiseUnit; - export type Unit = | ConstUnit - | HigherUnit; + | OscUnit + | NoiseUnit; export type UnitId = string; @@ -41,12 +38,25 @@ export type Pos = { x: number; y: number }; type WithPos = { pos: Pos }; -export type OscUnit = WithPos & { - kind: "osc"; +export type OscUnitInputs = { coarse: Input; fine: Input; superfine: Input; amount: Input; +} +export type NoiseUnitInputs = { + amount: Input; +} +type UnitInputs = { + osc: (keyof OscUnitInputs)[], + noise: (keyof NoiseUnitInputs)[] +} +export const unitInputs: UnitInputs = { + osc: ['coarse', 'fine', 'superfine', 'amount'], + noise: ['amount'] +} +export type OscUnit = WithPos & OscUnitInputs & { + kind: "osc"; }; export type NoiseUnit = WithPos & { @@ -228,8 +238,15 @@ export const inp = { } else { // it's there and we removed it - return deduped; + // if the last input signal was removed, we're back to a value. + return deduped.length === 0 ? 0 : deduped; } } + }, + connected: (input: Input, output: Output): boolean => { + if (Array.isArray(input)) { + return Boolean(input.find((o) => o.id === output.id)); + } + return false; } } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -4,7 +4,7 @@ import Sink from '$lib/Sink.svelte'; import Osc from '$lib/Osc.svelte'; import Slider from '$lib/Slider.svelte'; - import { inp, ensure, rescale, range, is, getUnit as _getUnit } from '$lib/types'; + import { inp, rescale, range, is, getUnit as _getUnit } from '$lib/types'; import type { Output, Input, Unit, UnitId, ConstUnit, UnitMap, Sinks } from '$lib/types'; let cvs: HTMLCanvasElement | undefined; @@ -68,6 +68,7 @@ const doc: SerializedState = fromUrl(); if (doc) { units = new Map(Object.entries(doc.units)); + uid = units.size; sinks = doc.sinks; } }); @@ -177,9 +178,15 @@ let signalDragging: false | Output = false; - const handleSignalStart = (obj: Output) => { - console.log('signal start', obj); - signalDragging = obj; + const handleSignalStart = (o: Output) => { + signalDragging = o; + const h = () => { + signalDragging = false; + document.removeEventListener('mouseup', h); + }; + setTimeout(() => { + document.addEventListener('mouseup', h); + }, 0); }; const _onSinkConnect = (ch: 'l' | 'c' | 'h'): void => { @@ -198,11 +205,26 @@ $: onSinkConnect = signalDragging ? _onSinkConnect : null; - $: unitEntries = units.entries(); 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; + + const getConnectHandler = (sdrag: false | Output, input: UnitId) => { + if (!sdrag) return null; + if (sdrag.id === input) return null; + return connectHandler.bind(null, sdrag.id, input); + }; + + const connectHandler = (output: UnitId, input: UnitId, k: string) => { + const unit = getUnit(input); + // @ts-ignore unit[k] is a channel but i don't wanna prove it. + units.set(input, { ...unit, [k]: inp.toggle(unit[k], { id: output }) }); + engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } }); + console.log(sinks); + signalDragging = false; + units = units; + }; </script> <canvas bind:this={cvs} /> @@ -212,20 +234,29 @@ <button on:click={mk.osc}>add osc</button> </div> -<h1>HI {unitEntries}</h1> <div id="sinks"> - {#each sinkEntries as [channel]} - <Sink {channel} {onSinkConnect} /> + {#each sinkEntries as [channel, input]} + <Sink + {input} + {channel} + {onSinkConnect} + connected={signalDragging && input ? inp.connected(input, signalDragging) : false} + /> {/each} </div> <div id="units"> - {#each [...unitEntries] as [id, unit]} + {#each [...units.entries()] as [id, unit]} <div class="unit" style={`top: ${unit.pos.y}px; left: ${unit.pos.x}px`}> <h2 on:mousedown={handleMouseDown.bind(null, id)}>{id} {unit.kind}</h2> {#if unit.kind === 'const'} <Slider {id} {units} handleInput={updateUnit} handleChange={updateUrl} /> {:else if unit.kind === 'osc'} - <Osc {unit} updateOsc={updateEntireUnit.bind(null, id)} /> + <Osc + {unit} + {signalDragging} + onConnect={getConnectHandler(signalDragging, id)} + updateOsc={updateEntireUnit.bind(null, id)} + /> {/if} <div class="output-dragger"