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 againBlend 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;
//...
}