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.
| 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 | – | ★☆☆ |
AmbiEncoder_64: X
AmbiEncoder_64: Y
AmbiEncoder_64: Z| 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) |
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.
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).
| 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 |
Beispiel: Klangquelle fliegt um den Hörer herum und kommt dabei näher (Spirale nach innen):
| 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 |
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)
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)
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")
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)