Audio Plugins erstellen, Teil 2

  • Tutorial
Alle Beiträge der Serie:
Teil 1. Einführung und Einrichtung
Teil 2. Lernen des Codes
Teil 3. VST und AU
Teil 4. Digitale Verzerrung
Teil 5. Presets und GUI
Teil 6. Synthese von Signalen
Teil 7. Empfangen von MIDI-Nachrichten
Teil 8. Virtuelles Keyboard
Teil 9 Hüllkurven
Teil 10. Verfeinerung der Benutzeroberfläche
Teil 11. Filter
Teil 12. Niederfrequenzoszillator
Teil 13. Neugestaltung
Teil 14. Polyphonie 1
Teil 15. Polyphonie 2
Teil 16. Antialiasing



Schauen wir uns unser Testprojekt genauer an. Die wichtigsten Dateien sind resource.h , MyFirstPlugin.h und MyFirstPlugin.cpp . Im Moment ist das Plugin eine einfache Lautstärkeregelung.


Code lernen



Konstanten, Flags und Bildquellen



Öffnen Sie resource.h im Navigator . Diese Datei enthält Konstanten wie den Namen des Plugins, seine Version, eine eindeutige ID und Links zu Bildquellen für die GUI. In den Zeilen 23-26 können Sie eine eindeutige ID angeben:

// 4 chars, single quotes. At least one capital letter
#define PLUG_UNIQUE_ID 'Ipef'
// make sure this is not the same as BUNDLE_MFR
#define PLUG_MFR_ID 'Acme'


ID ist wichtig für die allgemeine Katalogisierung von Plugins. Sie können es hier registrieren . In den Zeilen 56 und höher legen Sie die ID und den Pfad zum Bild für den Lautstärkeregler fest, der beim Starten des Plugins angezeigt wird:

// Unique IDs for each image resource.
#define KNOB_ID 101
// Image resource locations for this plug.
#define KNOB_FN "resources/img/knob.png"


Suchen Sie im Navigator und öffnen Sie Ressourcen → img → knob.png . Dies ist ein Sprite mit 60 verschiedenen Stiftpositionen, die jeweils 48 x 48 Pixel groß sind. Wenn Sie das Plugin starten und den Knopf drehen, dreht sich das Bild des Griffs nicht. Das Programm zeigt nur den entsprechenden Teil dieses Sprites.
Warum dreht es sich nicht? Stellen Sie sich vor, der Stift hat einige Kerben und wirft einen Schatten. Während der Drehung dreht sich dann auch der Schatten. Dies geschieht in Wirklichkeit nicht, wie Sie (ich hoffe) wissen.

Unten in resource.h können Sie die Größe des Plugin-Fensters einstellen:

#define GUI_WIDTH 300
#define GUI_HEIGHT 300


Spiele mit den Werten herum und starte das Plugin.

Plugin Klassenschnittstelle



Öffnen Sie MyFirstPlugin.h . Es enthält die Schnittstelle für die Plugin-Klasse. Der Teil publicenthält einen Konstruktor, einen Destruktor und drei Mitgliedsfunktionen der Klasse:

  • Reset wird aufgerufen, wenn sich die Abtastrate ändert.
  • OnParamChange wird aufgerufen, wenn die Parameter des Plugins geändert werden, z. B. wenn wir den Regler drehen.
  • ProcessDoubleReplacingDies ist der Kern des Plugins. Hier wird die Verarbeitung des eingehenden Audios durchgeführt.


Der Teil privateenthält nur eine Typvariable double, die den aktuellen Volumenwert speichert.

Implementierung



Wir kommen zum interessanten Teil! Öffnen Sie MyFirstPlugin.cpp . Sofort sehen wir einen interessanten Empfänger mit dem Typ enum:

enum EParams
{
  kGain = 0,
  kNumParams
};


Einstellung kGain = 0und setzt kNumParamsdanach, kNumParamswird eine Menge von Plug-in - Parametern (in diesem Fall 1).
Im Folgenden enumwerden die in resource.h beschriebenen Konstanten verwendet und die Handle-Koordinaten im Plugin-Fenster festgelegt:

enum ELayout
{
  kWidth = GUI_WIDTH,
  kHeight = GUI_HEIGHT,
  kGainX = 100,
  kGainY = 100,
  kKnobFrames = 60
};


Es bestimmt auch die Anzahl der Frames in knob.png bis 60.
Als nächstes beginnt die Implementierung des Konstruktors. Die Attribute des Plugins sind gesetzt:

//arguments are: name, defaultVal, minVal, maxVal, step, label
GetParam(kGain)->InitDouble("Gain", 50., 0., 100.0, 0.01, "%");


In der GUI ist bisher weder ein Wert noch ein Prozentzeichen sichtbar, aber der Wert kann von 0bis variieren 100. Der Standardwert ist 50. Möglicherweise stellen Sie fest, dass die Abstufung der Werte auf dem Kreis nicht einheitlich ist. Das liegt an SetShape(2.). Wenn Sie dies durch ersetzen SetShape(1.), ist die Verteilung der Werte linear. Es SetShapesetzt das nichtlineare Verhalten.
Als Nächstes erstellt der Designer einen grafischen Kontext in der gewünschten Größe und legt die Hintergrundfarbe Rot fest:

IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight);
pGraphics->AttachPanelBackground(&COLOR_RED);


Danach wird knob.png geladen , ein neues IKnobMultiControl mit einem Bild erstellt und an die GUI angehängt. IKnobMultiControlIst eine Klasse für Interface-Handles.
Beachten Sie, wie der Parameter kKnobFrames übergeben wird, um die Anzahl der Bilder im Sprite anzugeben:

IBitmap knob = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, kKnobFrames);
pGraphics->AttachControl(new IKnobMultiControl(this, kGainX, kGainY, kGain, &knob));


Schließlich bindet der Designer den Grafikkontext und erstellt eine Standardvorgabe für das Plugin:

MakeDefaultPreset((char *) "-", kNumPrograms);


Schauen Sie sich an OnParamChange(am Ende der Datei). IMutexLocksorgt für Streaming-Sicherheit - ein Konzept, das wir später diskutieren werden. Alles andere ist nur eine Reihe von Optionen, abhängig davon, welcher Parameter sich ändert:

case kGain:
    mGain = GetParam(kGain)->Value() / 100.;
    break;


Wie wir uns erinnern, kGainändert es sich von 0 auf 100. Nachdem wir den Wert durch 100 geteilt haben, weisen wir privatedem Klassenmitglied einen Wert von 0 auf 1 zu mGain.

Daher haben wir den Prozess der Erstellung einer GUI und der Bindung von Parametern wie z mGain. Schauen wir uns nun an, wie das Plugin mit eingehendem Audio umgeht. In unserem Fall ist ein Audiostream eine Folge von Samples, die von einem Datentyp repräsentiert werden double, von denen jeder die Signalamplitude zu einem bestimmten Zeitpunkt enthält.
Der erste Parameter, der an die Funktion übergeben wird, ProcessDoubleReplacingist dieser double** inputs. Eine Folge von Typwerten doublekann mit übergeben werdendouble*. Da das Plugin jedoch zwei Kanäle (Stereo) oder mehr verarbeitet, benötigen wir mehrere Sequenzen von Samples, mit denen wir kommunizieren double**. Die ersten beiden Zeilen der Funktion veranschaulichen dies:

double* in1 = inputs[0];
double* in2 = inputs[1];


in1zeigt die erste Sequenz von Samples an (linker Kanal), in2- Samples des rechten Kanals. Nachdem Sie ähnliche Aktionen für den Ausgabepuffer ausgeführt haben, können Sie die Elemente des Eingabe- und Ausgabepuffers durchlaufen:

for (int s = 0; s < nFrames; ++s, ++in1, ++in2, ++out1, ++out2)
{
  *out1 = *in1 * mGain;
  *out2 = *in2 * mGain;
}


Für jedes Sample nehmen wir einen Eingabewert, multiplizieren ihn mit mGainund schreiben ihn in den Ausgabepuffer. nFramesgibt an, wie viele Samples pro Kanal verfügbar sind, damit wir die Länge der Puffer kennen.
Möglicherweise haben Sie bemerkt, dass Sie sich beim Ausführen des Plugins als eigenständige Anwendung über die Lautsprecher anhören können, wenn der Computer über ein eingebautes Mikrofon verfügt. Dies liegt daran, dass die Anwendung standardmäßig dieses Mikrofon als Eingangsquelle verwendet. Um dies (und noch etwas anderes) zu ändern, gehen Sie zu den Einstellungen :





Legen Sie den Haltepunkt in der Funktion fest Reset. Ändern Sie die Abtastrate rechts und übernehmen Sie die Änderungen. Der Debugger unterbricht die Ausführung des Codes in der Funktion Reset. Unten rechts, wo es funktioniert lldb, drücken Sie die Eingabetaste print GetSampleRate().



Bitte beachten Sie, dass Sie jede Funktion auch vom Debugger aus aufrufen können, um die korrekten Werte anzuzeigen. Klicken Sie oben links auf Stopp, wenn Sie es sich ansehen und beschließen, fortzufahren.
Jetzt ist es Zeit, ein Plugin aus dem Code zu erstellen und es auf den Host hochzuladen. Dies wird das Thema des nächsten Beitrags sein.
In der Zwischenzeit

Zusätzliche Lektüre



Um einige Lücken zu schließen, empfehle ich dringend, diese Folien von dem erfinderischen Herrn Oli Larkin zu lesen . Sie enthalten einige der wichtigsten Erläuterungen zu WDL-OL .

Originalartikel:
martin-finke.de/blog/articles/audio-plugins-003-examining-the-code

Jetzt auch beliebt: