commit bfb66e6cafb9d9e568154f6d43515fc04e61e061
parent 83325dc37bf001abae5e73b61ad0eb3709071ee1
Author: massi <mdsiboldi@gmail.com>
Date: Sat, 5 Aug 2023 15:29:08 -0700
just a few things ya know
Diffstat:
11 files changed, 529 insertions(+), 597 deletions(-)
diff --git a/src/lib/ConstUnit.svelte b/src/lib/ConstUnit.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import Control from '$lib/Control.svelte';
+ import { unitStore } from '$lib/stores';
+ import { ensure, getUnit, type UnitId } from '$lib/types';
+
+ export let id: UnitId;
+
+ $: unit = ensure.unit.const(getUnit($unitStore, id));
+</script>
+
+<div>
+ <h1>just a const...</h1>
+ <Control {id} controlName="value" />
+</div>
+
+<style>
+ h1 {
+ color: white;
+ }
+</style>
diff --git a/src/lib/Control.svelte b/src/lib/Control.svelte
@@ -0,0 +1,84 @@
+<script lang="ts">
+ import NumberSelector from '$lib/NumberSelector.svelte';
+ import { unitStore, unitToConnect } from '$lib/stores';
+ import {
+ getUnit,
+ range,
+ type Range,
+ type OscUnit,
+ type UnitId,
+ rescale,
+ type Unit,
+ type Input,
+ wrangle,
+ inp
+ } from './types';
+ import InputDragger from './InputDragger.svelte';
+
+ export let id: UnitId;
+ export let controlName: string;
+
+ $: unit = getUnit($unitStore, id);
+ // @ts-ignore caller responsible for ensuring unit has the appropriate control name.
+ $: controlRange = range[unit.kind][controlName] as range;
+ // @ts-ignore caller responsible for ensuring unit has the appropriate control name.
+ $: input = unit.controls[controlName] as Input;
+ $: values =
+ typeof input === 'number'
+ ? {
+ raw: input,
+ control: wrangle(input, range.signal, controlRange)
+ }
+ : null;
+
+ $: updateValue = (controlValue: number) => {
+ const n = Math.round(rescale(controlValue, controlRange, range.signal));
+ unitStore.setUnit(id, { ...unit, controls: { ...unit.controls, [controlName]: n } } as Unit);
+ };
+ $: onConnect = $unitToConnect
+ ? () => {
+ const _utc = $unitToConnect;
+ if (!_utc) return;
+ console.log('connecting', $unitToConnect, controlName);
+ unitStore.setUnit(id, {
+ ...unit,
+ controls: { ...unit.controls, [controlName]: inp.toggle(input, _utc) }
+ } as Unit);
+ }
+ : null;
+ let connected: boolean;
+ $: {
+ const _unitToConnect = $unitToConnect;
+ if (_unitToConnect !== false && Array.isArray(input)) {
+ connected = Boolean(input.find(({ id }) => id === _unitToConnect.id));
+ } else {
+ connected = false;
+ }
+ }
+</script>
+
+<div class="control-wrapper">
+ <h3>{controlName}</h3>
+ <InputDragger {connected} {onConnect} />
+ {#if values !== null}
+ <input
+ type="range"
+ min={controlRange.min}
+ max={controlRange.max}
+ step={1}
+ value={values.control}
+ on:input={(e) => updateValue(Number(e.currentTarget?.value))}
+ />
+ {/if}
+</div>
+
+<style>
+ .control-wrapper {
+ position: relative;
+ width: 100%;
+ height: 80px;
+ background: rgba(255, 255, 255, 0.3);
+ padding: 10px;
+ display: flexbox;
+ }
+</style>
diff --git a/src/lib/Noise.svelte b/src/lib/Noise.svelte
@@ -1,76 +0,0 @@
-<script lang="ts">
- import type { NoiseUnit, NoiseUnitInputs, 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: NoiseUnit;
- export let signalDragging: false | Output;
- export let update: (n: NoiseUnit) => void;
- export let onConnect: ((k: string) => void) | null;
-
- $: vals = {
- amount:
- typeof unit.amount === 'number'
- ? wrangle(unit.amount, range.signal, range.noise.amount)
- : null
- };
-
- const updateValue = (k: keyof NoiseUnitInputs, n: number) => {
- update({ ...unit, [k]: Math.round(rescale(n, range.noise[k], range.signal)) });
- };
-
- const isConnected = (k: keyof NoiseUnitInputs, 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 unitInputs.noise 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
- type="range"
- min={range.osc[k].min}
- max={range.osc[k].max}
- step={1}
- 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}
-</div>
-
-<style>
- .sect {
- position: relative;
- width: 100%;
- height: 80px;
- }
- .unit-container {
- background: rgba(255, 255, 255, 0.3);
- padding: 10px;
- display: flexbox;
- }
- h3 {
- padding: 0;
- margin: 0;
- }
-</style>
diff --git a/src/lib/NoiseUnit.svelte b/src/lib/NoiseUnit.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import Control from '$lib/Control.svelte';
+ import { unitStore } from '$lib/stores';
+ import { ensure, getUnit, type UnitId } from '$lib/types';
+
+ export let id: UnitId;
+
+ $: unit = ensure.unit.noise(getUnit($unitStore, id));
+</script>
+
+<div>
+ <h1>noiseyboi</h1>
+ <Control {id} controlName="amount" />
+</div>
+
+<style>
+ h1 {
+ color: green;
+ }
+</style>
diff --git a/src/lib/Osc.svelte b/src/lib/Osc.svelte
@@ -1,81 +0,0 @@
-<script lang="ts">
- 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:
- typeof unit.coarse === 'number' ? wrangle(unit.coarse, range.signal, range.osc.coarse) : null,
- fine: typeof unit.fine === 'number' ? wrangle(unit.fine, range.signal, range.osc.fine) : null,
- superfine:
- typeof unit.superfine === 'number'
- ? wrangle(unit.superfine, range.signal, range.osc.superfine)
- : null,
- amount:
- typeof unit.amount === 'number' ? wrangle(unit.amount, range.signal, range.osc.amount) : null
- };
-
- 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 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
- type="range"
- min={range.osc[k].min}
- max={range.osc[k].max}
- step={1}
- 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}
-</div>
-
-<style>
- .sect {
- position: relative;
- width: 100%;
- height: 80px;
- }
- .unit-container {
- background: rgba(255, 255, 255, 0.3);
- padding: 10px;
- display: flexbox;
- }
- h3 {
- padding: 0;
- margin: 0;
- }
-</style>
diff --git a/src/lib/OscUnit.svelte b/src/lib/OscUnit.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import Control from '$lib/Control.svelte';
+ import { unitStore } from '$lib/stores';
+ import { ensure, getUnit, type UnitId } from '$lib/types';
+
+ export let id: UnitId;
+
+ $: unit = ensure.unit.osc(getUnit($unitStore, id));
+</script>
+
+<div>
+ <h1>Os-KILL-8r</h1>
+ <Control {id} controlName="coarse" />
+ <Control {id} controlName="fine" />
+ <Control {id} controlName="superfine" />
+ <Control {id} controlName="amount" />
+</div>
+
+<style>
+ h1 {
+ color: red;
+ }
+</style>
diff --git a/src/lib/engine.worker.ts b/src/lib/engine.worker.ts
@@ -1,22 +1,14 @@
import type {
- EngineMessage,
- Input,
- SynthConfig,
- Unit,
- UnitId,
- UnitState,
- UnitStateMap,
-} from "$lib/types";
+ EngineMessage,
+ Input,
+ SynthConfig,
+ Unit,
+ UnitId,
+ UnitState,
+ UnitStateMap
+} from '$lib/types';
import { oklch } from '$lib/color';
-import {
- ROWS,
- COLS,
- getUnit,
- LOOP_CYCLES,
- range,
- rescale,
- wrangle,
-} from "$lib/types";
+import { ROWS, COLS, getUnit, LOOP_CYCLES, range, rescale, wrangle } from '$lib/types';
let config: SynthConfig | undefined = undefined;
@@ -25,181 +17,175 @@ let unitState: UnitStateMap = new Map();
let canvas: OffscreenCanvas = new OffscreenCanvas(1, 1);
onmessage = (message: { data: EngineMessage }) => {
- const { data } = message;
- switch (data.kind) {
- case "config": {
- config = data.content;
- break;
- }
- case "window": {
- canvas = new OffscreenCanvas(data.content.width, data.content.height);
- }
- }
+ const { data } = message;
+ switch (data.kind) {
+ case 'config': {
+ config = data.content;
+ break;
+ }
+ case 'window': {
+ canvas = new OffscreenCanvas(data.content.width, data.content.height);
+ }
+ }
};
function getUnitState(id: UnitId): UnitState {
- if (!config) {
- throw new Error("no config, can't get unit state.")
- }
- const unit = getUnit(config.units, id);
- const theState = unitState.get(id);
- switch (unit.kind) {
- case "osc": {
- if (theState === undefined) {
- return 0;
- }
- if (typeof theState !== "number" || Number.isNaN(theState)) {
- throw new Error(`invalid state for osc unit ${id}: ${theState}`);
- }
- break;
- }
- default: {
- throw new Error("state for this invalid or NYI");
- }
- }
- return theState;
+ if (!config) {
+ throw new Error("no config, can't get unit state.");
+ }
+ const unit = getUnit(config.units, id);
+ const theState = unitState.get(id);
+ switch (unit.kind) {
+ case 'osc': {
+ if (theState === undefined) {
+ return 0;
+ }
+ if (typeof theState !== 'number' || Number.isNaN(theState)) {
+ throw new Error(`invalid state for osc unit ${id}: ${theState}`);
+ }
+ break;
+ }
+ default: {
+ throw new Error('state for this invalid or NYI');
+ }
+ }
+ return theState;
}
function setUnitState(id: UnitId, state: UnitState) {
- // TODO: type safety
- unitState.set(id, state);
+ // TODO: type safety
+ unitState.set(id, state);
}
type OscShapes = {
- [k: string]: (p: number, a: number) => number
-}
+ [k: string]: (p: number, a: number) => number;
+};
const oscShapes: OscShapes = {
- sine: (position: number, amount: number) => {
- return Math.sin(2 * Math.PI * position) * amount;
- },
- square: (p, a) => {
- return (p < 0.5 ? -1 : 1) * a;
- },
- triangle: (p, a) => p * a
-}
+ sine: (position: number, amount: number) => {
+ return Math.sin(2 * Math.PI * position) * amount;
+ },
+ square: (p, a) => {
+ return (p < 0.5 ? -1 : 1) * a;
+ },
+ triangle: (p, a) => p * a
+};
function vUnit(x: { id: UnitId }): number {
- const { id } = x;
- const unit: Unit = getUnit(config!.units, id);
- switch (unit.kind) {
- case "osc": {
- const position = getUnitState(id);
- return oscShapes.sine(position / LOOP_CYCLES, v(unit.amount))
- }
- case "const": {
- // range is whatever the control for it is???
- return unit.value;
- }
- case "noise": {
- return Math.random() * v(unit.amount);
- }
- }
+ const { id } = x;
+ const unit: Unit = getUnit(config!.units, id);
+ switch (unit.kind) {
+ case 'osc': {
+ const position = getUnitState(id);
+ return oscShapes.sine(position / LOOP_CYCLES, v(unit.controls.amount));
+ }
+ case 'const': {
+ return v(unit.controls.value);
+ }
+ case 'noise': {
+ return Math.random() * v(unit.controls.amount);
+ }
+ }
}
-const perf = {}
+const perf = {};
const p = {
- start: (tag: string) => {
- if (!perf[tag]) perf[tag] = {
- n: 0,
- cum: 0,
- avg: 0
- }
- perf[tag]._t = performance.now();
- },
- end: (tag: string) => {
- const r = perf[tag]
- r.n++;
- r.cum += performance.now() - r._t;
- r.avg = r.cum / r.n;
- }
-}
+ start: (tag: string) => {
+ if (!perf[tag])
+ perf[tag] = {
+ n: 0,
+ cum: 0,
+ avg: 0
+ };
+ perf[tag]._t = performance.now();
+ },
+ end: (tag: string) => {
+ const r = perf[tag];
+ r.n++;
+ r.cum += performance.now() - r._t;
+ r.avg = r.cum / r.n;
+ }
+};
-setInterval(() => console.log(perf), 5000)
+setInterval(() => console.log(perf), 5000);
function v(input: Input): number {
- if (typeof input === "number") {
- return input;
- }
- const result = input.reduce((a, b) => a + vUnit(b), 0);
- return result;
+ if (typeof input === 'number') {
+ return input;
+ }
+ const result = input.reduce((a, b) => a + vUnit(b), 0);
+ return result;
}
function update() {
- // update all unit states
- if (!config) {
- throw new Error("no config, can't update unit state.")
- }
- const ids = Object.keys(config.units);
- for (let id of ids) {
- const unit = getUnit(config.units, id);
- switch (unit.kind) {
- case "osc": {
- const position = getUnitState(id);
- const coarse = rescale(v(unit.coarse), range.signal, range.osc.coarse);
- const fine = rescale(v(unit.fine), range.signal, range.osc.fine);
- const superfine = rescale(
- v(unit.superfine),
- range.signal,
- range.osc.superfine
- );
- setUnitState(
- id,
- (position + coarse * LOOP_CYCLES / 100 + fine * 20000 +
- superfine * 10) %
- LOOP_CYCLES,
- );
- break;
- }
- }
- }
+ // update all unit states
+ if (!config) {
+ throw new Error("no config, can't update unit state.");
+ }
+ const ids = Object.keys(config.units);
+ for (let id of ids) {
+ const unit = getUnit(config.units, id);
+ switch (unit.kind) {
+ case 'osc': {
+ const position = getUnitState(id);
+ const coarse = rescale(v(unit.controls.coarse), range.signal, range.osc.coarse);
+ const fine = rescale(v(unit.controls.fine), range.signal, range.osc.fine);
+ const superfine = rescale(v(unit.controls.superfine), range.signal, range.osc.superfine);
+ setUnitState(
+ id,
+ (position + (coarse * LOOP_CYCLES) / 100 + fine * 20000 + superfine * 10) % LOOP_CYCLES
+ );
+ break;
+ }
+ }
+ }
}
function drawSquares() {
- if (config) {
- p.start("drawSquares");
- const ctx = canvas.getContext("2d", {
- antialias: false,
- alpha: false,
- });
- if (!ctx) {
- throw new Error("couldnt get ctx");
- }
-
- // 4 = R G B A
- const data = new Uint8ClampedArray(ROWS * COLS * 4);
- let di = 0;
-
- for (let row = 0; row < ROWS; row++) {
- for (let col = 0; col < COLS; col++) {
- const l = config.sinks.l == null ? 0 : v(config.sinks.l);
- const c = config.sinks.c == null ? 0 : v(config.sinks.c);
- const h = config.sinks.h == null ? 0 : v(config.sinks.h);
- const rgb = oklch(
- wrangle(l, range.signal, {
- min: 0,
- max: 1,
- }),
- wrangle(c, range.signal, {
- min: 0,
- max: 0.5,
- }),
- wrangle(h, range.signal, {
- min: 0,
- max: 360,
- })
- );
- data[di++] = rgb.r;
- data[di++] = rgb.g;
- data[di++] = rgb.b;
- data[di++] = 255;
- update();
- }
- }
- postMessage({ kind: "buf", content: data.buffer }, [data.buffer]);
- p.end("drawSquares");
- }
- requestAnimationFrame(drawSquares);
+ if (config) {
+ p.start('drawSquares');
+ const ctx = canvas.getContext('2d', {
+ antialias: false,
+ alpha: false
+ });
+ if (!ctx) {
+ throw new Error('couldnt get ctx');
+ }
+
+ // 4 = R G B A
+ const data = new Uint8ClampedArray(ROWS * COLS * 4);
+ let di = 0;
+
+ for (let row = 0; row < ROWS; row++) {
+ for (let col = 0; col < COLS; col++) {
+ const l = config.sinks.l == null ? 0 : v(config.sinks.l);
+ const c = config.sinks.c == null ? 0 : v(config.sinks.c);
+ const h = config.sinks.h == null ? 0 : v(config.sinks.h);
+ const rgb = oklch(
+ wrangle(l, range.signal, {
+ min: 0,
+ max: 1
+ }),
+ wrangle(c, range.signal, {
+ min: 0,
+ max: 0.5
+ }),
+ wrangle(h, range.signal, {
+ min: 0,
+ max: 360
+ })
+ );
+ data[di++] = rgb.r;
+ data[di++] = rgb.g;
+ data[di++] = rgb.b;
+ data[di++] = 255;
+ update();
+ }
+ }
+ postMessage({ kind: 'buf', content: data.buffer }, [data.buffer]);
+ p.end('drawSquares');
+ }
+ requestAnimationFrame(drawSquares);
}
drawSquares();
diff --git a/src/lib/stores.ts b/src/lib/stores.ts
@@ -1,16 +1,17 @@
import { writable } from 'svelte/store';
-import type { Unit, UnitId, Units } from '$lib/types';
+import type { Output, Unit, UnitId, UnitKind, Units } from '$lib/types';
-const mkUnitStore = () => {
- const { subscribe, set, update } = writable<Units>({});
- return {
- subscribe,
- set,
- setUnit(id: UnitId, unit: Unit) {
- update(units => ({ ...units, [id]: unit }));
- }
- };
+const mkUnitStore = <T = Units>() => {
+ const { subscribe, set, update } = writable<T>({});
+ return {
+ subscribe,
+ set,
+ setUnit(id: UnitId, unit: Unit) {
+ update((units) => ({ ...units, [id]: unit }));
+ }
+ };
+};
-}
+export const unitToConnect = writable<Output | false>(false);
export const unitStore = mkUnitStore();
diff --git a/src/lib/types.ts b/src/lib/types.ts
@@ -7,261 +7,230 @@ export const CELLS = ROWS * COLS;
export type WithTarget<E, T> = E & { currentTarget: T };
export type Color = {
- r: number,
- g: number,
- b: number
-}
+ r: number;
+ g: number;
+ b: number;
+};
export type SynthConfig = {
- sinks: Sinks;
- units: Units;
+ sinks: Sinks;
+ units: Units;
};
-export type EngineMessage = {
- kind: "config";
- content: SynthConfig;
-} | {
- kind: "window";
- content: {
- height: number;
- width: number;
- };
-} | {
- kind: "canvas";
- content: OffscreenCanvas;
+export type EngineMessage =
+ | {
+ kind: 'config';
+ content: SynthConfig;
+ }
+ | {
+ kind: 'window';
+ content: {
+ height: number;
+ width: number;
+ };
+ }
+ | {
+ kind: 'canvas';
+ content: OffscreenCanvas;
+ };
+
+type Pos = {
+ x: number;
+ y: number;
};
-export type Unit =
- | ConstUnit
- | OscUnit
- | NoiseUnit;
-
-export type UnitId = string;
+export type ConstUnit = {
+ kind: 'const';
+ pos: Pos;
+ controls: {
+ value: Input;
+ };
+};
-export type Output = { id: UnitId };
-export type Input = number | Output[];
+export type OscUnit = {
+ kind: 'osc';
+ pos: Pos;
+ controls: {
+ coarse: Input;
+ fine: Input;
+ superfine: Input;
+ amount: Input;
+ };
+};
-export type Pos = { x: number; y: number };
+export type NoiseUnit = {
+ kind: 'noise';
+ pos: Pos;
+ controls: {
+ amount: Input;
+ };
+};
-type WithPos = { pos: Pos };
+export type Unit = OscUnit | ConstUnit | NoiseUnit;
-export type OscUnitInputs = {
- coarse: Input;
- fine: Input;
- superfine: Input;
- amount: Input;
-}
-export type NoiseUnitInputs = {
- amount: Input;
-}
-export type SmoothUnitInputs = {
- frames: Input;
-}
+export type UnitKind = keyof UnitMap;
-type UnitInputs = {
- osc: (keyof OscUnitInputs)[],
- noise: (keyof NoiseUnitInputs)[],
- smooth: (keyof SmoothUnitInputs)[]
-}
-export const unitInputs: UnitInputs = {
- osc: ['coarse', 'fine', 'superfine', 'amount'],
- noise: ['amount'],
- smooth: ['frames']
-}
-
-export type SmoothUnit = WithPos & SmoothUnitInputs & {
- kind: "smooth";
-}
-export type OscUnit = WithPos & OscUnitInputs & {
- kind: "osc";
+type UnitMap = {
+ const: ConstUnit;
+ osc: OscUnit;
+ noise: NoiseUnit;
};
-export type NoiseUnit = WithPos & NoiseUnitInputs & {
- kind: "noise";
-};
+export type UnitId = string;
-// outputs number as it is
-export type ConstUnit = WithPos & {
- kind: "const";
- value: number;
-};
+export type Output = { id: UnitId };
+export type Input = number | Output[];
export type Units = { [u: UnitId]: Unit };
-export type UnitMap = Map<UnitId, Unit>;
export type UnitStateMap = Map<UnitId, UnitState>;
export type UnitState = any;
export type Sinks = {
- l: Input;
- c: Input;
- h: Input;
+ l: Input;
+ c: Input;
+ h: Input;
};
-type Range = {
- min: number;
- max: number;
+export type Range = {
+ min: number;
+ max: number;
};
export const range = {
- osc: {
- coarse: {
- min: -50,
- max: 50,
- },
- fine: {
- min: -50,
- max: 50,
- },
- superfine: {
- min: -50,
- max: 50,
- },
- amount: {
- min: -50,
- max: 50,
- },
- output: {
- min: -1,
- max: 1,
- },
- },
- noise: {
- amount: {
- min: -50,
- max: 50,
- },
- },
- smooth: {
- frames: {
- min: 0,
- max: 50
- }
- },
- color: {
- min: 0,
- max: 255,
- },
- pmone: {
- min: -1,
- max: 1,
- },
- slider: {
- min: -50,
- max: 50,
- },
- signal: {
- min: -5_000_000,
- max: 5_000_000,
- },
-
+ osc: {
+ coarse: {
+ min: -50,
+ max: 50
+ },
+ fine: {
+ min: -50,
+ max: 50
+ },
+ superfine: {
+ min: -50,
+ max: 50
+ },
+ amount: {
+ min: -50,
+ max: 50
+ },
+ output: {
+ min: -1,
+ max: 1
+ }
+ },
+ const: {
+ value: {
+ min: -5_000_000,
+ max: 5_000_000
+ }
+ },
+ noise: {
+ amount: {
+ min: -50,
+ max: 50
+ }
+ },
+ smooth: {
+ frames: {
+ min: 0,
+ max: 50
+ }
+ },
+ color: {
+ min: 0,
+ max: 255
+ },
+ pmone: {
+ min: -1,
+ max: 1
+ },
+ slider: {
+ min: -50,
+ max: 50
+ },
+ signal: {
+ min: -5_000_000,
+ max: 5_000_000
+ }
};
-//export function rangeForInput<U extends Unit>(
-// units: UnitMap,
-// id: UnitId,
-// k: keyof Omit<U, "kind">,
-//): Range {
-// let unit = getUnit(units, id);
-// switch (unit.kind) {
-// case "const": {
-// return range.slider;
-// }
-// case "osc": {
-// switch (k) {
-// case "amount": {
-// return range.osc.amount;
-// }
-// case "coarse": {
-// return range.osc.coarse;
-// }
-// case "fine": {
-// return range.osc.fine;
-// }
-// default: {
-// throw new Error(`cannot find range for ${String(k)} in ${unit.kind}`)
-// }
-// }
-// }
-// case "combinator": {
-// return unit.sources.reduce((accum, (src) => {
-// console.log('hi');
-// return
-// }))
-// }
-// default:
-// throw new Error("NYI: range for " + unit.kind);
-// }
-//}
-
export function clamp(n: number, range: Range) {
- return Math.max(range.min, Math.min(range.max, n));
+ return Math.max(range.min, Math.min(range.max, n));
}
export function rescale(n: number, origin: Range, dest: Range) {
- return ((n - origin.min) / (origin.max - origin.min)) *
- (dest.max - dest.min) + dest.min;
+ return ((n - origin.min) / (origin.max - origin.min)) * (dest.max - dest.min) + dest.min;
}
export function wrangle(n: number, origin: Range, dest: Range) {
- return rescale(clamp(n, origin), origin, dest);
+ return rescale(clamp(n, origin), origin, dest);
}
export function getUnit(units: Units, id: UnitId): Unit {
- const result = units[id];
- if (!result) throw new Error("invalid id for unit: " + id);
- return result;
+ const result = units[id];
+ if (!result) throw new Error('invalid id for unit: ' + id);
+ return result;
}
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";
- },
- osc: (u: Unit): u is OscUnit => {
- return u.kind === "osc";
- },
- },
+ input: (i: any): i is Input => {
+ return i === Object(i) && i.id;
+ },
+ unit: {
+ const: (u: Unit): u is ConstUnit => {
+ return u.kind === 'const';
+ },
+ osc: (u: Unit): u is OscUnit => {
+ return u.kind === 'osc';
+ },
+ noise: (u: Unit): u is NoiseUnit => {
+ return u.kind === 'noise';
+ }
+ }
};
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;
- } else {
- throw new Error("this is not an osc");
- }
- },
- },
+ 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;
+ } else {
+ throw new Error('this is not an osc');
+ }
+ },
+ noise: (u: Unit): NoiseUnit => {
+ if (is.unit.noise(u)) {
+ return u;
+ } else {
+ throw new Error('this is not a noise unit');
+ }
+ }
+ }
};
export const inp = {
- toggle: (input: Input, output: Output): Input => {
- if (typeof input === "number") {
- return [output]
- }
- else {
- const deduped = input.filter((o) => o.id !== output.id)
- if (input.length === deduped.length) {
- return [...input, output];
- }
- else {
- // it's there and we removed it
- // 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;
- }
-}
+ toggle: (input: Input, output: Output): Input => {
+ if (typeof input === 'number') {
+ return [output];
+ } else {
+ const deduped = input.filter((o) => o.id !== output.id);
+ if (input.length === deduped.length) {
+ return [...input, output];
+ } else {
+ // it's there and we removed it
+ // 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
@@ -1,13 +1,13 @@
<script lang="ts">
- import { unitStore } from '$lib/stores';
+ import { unitStore, unitToConnect } from '$lib/stores';
import { onMount } from 'svelte';
import { debounce } from 'lodash';
import Sink from '$lib/Sink.svelte';
- import Osc from '$lib/Osc.svelte';
- import Noise from '$lib/Noise.svelte';
- import Slider from '$lib/Slider.svelte';
import { COLS, ROWS, inp, rescale, range, is, getUnit as _getUnit } from '$lib/types';
import type { Output, Input, Unit, UnitId, ConstUnit, Units, Sinks } from '$lib/types';
+ import OscUnit from '$lib/OscUnit.svelte';
+ import NoiseUnit from '$lib/NoiseUnit.svelte';
+ import ConstUnit from '$lib/ConstUnit.svelte';
let cvs: HTMLCanvasElement | undefined;
@@ -20,7 +20,6 @@
};
onMount(loadWorker);
- $: engineWorker && engineWorker.postMessage({ kind: 'config', content: { units, sinks } });
$: {
if (engineWorker) {
@@ -70,6 +69,13 @@
$: units = $unitStore;
let sinks: Sinks = { l: 0, c: 0, h: 0 };
+ // let's update the engine whenever the config changes for now, even though we could ignore pos at least.
+ $: {
+ if (engineWorker) {
+ console.log('updating the engine');
+ engineWorker.postMessage({ kind: 'config', content: { units: $unitStore, sinks } });
+ }
+ }
onMount(() => {
const doc: SerializedState = fromUrl();
if (doc) {
@@ -118,22 +124,19 @@
c: function mkConst(): UnitId {
return addUnit({
kind: 'const',
- value: randConst(),
+ controls: { value: randConst() },
pos: { x: 0, y: 0 }
});
},
osc: function mkOsc(): UnitId {
return addUnit({
kind: 'osc',
- coarse: randConst(),
- fine: randConst(),
- superfine: 0,
- amount: randConst(),
+ controls: { coarse: randConst(), fine: randConst(), superfine: 0, amount: randConst() },
pos: { x: 0, y: 0 }
});
},
noise: function noise(): UnitId {
- return addUnit({ kind: 'noise', amount: randConst(), pos: { x: 0, y: 0 } });
+ return addUnit({ kind: 'noise', controls: { amount: randConst() }, pos: { x: 0, y: 0 } });
}
};
@@ -145,16 +148,6 @@
}
};
- function updateUnit(id: UnitId, value: number) {
- const unit = get.constUnit(id);
- unit.value = value;
- unitStore.setUnit(id, unit);
- }
-
- function updateEntireUnit(id: UnitId, unit: Unit) {
- unitStore.setUnit(id, unit);
- }
-
let dragging: false | UnitId = false;
const handleMouseMove = (e: MouseEvent) => {
@@ -181,12 +174,10 @@
document.addEventListener('mouseup', handleMouseUp);
};
- let signalDragging: false | Output = false;
-
const handleSignalStart = (o: Output) => {
- signalDragging = o;
+ $unitToConnect = o;
const h = () => {
- signalDragging = false;
+ $unitToConnect = false;
document.removeEventListener('mouseup', h);
};
setTimeout(() => {
@@ -195,20 +186,20 @@
};
const _onSinkConnect = (ch: 'l' | 'c' | 'h'): void => {
- if (!signalDragging) {
+ if (!$unitToConnect) {
throw new Error('cant connect sink to nonexistant signal');
}
- sinks[ch] = inp.toggle(sinks[ch], signalDragging);
+ sinks[ch] = inp.toggle(sinks[ch], $unitToConnect);
engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } });
console.log(sinks);
- signalDragging = false;
+ $unitToConnect = false;
};
$: {
console.log(units);
}
- $: onSinkConnect = signalDragging ? _onSinkConnect : null;
+ $: onSinkConnect = $unitToConnect ? _onSinkConnect : null;
type SinkEntries = [keyof Sinks, Input | null][];
// Object.entries COULD have extra stuff, so it doesn't assume keys are keyof Sinks exactly.
@@ -226,7 +217,7 @@
// @ts-ignore unit[k] is a channel but i don't wanna prove it.
unitStore.setUnit(input, { ...unit, [k]: inp.toggle(unit[k], { id: output }) });
engineWorker && engineWorker.postMessage({ kind: 'config', content: { sinks, units } });
- signalDragging = false;
+ $unitToConnect = false;
};
</script>
@@ -244,7 +235,7 @@
{input}
{channel}
{onSinkConnect}
- connected={signalDragging && input ? inp.connected(input, signalDragging) : false}
+ connected={$unitToConnect && input ? inp.connected(input, $unitToConnect) : false}
/>
{/each}
</div>
@@ -253,21 +244,11 @@
<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} />
+ <ConstUnit {id} />
{:else if unit.kind === 'osc'}
- <Osc
- {unit}
- {signalDragging}
- onConnect={getConnectHandler(signalDragging, id)}
- updateOsc={updateEntireUnit.bind(null, id)}
- />
+ <OscUnit {id} />
{:else if unit.kind === 'noise'}
- <Noise
- {unit}
- {signalDragging}
- onConnect={getConnectHandler(signalDragging, id)}
- update={updateEntireUnit.bind(null, id)}
- />
+ <NoiseUnit {id} />
{/if}
<div
class="output-dragger"
diff --git a/tsconfig.json b/tsconfig.json
@@ -9,10 +9,14 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
- "lib": ["webworker", "es2019"]
+ "noErrorTruncation": true,
+ "lib": [
+ "webworker",
+ "es2019"
+ ]
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
-}
+}
+\ No newline at end of file