📐 Ü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)

💡 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

Parameter | Bereich | Modus | Automation-Prio
--- | --- | --- | ---
X | −1.0 … +1.0 | Kartesisch | ★★★ Hoch
Y | −1.0 … +1.0 | Kartesisch | ★★★ Hoch
Z | −1.0 … +1.0 | Kartesisch | ★★★ Hoch
Azimuth | 0 … 360° | Sphärisch | ★★☆
Elevation | −90 … +90° | Sphärisch | ★★☆
Distance/Radius | 0.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)

Muster | X | Y | Z | Anwendung
--- | --- | --- | --- | ---
Kreis horizontal | sin(t) | cos(t) | 0 | Rotierende Quelle auf Horizont
Kreis vertikal (sagittal) | 0 | cos(t) | sin(t) | Elevation-Loop vorne/hinten
Lemniskate (∞) | sin(t) | sin(2t)/2 | 0 | Pendelnde 8er-Figur
Spiral abwärts | sin(t)·r(t) | cos(t)·r(t) | 1→−1 | Herabfallender Sound
Zufalls-Walk | noise | noise | noise | Diffuse 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

Frequenzband | Wahrnehmung | Effekt
--- | --- | ---
~3 kHz Boost | Vorne / Frontal | Erhöhte Präsenz, Quelle «kommt auf dich zu»
~8 kHz Boost | Elevation oben | Quelle klingt erhöht / über dem Kopf
~10 kHz Notch | Hinten / Rückwärts | Charakteristischer «hinten»-Klang
~13 kHz Boost | Elevation + vorne | Extreme 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?

Situation | Empfehlung | Begründung
--- | --- | ---
Gleichmässige Rotation / Loop | Part A | Konstante Lautstärke, keine Färbung erwünscht
Fly-by, Vorbeiflug | Part B | Proximity-Effekt erzeugt realistische Nähe
Elektronische/abstrakte Musik | Part A | Blauert-Cues können als «unnatürlich» stören
Field Recordings, Foley | Part B | Realistische akustische Distanz-Simulation
Binaural-Rendering (Kopfhörer) | Part B + Blauert | HRTF-Cues essentiell für Elevation-Wahrnehmung
Lautsprecher-Array > 8 Ch | Part A | Elevation 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.