014 - ParameterSources

The ParameterSources Module enables merging from multiple sources.

The Problem

Your artwork has parameters in:

  • Tweakpane – For live interaction
  • JSON Preset – For saving/loading
  • MIDI Controller – Physical knobs
  • OSC – From other apps (TouchOSC, Max/MSP)
  • Sockets – From Art.Works! Studio or other artworks
  • Node Editor – Visual programming

Each source wants to control the same parameters. Who wins?

The Solution: ParameterComposer

The ParameterComposer merges all sources into one ParameterObject:

┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  Tweakpane  │  │    MIDI     │  │    JSON     │
│   Source    │  │   Source    │  │   Source    │
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘
       │                │                │
       ▼                ▼                ▼
┌────────────────────────────────────────────────┐
│              ParameterComposer                 │
│  ┌──────────────────────────────────────────┐  │
│  │            Merge Strategy                │  │
│  │   Priority │ Last-Write │ Blend │ Custom │  │
│  └──────────────────────────────────────────┘  │
└───────────────────────┬────────────────────────┘
                        │
                        ▼
               ┌─────────────────┐
               │ ParameterObject │
               └─────────────────┘

Components

Class File Function
ParameterComposer ParameterComposer.ts Merges Sources
ParameterSource ParameterSource.ts Base Interface
TweakpaneSource TweakpaneSource.ts Tweakpane Adapter
JSONSource JSONSource.ts JSON/Preset Files
MIDISource MIDISource.ts MIDI Controller
OSCSource OSCSource.ts Open Sound Control
SocketSource SocketSource.ts WebSocket/Socket.io
NodeEditorSource NodeEditorSource.ts Visual Editor

ParameterComposer

Central merge manager.

Configuration

const composer = new ParameterComposer({
  mergeStrategy: 'priority',
  sources: [
    { source: tweakpaneSource, priority: 1 },
    { source: midiSource, priority: 2 },
    { source: socketSource, priority: 3 },
  ],
});

Merge Strategies

Strategy Description
priority Higher priority wins
lastWrite Most recent change wins
blend Average/blend values
custom Your own function

API

Method Description
addSource(source, opts) Add Source
removeSource(source) Remove Source
getComposed() Get Merged Result
update(deltaTime) Update All Sources
lock(key) Lock Parameter
unlock(key) Unlock Parameter

ParameterSource Interface

All sources implement:

interface ParameterSource {
  readonly id: string;
  readonly name: string;
  
  connect(): Promise<void>;
  disconnect(): void;
  isConnected(): boolean;
  
  getValues(): Record<string, any>;
  setValue(key: string, value: any): void;
  
  onChange(callback: (key, value) => void): void;
}

TweakpaneSource

Wraps existing Tweakpane for the Composer.

const tweakpaneSource = new TweakpaneSource({
  pane: myTweakpane,
  namespace: 'ui',
});

JSONSource

Loads parameters from JSON files.

const jsonSource = new JSONSource({
  path: './presets/default.json',
  autoReload: true,
  watchFile: true,
});

Features

Feature Description
autoReload Reload on File Change
watchFile File Watcher
merge Merge vs. Replace

MIDISource

MIDI controller input.

const midiSource = new MIDISource({
  deviceName: 'nanoKONTROL2',
  mapping: {
    'brush.size': { channel: 1, cc: 0, min: 10, max: 500 },
    'brush.rotation': { channel: 1, cc: 1, min: 0, max: 360 },
    'animation.speed': { channel: 1, cc: 2, min: 0.1, max: 2.0 },
  },
});

Mapping Options

Option Description
channel MIDI Channel (1-16)
cc Control Change Number
note Note Number (for On/Off)
min, max Value Range Mapping
curve Response Curve

Response Curves

Curve Description
linear Linear
log Logarithmic
exp Exponential
scurve S-Curve

OSCSource

Open Sound Control protocol.

const oscSource = new OSCSource({
  port: 8000,
  mapping: {
    '/brush/size': 'brush.size',
    '/color/r': 'color.red',
    '/color/g': 'color.green',
    '/color/b': 'color.blue',
  },
});

Features

  • UDP and WebSocket Transport
  • Pattern Matching (/brush/*)
  • Multi-Value Messages

SocketSource

WebSocket/Socket.io connection.

const socketSource = new SocketSource({
  url: 'http://localhost:3090',
  room: 'artwork:001',
  parameterPath: 'parameters',
});

Integration with StudioLink

SocketSource integrates with StudioLinkManager:

const studioLink = new StudioLinkManager({ artworkId: 'my-art' });
const socketSource = SocketSource.fromStudioLink(studioLink);

NodeEditorSource

Connects to node-based editor.

const nodeSource = new NodeEditorSource({
  editorInstance: myNodeEditor,
  outputNodeId: 'output-1',
});

Priority System

Priority 1 (Lowest): JSON Preset
    │
    ▼
Priority 2: Tweakpane (User can override Preset)
    │
    ▼
Priority 3: MIDI Controller (Physical wins over UI)
    │
    ▼
Priority 4: Socket (Studio can override Everything)
    │
    ▼
Priority 5 (Highest): Locked Parameters (Unchangeable)

Locking

composer.lock('brush.color'); // Cannot be changed by any source
composer.unlock('brush.color'); // Can be changed again

Blend Strategy

For numeric values:

const composer = new ParameterComposer({
  mergeStrategy: 'blend',
  blendOptions: {
    'brush.size': { mode: 'average' },
    'position.x': { mode: 'weighted', weights: [0.3, 0.7] },
  },
});

Blend Modes

Mode Description
average Arithmetic Average
weighted Weighted Average
min Minimum of All
max Maximum of All
multiply Product

Event Flow

   MIDI Knob Turn
        │
        ▼
   MIDISource.onChange()
        │
        ▼
   ParameterComposer.handleSourceChange()
        │
        ├── Apply Merge Strategy
        │
        ├── Update ParameterObject
        │
        └── Emit 'parameter:changed'
                │
                ▼
        Artwork.onParameterChange()

Usage Example

import {
  ParameterComposer,
  TweakpaneSource,
  MIDISource,
  JSONSource,
} from '@carstennichte/cc-toolbox';

// Configure sources
const sources = {
  preset: new JSONSource({ path: './preset.json' }),
  ui: new TweakpaneSource({ pane: myPane }),
  midi: new MIDISource({ deviceName: 'nanoKONTROL2', mapping: {...} }),
};

// Create composer
const composer = new ParameterComposer({
  mergeStrategy: 'priority',
  sources: [
    { source: sources.preset, priority: 1 },
    { source: sources.ui, priority: 2 },
    { source: sources.midi, priority: 3 },
  ],
});

// Connect all sources
await composer.connectAll();

// In draw loop
function draw() {
  composer.update(deltaTime);
  const params = composer.getComposed();
  
  // Use merged parameters
  brush.size = params.brush.size;
  //...
}