ICST AmbiEncoder_64 + REAPER Automation
Tutorial für Composer & Sound Engineers

Workshop 2026 · ZHdK ICST
📐 Übersicht
🔵 Part A – XYZ ohne Distanz
🟠 Part B – Distanz + Blauert
⚙️ Lua / Python Scripts
Plugin-Architektur

Was macht der AmbiEncoder_64?

Der ICST AmbiEncoder_64 codiert eine Mono-Quelle in Ambisonics B-Format bis zur 7. Ordnung (64 Kanäle). Er unterstützt zwei Koordinaten-Modi sowie optionale Distanz-Enkodierung mit psychoakustischer Filterung.

Mono-Quelle
REAPER-Track
AmbiEncoder_64
VST3 / AU
B-Format
bis 64 Kanäle
Decoder
Speaker / Bino

Koordinaten-Modi im Vergleich

🔵 Sphärisch (Az/El/R)
Azimuth 0–360° (horizontal)
Elevation −90° bis +90°
Radius 0.0 – ∞ (optional)

✓ Intuitiv für Panoramierung
✓ Konstante Distanz leicht haltbar
✗ Gimbal Lock an Polen (±90°)
🟠 Kartesisch (X/Y/Z)
X links (−1) → rechts (+1)
Y hinten (−1) → vorne (+1)
Z unten (−1) → oben (+1)

✓ Kontinuierliche Automation
✓ Keine Singularitäten
✓ Distanz = √(X²+Y²+Z²)

XYZ-Konvention (ICST)

X Y Z rechts vorne oben 👂 r = 1
💡 Bei XYZ-Automation auf der Einheitskugel (r=1) bleibt die Distanz konstant. Formel: X=sin(az)·cos(el), Y=cos(az)·cos(el), Z=sin(el)

Alle automatisierbaren Parameter

ParameterBereichModusAutomation-Prio
X−1.0 … +1.0Kartesisch★★★ Hoch
Y−1.0 … +1.0Kartesisch★★★ Hoch
Z−1.0 … +1.0Kartesisch★★★ Hoch
Azimuth0 … 360°Sphärisch★★☆
Elevation−90 … +90°Sphärisch★★☆
Distance/Radius0.0 … 1.0+Beide★★★ Part B
Gain−inf … +12 dB★☆☆
Blauert Filter (on/off)0/1★☆☆
A
XYZ-Automation ohne Distanz-Enkodierung
Klangquelle bewegt sich auf der Einheitskugel – konstante Lautstärke, kein Proximity-Effekt

A.1 – Plugin konfigurieren (einmalig)

  • 1.
    AmbiEncoder_64 auf dem Mono-Track als Insert hinzufügen (VST3 empfohlen).
  • 2.
    Koordinaten-Modus → XYZ wählen (im Plugin-GUI oben links).
    Im XYZ-Modus sind Azimuth/Elevation-Regler ausgegraut. X, Y, Z werden aktiv.
  • 3.
    Distance / Near-Field Compensation → OFF (Schalter deaktivieren oder Radius-Bypass aktivieren).
    Wenn Distance aktiv bleibt und Radius < 1.0 ist, wird Proximity-Effekt angewendet – in Part A nicht erwünscht.
  • 4.
    Blauert Filter → OFF für diesen Workflow.
  • 5.
    Ambisonics-Ordnung wählen (3. Ordnung = 16 Kanäle empfohlen für Workshop-Setup).

A.2 – Startposition manuell setzen

X (links/rechts)
0.0 = Mitte
−1.0 = links, +1.0 = rechts
Y (vorne/hinten)
+1.0 = vorne
−1.0 = hinten, +1.0 = vorne
Z (oben/unten)
0.0 = Horizont
−1.0 = Boden, +1.0 = Decke
Einheitskugel Check
√(X²+Y²+Z²) = 1
Solange Betrag = 1, bleibt Lautstärke konstant
💡 Startempfehlung: X=0, Y=1, Z=0 → direkt vorne auf Hörhorizonthöhe

A.3 – Automation-Lanes in REAPER anlegen

  • 1.
    Track auswählen → A-Button (Automation Lanes) anklicken oder Cmd+Shift+A.
  • 2.
    Im Automation-Dialog: Add/Remove Envelopes → Plugin-Parameter suchen:
    AmbiEncoder_64: X
    AmbiEncoder_64: Y
    AmbiEncoder_64: Z
  • 3.
    Alle drei aktivieren → jedes bekommt eine farbige Automation-Spur unter dem Track.
  • 4.
    Farben zuweisen (Rechtsklick auf Envelope → Set color):
    X = Blau · Y = Grün · Z = Rot — entspricht Konvention oben.
REAPER zeigt Automation-Envelopes nur wenn der Track-Höhe aufgezogen ist (mindestens 80px empfohlen).

A.4 – Workflow: Automation effizient schreiben

Methode 1: Real-Time Write
  1. Automation Mode → Write
  2. Playback starten
  3. X/Y/Z-Regler im Plugin live bewegen
  4. REAPER zeichnet Envelope-Kurve auf
  5. Danach Modus → Read
✓ Ideal für organische, gespielte Bewegungen
Methode 2: Envelope-Punkte zeichnen
  1. Automation Lane im Arrange sichtbar
  2. Pencil-Tool (D-Taste) aktivieren
  3. Punkte klicken, Kurven ziehen
  4. Kurventyp: Rechtsklick → Linear / Bezier / Square
✓ Ideal für präzise, geometrische Trajektorien
⚠ Write-Modus überschreibt vorhandene Automation. Backup mit Ctrl+Z sofort möglich.

A.5 – Typische Trajektorie-Muster (ohne Distanz)

MusterXYZAnwendung
Kreis horizontalsin(t)cos(t)0Rotierende Quelle auf Horizont
Kreis vertikal (sagittal)0cos(t)sin(t)Elevation-Loop vorne/hinten
Lemniskate (∞)sin(t)sin(2t)/20Pendelnde 8er-Figur
Spiral abwärtssin(t)·r(t)cos(t)·r(t)1→−1Herabfallender Sound
Zufalls-WalknoisenoisenoiseDiffuse Bewegung (per Script)
Alle Formeln als Lua-Script im Tab «Scripts» → automatische Envelope-Generierung ohne Echtzeit-Recording.

A.6 – Effizienz-Tipps für XYZ-Editing

  • Envelope-Scaling: Alle Punkte einer Lane markieren → V → Scale Envelope Points (z.B. Amplitude halbieren ohne neu zeichnen)
  • Copy-Paste zwischen Lanes: X-Envelope kopieren, in Z einfügen, um gleiche Kurve auf anderer Achse zu nutzen
  • Automation Items: Längen-unabhängige Automation-Clips anlegen (Loop, Reverse, Speed). Rechtsklick auf Lane → Create Automation Item
  • SWS Envelope-Actions: SWS: Create sine envelope in der Action List → automatisch sinusförmige Kurve generieren
  • Trim-Modus: Bestehende Automation nur verschieben (nicht überschreiben) → Automation Mode → Trim/Read
B
XYZ mit Distanz-Enkodierung & Blauert-Filter
Near-field Proximity + psychoakustische Elevation-Cues für realistische Raumwahrnehmung

B.1 – Distanz-Enkodierung: Konzept

Bei kleinem Radius (< 1.0) aktiviert der AmbiEncoder den Near-Field Compensation Filter: Bassfrequenzen werden angehoben (Proximity-Effekt), höhere Ordnungen werden im Pegel angepasst, um physikalisch korrekte Wellenfront-Krümmung zu simulieren.

Radius = 1.0
Fernfeld
Ebene Wellenfront, kein Proximity
Radius = 0.5
Mittelfeld
Leichte Bassbetonung, spürbare Nähe
Radius < 0.2
Nahfeld
Starker Proximity-Effekt, sehr präsent
⚠ Sehr kleine Radius-Werte (< 0.1) können Übersteuerung und instabiles Verhalten erzeugen. Gain-Kompensation notwendig.

B.2 – Blauert-Filter: Was er macht

Der Blauert-Filter (nach Jens Blauert, «Spatial Hearing», 1983/1997) fügt richtungsabhängige Frequenz-Einfärbungen hinzu, die das menschliche Ohr als Elevation-Cues interpretiert. Diese Cues kommen normalerweise von der Ohrmuschel (HRTF-Pinna-Effekt).

Blauert's Bänder – frequenzabhängige Elevation-Codierung
200 Hz1 kHz3 kHz↑ front 8 kHz↑ elev 10 kHz↑ back 13 kHz↑ elev16 kHz
FrequenzbandWahrnehmungEffekt
~3 kHz BoostVorne / FrontalErhöhte Präsenz, Quelle «kommt auf dich zu»
~8 kHz BoostElevation obenQuelle klingt erhöht / über dem Kopf
~10 kHz NotchHinten / RückwärtsCharakteristischer «hinten»-Klang
~13 kHz BoostElevation + vorneExtreme Höhe, «Decken»-Wahrnehmung
💡 Blauert-Filter ist besonders wirksam bei Lautsprecherwiedergabe und Kopfhörern. Bei Mehrkanal-Lautsprechern ohne HRTF-Decoder ist der Effekt subtiler.

B.3 – Plugin konfigurieren für Part B

  • 1.
    XYZ-Modus aktiv lassen (wie Part A).
  • 2.
    Near-Field Compensation → ON (Distance-Enkodierung aktivieren).
  • 3.
    Blauert Filter → ON. Optional: Blauert-Gain anpassen (Stärke des Effekts, 0–100%).
  • 4.
    Radius / Distance Startposition: empfohlen 0.8–1.0 (nicht direkt 0.0 → Clipping-Risiko).
  • 5.
    Gain-Kompensation einrichten: Bei Radius-Automation Gain-Envelope gegenläufig anlegen (näherkommen → Gain runter), damit wahrgenommene Lautstärke konstant bleibt.
⚠ Ohne Gain-Kompensation klingt eine Quelle die von r=1.0 auf r=0.1 bewegt wird ~20 dB lauter. Für dramatische Effekte gewollt – für subtile Bewegungen kompensieren.

B.4 – Distanz-Automation in REAPER

  • 1.
    Automation-Lane hinzufügen: AmbiEncoder_64: Radius (oder «Distance»)
  • 2.
    Gleichzeitig AmbiEncoder_64: Gain als Kompensations-Lane anlegen.
  • 3.
    Typisches Fly-By-Muster:
    t=0s: X=0, Y=1.0, Radius=1.0, Gain=0dB
    t=2s: X=0, Y=0.2, Radius=0.2, Gain=−14dB  ← Vorbeiflug-Nähe
    t=4s: X=0, Y=−1.0, Radius=1.0, Gain=0dB
  • 4.
    Kurventyp für Radius: Bezier → weicher Ein- und Ausklang der Nähewahrnehmung.

B.5 – Kombinierter XYZ + Distanz Workflow

Beispiel: Klangquelle fliegt um den Hörer herum und kommt dabei näher (Spirale nach innen):

Automation Lanes
X = sin(t) · r(t)
Y = cos(t) · r(t)
Z = 0 (oder leichte Elevation)
Radius = 1.0 → 0.3 (abnehmend)
Gain = Kompensation (gegenläufig)
Lua-Script (→ Scripts-Tab)
Das Script spiral_approach.lua im Scripts-Tab generiert diese Automation vollautomatisch in alle Lanes.
💡 Blauert-Filter verstärkt den Effekt: Bei frontaler Annäherung (Y steigt) klingen die 3kHz-Boost-Cues natürlicher und die Quelle wird als «wirklich näherkommen» wahrgenommen.

B.6 – Part A vs Part B: Wann was benutzen?

SituationEmpfehlungBegründung
Gleichmässige Rotation / LoopPart AKonstante Lautstärke, keine Färbung erwünscht
Fly-by, VorbeiflugPart BProximity-Effekt erzeugt realistische Nähe
Elektronische/abstrakte MusikPart ABlauert-Cues können als «unnatürlich» stören
Field Recordings, FoleyPart BRealistische akustische Distanz-Simulation
Binaural-Rendering (Kopfhörer)Part B + BlauertHRTF-Cues essentiell für Elevation-Wahrnehmung
Lautsprecher-Array > 8 ChPart AElevation kommt vom Decoder, kein Blauert nötig
Lua & Python Automation Scripts
Envelope-Generatoren für REAPER – direkt in die Action List laden
Alle Scripts via Actions → Load ReaScript in REAPER laden. Datei im Bundle-Ordner speichern.

Script 1 – Kreisbewegung auf Einheitskugel (Part A)

Schreibt X/Y-Envelope für eine Rotation auf dem Hörhorizont. Z bleibt 0. Kein Distanzeffekt.

-- ICST_AmbiEncoder_Circle_XY.lua
-- Erzeugt X/Y-Kreisbahn auf dem Hörhorizont (Z=0)
-- Part A – keine Distanz-Enkodierung

local TWO_PI = 2 * math.pi

-- ── Parameter ──────────────────────────────────
local DURATION_SEC = 8     -- Gesamtdauer der Kreisbewegung
local ROTATIONS   = 1     -- Anzahl vollständige Umdrehungen
local POINTS      = 200   -- Envelope-Auflösung
local START_ANG   = 0     -- Startwinkel (0 = vorne)

-- ── Hilfsfunktionen ─────────────────────────────
local function getEnvByName(track, name)
  for i = 0, reaper.TrackFX_GetCount(track) - 1 do
    for p = 0, reaper.TrackFX_GetNumParams(track, i) - 1 do
      local _, pname = reaper.TrackFX_GetParamName(track, i, p, "")
      if pname:lower():find(name:lower()) then
        return reaper.GetFXEnvelope(track, i, p, true)
      end
    end
  end
  return nil
end

local function fillEnvelope(env, times, vals)
  reaper.DeleteEnvelopePointRange(env, times[1] - 0.001, times[#times] + 0.001)
  for i, t in ipairs(times) do
    reaper.InsertEnvelopePoint(env, t, vals[i], 0, 0, false, true)
  end
  reaper.Envelope_SortPoints(env)
end

-- ── Haupt-Routine ────────────────────────────────
local track = reaper.GetSelectedTrack(0, 0)
if not track then
  reaper.ShowMessageBox("Bitte einen Track auswählen!", "Fehler", 0)
  return
end

local envX = getEnvByName(track, "param_x")  -- Passe Namen an dein Plugin an
local envY = getEnvByName(track, "param_y")
if not envX or not envY then
  reaper.ShowMessageBox("X oder Y Envelope nicht gefunden.\nParameter-Name prüfen.", "Fehler", 0)
  return
end

local cursorPos = reaper.GetCursorPosition()
local times, xs, ys = {}, {}, {}

for i = 0, POINTS do
  local frac = i / POINTS
  local t    = cursorPos + frac * DURATION_SEC
  local ang  = START_ANG + frac * ROTATIONS * TWO_PI
  times[i+1] = t
  xs[i+1]    = math.sin(ang)   -- X = links/rechts
  ys[i+1]    = math.cos(ang)   -- Y = vorne/hinten
end

reaper.Undo_BeginBlock()
fillEnvelope(envX, times, xs)
fillEnvelope(envY, times, ys)
reaper.Undo_EndBlock("ICST: Kreisbewegung X/Y", -1)

reaper.ShowMessageBox(
  string.format("✓ Kreisbewegung geschrieben\n%d Punkte · %.1f s · %d Rotation(en)",
    POINTS, DURATION_SEC, ROTATIONS),
  "ICST AmbiEncoder", 0)

Script 2 – Spiralförmige Annäherung mit Distanz (Part B)

Schreibt X/Y/Radius-Envelopes: Quelle rotiert und kommt gleichzeitig näher. Gain-Kompensation optional.

-- ICST_AmbiEncoder_Spiral_Approach.lua
-- Spiralbahn: Rotation + Annäherung
-- Part B – mit Distanz-Enkodierung

local TWO_PI = 2 * math.pi

-- ── Parameter ──────────────────────────────────
local DURATION_SEC  = 12    -- Gesamtdauer
local ROTATIONS    = 2     -- Umdrehungen während Annäherung
local RADIUS_START = 1.0  -- Startdistanz (Fernfeld)
local RADIUS_END   = 0.25 -- Enddistanz (Nahfeld)
local GAIN_COMP    = true  -- Lautstärke kompensieren?
local POINTS       = 300

-- ── Envelope-Lookup ─────────────────────────────
local function getFXEnv(track, paramSubstr)
  for i = 0, reaper.TrackFX_GetCount(track)-1 do
    for p = 0, reaper.TrackFX_GetNumParams(track,i)-1 do
      local _, n = reaper.TrackFX_GetParamName(track, i, p, "")
      if n:lower():find(paramSubstr:lower()) then
        return reaper.GetFXEnvelope(track, i, p, true), i, p
      end
    end
  end
end

local function writeEnv(env, tArr, vArr)
  reaper.DeleteEnvelopePointRange(env, tArr[1]-.01, tArr[#tArr]+.01)
  for i = 1, #tArr do
    reaper.InsertEnvelopePoint(env, tArr[i], vArr[i], 0, 0, false, true)
  end
  reaper.Envelope_SortPoints(env)
end

local track = reaper.GetSelectedTrack(0, 0)
if not track then reaper.ShowMessageBox("Track auswählen!","",0); return end

local envX = getFXEnv(track, "param_x")
local envY = getFXEnv(track, "param_y")
local envR = getFXEnv(track, "radius")
local envG = GAIN_COMP and getFXEnv(track, "gain")

local cursor = reaper.GetCursorPosition()
local tArr, xArr, yArr, rArr, gArr = {},{},{},{},{}

for i = 0, POINTS do
  local frac = i / POINTS
  local t    = cursor + frac * DURATION_SEC
  local ang  = frac * ROTATIONS * TWO_PI
  local r    = RADIUS_START + (RADIUS_END - RADIUS_START) * frac

  tArr[i+1] = t
  xArr[i+1] = math.sin(ang) * r
  yArr[i+1] = math.cos(ang) * r
  rArr[i+1] = r
  -- Gain-Kompensation: ~6dB pro Halbierung der Distanz
  gArr[i+1] = -20 * math.log(r / RADIUS_START, 10) -- in dB
end

reaper.Undo_BeginBlock()
if envX then writeEnv(envX, tArr, xArr) end
if envY then writeEnv(envY, tArr, yArr) end
if envR then writeEnv(envR, tArr, rArr) end
if envG and GAIN_COMP then writeEnv(envG, tArr, gArr) end
reaper.Undo_EndBlock("ICST: Spiral Approach", -1)

Script 3 – Python: Echtzeit OSC Automation (extern)

Sendet XYZ-Daten direkt per OSC an Max/AmbiEncoder – ohne REAPER-Envelope. Nützlich für Live-Generierung.

#!/usr/bin/env python3
# ICST_live_xyz_osc.py
# Sendet Kreisbewegung als OSC-Pakete an localhost:50010
# Benötigt nur Python stdlib

import socket, struct, time, math

## ── Konfiguration ──────────────────────────────
HOST     = "127.0.0.1"
PORT     = 50010
OSC_ADDR = "/ambi/xyz"     # Anpassen an Max-Route
FPS      = 30               # Pakete pro Sekunde
DURATION = 10              # Sekunden
ROTATIONS= 2               # Umdrehungen

## ── OSC Helpers ────────────────────────────────
def osc_pad(d):
    return d + b'\0' * ((4 - (len(d) % 4)) % 4)

def osc_str(s):
    return osc_pad(s.encode() + b'\0')

def osc_packet_fff(address, x, y, z):
    """OSC message mit 3 Float-Argumenten"""
    return (
        osc_str(address) +
        osc_str(",fff") +
        struct.pack(">fff", x, y, z)
    )

## ── Hauptschleife ──────────────────────────────
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
total_frames = int(DURATION * FPS)

print(f"Starte XYZ-OSC · {FPS} fps · {DURATION}s · Port {PORT}")

for frame in range(total_frames):
    frac = frame / total_frames
    ang  = frac * ROTATIONS * 2 * math.pi

    x = math.sin(ang)
    y = math.cos(ang)
    z = 0.0               # Für Part B: z = math.sin(frac*math.pi)
    r = 1.0               # Für Part B: r = 1.0 - 0.7*frac

    pkt = osc_packet_fff(OSC_ADDR, x*r, y*r, z*r)
    sock.sendto(pkt, (HOST, PORT))
    time.sleep(1.0 / FPS)

sock.close()
print("✓ Fertig")
💡 Für Part B: Zeile r = 1.0 - 0.7*frac einkommentieren → Radius schrumpft von 1.0 auf 0.3.

Script 4 – Parameter-Namen des AmbiEncoder auslesen

Listet alle FX-Parameter des selektierten Tracks in der REAPER Console – damit die richtigen Namen für Scripts 1–2 bekannt sind.

-- ICST_list_params.lua
-- Zeigt alle FX-Parameter des selektierten Tracks

local track = reaper.GetSelectedTrack(0, 0)
if not track then reaper.ShowConsoleMsg("Kein Track selektiert!\n"); return end

local out = "FX-Parameter:\n" .. string.rep("-", 50) .. "\n"
for fx = 0, reaper.TrackFX_GetCount(track)-1 do
  local _, fxName = reaper.TrackFX_GetFXName(track, fx, "")
  out = out .. string.format("\n[FX %d] %s\n", fx, fxName)
  for p = 0, reaper.TrackFX_GetNumParams(track, fx)-1 do
    local _, pn = reaper.TrackFX_GetParamName(track, fx, p, "")
    local val    = reaper.TrackFX_GetParam(track, fx, p)
    out = out .. string.format("  [%2d] %-30s = %.4f\n", p, pn, val)
  end
end
reaper.ShowConsoleMsg(out)
Output erscheint in REAPER Console (Ctrl+Alt+R). Die Parameternamen dort in Scripts 1 & 2 eintragen.