Anleitung

Eigene Module programmieren

Mit einer kleinen Skriptsprache baust du eigene Module für den Analog Modular Simulator: Regler, Schalter, Buchsen und LEDs deklarieren, die Signalverarbeitung pro Sample beschreiben – fertig ist ein Modul, das im Rack klingt wie jedes andere.

Für KI-Assistenten: Die Datei AGENTS.md beschreibt die komplette Sprache mit allen Funktionen, Grenzen und Beispielen – gib sie deinem Assistenten, und er schreibt direkt lauffähige Module.

AGENTS.md herunterladen

Schnellstart

Ein eigenes Modul ist eine Textdatei mit der Endung .amsmod. Sie lässt sich in jedem Texteditor schreiben.

  1. Lade das Beispiel fold.amsmod herunter oder kopiere den Code unten in eine neue Datei.
  2. Öffne den Simulator und wähle Datei → Eigene Module laden … (oder in der Modulbibliothek unten „Eigene Module laden …“).
  3. Wähle die Datei. Jedes Modul erscheint als Vorschau mit laufenden LEDs – mit „Hinzufügen“ landet es im Rack und unter „Eigene Module“ in der Bibliothek.
// Wavefolder with a level meter – an example custom module for the Analog Modular Simulator.
module fold "Fold" hp 6 theme black {
  desc de "Wavefolder: faltet die Welle je nach FOLD mehrfach um – aus einem Sinus wird ein obertonreicher Klang."
  desc en "Wavefolder: folds the wave over itself depending on FOLD – a sine turns into a rich, buzzy tone."

  knob   amount "FOLD" 0..1 = 0.3
  knob   depth  "CV"   0..1 = 0.5 size s tip "CV amount"
  switch mode   "MODE" "SIN" "TRI" = 0
  in     x      "IN"   audio
  in     cv     "CV"   cv
  out    y      "OUT"  audio
  led    hot    red
  meter  level  "LEVEL"

  process {
    g = 1 + 4 * clamp(amount + cv / 10 * depth, 0, 1)
    s = x / AUDIO * g
    y = (mode == 0 ? sin(s * PI / 2) : fold(s, -1, 1)) * AUDIO
    hot = abs(y) > 4.5
    level = peak(y)
  }
}
fold.amsmod herunterladen

Aufbau einer Datei

Eine Datei enthält einen oder mehrere Blöcke module id "Name" { … }. Jeder Block wird ein eigenes Modul; die id (Kleinbuchstaben, Ziffern, _) ist seine feste Kennung, der Name steht auf der Frontplatte.

Im Block stehen die Deklarationen – je eine pro Zeile –, eigene Funktionen (fn) und genau ein process { … }. Kommentare beginnen mit // oder #. Innerhalb runder und eckiger Klammern darf eine Zeile umbrechen.

Reservierte Namen

Schlüsselwörter, Funktionen und Konstanten sind reserviert und können kein Name sein – ein Eingang heißt also nicht voct, hz oder gate. Neben allen Funktionen der Referenz sind das:

module desc knob switch button in out led meter state buffer const process fn for return if else and or not true false PI TAU E C4 AUDIO GATE ENV SR

Bedienelemente und Buchsen

Jede Deklaration bekommt eine id (der Name im Code) und meist eine Beschriftung in Anführungszeichen (höchstens 8 Zeichen, wie bei echter Hardware meist in Großbuchstaben). Mit [n] hinter der id wird daraus ein Array aus n gleichen Elementen – siehe Schleifen und Arrays.

  • knob id "LABEL" min..max = start – Drehregler. Der Wert hat die Einheit des Reglers und wird geglättet. Optionen: log (logarithmisch, min > 0), int (ganzzahlig, ungeglättet), unit hz|s|ms|db|pct|v|st|bpm (Anzeige im Tooltip; pct erwartet 0..1), size s|m|l, tip "Tooltip", at x y, skin """<svg>…""".
  • switch id "TITEL" "A" "B" ["C"] = 0 – Kippschalter mit 2 oder 3 Stellungen; der Wert ist 0, 1 oder 2.
  • button id "LABEL" = off – rastender Taster mit LED; der Wert ist ein Wahrheitswert (true/false).
  • in id "LABEL" audio|pitch|gate|cv – Eingangsbuchse, Wert in Volt, 0 ohne Kabel. Die Signalart wählt nur das Testsignal der Vorschau. Mit normal andere liest ein leerer Eingang den Wert eines anderen Eingangs.
  • out id "LABEL" audio|pitch|gate|env|mod|cv – Ausgangsbuchse in Volt. Ein Ausgang behält seinen Wert, bis er neu zugewiesen wird. Die Signalart erscheint im Tooltip am Stecker; via a, b statt der Art reicht die Art von Eingängen durch.
  • led id red|green|yellow|blue – LED, Helligkeit 0..1 oder ein Wahrheitswert; sie leuchtet etwa 150 ms nach, damit auch kurze Impulse sichtbar sind.
  • meter id "LABEL" – Pegelkette aus sechs LEDs wie am Output-Modul, Wert 0..1 – meist level = peak(signal).

Zustand, Puffer, Konstanten

  • state id = 0 – ein Wert, der von Sample zu Sample erhalten bleibt (Zahl oder Wahrheitswert). Lokale Variablen gelten dagegen nur im aktuellen Sample. Als Array: state id[8] = 0 oder mit Liste state id[] = [0, 1, 0, 1].
  • buffer id 500 ms – Verzögerungsleitung für write und read/readi; höchstens vier Puffer, zusammen 8 Sekunden.
  • const id = 2 * PI – Konstante, die beim Übersetzen ausgerechnet wird (Zahlen, Konstanten und reine Funktionen). Als Tabelle: const skala[] = [0, 2, 4, 7, 9].

Der process-Block

process { … } läuft einmal pro Sample – bei 48 kHz also 48 000-mal je Sekunde. Er besteht aus Zuweisungen (=, +=, -=, *=, /=, auch an Array-Elemente a[i] = … und als x, y = f(…)), if … { } else { }, for i in a..b { } und write(puffer, wert). Jedes Modul braucht pro Sample eine feste, begrenzte Rechenzeit – deshalb haben Schleifen eine feste Anzahl Durchläufe.

Eine lokale Variable entsteht mit ihrer ersten Zuweisung und darf erst gelesen werden, wenn sie auf jedem Weg zugewiesen ist – ein if ohne else reicht nicht. Werte über mehrere Samples gehören in einen state.

Es gibt zwei Typen: Zahlen und Wahrheitswerte. Sie mischen sich nie von selbst. Bedingungen brauchen Wahrheitswerte (x > 1, high(x)), Rechnungen Zahlen: num(b) macht 0/1, gate(b) 0/10 V. An einen Ausgang gehört immer eine Zahl.

if und bedingung ? a : b rechnen nur den gewählten Zweig. and und or werten dagegen immer beide Seiten aus, damit rise(a) and rise(b) keine Flanke verpasst.

Wie jedes Modul im Rack sieht ein eigenes Modul seine Eingänge ein Sample verzögert – Rückkopplungen über Kabel sind erlaubt. Sichere Mathematik: Division durch 0 ergibt 0, ungültige Werte an Ausgängen werden zu 0, Ausgänge sind auf ±100 V begrenzt.

Operatoren

Von stark nach schwach bindend: Klammern und Aufrufe · ^ (Potenz) · Vorzeichen - + · * / % · + - · Vergleiche < <= > >= == != (nicht verkettbar) · not bzw. ! · and bzw. && · or bzw. || · ? :.

Spannungen und Konstanten

Wie im echten Eurorack: Audio ±5 V, Hüllkurven 0 bis 10 V, Gates und Trigger 0/10 V (ein Gate gilt ab 1 V als an und unter 0,1 V als aus), Tonhöhe 1 V pro Oktave mit 0 V = C4 (261,63 Hz).

Die eingebauten Generatoren liefern normierte Werte (±1 bzw. 0…1) – in Volt rechnet man erst an der Buchse: y = saw(f) * AUDIO. Zeiten stehen in Sekunden (pulse in Millisekunden), Frequenzen in Hz.

Konstanten: PI, TAU, E, C4 (261,6256), AUDIO (5), GATE und ENV (10), SR (Abtastrate zur Laufzeit), true, false.

Funktionen

Argumente mit Standardwert dürfen fehlen. num steht für eine Zahl, bool für einen Wahrheitswert, buffer und jack für den Namen eines Puffers bzw. einer Buchse (Ein- oder Ausgang).

Mathe

abs(x: num) → num
Betrag.
sign(x: num) → num
Vorzeichen: −1, 0 oder 1.
floor(x: num) → num
Abrunden.
ceil(x: num) → num
Aufrunden.
round(x: num) → num
Runden (halbe Werte von 0 weg).
frac(x: num) → num
Nachkommaanteil x − floor(x), immer 0…1.
sqrt(x: num) → num
Quadratwurzel; negative Werte ergeben 0.
exp(x: num) → num
e hoch x (x höchstens 80).
log(x: num) → num
Natürlicher Logarithmus; Werte ≤ 0 zählen wie 1e-30.
log2(x: num) → num
Logarithmus zur Basis 2.
exp2(x: num) → num
2 hoch x.
sin(x: num) → num
Sinus (Bogenmaß).
cos(x: num) → num
Kosinus (Bogenmaß).
tan(x: num) → num
Tangens (Bogenmaß), begrenzt auf ±1 000 000.
tanh(x: num) → num
Weiche Sättigung, ab |x| = 3 genau ±1.
atan(x: num) → num
Arkustangens.
atan2(y: num, x: num) → num
Winkel des Punkts (x, y) in −π…π.
min(a: num, b: num) → num
Kleinerer Wert.
max(a: num, b: num) → num
Größerer Wert.
clamp(x: num, lo: num, hi: num) → num
Wert auf lo…hi begrenzen.
mix(a: num, b: num, t: num) → num
Überblendung: a + (b − a) · t (t = 0 → a, t = 1 → b).
mod(a: num, b: num) → num
Rest der Division, abgerundet (auch für negative Zahlen positiv bei positivem b).
pow(a: num, b: num) → num
Potenz a^b; negative Basis mit gebrochenem Exponenten wird gespiegelt.
fold(x: num, lo: num = -5, hi: num = 5) → num
Wavefolding: faltet x zwischen lo und hi hin und her.
wrap(x: num, lo: num = 0, hi: num = 1) → num
Umbruch: x läuft in lo…hi im Kreis.
softclip(x: num, lim: num = 5) → num
Weiche Begrenzung auf ±lim (linear bis 80 %).

Umrechnung

hz(v: num) → num
Tonhöhe in V/Oct → Frequenz in Hz (0 V = C4), dieselbe Stimmung wie der VCO.
voct(f: num) → num
Frequenz in Hz → V/Oct.
semi(v: num) → num
Auf Halbtöne runden (1/12 V).
dbamp(db: num) → num
Dezibel → Verstärkungsfaktor.
ampdb(x: num) → num
Verstärkungsfaktor → Dezibel.
gate(b: bool) → num
Wahrheitswert → 0 oder 10 V (für Gate-Ausgänge).
num(b: bool) → num
Wahrheitswert → 0 oder 1.
connected(jack: jack) → bool
Ist an dieser Buchse ein Kabel gesteckt? Geht für Eingänge und Ausgänge – ein ungesteckter Ausgang kann so in einen internen Bus gehen wie die Ausgänge 1–4 des Calculus.

Oszillatoren und Zufall

phasor(hz: num, reset: bool = false) → num
Phase 0…1 mit hz Durchläufen pro Sekunde (taktstabil in f64); reset setzt sie auf 0 (Hard-Sync).
sine(hz: num, reset: bool = false) → num
Sinus-Oszillator ±1.
tri(hz: num, reset: bool = false) → num
Dreieck-Oszillator ±1.
saw(hz: num, reset: bool = false) → num
Sägezahn ±1, bandbegrenzt (PolyBLEP); reset = Hard-Sync.
square(hz: num, pw: num = 0.5, reset: bool = false) → num
Rechteck ±1 mit Pulsbreite pw (0,02…0,98), bandbegrenzt; reset = Hard-Sync.
noise() → num
Weißes Rauschen ±1.
random(trig: bool) → num
Zufallswert 0…1, neu bei jedem trig.

Trigger, Gates, Zähler

rise(x: num) → bool
Steigende Flanke: genau im Sample, in dem x über 1 V steigt (Schmitt-Trigger 1 V / 0,1 V).
fall(x: num) → bool
Fallende Flanke: genau im Sample, in dem x unter 0,1 V fällt.
high(x: num) → bool
Pegel mit Hysterese: an ab 1 V, aus unter 0,1 V.
changed(x: num) → bool
Wahr, wenn x sich seit dem letzten Sample geändert hat.
prev(x: num) → num
Wert von x im vorigen Sample.
pulse(trig: bool, ms: num = 5) → bool
Impuls von ms Millisekunden ab trig (nachtriggerbar) – für Trigger-Ausgänge und LEDs.
counter(up: bool, reset: bool = false, n: num = 0) → num
Zählt bei jedem up hoch, reset setzt auf 0; mit n ≥ 1 läuft er im Kreis 0…n−1.
every(trig: bool, n: num) → bool
Wahr beim 1., (n+1)., (2n+1). … Trigger – ein Takt-Teiler.
toggle(trig: bool) → bool
Kippt bei jedem Trigger zwischen falsch und wahr (Flipflop).
latch(set: bool, reset: bool) → bool
Wird bei set wahr und bei reset falsch (reset gewinnt).
sah(x: num, trig: bool) → num
Sample & Hold: übernimmt x bei jedem Trigger und hält den Wert.
timer(trig: bool) → num
Sekunden seit dem letzten Trigger.

Hüllkurven und Glätten

ad(trig: bool, a: num, d: num) → num
Hüllkurve 0…1: steigt in a Sekunden, fällt in d Sekunden; neuer Trigger startet ab dem aktuellen Pegel.
adsr(gate: bool, a: num, d: num, s: num, r: num) → num
Hüllkurve 0…1 mit Gate (Wahrheitswert): Attack, Decay, Sustain (0…1), Release – Zeiten für den vollen Hub.
lag(x: num, s: num) → num
Weiches Folgen mit Zeitkonstante s Sekunden (Einpol-Tiefpass) – Glide, Glättung.
slew(x: num, up: num, down: num = up) → num
Begrenzt die Änderung auf up bzw. down Volt pro Sekunde (lineares Gleiten).
smooth(x: num) → num
Glättung um 15 ms gegen Knistern.
peak(x: num) → num
Spitzenwert für Pegelketten: |x|/5, steigt sofort, fällt in etwa 300 ms.

Filter

lp1(x: num, hz: num) → num
Tiefpass 1. Ordnung (6 dB/Oktave).
hp1(x: num, hz: num) → num
Hochpass 1. Ordnung (6 dB/Oktave).
lp(x: num, hz: num, res: num = 0) → num
Tiefpass 2. Ordnung (State-Variable, 12 dB/Oktave); res 0…1 betont die Grenzfrequenz.
bp(x: num, hz: num, res: num = 0) → num
Bandpass 2. Ordnung.
hp(x: num, hz: num, res: num = 0) → num
Hochpass 2. Ordnung.
notch(x: num, hz: num, res: num = 0) → num
Kerbfilter 2. Ordnung.
dcblock(x: num) → num
Entfernt Gleichspannung (Hochpass 20 Hz).

Verzögerung

read(buf: buffer, s: num) → num
Wert des Puffers von vor s Sekunden (interpoliert, mindestens 1 Sample, höchstens die Pufferlänge).
readi(buf: buffer, n: num) → num
Wert des Puffers von vor n ganzen Samples.
write(buf: buffer, x: num)
Schreibt x in den Puffer (als eigene Anweisung, einmal je Sample).

Zustand je Aufruf

Funktionen mit Gedächtnis – Oszillatoren, Flanken, Zähler, Hüllkurven, Filter – haben an jeder Stelle im Text einen eigenen Zustand. Zweimal sine(220) sind zwei unabhängige Oszillatoren. Dasselbe gilt für jeden Aufruf einer eigenen Funktion und jeden Durchlauf einer Schleife – zu verwalten gibt es nichts.

Steht ein solcher Aufruf in einem Zweig, der gerade nicht läuft, pausiert er: Die Phase steht still, der Zähler zählt nicht. Soll etwas immer weiterlaufen, gehört der Aufruf vor das if in eine Variable.

Eigene Funktionen

Mit fn name(a, b) { … return wert } fasst du Rechnungen zusammen, die mehrfach vorkommen. Funktionen stehen im Modulblock neben den Deklarationen und dürfen andere Funktionen aufrufen, aber nicht sich selbst. Beim Übersetzen wird jede Funktion an ihrer Aufrufstelle eingesetzt – ein Aufruf kostet so viel wie ihr Rumpf, nicht mehr.

fn allpass(x, a) {               // own state per call site
  state x1 = 0
  state y1 = 0
  y = a * (x - y1) + x1
  x1 = x
  y1 = y
  return y
}

fn split(v, hard: bool = false) { // two results, a default value
  if hard { return sign(v), abs(v) }
  else { return v, abs(v) }
}

process {
  y = allpass(allpass(x, 0.3), 0.6)   // two independent allpasses
  s, _ = split(y)                     // _ discards a value
}

Parameter sind Zahlen; x: bool macht einen Wahrheitswert daraus, x = 0.5 einen Standardwert (Parameter mit Standardwert stehen am Ende). Im Rumpf dürfen Parameter verändert werden. return a, b liefert mehrere Werte, die x, y = f(…) übernimmt; _ verwirft einen. return steht am Ende der Funktion bzw. am Ende jedes Zweigs ihres letzten if.

state im Rumpf gibt jeder Aufrufstelle einen eigenen gespeicherten Wert – so entstehen eigene Bausteine wie ein Allpass oder ein Integrator, die sich wie eingebaute Funktionen verhalten. Eine Funktion liest alles im Modul (Regler, Buchsen, state, Konstanten), ändert aber nur ihre eigenen Werte: Ergebnisse gibt sie zurück, zugewiesen wird in process. Das Beispiel Integral baut so den Calculus nach.

Schleifen und Arrays

Ein Array ist eine Reihe gleichartiger Werte: state hist[4] = 0 (oder mit Liste), const scale[] = [0, 2, 4, 7, 9] (Länge aus der Liste) – und genauso Regler, Schalter, Taster, Buchsen, LEDs und Pegelketten. knob pitch[8] # … legt acht Regler mit den IDs pitch1 bis pitch8 an; ein # in der Beschriftung wird zur Nummer, mit at x y step dx dy stehen sie in festem Abstand.

const N = 8
knob   pitch[N] "#" -2..2 = 0 unit v size s at 8 30 step 9.3 0   // pitch1 … pitch8, labels 1 … 8
button on[N]    ""  = on               at 8 44 step 9.3 0
led    l[N]     green                  at 8 52 step 9.3 0
state  hist[4] = 0
const  scale[] = [0, 2, 4, 7, 9]

process {
  n = counter(rise(clk), false, N)
  cv = pitch[n]                      // index at run time
  for i in 0..N { l[i] = n == i }    // i = 0 … 7, unrolled when compiling
}

Elemente liest und schreibt man mit a[i], gezählt ab 0. Der Index darf zur Laufzeit entstehen, etwa aus einem Zähler – er wird abgerundet und auf den gültigen Bereich begrenzt (einen Umlauf schreibt man selbst: a[n % N]). Bei connected(buchse[i]) muss er beim Übersetzen feststehen.

for i in 0..N { … } wiederholt den Rumpf für i = 0 bis N − 1. Die Grenzen sind feste ganze Zahlen (höchstens 256 Durchläufe); beim Übersetzen wird die Schleife entrollt, i ist in jedem Durchlauf eine Konstante. Jeder Durchlauf ist eine eigene Aufrufstelle – Oszillatoren oder Filter darin laufen unabhängig. break und while gibt es nicht: Die Rechenzeit je Sample steht immer fest.

Panel-Layout

Das Panel entsteht automatisch: oben die Regler, darunter Schalter und Taster, dann LEDs und Pegelketten; Ein- und Ausgänge sitzen unten (Ausgänge zuletzt, auf dunklem Feld). Jede Reihe wird mittig gesetzt.

Mit at x y steht ein Element genau dort (Mitte in Millimetern ab der linken oberen Ecke; das Panel ist 128,5 mm hoch und HP × 5,08 − 0,3 mm breit). Bei Arrays setzt at x y step dx dy das erste Element, jedes weitere steht um dx/dy versetzt. Das automatische Layout weicht festen Plätzen nicht aus – einen Abschnitt also ganz oder gar nicht von Hand setzen.

Passen die Bedienelemente nicht, meldet der Compiler layoutTooFull mit einem Vorschlag für die Breite.

Eigene Grafiken

Frontplatte und Kappen lassen sich mit SVG selbst gestalten. face zeichnet über die ganze Platte – unter Schrauben, Titel und Bedienelementen; als viewBox passt die Panelgröße in Millimetern (bei 6 HP z. B. 0 0 30 128.5). skin an einem knob oder button ersetzt die gezeichnete Kappe (die Skala und der Zeiger bzw. die LED bleiben).

Grafiken stehen in einem mehrzeiligen Text zwischen """ und """. Sie werden geprüft: Erlaubt sind nur Formen, Gruppen, Farbverläufe und Text – keine Skripte, Ereignis-Attribute, Stile, Klassen, Bilder, Filter oder Verweise nach außen. Verweise auf eigene IDs (fill=url(#verlauf)) sind erlaubt; IDs werden je Modul eindeutig gemacht.

Erlaubte Elemente

g path rect circle ellipse line polyline polygon text tspan defs linearGradient radialGradient stop clipPath

Erlaubte Attribute

x y x1 y1 x2 y2 cx cy r rx ry fx fy width height d points fill fill-opacity fill-rule stroke stroke-width stroke-opacity stroke-linecap stroke-linejoin stroke-dasharray stroke-dashoffset stroke-miterlimit opacity transform id offset stop-color stop-opacity gradientUnits gradientTransform spreadMethod clip-path clip-rule clipPathUnits font-size font-weight font-style text-anchor dominant-baseline letter-spacing visibility

// Custom artwork: front panel (face) and knob cap (skin) as SVG – a starting point for your own design.
module glow "Glow" hp 6 theme black {
  desc de "Abschwächer mit eigener Frontplatte: Farbverlauf, Ring und eine eigene Knopfkappe – Vorlage für eigene Grafiken."
  desc en "Attenuator with custom panel art: colour gradient, ring and a custom knob cap – a template for your own artwork."

  face """
  <svg viewBox="0 0 30 128.5">
    <defs>
      <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0" stop-color="#3a2150"/>
        <stop offset="1" stop-color="#10223a"/>
      </linearGradient>
    </defs>
    <rect width="30" height="128.5" fill="url(#bg)"/>
    <circle cx="15" cy="25.8" r="10" fill="none" stroke="#b97a4b" stroke-width="0.4" stroke-dasharray="1 1"/>
  </svg>
  """

  // The automatic layout puts the first large knob at the top centre (y ≈ 25.8 mm) – the ring sits around it.
  knob level "LEVEL" 0..1 = 0.7 size l skin """
  <svg viewBox="-10 -10 20 20">
    <circle r="10" fill="#b97a4b"/>
    <circle r="7.2" fill="#3a2150" stroke="#f0dcc4" stroke-width="0.8"/>
  </svg>
  """

  in    x   "IN"  audio
  out   y   "OUT" audio
  meter lvl "LEVEL"

  process {
    y = x * level
    lvl = peak(y)
  }
}

Laden, Ersetzen, Teilen

Datei → Eigene Module laden … öffnet ein Fenster: Datei wählen (am Computer auch hineinziehen), ein kurzer Ladekreis, dann erscheint jedes Modul der Datei mit fertigem Panel und laufender Vorschau – LEDs und Pegelketten zeigen, wie es auf Testsignale reagiert; darunter steht, welchen Anteil eines CPU-Kerns das Modul höchstens braucht. Für jedes Modul entscheidest du: Hinzufügen oder nicht.

Gibt es im Patch schon ein Modul mit derselben id, heißt der Knopf „Ersetzen“: Alle Exemplare bekommen die neue Fassung und behalten Position, Reglerwerte (über die id der Regler) und Kabel (über die id der Buchsen).

Eigene Module gehören zum Patch: Sie werden mit ihm gespeichert und mit der Patch-Datei geteilt. In der Bibliothek stehen sie unter „Eigene Module“; über ⋯ bzw. Rechtsklick lassen sie sich als .amsmod speichern, aus einer Datei ersetzen oder entfernen.

Am Handy funktioniert alles genauso: Datei wählen, Vorschau ansehen, hinzufügen. Manche Dateimanager hängen .txt an – auch solche Dateien werden gelesen.

Fehlermeldungen

Findet der Compiler einen Fehler, zeigt das Fenster ihn sofort – immer als englischen Satz mit Zeile und Spalte und dem markierten Ausschnitt. Syntaxfehler betreffen die ganze Datei; Fehler in einem Modul nur dieses, die übrigen lassen sich trotzdem hinzufügen.

Jeder Fehlercode hat hier einen Eintrag; im Fenster führt ein Klick auf den Code direkt dorthin.

  • fileTooLarge

    The file is larger than max KB.

  • fileTooManyLines

    The file has more than max lines.

  • fileNotText

    The file is not a text file – please save it as UTF-8.

  • fileEmpty

    The file contains no module.

  • fileTooManyModules

    A file may contain at most max modules.

  • lexBadChar

    Unexpected character “char”.

  • lexBadNumber

    “text” is not a valid number.

  • lexUnclosedString

    The text is not closed – the closing quotation mark is missing.

  • lexBadString

    Texts must not contain control characters.

  • syntaxExpected

    Expected expected, found found.

  • syntaxUnexpected

    Unexpected: found.

  • syntaxTopLevel

    Outside a module only “module …” is allowed (found: found).

  • syntaxNoProcess

    The module has no “process { … }” block.

  • syntaxDuplicateProcess

    A module has exactly one “process” block.

  • syntaxUnclosedBlock

    The block is not closed – “}” is missing.

  • syntaxChainedCompare

    Comparisons cannot be chained – join them with “and”, e.g. “a < x and x < b”.

  • syntaxNesting

    Nested too deeply (at most max levels).

  • syntaxStatement

    A statement is expected here (assignment, “if”, “for”, “return” or “write”), found: found.

  • declDuplicate

    “name” is already declared.

  • declDuplicateModule

    The module ID “name” appears more than once in the file.

  • declReserved

    “name” is a reserved word and cannot be a name.

  • declBadId

    The module ID “name” may only contain lowercase letters, digits and _ and must start with a letter.

  • declEmptyName

    The module name must not be empty.

  • declIdTooLong

    Names have at most max characters.

  • declTextTooLong

    The text is too long (at most max characters).

  • declBadRange

    Invalid range: the minimum must be less than the maximum (with “log” both above 0, not combined with “int”).

  • declBadDefault

    The default value is outside the range.

  • declBadOption

    The option “name” is unknown or not allowed here (on arrays “at” needs “step”; “normal” is not possible there).

  • declDuplicateOption

    The positions of a switch must differ.

  • declBadTheme

    Unknown theme “name” (alu, black, cream, blue, wasp).

  • declBadHp

    The width must be a whole number from min to max HP.

  • declBadSignal

    Unknown signal type “name”.

  • declBadColor

    Unknown LED colour “name” (red, green, yellow, blue).

  • declBadUnit

    Unknown unit “name” (hz, s, ms, db, pct, v, st, bpm).

  • declBadSize

    Unknown knob size “name” (s, m, l).

  • declBadNormal

    “name” is not a suitable input.

  • declBadSwitch

    A switch has 2 or 3 positions.

  • declTooMany

    Too many what (at most max).

  • declBufferTooLong

    Buffers are at most max s long in total.

  • declNotConst

    Only a constant expression is allowed here (numbers, constants, pure functions).

  • declDuplicateDesc

    The description is given twice.

  • declDuplicateFace

    The panel artwork (“face”) is given twice.

  • svgSyntax

    The SVG graphic is not well-formed (at “detail”).

  • svgElement

    The SVG element “name” is not allowed (only shapes, groups, gradients and text).

  • svgAttribute

    The SVG attribute “name” is not allowed (no scripts, styles, classes or links).

  • svgValue

    The value of “name” is not allowed (references only to own IDs such as url(#gradient)).

  • svgTooLarge

    The SVG graphic is too large (at most max KB).

  • nameUnknown

    Unknown name “name”.

  • callUnknown

    Unknown function “name”.

  • callArity

    “name” expects min to max arguments, not got.

  • callArgType

    Argument index of “name” must be expected.

  • callNeedsBuffer

    “name” needs a buffer (“buffer”).

  • callNeedsInput

    “name” needs the name of an input or output jack as its argument.

  • callUnusedResult

    The result of “name” is not used – assign it to a variable.

  • callNoValue

    “name” returns no value and can only be used as a statement.

  • assignReadonly

    “name” cannot be assigned – knobs, inputs, constants, buffers, functions and loop variables are read-only.

  • assignTypeChange

    “name” already has a different type (number or boolean).

  • useBeforeAssign

    “name” is read before it is assigned on every path – use “state” for values across samples.

  • typeNeedBool

    A boolean is expected here, e.g. “x > 1” or “high(x)”.

  • typeNeedNum

    A number is expected here – convert booleans with “num(b)” or “gate(b)”.

  • typeBoolToOut

    Output “name” needs a voltage – “gate(b)” turns a boolean into 0/10 V.

  • fnRecursive

    The function “name” calls itself (directly or through other functions) – recursion is not allowed.

  • fnReturnOutside

    “return” is only allowed inside a function.

  • fnReturnNotLast

    “return” must be the last statement of the function or of a branch of its last “if”.

  • fnMissingReturn

    Not every path through “name” ends with “return”.

  • fnReturnMismatch

    All “return” statements of “name” must give the same number of values of the same types.

  • fnResultCount

    The number of results does not match: “name” gives count, so assign exactly that many names (a, b = name(…)).

  • fnWritesOutside

    A function can only change its own values and state – return the value and assign “name” in process.

  • fnStatePlacement

    “state” belongs at module level or at the top level of a function body.

  • fnParamOrder

    Parameters with a default value come last – “name” needs one as well.

  • loopBadRange

    A loop needs whole-number constant bounds a..b with a < b and at most max steps.

  • arrayNotArray

    “name” is not an array.

  • arrayNeedsIndex

    “name” is an array – use an element such as “name[0]”.

  • arrayIndexRange

    Index index is outside “name” (0 to max).

  • arrayIndexConst

    Here the index of “name” must be known when compiling (a number, a constant or a loop variable).

  • arrayBadLength

    The length of “name” must be a whole-number constant from 1 to max.

  • arrayListLength

    The list for “name” has got values, but the array has len.

  • limitCode

    The program is too long (at most max words).

  • limitRegisters

    Too many values at once (at most max registers).

  • limitState

    Too many stateful calls (at most max state slots).

  • limitCost

    The module is too expensive (cost of at most max units per sample).

  • limitExpand

    Functions and loops expand to too much code (more than max statements).

  • layoutTooFull

    The controls do not fit on hp HP (suggestion: suggest HP).

  • layoutOverlap

    “a” and “b” overlap.

  • layoutOutside

    “name” lies outside the usable area.

  • layoutTitle

    The module name does not fit on hp HP.

Hinweise (blockieren nicht)

  • warnUnusedControl

    “name” is never read.

  • warnOutputNeverSet

    Output “name” is never assigned.

  • warnUnusedVar

    “name” is assigned but never read.

  • warnBufferNeverWritten

    Buffer “name” is never written.

  • warnUnusedFunction

    The function “name” is never called.

Grenzen und Leistung

Damit kein Modul den Ton stören kann, hat jedes eine feste Obergrenze an Rechenzeit pro Sample. Eine Einheit entspricht grob einer einfachen Rechnung; Filter und Hüllkurven kosten mehr. Die Grenze reicht für Dutzende Filter oder ein ganzes Modul wie den Calculus samt Überabtastung (Beispiel Integral) – das Fenster beim Laden zeigt, welchen Anteil eines CPU-Kerns ein Modul höchstens braucht. Wie viele solcher Module gleichzeitig laufen, hängt vom Gerät ab.

Kosten je Sample (Einheiten, ≈ 2 ns)1024
Regler, Schalter und Taster je Modul32
Eingänge je Modul16
Ausgänge je Modul16
LEDs und Pegelketten je Modul16
Puffer je Modul · Gesamtlänge4 · 8 s
Programmlänge (Wörter)4096
Zustandsplätze (Funktionen mit Gedächtnis)256
Elemente je Array64
Durchläufe je Schleife · Anweisungen nach Entrollen und Einsetzen256 · 20000
Eigene Funktionen · Parameter · Rückgabewerte64 · 16 · 8
Dateigröße · Zeilen128 KB · 5000
Module je Datei16
Eigene Module je Patch32
Zeichen für Name · Beschriftung · Beschreibung20 · 8 · 160
Breite in HP4–32
Größe einer SVG-Grafik32 KB

Beispiele

Alle Beispiele lassen sich herunterladen und direkt laden – sie decken die Funktionsgruppen ab und eignen sich als Vorlage.

Fold – Wavefolder mit PegelketteMathe, Schalter, LED und Pegelkette: Der Sinus wird je nach FOLD mehrfach gefaltet.
Herunterladen
// Wavefolder with a level meter – an example custom module for the Analog Modular Simulator.
module fold "Fold" hp 6 theme black {
  desc de "Wavefolder: faltet die Welle je nach FOLD mehrfach um – aus einem Sinus wird ein obertonreicher Klang."
  desc en "Wavefolder: folds the wave over itself depending on FOLD – a sine turns into a rich, buzzy tone."

  knob   amount "FOLD" 0..1 = 0.3
  knob   depth  "CV"   0..1 = 0.5 size s tip "CV amount"
  switch mode   "MODE" "SIN" "TRI" = 0
  in     x      "IN"   audio
  in     cv     "CV"   cv
  out    y      "OUT"  audio
  led    hot    red
  meter  level  "LEVEL"

  process {
    g = 1 + 4 * clamp(amount + cv / 10 * depth, 0, 1)
    s = x / AUDIO * g
    y = (mode == 0 ? sin(s * PI / 2) : fold(s, -1, 1)) * AUDIO
    hot = abs(y) > 4.5
    level = peak(y)
  }
}
Divider und Toggle – zwei Module in einer DateiTaktteiler mit Reset und ein Flipflop: Flanken, Zähler, Impulse und Gates.
Herunterladen
// Two clock tools in one file – every `module` block becomes a module of its own.

module divider "Divider" hp 4 {
  desc de "Taktteiler: gibt jeden n-ten Takt als Trigger weiter (÷2 bis ÷8), RST beginnt von vorn."
  desc en "Clock divider: passes on every n-th clock as a trigger (÷2 to ÷8), RST starts over."

  knob div   "DIV" 2..8 = 4 int
  in   clk   "CLK" gate
  in   reset "RST" gate
  out  q     "OUT" gate
  led  act   yellow

  process {
    t = rise(clk)
    n = counter(t, rise(reset), div)
    fire = t and n == 1
    q = gate(pulse(fire, 10))
    act = pulse(fire, 60)
  }
}

module toggle "Toggle" {
  desc de "Flipflop: jeder Takt schaltet den Ausgang um – halbes Tempo mit 50 % Tastverhältnis, INV liefert das Gegenteil."
  desc en "Flip-flop: every clock flips the output – half tempo at 50 % duty cycle, INV gives the opposite."

  in  clk "CLK" gate
  out q   "Q"   gate
  out nq  "INV" gate
  led on  green

  process {
    s = toggle(rise(clk))
    q = gate(s)
    nq = gate(not s)
    on = s
  }
}
Glow – eigene Frontplatte und KnopfkappeVorlage für eigene Grafiken: SVG-Farbverlauf als Frontplatte, eigene Kappe am großen Regler.
Drift – gleitende ZufallsspannungZufall bei jedem Takt oder in freier Rate, weich geglättet.
Herunterladen
// Random, smoothly gliding control voltage – brings filters, pitch or effects to life.
module drift "Drift" hp 4 {
  desc de "Zufallsspannung: springt bei jedem Takt (ohne Takt in freier Rate) auf einen neuen Wert und gleitet weich dorthin."
  desc en "Random voltage: jumps to a new value on every clock (without a clock at a free rate) and glides there smoothly."

  knob rate  "RATE"  0.05..10 = 0.5 log unit hz
  knob glide "GLIDE" 0..2 = 0.3 unit s
  knob range "RANGE" 0..10 = 5 unit v
  in   clk   "CLK"   gate
  out  y     "OUT"   cv
  led  tick  yellow

  process {
    ph = phasor(rate)
    wrapped = ph < prev(ph)
    t = connected(clk) ? rise(clk) : wrapped
    y = lag(random(t) * 2 - 1, glide) * range
    tick = pulse(t, 40)
  }
}
Quantizer – Halbtöne und PentatonikTonhöhe runden, mit Trigger halten, bei jedem neuen Ton ein Gate.
Herunterladen
// Quantizer: snaps a control voltage to semitones or the major pentatonic scale.
module quant "Quantizer" {
  desc de "Quantisierer: rundet V/Oct auf Halbtöne oder die Dur-Pentatonik; mit Trigger hält er den Ton bis zum nächsten Takt."
  desc en "Quantizer: rounds V/Oct to semitones or the major pentatonic scale; with a trigger it holds the note until the next clock."

  switch scale "SCALE" "CHR" "PEN" = 0
  in     x     "IN"   pitch
  in     clk   "TRIG" gate
  out    y     "OUT"  pitch
  out    g     "GATE" gate

  process {
    s = semi(x)
    // Pentatonic: round the semitone within the octave down to 0, 2, 4, 7 or 9.
    oct = floor(s)
    n = round((s - oct) * 12)
    pen = n >= 9 ? 9 : n >= 7 ? 7 : n >= 4 ? 4 : n >= 2 ? 2 : 0
    q = scale == 0 ? s : oct + pen / 12
    y = connected(clk) ? sah(q, rise(clk)) : q
    g = gate(pulse(changed(y), 5))
  }
}
LPG – Low-Pass-GateEine Hüllkurve öffnet Filter und Lautstärke zugleich.
Herunterladen
// Low-pass gate: one envelope opens filter and volume together – the typical "plonk".
module lpg "LPG" hp 4 {
  desc de "Low-Pass-Gate: Jeder Trigger lässt Lautstärke und Helligkeit gemeinsam ausklingen – wie ein angeschlagenes Instrument."
  desc en "Low-pass gate: each trigger lets volume and brightness decay together – like a struck instrument."

  knob decay "DECAY" 0.02..3 = 0.4 log unit s
  knob tone  "TONE"  0..1 = 0.6
  in   x     "IN"    audio
  in   trig  "TRIG"  gate
  out  y     "OUT"   audio
  led  env   green

  process {
    e = ad(rise(trig), 0.002, decay)
    cutoff = 80 * exp2(e * (2 + tone * 6))
    y = lp(x, cutoff, 0.1) * e
    env = e
  }
}
Chorus – modulierte VerzögerungPuffer, write und read mit einem langsamen Sinus.
Herunterladen
// Chorus: a slowly wavering delay doubles the sound.
module chorus "Chorus" hp 6 {
  desc de "Chorus: mischt das Signal mit einer Kopie, deren Verzögerung ein langsamer Sinus schwanken lässt – breit und schwebend."
  desc en "Chorus: mixes the signal with a copy whose delay is swayed by a slow sine – wide and shimmering."

  knob   rate  "RATE"  0.05..3 = 0.4 log unit hz
  knob   depth "DEPTH" 0..1 = 0.5
  knob   blend "MIX"   0..1 = 0.5
  in     x     "IN"    audio
  out    y     "OUT"   audio
  buffer line 50 ms

  process {
    write(line, x)
    d = 0.012 + 0.008 * depth * sine(rate)
    y = mix(x, read(line, d), blend)
  }
}
Osc – Oszillator mit Hard-SyncBandbegrenzter Sägezahn und Rechteck mit V/Oct und Sync.
Herunterladen
// Oscillator with saw, square with pulse width and hard sync – band-limited (PolyBLEP).
module osc "Osc" hp 6 {
  desc de "Oszillator: Sägezahn und Rechteck (Pulsbreite per Regler), V/Oct-Eingang und Hard-Sync – bandbegrenzt, ohne Aliasing."
  desc en "Oscillator: saw and square (pulse width by knob), V/Oct input and hard sync – band-limited, without aliasing."

  knob tune "TUNE"  -2..2 = 0 unit v tip "Coarse tune (octaves)"
  knob pw   "PW"    0.05..0.95 = 0.5 unit pct
  in   pitch "V/OCT" pitch
  in   sync "SYNC"  gate
  out  sw   "SAW"   audio
  out  sq   "SQR"   audio

  process {
    f = hz(pitch + tune)
    r = rise(sync)
    sw = saw(f, r) * AUDIO
    sq = square(f, pw, r) * AUDIO
  }
}
Steps – 8-Schritt-SequencerArrays aus Reglern, Tastern und LEDs, ein Index zur Laufzeit und eine Schleife.
Herunterladen
// Steps: an 8-step sequencer – arrays of knobs, buttons and LEDs, a runtime index and a loop.
module steps "Steps" hp 16 {
  desc de "8-Schritt-Sequencer: Jeder Takt geht einen Schritt weiter, Taster schalten Schritte stumm, LEN kürzt die Folge, RST beginnt von vorn."
  desc en "8-step sequencer: every clock moves one step on, buttons mute steps, LEN shortens the sequence, RST starts over."

  const N = 8
  // Arrays: pitch1 … pitch8, on1 … on8, l1 … l8 – "#" in a label becomes the step number, "at … step" spaces them.
  knob   pitch[N] "#" -2..2 = [0, 0.25, 0.583, 0.25, 1, 0.583, 0.333, 0.75] unit v size s at 8 30 step 9.3 0
  button on[N]    ""  = on at 8 44 step 9.3 0
  led    l[N]     green    at 8 52 step 9.3 0
  knob   len "LEN" 1..N = N int at 40.5 70
  in     clk "CLK"  gate  at 14 100
  in     rst "RST"  gate  at 28 100
  out    cv  "CV"   pitch at 53 100
  out    g   "GATE" gate  at 67 100
  state  n = 0                        // current step (0 = step 1)
  state  armed = false                // the first clock after start or RST plays step 1

  process {
    t = rise(clk)
    if rise(rst) {
      n = 0
      armed = false
    } else if t {
      if armed { n = (n + 1) % len } else { armed = true }
    }
    cv = pitch[n]                     // index at run time
    g = gate(high(clk) and on[n])
    for i in 0..N { l[i] = n == i }   // unrolled: eight assignments
  }
}
Integral – der Calculus als SkriptEin komplexes Modul: eigene Funktionen mit eigenem Zustand, Integrator in f64 mit Überabtastung, eine Schleife über den Bus und connected() an Ausgängen.
Herunterladen
// Integral: a two-channel function generator written as a script – the built-in Calculus rebuilt with custom
// functions, per-call state, loops, arrays and connected() on outputs.
//
// Channel 1 and 4 are the same function "channel" called twice; each call has its own state. The core is a
// voltage-dependent integrator in f64 that carries the remaining time over segment ends (exact cycle length up to
// audio rate) and runs twice per sample with a half-band filter (anti-aliasing). Known differences to the built-in
// module: RISE, FALL and RESP are smoothed like every knob, there is no idle shortcut (the load is always the worst
// case) and the half-band filter computes in f64.
module integral "Integral" hp 20 theme black {
  desc de "Funktionsgenerator als Skript: zwei Kanäle (Hüllkurve, LFO, Slew bis Audiorate), zwei Offsets, SUM/OR/INV – ein Nachbau des Calculus."
  desc en "Function generator as a script: two channels (envelope, LFO, slew up to audio rate), two offsets, SUM/OR/INV – the Calculus rebuilt."

  // Channel 1 (left)
  in     sig1   "SIGNAL" cv   at 8 25
  in     trig1  "TRIG"   gate at 20 25
  button cycle1 "CYCLE"       at 10 37
  led    cyc1   red           at 17 37
  knob   rise1  "RISE" 0.0005..180 = 0.3 log unit s size l at 26 40
  knob   fall1  "FALL" 0.0005..180 = 0.3 log unit s size l at 26 58
  knob   resp1  "RESP" 0..1 = 1 / 3 unit pct tip "Response: log – linear – exp" at 26 75
  in     rcv1   "RISE"  cv   at 8 48.5
  in     bcv1   "BOTH"  cv   at 8 60
  in     fcv1   "FALL"  cv   at 8 71.5
  in     cin1   "CYCLE" gate at 8 83

  // Channel 4 (right, mirrored)
  in     sig4   "SIGNAL" cv   at 93.3 25
  in     trig4  "TRIG"   gate at 81.3 25
  button cycle4 "CYCLE"       at 91.3 37
  led    cyc4   red           at 84.3 37
  knob   rise4  "RISE" 0.0005..180 = 0.3 log unit s size l at 75.3 40
  knob   fall4  "FALL" 0.0005..180 = 0.3 log unit s size l at 75.3 58
  knob   resp4  "RESP" 0..1 = 1 / 3 unit pct tip "Response: log – linear – exp" at 75.3 75
  in     rcv4   "RISE"  cv   at 93.3 48.5
  in     bcv4   "BOTH"  cv   at 93.3 60
  in     fcv4   "FALL"  cv   at 93.3 71.5
  in     cin4   "CYCLE" gate at 93.3 83

  // Channels 2 and 3, attenuverters 1–4 and the bus
  in     in2    "IN 2" cv at 40 25
  in     in3    "IN 3" cv at 61.3 25
  knob   att[4] "#" -1..1 = [1, 0, 0, 1] unit pct at 50.65 36 step 0 14.5
  out    unity1 "UNITY" env  at 9 99.5
  led    u1p    green        at 17.2 95
  led    u1n    red          at 17.2 100
  out    var[4] "#"     env  at 25 99.5 step 14 0
  led    u4p    green        at 84.1 95
  led    u4n    red          at 84.1 100
  out    unity4 "UNITY" env  at 92.3 99.5
  out    eor    "EOR"   gate at 9 113.5
  led    eorl   yellow       at 17.2 111.5
  out    orout  "OR"    cv   at 32 113.5
  out    sum    "SUM"   cv   at 48 113.5
  led    sump   green        at 56 109
  led    sumn   red          at 56 114
  out    inv    "INV"   cv   at 64 113.5
  led    eocl   yellow       at 84.1 111.5
  out    eoc    "EOC"   gate at 92.3 113.5

  const LIN  = 1 / 3              // linear mark of RESP
  const EXPO = 2 / 3              // exponential mark
  const IEXP = 1 / 2.085          // curve constants: log ≈ 2×, exp ≈ 0.5×, hyper-exp ≈ 0.3× the linear time
  const IHYP = 1 / 3.3
  const MAXR = 10 / 50e-6         // fastest segment: 10 V in 50 µs

  // Linear up to ±10 V, then soft up to ±10.5 V (supply rails).
  fn rail(x) {
    ax = abs(x)
    return ax <= 10 ? x : sign(x) * (10 + 0.5 * tanh((ax - 10) * 2))
  }

  // First-order allpass – every call keeps its own state.
  fn allpass(x, a) {
    state x1 = 0
    state y1 = 0
    y = a * (x - y1) + x1
    x1 = x
    y1 = y
    return y
  }

  // Two samples at double rate (mid first) → one sample: polyphase half-band.
  fn halfband(mid, cur) {
    return 0.5 * (allpass(allpass(cur, 0.07986643), 0.54532365) + allpass(allpass(mid, 0.28382935), 0.8344119))
  }

  // Rate factor at voltage v: slower near the top for log, faster for exp.
  fn response(v, curve) {
    a = abs(v) + 1
    if abs(curve - LIN) < 0.0001 { return 1 }
    else if curve <= LIN {
      lg = 3 / a
      return lg + (1 - lg) * (curve * 3)
    } else if curve <= EXPO { return 1 + (a * IEXP - 1) * ((curve - LIN) * 3) }
    else { return a * IEXP + (a * a * IHYP - a * IEXP) * ((curve - EXPO) * 3) }
  }

  // Integrate `left` seconds towards the apex (rising) or the signal; the time left at a segment end carries over.
  fn advance(v, up: bool, apex, sig, left, rup, rdn, curve, cyc: bool) {
    done = false
    for k in 0..3 {
      if not done {
        target = up ? apex : sig
        d = target - v
        if up and d <= 0 { up = false }
        else if d == 0 {
          if cyc {
            up = true
            apex = 8
          } else { done = true }
        } else {
          rising = d > 0
          rate = (rising ? rup : rdn) * response(v, curve)
          need = abs(d) / rate
          if need > left {
            v += (rising ? rate : -rate) * left
            done = true
          } else {
            v = target
            left -= need
            if up { up = false }
            else if not rising and cyc {
              up = true
              apex = 8
            }
            done = left <= 0
          }
        }
      }
    }
    return v, up, apex
  }

  // One function channel: unity output, end-of-rise state and cycling.
  fn channel(sig, trig, trise, tfall, curve, cbtn: bool, cin, rcv, fcv, both) {
    state v = 0
    state up = false
    state apex = 10
    state rising = false
    state cgate = false
    state sprev = 0
    cgate = cgate ? cin > 2 : cin >= 2.5
    cyc = cbtn or cgate
    if rise(trig) and not rising {
      up = true
      apex = cyc ? 8 : 10
    }
    b = exp2(clamp(both, -10, 10))
    rup = min(10 / max(trise, 0.00001) * max(1 - 0.11 * rcv, 0.12) * b, MAXR)
    rdn = min(10 / max(tfall, 0.00001) * max(1 - 0.11 * fcv, 0.12) * b, MAXR)
    c = clamp(curve, 0, 1)
    dt = 0.5 / SR
    mid = 0.5 * (sig + sprev)
    sprev = sig
    v, up, apex = advance(v, up, apex, mid, dt, rup, rdn, c, cyc)
    vmid = v
    v, up, apex = advance(v, up, apex, sig, dt, rup, rdn, c, cyc)
    rising = up or sig > v
    return rail(halfband(vmid, v)), not rising and v > 0.01, cyc
  }

  process {
    u1, hi1, cy1 = channel(sig1, trig1, rise1, fall1, resp1, cycle1, cin1, rcv1, fcv1, bcv1)
    u4, hi4, cy4 = channel(sig4, trig4, rise4, fall4, resp4, cycle4, cin4, rcv4, fcv4, bcv4)
    unity1 = u1
    unity4 = u4
    eor = gate(hi1)
    eoc = gate(not hi4)
    var[0] = rail(u1 * att[0])
    var[1] = rail((connected(in2) ? in2 : 5) * 2 * att[1])
    var[2] = rail((connected(in3) ? in3 : 2.5) * 2 * att[2])
    var[3] = rail(u4 * att[3])
    // Bus: only channels whose variable output is not patched.
    bus = 0
    top = 0
    for k in 0..4 {
      t = connected(var[k]) ? 0 : var[k]
      bus += t
      top = max(top, t)
    }
    s = rail(bus)
    sum = s
    inv = -s
    orout = top
    cyc1 = cy1
    cyc4 = cy4
    eorl = hi1
    eocl = not hi4
    u1p = u1 / 8
    u1n = -u1 / 8
    u4p = u4 / 8
    u4n = -u4 / 8
    sump = s / 10
    sumn = -s / 10
  }
}

AGENTS.md für KI-Assistenten

Die Datei AGENTS.md beschreibt die Sprache vollständig und knapp auf Englisch: Aufbau, alle Deklarationen, Typregeln, jede Funktion mit Signatur, Grenzen, Fehlercodes, eine Checkliste und alle Beispiele. Sie wird bei jedem Update aus dem Compiler erzeugt und ist damit immer aktuell.

Gib sie deinem KI-Assistenten zusammen mit deiner Idee („ein Bitcrusher mit Rate- und Bits-Regler“) – heraus kommt eine .amsmod-Datei, die du direkt laden kannst.

AGENTS.md herunterladen