ParameterSource - Multi-Source Parameter System

Parameter aus verschiedenen Quellen zusammenführen: Tweakpane, MIDI, OSC, JSON, Sockets, Node Editor.

Das Problem

In komplexen Setups kommen Parameter aus verschiedenen Quellen:

  • Tweakpane UI - Zum Entwickeln und Testen
  • JSON Files - Presets und gespeicherte Snapshots
  • MIDI Controller - Live Performance mit Hardware
  • OSC - TouchOSC, Lemur und andere Apps
  • Sockets - Studio-Steuerung und Collaboration
  • Node Editor - Visuelle Programmierung

Was passiert wenn mehrere Quellen den gleichen Parameter setzen wollen?

Die Lösung: ParameterComposer

Der ParameterComposer führt alle Quellen intelligent zusammen:

Tweakpane (50) ──┐
JSON (10) ───────┼──► ParameterComposer ──► parameter.brush.size
MIDI (30) ───────┤
Socket (90) ─────┘

Jede Quelle hat eine Priorität. Höhere Priorität gewinnt bei Konflikten.

Schnellstart

import {
  createParameterSystem,
  createSchema,
  numParam,
  colorParam,
  boolParam,
} from '@carstennichte/cc-toolbox';

// 1. Parameter-Schema definieren
const schema = createSchema([
  numParam('brush.size', 10, 1, 100, { step: 1, label: 'Größe' }),
  numParam('brush.opacity', 1.0, 0, 1, { step: 0.01, label: 'Deckkraft' }),
  colorParam('brush.color', '#ff0000', { label: 'Farbe' }),
  boolParam('brush.active', true, { label: 'Aktiv' }),
]);

// 2. System mit Quellen erstellen
const system = createParameterSystem({
  parameter: myParameter,
  schema,
  tweakpane: { manager: myTweakpaneManager },
  midi: {
    ccMappings: {
      1: 'brush.size',      // Mod Wheel → Größe
      7: 'brush.opacity',   // Volume → Deckkraft
    }
  },
});

// 3. Verbinden
await system.connect();

Verfügbare Quellen

Tweakpane (Priorität: 50)

Die bekannte GUI zum Entwickeln. Zeigt aktuelle Werte und erlaubt Bearbeitung.

const system = createParameterSystem({
  parameter,
  schema,
  tweakpane: { 
    manager: myTweakpaneManager,
    syncFromComposer: true,  // Zeigt merged values
  },
});

JSON (Priorität: 10)

Für Presets und gespeicherte Einstellungen.

const system = createParameterSystem({
  parameter,
  schema,
  json: {
    source: 'url',
    url: '/presets/default.json',
  },
});

// Oder aus localStorage
const system = createParameterSystem({
  parameter,
  schema,
  json: {
    source: 'storage',
    storageKey: 'my-artwork-preset',
  },
});

MIDI (Priorität: 30)

Hardware Controller für Live Performance.

const system = createParameterSystem({
  parameter,
  schema,
  midi: {
    deviceFilter: 'Arturia',  // Optional
    channel: 1,               // Optional
    ccMappings: {
      1: 'brush.size',        // CC 1 = Mod Wheel
      7: 'brush.opacity',     // CC 7 = Volume
      74: 'effect.cutoff',    // CC 74 = Filter
    },
  },
});

// Learn Mode für unbekannte Controller
system.sources.midi.startLearn((cc, channel) => {
  console.log(`CC ${cc} auf Kanal ${channel}`);
  // Hier Mapping speichern...
  system.sources.midi.stopLearn();
});

Standard CC-Nummern:

CC Name Typische Verwendung
1 Mod Wheel Intensität
7 Volume Deckkraft
10 Pan Position X
74 Filter Cutoff Effekt-Stärke

OSC (Priorität: 30)

Für TouchOSC, Lemur und ähnliche Apps.

const system = createParameterSystem({
  parameter,
  schema,
  osc: {
    port: 9000,
    addressMappings: {
      '/page1/fader1': 'brush.size',
      '/page1/fader2': 'brush.opacity',
      '/page1/xy1/x': 'position.x',
      '/page1/xy1/y': 'position.y',
    },
  },
});

Hinweis: OSC im Browser benötigt eine WebSocket-Bridge (z.B. osc-web-bridge).

Sockets (Priorität: 90)

Höchste Priorität - für Studio-Steuerung und Collaboration.

const system = createParameterSystem({
  parameter,
  schema,
  socket: {
    url: 'http://localhost:3090',
    room: 'artwork:my-artwork',
    artworkId: 'my-artwork',
    syncToServer: true,
  },
});

Node Editor (Priorität: 70)

Visuelle Programmierung im Art.Works! Studio.

const system = createParameterSystem({
  parameter,
  schema,
  nodeEditor: {
    socketUrl: 'http://localhost:3090',
    artworkId: 'my-artwork',
  },
});

// Prüfen ob Parameter von Node kontrolliert wird
if (system.sources.nodeEditor.isControlled('brush.size')) {
  // Tweakpane-Control deaktivieren
}

Merge-Strategien

Was passiert wenn mehrere Quellen den gleichen Parameter setzen?

const system = createParameterSystem({
  parameter,
  schema,
  mergeStrategies: {
    'brush.size': 'override',      // Höchste Priorität gewinnt (default)
    'brush.opacity': 'multiply',   // Werte multiplizieren
    'effect.intensity': 'max',     // Höchster Wert
    'position.x': 'blend',         // Gewichtetes Mittel nach Priorität
  },
});

Verfügbare Strategien:

Strategie Beschreibung
override Höchste Priorität gewinnt (Standard)
blend Gewichtetes Mittel nach Priorität
add Werte addieren
multiply Werte multiplizieren
max Höchster Wert
min Niedrigster Wert
first Erste Quelle die einen Wert hat
last Letzte Änderung gewinnt

Auf Änderungen reagieren

// Alle Änderungen
system.composer.onChange(event => {
  console.log(`${event.path}: ${event.oldValue}${event.value}`);
  console.log(`Quelle: ${event.sourceId}`);
});

// Nur bestimmte Parameter
system.composer.onChange(event => {
  if (event.path.startsWith('brush.')) {
    updateBrush(event.path, event.value);
  }
});

Prioritäten-Übersicht

Quelle Priorität Typischer Einsatz
JSON 10 Basis-Presets, Defaults
MIDI 30 Hardware Controller
OSC 30 Mobile Apps
Tweakpane 50 Entwicklung, Fine-Tuning
Node Editor 70 Visuelle Programmierung
Sockets. 90 Studio-Override, Collaboration

Debugging

const system = createParameterSystem({
  // ...
  debug: true,  // Aktiviert Logging
});

// Manuell Quellen-Status prüfen
system.sources.forEach((source, id) => {
  console.log(`${id}: ${source.isConnected() ? '✅' : '❌'}`);
});

// Aktuellen Wert mit Quelle abrufen
const { value, sourceId } = system.composer.getValueWithSource('brush.size');
console.log(`brush.size = ${value} (von ${sourceId})`);

Zusammenfassung

  1. Schema definieren - Welche Parameter gibt es?
  2. Quellen konfigurieren - Woher kommen die Werte?
  3. Merge-Strategien wählen - Was passiert bei Konflikten?
  4. Verbinden und nutzen - await system.connect()

Das ParameterSource Pattern macht komplexe Multi-Input Setups einfach und vorhersagbar.