Zum Inhalt springen
PixDrive
Alle Artikel

Script Engine: eigene Effekte mit Lua

Eigene LED-Effekte in Lua schreiben, testen und speichern – mit Parametern, Audio-Reaktion, 2D-Matrizen und Mehrkern-Rendering.

Aktualisiert am

Wenn die mitgelieferten Effekte nicht reichen, schreibst du in PixDrive Studio deine eigenen. Die Script Engine führt Lua 5.4 aus: Dein Skript wird für jedes Bild (Frame) aufgerufen und setzt die Farbe jeder einzelnen LED. Änderungen siehst du sofort in der Vorschau und auf deinen Geräten.

Diese Anleitung führt dich vom ersten Skript bis zu Parametern, Audio-Reaktion, 2D-Matrizen und Mehrkern-Rendering.

Dein erstes Skript

  1. Öffne oben in der Werkzeugleiste den Bereich Skripte. Du siehst die LED-Vorschau, darunter den Code-Editor und rechts die Seitenleiste Parameter.
  2. Klicke links unter Beispiele auf Rainbow. Der Code wird in den Editor geladen.
  3. Wähle in der Geräteliste die Fixtures aus, auf denen der Effekt laufen soll. Ohne Auswahl läuft das Skript auf allen Fixtures.
  4. Klicke unten auf Ausführen. In der Statusleiste erscheint „Skript läuft“, die Vorschau und deine LEDs zeigen den Regenbogen.
  5. Ändere im Code eine Zahl, zum Beispiel die Geschwindigkeit, und klicke erneut auf Ausführen.
  6. Mit Speichern legst du das Skript unter einem Namen in der Bibliothek ab.

Zum Stoppen klickst du in der Liste Aktive Effekte beim laufenden Skript auf das ×. Laufende Skripte erkennst du dort an einem gelben Punkt.

Aufbau eines Skripts

Jedes Skript muss eine Funktion render() definieren. Sie wird einmal pro Frame aufgerufen. Code außerhalb von render() läuft nur einmal beim Start – ideal, um Werte vorzubereiten.

-- läuft einmal beim Start
local offset = 0.0

function render()
    -- läuft in jedem Frame
    offset = offset + delta * 60.0
    for i = 0, led_count - 1 do
        local hue = (i / led_count * 360.0 + offset) % 360.0
        set_led_hsv(i, hue, 1.0, 1.0)
    end
end

Ein paar Lua-Grundlagen, die du ständig brauchst:

  • Variablen mit local anlegen: local x = 5
  • Schleife über alle LEDs: for i = 0, led_count - 1 do … end
  • Bedingung: if x > 0 then … else … end
  • Kommentare: -- eine Zeile oder --[[ mehrere Zeilen ]]
  • Division liefert immer eine Kommazahl: 1 / 2 ergibt 0.5. Ganzzahlige Division mit //: 7 // 2 ergibt 3.

LEDs setzen

LEDs werden direkt über Funktionen gesetzt. Die Indizes laufen von 0 bis led_count - 1.

Funktion Wirkung
set_led_hsv(i, h, s, v) Farbe als Farbton, Sättigung, Helligkeit. h: 0–360, s und v: 0.0–1.0
set_led_rgb(i, r, g, b) Farbe als Rot, Grün, Blau, jeweils 0.0–1.0
set_led_rgba(i, r, g, b, a) wie RGB, zusätzlich mit Transparenz a (0.0–1.0)
set_led_cct(i, helligkeit, ct) nur Weiß: ct 0.0 = warm (2700 K) bis 1.0 = kalt (6500 K)

Farbwerte außerhalb von 0.0–1.0 begrenzt du mit clamp(wert, 0.0, 1.0), den Farbton hältst du mit % 360.0 im gültigen Bereich.

Verfügbare Variablen

Diese Werte setzt PixDrive vor jedem Frame:

Variable Bedeutung
time Sekunden seit Start des Effekts
delta Sekunden seit dem letzten Frame – für gleichmäßige Bewegung unabhängig von der Framerate
frame Nummer des aktuellen Frames, zählt ab 0
led_count Anzahl der LEDs
matrix_breite Breite der Matrix in Pixeln, 0 bei einem LED-Streifen
matrix_hoehe Höhe der Matrix in Pixeln, 0 bei einem LED-Streifen
led_start, led_end LED-Bereich dieses Rechenkerns, siehe Leistung

Parameter: Werte live verstellen

Mit Parametern machst du Werte wie Geschwindigkeit oder Farbton einstellbar, ohne den Code anzufassen.

  1. Klicke in der Seitenleiste Parameter auf + Neuer Parameter.
  2. Vergib eine Parameter ID (z. B. speed) und einen Parameter Name (z. B. „Geschwindigkeit“).
  3. Wähle den Typ Float (Kommazahl), Integer (Ganzzahl) oder Boolean (an/aus) und trage bei Zahlen Min, Max und Default ein.
  4. Bestätige mit OK.

Im Skript steht der Wert dann als Variable mit dem Präfix param_ bereit – aus speed wird param_speed:

-- @params: [{"id":"speed","name":"Geschwindigkeit","param_type":{"type":"Float","config":{"default":1.0,"min":0.1,"max":5.0,"step":0.05}},"automatable":true}]
function render()
    local t = time * param_speed
    for i = 0, led_count - 1 do
        set_led_hsv(i, (i / led_count * 360.0 + t * 60.0) % 360.0, 1.0, 1.0)
    end
end

Die erste Zeile mit -- @params: schreibt PixDrive automatisch, sobald du einen Parameter anlegst oder entfernst. Du kannst sie auch von Hand bearbeiten – dann muss die ganze Definition in einer Zeile stehen, die Seitenleiste übernimmt die Änderung.

Während das Skript läuft, erscheinen unter der Parameterliste Regler. Änderungen wirken sofort auf die laufende Ausgabe.

2D-Matrizen

Für LED-Matrizen liefern pos_x(i) und pos_y(i) die Position einer LED im Bild, jeweils von 0.0 bis 1.0 (pos_x: links → rechts, pos_y: oben → unten). Verkabelung, Startecke und Schlangenlinien-Führung der Matrix sind dabei schon berücksichtigt.

-- konzentrische Ringe
function render()
    for i = 0, led_count - 1 do
        local x = pos_x(i) - 0.5
        local y = pos_y(i) - 0.5
        local dist = sqrt(x * x + y * y)
        set_led_hsv(i, (dist * 720.0 - time * 90.0) % 360.0, 1.0, 1.0)
    end
end

Soll ein Skript auf Streifen und Matrizen funktionieren, prüfst du matrix_breite > 0 und rechnest bei Streifen mit i / led_count.

Auf Musik reagieren

Ist in den Einstellungen ein Mikrofon aktiviert, stehen Audiowerte zur Verfügung. Ohne Mikrofon sind sie 0.0 bzw. false – dein Skript läuft trotzdem.

Variable Bedeutung
audio_bass Bass (60–250 Hz), 0.0–1.0
audio_mids Mitten (250 Hz–2 kHz), 0.0–1.0
audio_highs Höhen (2–16 kHz), 0.0–1.0
audio_volume Gesamtlautstärke, 0.0–1.0
audio_beat true, wenn ein Beat erkannt wurde
-- Helligkeit folgt dem Bass
function render()
    for i = 0, led_count - 1 do
        local hue = (i / led_count * 360.0 + time * 30.0) % 360.0
        set_led_hsv(i, hue, 1.0, audio_bass * 0.8 + 0.2)
    end
end

Mathe-Funktionen

Die wichtigsten Funktionen stehen ohne math.-Präfix bereit:

Funktion Ergebnis
sin(x), cos(x), tan(x) Winkelfunktionen im Bogenmaß – eine volle Umdrehung ist 2 * pi
atan2(y, x) Winkel eines Punkts, −π bis π
sqrt(x), pow(x, y), abs(x) Wurzel, Potenz, Betrag
floor(x), ceil(x) ab- bzw. aufrunden
min(a, b), max(a, b) kleinerer bzw. größerer Wert
clamp(x, lo, hi) x auf den Bereich lo–hi begrenzen
lerp(a, b, t) zwischen a und b überblenden, t von 0.0 bis 1.0
fmod(x, y) Rest einer Division, wie x % y
pi 3.14159…

Alle weiteren Funktionen der Lua-Standardbibliothek, etwa math.random, kannst du ebenfalls nutzen.

Werte zwischen Frames merken

Variablen, die du außerhalb von render() anlegst, behalten ihren Wert von Frame zu Frame:

-- ein Lichtpunkt läuft über den Streifen
local position = 0.0

function render()
    position = position + delta * 30.0
    if position >= led_count then position = 0.0 end
    local p = floor(position)
    for i = 0, led_count - 1 do
        local bright = clamp(1.0 - abs(i - p) * 0.3, 0.0, 1.0)
        set_led_hsv(i, 40.0, 1.0, bright)
    end
end

Nutze für Bewegung delta statt einer festen Schrittweite pro Frame – so läuft der Effekt bei jeder Framerate gleich schnell.

Leistung bei vielen LEDs

Standardmäßig rechnet ein Skript auf einem Prozessorkern. Bei sehr vielen LEDs und aufwendigen Berechnungen pro Pixel kann die Framerate sinken. Dann schaltest du Mehrkern-Rendering ein:

  1. Setze ganz oben im Skript parallel = true.
  2. Laufe in der Schleife von led_start bis led_end - 1 statt von 0 bis led_count - 1. Für Positionen rechnest du weiter mit led_count.
parallel = true

function render()
    for i = led_start, led_end - 1 do
        set_led_hsv(i, (i / led_count * 360.0 + time * 60.0) % 360.0, 1.0, 1.0)
    end
end

Alternativ nimmt dir for_each_led(function(i) … end) die Schleife ab. Gut zu wissen:

  • Aufgeteilt wird erst ab 1024 LEDs, auf bis zu 8 Kerne. Darunter läuft das Skript normal auf einem Kern.
  • Jeder Kern hat seine eigenen Variablen. Werte, die du dir zwischen Frames merkst, sind nicht zwischen den Kernen geteilt.
  • Schreibt ein Kern auf LEDs außerhalb seines Bereichs, wird das ignoriert.

Weitere Tipps: Berechne Werte, die für alle LEDs gleich sind, vor der Schleife, und lege Tabellen außerhalb von render() an, statt sie in jedem Frame neu zu erzeugen.

Fehler finden

  • Validieren prüft den Code, ohne ihn auszuführen – etwa ob die Syntax stimmt und render() vorhanden ist.
  • Fehler erscheinen rot unter dem Editor, zum Beispiel [Laufzeit] Zeile 12: …. Die betroffene Zeile wird im Editor markiert.
  • Ein Laufzeitfehler, der in jedem Frame auftritt, wird nur einmal angezeigt. Mit Leeren setzt du die Liste zurück.

Häufige Ursachen: eine fehlende render()-Funktion, ein vergessenes end, oder eine Parameter-Variable ohne das Präfix param_.

Skripte weiterverwenden

  • Bibliothek: Gespeicherte Skripte findest du links unter Bibliothek. Ein Klick lädt sie in den Editor. Speichern überschreibt das geladene Skript direkt, Als neues Skript speichern legt eine Kopie an. Das Papierkorb-Symbol löscht ein Skript sofort und ohne Rückfrage.
  • Sequenzer: Im Sequenzer legst du ein Bibliotheks-Skript mit einem Klick als 5-Sekunden-Clip an der Abspielposition ab.
  • Presets: Ein neues Preset übernimmt alle gerade laufenden Effekte, also auch ein Skript, das du mit Ausführen gestartet hast.
  • Export und Import: Über .zfx exportierst du ein Skript als Effekt-Datei, Importieren lädt eine .zfx-Datei in den Editor.

Skripte mit KI schreiben

Der Button LLM kopiert eine vollständige Beschreibung der Script-API in die Zwischenablage. Füge sie in einen KI-Chat deiner Wahl ein und beschreibe den gewünschten Effekt – den erzeugten Code kopierst du in den Editor und startest ihn mit Ausführen.

Demo-Version

In der Demo ist die Script Engine voll nutzbar, Skripte dürfen aber höchstens 20 Zeilen lang sein. Die -- @params:-Zeile zählt mit, die aktuelle Länge zeigt der Editor oben an. Längere Skripte lassen sich weiter validieren und speichern, starten aber erst mit einer Lizenz. Der Sequenzer ist in der Demo nicht verfügbar.