Sampling, Recording, Programmierung & Software

Kategorie: C++ Seite 1 von 3

Programmiere einen einfachen 3-Band-Compressor mit diesem ausführlichen C++/JUCE Videotutorial

MatKat Music bietet einen ca. 8-stündigen Videokurs an, der zur Zeit absolut nichts kostet und den ambitionierten Programmierneuling an die Hand nimmt und ihm hilft, ein Compressor Plugin zu programmieren.

Das Tutorial beinhaltet das Setup der Entwicklungsumgebung unter Windows und MacOS, behandelt ein paar Grundlagen der Plugin-Entwicklung und steigt dann tief in den DSP Code und die GUI-Entwicklung ein.

Der Kurs nutzt das C++-Framework JUCE, welches speziell für Audio-Anwendungen optimiert ist. Auch wenn es zahlreiche Tutorials auf den JUCE-Webseiten gibt, ist dieses Videotutorial eine Spitzengelegenheit in die Materie einzusteigen. Und das Ganze dann auch noch völlig kostenlos!

JUCE 6 Tutorial: JUCE Framework unter Windows installieren…

Es wird mal wieder Zeit für ein Update zur aktuellen Version von JUCE. Seit ungefähr einem Jahr gibt es mittlerweile die Version 6 des beliebten C++ Frameworks zur Entwicklung von Audio-Plugins und ich möchte an dieser Stellen nochmal Schritt für Schritt durch die Einrichtung unter Windows 10 gehen.

Zunächst einmal installiere ich die aktuelle Version von Git. Dazu gehe ich auf die Git-Webseite und downloade den aktuellen Installer für ein 64Bit Windows. Nach dem Download kann ich Git einfach durch Starten des Installers installieren.

Während der Installation kann man Auswählen, welcher Code Editor standardmäßig mit Git verwendet werden soll. Hier kann man seinen installierten Editor auswählen (hier: Visual Studio Code), oder einen anderen Editor direkt installieren oder aber den Terminal-Editor Vim auswählen.

Faust – Echtzeit Audio Programmierung für Anwendungen und Plugins…

Faust (Functional AUdio STream) ist eine Programmiersprache, die speziell für die Erstellung von digitalen Synthesizern und Audio Effekten gedacht ist. Faust unterstützt das funktionale Programmierparadigma und man kann den geschriebenen DSP-Code schnell in eine andere allgemeine Programmiersprache, wie C, C++, Java, JavaScript oder Web Assembly, übersetzen.

Außerdem ist es möglich diesen generierten Code leicht in verschiedene Objekte zu kompilieren: Audio-Plugins, Standalone-Anwendungen, Smartphone- oder Web Apps.

Quelle: https://faust.grame.fr/

Faust Programme werden kompiliert und nicht interpretiert. Sie werden in möglichst optimierten C++ Code umgewandelt. Angeblich soll dieser generierte Code effizienter sein, als der den ein fortgeschrittener C++ Entwickler schreiben würde … zumindest genauso effizient. Hut ab!

Faust ist zwar im Grunde eine textuelle Sprache, aber trotzdem ähnelt das alles einem Block Diagramm. Sie vereint die funktionelle Programmierung mit dem Erstellen von Blockdiagrammen. Man programmiert quasi Blockdiagramme mithilfe von Funktionen. Kurz gesagt: Ein Faust Programm beschreibt einen Signalprozessor.

Die meisten Audiotools können als Signalprozessoren dargestellt werden. Sie verfügen über Audio-Ein– und –Ausgänge und Kontrollsignale werden mithilfe von Drehreglern, Schiebern und diversen Anzeigen dargestellt.

Audio Programmierung #09 – C++ Grundlagen: Funktionen (Teil 1)

Bisher haben wir all unseren Programmcode in die main()-Funktion geschrieben. Wenn man nur kleine Programme oder Beispiele schreibt, kann das durchaus Sinn machen. Wenn ein Programm aber größer und komplexer wird, sollte man den Code strukturieren. Dabei helfen Funktionen.

Funktionen helfen dabei ein Programm in logische Blöcke zu unterteilen, die man dann aufrufen kann, wenn man sie benötigt. Eine Funktion ist quasi ein Unterprogramm, dem man Parameter übergeben kann und die etwas zurückgeben kann. Damit sie ihre Arbeit verrichtet, muss sie aufgerufen werden.

Ein klassisches Beispiel für eine Anwendung ist die Berechnung des Umfangs und der Fläche eines Kreises. Der Nutzer gibt einen Radius an und das Programm berechnet diese beiden Werte. Man könnte das nun alles in die main()-Funktion schreiben. Eine bessere Lösung wäre es, wenn man den Code in logische Blöcke unterteilt: Eine Funktion, die den Umfang berechnet und eine Funktion, die die Fläche des Kreises bestimmt.

// circle01.cpp

#include <iostream>

using namespace std;

const double PI = 3.14159265;

// Function declarations (prototypes)
double area(double);
double circumference(double);

int main() {
    cout << "Enter radius: ";
    double radius = 0;
    cin >> radius;

    // Call function area()
    cout << "Area is: " << area(radius) << endl;

    // Call function circumference()
    cout << "Circumference is: " 
         << circumference(radius) << endl;
    
    return 0;
}

// Function definitios (implementations)
double area(double radius) {
    return PI * radius * radius;
}

double circumference(double radius) {
    return 2 * PI * radius;
}
Enter radius: 4
Area is: 50.2655
Circumference is: 25.1327

Audio Programmierung #08 – C++ Grundlagen: Verschachtelte Schleifen

Genauso, wie man manchmal verschachtelte if-Anweisungen benötigt, ist es nicht ungewöhnlich auch Schleifen zu verschachteln. Wenn wir beispielsweise zwei Arrays mit Integern haben und jedes der Elemente miteinander multiplizieren wollen – wenn es sich um mathematische Vektoren handelt – dann benutzt man dazu am besten eine verschachtelte Schleife.

Die erste Schleife läuft durch myNums1, während die innere Schleife durch myNums2 läuft.

// nestedLoops.cpp

#include <iostream>

using namespace std;

int main() {
    const int ARRAY1_LEN = 3;
    const int ARRAY2_LEN = 2;

    int myNums1[ARRAY1_LEN] = {35, -3, 0};
    int myNums2[ARRAY2_LEN] = {20, -1};

    cout << "Multiplying each int in myNums1 by each"
         << " im MyNums2:" << endl;

    for(int index1 = 0; index1 < ARRAY1_LEN; ++index1) {
        for(int index2 = 0; index2 < ARRAY2_LEN; ++index2) {
            cout << myNums1[index1] << " X " << myNums2[index2]
                 << " = " << myNums1[index1] * myNums2[index2]
                 << endl;
        }
    }
    return 0;
}
Multiplying each int in myNums1 by each im MyNums2:
35 X 20 = 700
35 X -1 = -35
-3 X 20 = -60
-3 X -1 = 3
0 X 20 = 0
0 X -1 = 0

Die besagten verschachtelten for Schleifen sind in Zeile 17 und 18. Die erste Schleife durchläuft das Array myNums1 und die zweite Schleife durchläuft das Array myNums2. Für jedes Element in myNums1 wird immer die komplette zweite Schleife ausgeführt.

Nun kann man seine Pure Data Patches auch in VCV Rack nutzen…

VCV Rack verfügt mittlerweile über eine nahezu unendliche Auswahl an Modulen – die meisten davon sind sogar kostenlos. Was aber, wenn ich meine eigenen Module erstellen will? Selbst für findige C++-Programmierer stellt das Rack eine komfortable Umgebung dar, seine eigenen Ideen relativ einfach umzusetzen. Und für alle, die in C++ nicht unbedingt zuhause sind, gibt es das Prototype-Modul.

Prototype kann man mit Skripten der Sprachen JavaScript, Lua oder SuperCollider füttern, neuerdings aber auch mit Vult’s eigener DSP Sprache und Pure Data Patches.

Seit Version 1.3.0 kann man also auch die visuelle Umgebung Pure Data nutzen, um Ideen für Module umzusetzen.

Man muss Prototype vorher mitteilen, wo auf dem Rechner sich die Pure Data Installation befindet und schon kann man seine Patches laden und ausführen. Prototype verfügt über 6 Ein- und Ausgänge…

Der einzige Wermutstropfen: Man kann nur eine Instanz von Prototype inklusive PD-Patch gleichzeitig nutzen. Diese Einschränkung hat aber nichts mit VCV Rack oder Prototype zu tun, sondern mit Pure Data selbst.

Ok, wer also nicht gleich in die C++ Welt einsteigen will, der kann sich dank Prototype erstmal mit JS, Lua, SuperCollider, Vult oder Pure Data ein wenig die Hände schmutzig machen.

https://community.vcvrack.com/t/pure-data-added-to-vcv-prototype/10583

Audio Programmierung #07 – C++ Grundlagen: Schleifen…

Bisher haben wir gesehen, dass man den Programmablauf ändern kann und der Benutzer entscheiden kann, ob er beispielsweise eine Multiplikation oder eine Addition durchführen will. Was aber, wenn er nach der ersten Addition eine weitere ausführen möchte, oder dann vielleicht doch mal eine Multiplikation? Was, wenn das Programm nicht einfach nach der ausgeführten Berechnung enden soll?

In diesem Fall müssen wir den Code immer wieder ausführen, bis eine Bedingung nicht mehr erfüllt ist und das Programm dann beendet. D.h. das Programm müsste in einer Schleife laufen. Iterative Anweisungen, auch Schleifen genannt, führen einen Codeblock solange aus, wie eine Bedingung true ist. Es gibt drei verschiedene Varianten von Schleifen. Die while und for Anweisungen prüfen zunächst eine Bedingung, bevor ein Codeblock ausgeführt wird und die do ... while Anweisung führt erst den Codeblock aus und prüft dann die Bedingung.

Die while Anweisung

Eine while Anweisung wiederholt einen Codeblock genau solange, wie eine gegebene Bedingung true ist. Die Syntax sieht so aus:

while (condition) {
    statements ...
}

Audio Programmierung #06 – C++ Grundlagen: Die Kontrolle des Programmablaufs (switch-case)

Das Ziel eines switch-case Konstrukts ist es, einen Ausdruck gegen eine größere Auswahl von Konstanten zu vergleichen und dann dementsprechend für jede Möglichkeit etwas anderes auszuführen. Die C++ Schlüsselwörter, die wir in diesem Zusammenhang oft sehen werden, sind switch, case, default und break.

Die allgemeine Syntax sieht so aus:

switch(expression) {

    case LabelA:
        DoSomething;
        break;

    case LabelB:
        DoSomethigElse;
        break;

    // and so on ...
    default:
        DoStuffWhenExpressionIsNotHandledAbove;
        break;
}
    

VST-Plugin Programmierung #04 – Eine JUCE GUI Anwendung kann mehr als nur Text darstellen…

Im letzten Artikel haben wir unserer GUI Anwendung mithilfe einer Komponente (main component) etwas Inhalt geschenkt. Dieser Inhalt bestand aber nur aus einfachem Text. Das geht natürlich noch besser. Wir machen zunächst ungefähr da weiter, wo wir das letzte mal aufgehört haben. Dazu öffnen wir das letzte Projekt MainComponentTutorial nochmal und ändern die paint() Methode wieder so ab, dass uns das beliebte „Hello World!“ wieder angezeigt wird. Außerdem können wir dann auch die Member-Variable currentSizeAsString wieder entfernen (auch deren Nutzung in der resize() Methode).

Damit sieht unsere paint() Methode wieder so aus:

void MainComponent::paint (Graphics& g)
{
    g.fillAll(Colours::darkgrey);
    g.setColour (Colours::orange);
    g.setFont (28.0f);
    g.drawText ("Hello World!", getLocalBounds(),
                Justification::centred, true);  
}

Wenn wir das Projekt nun kompilieren und ausführen, haben wir wieder unser dunkelgraues Fenster mit dem orangen „Hello World!“ Text in der Mitte.

Mithilfe der Graphics Klasse können wir nun noch andere Dinge in unsere MainComponent „zeichnen“.

Audio Programmierung #05 – C++ Grundlagen: Die Kontrolle des Programmablaufs (if … else)

Die meiste Software verhält sich den Eingaben des Nutzers entsprechend. D.h. der Nutzer kontrolliert den Ablauf des Programms. Damit eine Applikation sich dementsprechend verhält, muss man Bedingungen erfüllen oder nicht erfüllen, um verschiedene Verzweigungen im Code zu betreten.

Bedingte Ausführung mit if ... else

Bisher haben wir nur Programme geschrieben, die seriell abliefen, d.h. es wurde eine Anweisung nach der anderen abgearbeitet – von oben nach unten. Jede Zeile wurde ausgeführt und keine Zeile wurde ignoriert. Aber die meisten Anwendungen funktionieren so nicht. Angenommen ein Programm soll zwei Zahlen multiplizieren, wenn der Nutzer ein m eingibt, oder sie addieren, wenn der Nutzer irgendwas anderes eingibt.

Dieses Szenario ist im folgenden Flußdiagramm dargestellt:

Seite 1 von 3

Präsentiert von WordPress & Theme erstellt von Anders Norén