Von zustandslos zu zustandsbehaftet: Filter & Komposition
In den vorangegangenen Teilen haben wir einen Oszillator gebaut. Er war „zustandslos“ – man gibt ihm eine Phase vor, und er liefert einen Wert. Es ist ihm egal, was vor einer Mikrosekunde passiert ist. Damit Musik jedoch „warm“ oder „organisch“ klingt, brauchen wir Objekte, die über ein Gedächtnis verfügen.
1. DSP allgemein: Das Konzept des Zustands
In der digitalen Signalverarbeitung (DSP) ist ein Filter zustandsbehaftet. Um den aktuellen Ausgangswert zu berechnen, muss ein Filter den vorherigen Ausgangswert kennen.
Mathematisch sieht ein einfacher einpoliger Tiefpassfilter so aus:
\[y[n] = y[n-1] + a \cdot (x[n] - y[n-1])\]
x[n] ist dein aktuelles Roh-Sample (der Eingang).
y[n-1] ist der Sample, den wir unmittelbar vor diesem berechnet haben (der Zustand).
a ist der Koeffizient (bestimmt durch Ihre Grenzfrequenz).
Da sich das Objekt an y[n-1] „erinnert“, bezeichnen wir es als zustandsbehaftet. In C++ bedeutet dies, dass unsere Filter-Klasse diesen Wert
als private Member-Variable speichern muss, die zwischen Aufrufen von process() erhalten bleibt.
2. Architektur: Komposition (Has-A)
Um diese zustandsbehafteten Module zu verwalten, verwenden wir Komposition. Während Anfänger oft versuchen, alles durch Vererbung zu lösen, bevorzugen Profis die Komposition: „Ein Synthesizer hat einen Hüllkurvengenerator und einen Filter.“
Modularität: Wir können die Filter-Implementierung austauschen, ohne den Oszillator zu verändern.
Kapselung: Die
SynthEnginemuss die Mathematik des Filters nicht kennen; sie übergibt dem Filter lediglich ein Sample und erhält ein geformtes Sample zurück.
3. Die Signalkette
Daten fließen in einer bestimmten Reihenfolge durch unsere zusammengesetzten Objekte:
Oszillator → Erzeugt den rohen, harmonisch reichen „Summen“.
Filter (VCF) → Subtrahiert Frequenzen (der zustandsbehaftete Teil).
Hüllkurve (ADSR) → Formt die endgültige Lautstärke.
4. Vollständiger Referenzcode
| |
| |
| |
| |
| |
5. Visuelles Debugging: Den Zustand visualisieren
Und schließlich: Woher wissen wir, dass unser zustandsbehaftetes System funktioniert? Da wir Ton nicht „sehen“ können, nutzen wir visuelles Debugging.
Durch die Integration von SDL_ttf können wir den internen Wert von currentCutoff auf dem Bildschirm anzeigen. In C++ erfordert dies ein sorgfältiges Ressourcenmanagement.
Wir erstellen in jedem Frame eine Oberfläche und eine Textur, um den sich ändernden Zustand widerzuspiegeln, und müssen diese sofort wieder löschen, um Speicherlecks zu vermeiden.
Wenn sich die Zahl auf dem Bildschirm ändert, der Klang jedoch nicht, wissen Sie, dass der Fehler in Ihrer DSP-Logik (y[n]-Berechnung) liegt und nicht in Ihrer Steuerungslogik (Tastatureingabe).
| |
Übersetzt mit DeepL.com (kostenlose Version)