Montag, 11. März 2013

FILTER DESIGN IN 10 SCHRITTEN

Aufgrund zahlreicher Anfragen werde ich diesmal nicht auf die MS Technik eingehen und stattdessen zeigen, wie man Filter einbindet.

Zu diesem Beispiel benutze ich die Biquad Filter Klasse von Nigel Redmon (http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/), welche zu diesem Zeitpunkt gratis und frei verwendet werden darf. Du kannst dir die Dateien Biquad.cpp und Biquad.h dort herunterladen.
Ausserdem gibt es dort auch eine (englisch sprachige) Erklärung, wie der Filter funktioniert und die Parameter übergeben werden.

LOS GEHT'S!
Erstelle ein neues Projekt und nenne es "FILTERTest"
Ich benutze folgende Dateinamen: "FILTERTest.cpp" und "FILTERTest.h".

Wir beginnen mit der Datei "FILTERTest.h"

Zuerst müssen wir mal wissen, welche und wieviele Parameter wir benötigen.
Ein Blick in die Datei Biquad.cpp oder Biquad.h verrät uns:

• Type
• Frequency
• Quality
• Gain

1. Diese Parameter müssen deklariert werden und in den Enumerator angegeben werden:

enum {
  kType = 0, //--Index beginnend von 0!
  kFreq1,  //-- == 1
  kQ1,    //-- == 2
  kGain1,  //-- == 3
  kNumParameters  // -- Insgesamt 4 Parameter, 0 bis 3 :-)
};

Dies passiert VOR der Klassendeklaration.
Schnell noch Programme, Eingänge, Ausgänge und ID des Plugins angeben...


// TODO: Change to reflect your plugin
const int kNumPrograms = 0;
const int kNumInputs = 2;
const int kNumOutputs = 2;
const unsigned long kUniqueId = 'XXXX';

Dann geht es weiter mit unserer Klasse "FILTERTest". Das kenne wir schon aus unserem ersten Plugin:

class FILTERTest : public AudioEffectX {
public:
  FILTERTest(audioMasterCallback audioMaster);
  ~FILTERTest();
  
  virtual VstInt32 canDo(char *text);
  virtual bool getEffectName(char* name);
  
  virtual float getParameter(VstInt32 index);
  virtual void getParameterDisplay(VstInt32 index, char *text);
  virtual void getParameterLabel(VstInt32 index, char *label);
  virtual void getParameterName(VstInt32 index, char *text);
  virtual VstPlugCategory getPlugCategory();
  
  virtual bool getProductString(char* text);
  virtual void getProgramName(char *name);
  virtual bool getVendorString(char* text);
  virtual VstInt32 getVendorVersion();

  virtual void process(float **inputs, float **outputs, VstInt32 sampleFrames);  
  virtual void processReplacing(float **inputs, float **outputs, VstInt32 sampleFrames);
  
  virtual void setParameter(VstInt32 index, float value);
  virtual void setProgramName(char *name);
  
private:

float fFreq1, fQ1, fGain1; //-- Deklaration der Parameter: Fließkommazahl!
int fType;  //-- Deklaration des ausgewählten Filtertyps: Hier kommt lediglich ein Integer zum Einsatz.

float fMaxFreq,fMinFreq; //-- Biquad arbeitet mit "echten" Werten: fMax == 20000.0, fMin == 20.0 usw.
float fMaxGain,fMinGain; //-- fMax == 30.0, fMin == -30.0
float fMaxQ,fMinQ;      //-- fMax == 0.01, fMin == 10.0
int fMaxType ,fMinType; //-- fMax == 6, fMin == 0 (Integerzahl)
};


Das wärs mal fürs erste mit der FILTERTest.h Datei!

//--------------------------------------------------------------------
//--------------------------------------------------------------------
//--------------------------------------------------------------------



2. WIR MACHEN WEITER MIT DER DATEI FILTERTest.cpp!


Dazu kopieren wir die Dateien Biquad.h und Biquad.cpp in unser Projekt, falls ihr das noch nicht gemacht habt.

Öffnet dann die Datei FILTERTest.cpp, definiert und bindet (include) folgende Datein ein:

#ifndef __FilterTest_H
#include "FilterTest.h"
#endif

#include "Biquad.h"

#include <math.h>

Die Einbindungen sind selbsterklärend, oder? 


3. Danach folgt unser Klassenkonstruktor mit der Initialisierung ALLER Parameter und Variablen

AudioEffect* createEffectInstance(audioMasterCallback audioMaster) {
return new FILTERTest(audioMaster);
}

FILTERTest::FILTERTest(audioMasterCallback audioMaster)
: AudioEffectX(audioMaster, kNumPrograms, kNumParameters) {
  setNumInputs(kNumInputs);
  setNumOutputs(kNumOutputs);
  setUniqueID(kUniqueId);
  
  fType = 0.0; //--Filter Type Init
  fFreq1 = 1000.0; //--Frequency Init
  fQ1 = 0.707; //--Quality Init
  fGain1 = 0; //--Gain Init
  
  
  fMaxFreq = 22000; //--Maximum Frequency
  fMinFreq = 10; //--Minimum Frequency
  fMaxQ = 10; //--Maximum Q
  fMinQ = 0.01; //--Minimum Q
  fMaxGain = 30; //--Maximum Faim
  fMinGain = -30; //--Minimum Gain
  fMaxType = 6.0; //--Maximum Type
  fMinType = 0.0; //--Minimum Type
  
  
  
}

Im Deconstructor und canDo() bleibt alles beim alten.
Wir geben unserem Plugin einen Namen:

FILTERTest::~FILTERTest() {
}

VstInt32 FILTERTest::canDo(char *text) {
  return 0;
}

bool FILTERTest::getEffectName(char* name) {
  strncpy(name, "FILTERTest", kVstMaxProductStrLen);
  return true;
}

4. Nun folgen einige "Switch" Anweisungen. Zuerst "getParameter()"


float FILTERTest::getParameter(VstInt32 index) {

  float v = 0.0; //-- Für "return v"
  
  switch (index) {
 
  case kType:
  v = fType;
  break;
  case kFreq1:
  v = fFreq1; 
  break;
  case kQ1:
  v = fQ1; 
  break;
  case kGain1:
  v = fGain1; 
  break;
  }
  return v;
}

5. Danach folgt das Parameter Display. Hier ist etwas Schreibarbeit nötig:


void FILTERTest::getParameterDisplay(VstInt32 index, char *text) {
  
    switch (index) {
  case kType:
if (fType == 0) //-- Unser vorher deklarierter Integer "fType" von 0 bis 6
{
vst_strncpy(text, "Low Pass", kVstMaxParamStrLen);
}
if (fType == 1)
{
vst_strncpy(text, "High Pass", kVstMaxParamStrLen);
}
if (fType == 2)
{
vst_strncpy(text, "Band Pass", kVstMaxParamStrLen);
}
if (fType == 3)
{
vst_strncpy(text, "Notch", kVstMaxParamStrLen);
}
if (fType == 4)
{
vst_strncpy(text, "Peak", kVstMaxParamStrLen);
}
if (fType == 5)
{
vst_strncpy(text, "Low Shelf", kVstMaxParamStrLen);
}
if (fType == 6)
{
vst_strncpy(text, "High Shelf", kVstMaxParamStrLen);
}
  break;
  case kFreq1:
float2string (fFreq1, text, kVstMaxParamStrLen); //--Testweise benutzen wir mal float2string()
  break;
  case kQ1:
float2string (fQ1, text, kVstMaxParamStrLen);
  break;
  case kGain1:
float2string (fGain1, text, kVstMaxParamStrLen);
  break;
  }
}


6. Das Label für unser Display

void FILTERTest::getParameterLabel(VstInt32 index, char *text) {

  switch(index){
  
case kType:
vst_strncpy(text, "", kVstMaxParamStrLen);
break;
case kFreq1:
vst_strncpy(text, "Hz", kVstMaxParamStrLen);
break;
case kQ1:
vst_strncpy(text, "Q", kVstMaxParamStrLen);
break;
case kGain1:
vst_strncpy(text, "dB", kVstMaxParamStrLen);
break;
  
  }
}


7. Der Name des jeweiligen Parameters

void FILTERTest::getParameterName(VstInt32 index, char *text) {
  
switch(index){
case kType:
vst_strncpy(text, "Type", kVstMaxParamStrLen);
break;
case kFreq1:
vst_strncpy(text, "Freq", kVstMaxParamStrLen);
break;
case kQ1:
vst_strncpy(text, "Qual", kVstMaxParamStrLen);
break;
case kGain1:
vst_strncpy(text, "Gain", kVstMaxParamStrLen);
break;
}
}

8. Nun folgen noch einige notwendige Angaben zum Plugin

VstPlugCategory FILTERTest::getPlugCategory() {
  return kPlugCategEffect;
}

bool FILTERTest::getProductString(char* text) {
  strcpy(text, "FILTERTest");
  return true;
}

void FILTERTest::getProgramName(char *name) {

}

bool FILTERTest::getVendorString(char* text) {
  strcpy(text, "CISDSPFACTORY");
  return true;
}

VstInt32 FILTERTest::getVendorVersion() {

  return 1000;
}



9. Nun folgt "setParameter()"

Die Parameter müssen im Format 20 - 20000 für Frequenz, 0.01 - 10 für Quality und -30 bis +30 für Gain übergeben werden. Bei dem Parameter fGain entscheiden wir uns für eine lineare Übergabe.
Da wir bei einer linearen Übergabe der Parameter sehr viel Verlust an Daten im Bassbereich bei fFreq1 hätten, basteln wir uns einfach eine logarithmische Skala. Deshalb haben wir die vordefinierte Headerdatei <math.h> eingebunden!


void FILTERTest::setParameter(VstInt32 index, float value) {
   //--Formel für logarithmische Skala (für besseres auswählen der tiefen Frequenzen)
   //--"fFreq1 = exp(log(20) + fFreq1 * (log(20000) - log(20)));"
   switch (index) {
  case kType:
  fType = value * (fMaxType - fMinType) + fMinType; //--Int - Formel für Auswahl des Filters 0 - 6 (siehe enum "Biquad.h") - keine Logarithmik nötig!
  break;
  case kFreq1:
  fFreq1 = exp(log(fMinFreq) + value * (log(fMaxFreq)-log(fMinFreq)));  //--Log
  break;
  case kQ1:
  fQ1 = exp(log(fMinQ) + value * (log(fMaxQ)-log(fMinQ))); //--Log
  break;
  case kGain1:
  fGain1 = value * (fMaxGain - fMinGain) + fMinGain; //--Lin
  break;
  }
}


AUFGABE: Probiere die lineare Übergabe der Frequenzen aus und sehe den Unterschied!


Da wir im Moment kein Programm für unseren Ersetzungseffekt brauchen, lassen wir setProgramName() frei.

void FILTERTest::setProgramName(char *name) {

}



10. EINBINDEN DES FILTERS

Wir erzeugen einfach aus der Biquad Klasse zwei neue Filter. Warum zwei? 
Pro Kanal einen. Diese können nicht gemeinasm genutzt werden!! 

//--INIT FILTERS--WICHTIG: AUSSERHALB EINER PROZEDUR!
Biquad *FilterL = new Biquad();
Biquad *FilterR = new Biquad();


So. Fertig mit der aufwändigsten Arbeit. Nun machen wir uns an die Verarbeitung der Daten!
Ein Blick in die Datei Biquad.cpp erklärt uns wie wir unsere Daten zu übergeben haben.
Unser vorher erstellter Filter "FilterL" wird mittels "setBiquad()" aktiviert und wir können die Variablen unserer Parameter übergeben. 

Und unseren Loop kochen wir nun auch ein wenig anders, und zwar in einer "for" Schleife.
Natürlich ist das auch mit einer while Schleife möglich, aber wir ersparen uns ein wenig Schreibarbeit.
Der Filter wird nun berechnet. Dies passiert mit der Anweisung "process()" aus der Datei "Biquad.h" und hat nichts mit der Anweisung "process()" aus der vstsdk2.4 Klasse zu tun!

void FILTERTest::process(float **inputs, float **outputs, VstInt32 sampleFrames) {

FilterL->setBiquad(fType, fFreq1 / sampleRate, fQ1, fGain1);
FilterR->setBiquad(fType, fFreq1 / sampleRate, fQ1, fGain1);

//--OUTPUT-LOOP------------------------------//
//------------------------------------------//
for(int i=0; i < sampleFrames; i++) { // i = 0...127
outputs[0][i] = FilterL->process((inputs[0][i]));//inputs[0][i];
outputs[1][i] = FilterR->process((inputs[1][i]));//inputs[1][i];
}

}


Nun kopieren wir den gesamten process() Block und fügen ihn ein. Einzige Änderung: 
Aus process() wird processReplacing().


void FILTERTest::processReplacing(float **inputs, float **outputs, VstInt32 sampleFrames) {

FilterL->setBiquad(fType, fFreq1 / sampleRate, fQ1, fGain1);
FilterR->setBiquad(fType, fFreq1 / sampleRate, fQ1, fGain1);


//--OUTPUT-LOOP------------------------------//
//------------------------------------------//
for(int i=0; i < sampleFrames; i++) { // i = 0...127
outputs[0][i] = FilterL->process((inputs[0][i]));//inputs[0][i];
outputs[1][i] = FilterR->process((inputs[1][i]));//inputs[1][i];
}

}



So, das wars! So einfach ist das mit den Filterklassen. 
Danke fürs Lesen meines Blogs. Falls noch Fragen auftauchen, dann stellt sie!

Chris


PS: Auf der zu Beginn erwähnten Seite http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ findest du auch sehr viel Information zu Filterdesign und DSP Programmierung.



Mittwoch, 27. Februar 2013

Teile eines VST Plugins

Heyho,

In Teil eins unseres Workshops geht es mal darum, wie so ein VST Plugin aufgebaut ist.
Erstelle wir zuerst mal ein neues Projekt.

Wie das geht steht auf

 http://teragonaudio.com/article/Making-a-VST-plugin-from-scratch-with-Xcode.html

Eine grandiose Resource ;)

Wenn ihr das geschafft habt, ist das meiste schon getan.
Jetzt geht ihr auf den Tree in der linken Seite und klickt  ctrl + apfel auf source.
Dann auf "add existing file". Dann navigiert ihr zu eurem VST SDK. Bei "public.sdk" gibts den Ordner "samples". Da drinnen gibts 2 Files: again.h und again.cpp.
Beide Dateien auswählen und dem Projekt hinzufügen. WICHTIG: IN DAS PROJEKT KOPIEREN!

Gut. Anhand dieses Beispiels werden wir nun das Plugin veranschaulichen.

Beginnen wir mit der Datei "again.h":
In dieser Datei wird mal alles deklariert, was wir in unserer again.cpp Datei dann verwenden wollen.


//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4 $Date: 2006/11/13 09:08:27 $
//
// Category     : VST 2.x SDK Samples
// Filename     : again.h
// Created by   : Steinberg Media Technologies
// Description  : Stereo plugin which applies Gain [-oo, 0dB]
//
//  2006, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------

#ifndef __again__
#define __again__

//- Wir fügen die externe Klasse audioeffectx.h hinzu. Ohne die geht gar nix ;)

#include "public.sdk/source/vst2.x/audioeffectx.h"

//-------------------------------------------------------------------------------------------------------
//- Erstellen der Klasse, welche auf AudioEffectX zugreift. 
//- Der Name der Klasse ist AGain.

class AGain : public AudioEffectX
{
public:
AGain (audioMasterCallback audioMaster);
~AGain ();

//- Der Teil in dem dann unser Prozess passiert und die eigentliche DSP Arbeit.Für den Anfang...
// Processing
virtual void processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames);

//- Nun folgen die Anweisungen für den Programm Namen. Das kennt ihr aus den sogenannten "Presets" eines VST Plugins

// Program
virtual void setProgramName (char* name);
virtual void getProgramName (char* name);
//- Die Parameter: set, get, das Label, das Display, der Name der Parameter 
// Parameters
virtual void setParameter (VstInt32 index, float value);
virtual float getParameter (VstInt32 index);
virtual void getParameterLabel (VstInt32 index, char* label);
virtual void getParameterDisplay (VstInt32 index, char* text);
virtual void getParameterName (VstInt32 index, char* text);

//- Der Name des Plugins, dein Company Name, plugin ID und Version

virtual bool getEffectName (char* name);
virtual bool getVendorString (char* text);
virtual bool getProductString (char* text);
virtual VstInt32 getVendorVersion ();

//- Der geschützte Teil des plugins: Hier kommen unsere z.B. fader, values etc. die wir verwenden wollen. 
protected:
float fGain ;
char programName[kVstMaxProgNameLen + 1];
};

#endif


Das wars schon mit der again.h Datei! Mehr gibt es dazu vorerst nicht zu sagen.
Da wir nur einen Parameter im Plugin haben, verzichten wir gezielt auf "enum". Falls du nicht weißt was das ist, dann zerbrich dir an dieser Stelle nicht den kopf darüber. Dazu etwas später mehr.


Weiter gehts mit der "again.cpp" Datei!



//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4 $Date: 2006/11/13 09:08:27 $
//
// Category     : VST 2.x SDK Samples
// Filename     : again.cpp
// Created by   : Steinberg Media Technologies
// Description  : Stereo plugin which applies Gain [-oo, 0dB]
//
//  2006, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------

//- WICHTIG: Die Header Datei includen!!!

#include "again.h"


//-------------------------------------------------------------------------------------------------------

//- Erstelle eine neue Effekt Instanz

AudioEffect* createEffectInstance (audioMasterCallback audioMaster)
{
return new AGain (audioMaster);
}

//-------------------------------------------------------------------------------------------------------

//- Initialisiere das plugin

AGain::AGain (audioMasterCallback audioMaster)
: AudioEffectX (audioMaster, 1, 1) // 1 Programm, 1 Parameter->(fGain)!!
{
setNumInputs (2); // Zwei Eingangs Kanäle
setNumOutputs (2); // Zwei Ausgangs Kanäle
setUniqueID ('cis1'); // 4 Zeichen, zur Identifikation des Plugins im Host
canProcessReplacing (); // Unterstützung der Replacing Funktion
// canDoubleReplacing (); //mit doppelter Präzision - nicht so wichtig im Moment

fGain = 1.f; // fGain auf höchsten Wert initialisieren: 0 dB

vst_strncpy (programName, "Erser Eintrag!", kVstMaxProgNameLen); // Programmname
}

//- den Deconstructor vergessen wir mal am Anfang.
//-------------------------------------------------------------------------------------------------------
AGain::~AGain ()
{
// nothing to do here
}

//-------------------------------------------------------------------------------------------------------
//- Wir setzen unseren Programmnamen

void AGain::setProgramName (char* name)
{
vst_strncpy (programName, name, kVstMaxProgNameLen);
}

//-----------------------------------------------------------------------------------------
//- Wir lesen unseren Programmnamen
void AGain::getProgramName (char* name)
{
vst_strncpy (name, programName, kVstMaxProgNameLen);
}


//-----------------------------------------------------------------------------------------

//- Setzen der Parameter: Hier ist schon der erste kleine Unterschied zu dem Steinberg Template: Für den Fall, dass mehrere Parameter hinzugefügt werden sollen, habe ich hier gleich von Anfang an eine Switch Anweisung erstellt. Das ist wichtig für das korrekte erkennen und verarbeiten der Parameter. Ich gehe davon aus, dass ihr C++ ein wenig beherrscht und werde eine Switch Anweisung nicht näher erklären ;)
void AGain::setParameter (VstInt32 index, float value)
{
switch(index){
case 0: fGain = value;break;

}
}

//-----------------------------------------------------------------------------------------

//- Auslesen der Parameter - ebenfalls mit einer Switch Anweisung
float AGain::getParameter (VstInt32 index)
{
float v = 0;

switch(index){
case 0: v = fGain;break;
}
return v;
}

//-----------------------------------------------------------------------------------------

//- Der Name unseres Parameters fGain
void AGain::getParameterName (VstInt32 index, char* label)
{
switch(index){
case 0: vst_strncpy (label, "Gain", kVstMaxParamStrLen);break;
}
}

//-----------------------------------------------------------------------------------------

//- Anzeigen des veränderten Werts von fGain. Zu beachten ist hier die fertige Klasse dB2string(). Diese wandelt mir den angezeigten "float" Standard Wert 0-1 zu -oo zu 0 dB um. Klasse, nicht? :p  Du kannst dich ein wenig spielen und "dB2string" zu "float2string" umschreiben und schauen was dann passiert. Richtig: Es wir eine Fließkommazahl anstatt der dB angezeigt.

void AGain::getParameterDisplay (VstInt32 index, char* text)
{
switch(index){
case 0: dB2string (fGain, text, kVstMaxParamStrLen);break;
}
}

//-----------------------------------------------------------------------------------------
//- Natürlich wollen wir auch wissen was wir da ausgelesen haben. In diesem Fall: Dezibel
void AGain::getParameterLabel (VstInt32 index, char* label)
{
switch(index){
case 0: vst_strncpy (label, "dB", kVstMaxParamStrLen);break;
}
}

//------------------------------------------------------------------------

//- Dieser Teil gibt den Namen des Plugins aus. Hier: "Gain"
bool AGain::getEffectName (char* name)
{
vst_strncpy (name, "Gain", kVstMaxEffectNameLen);
return true;
}

//------------------------------------------------------------------------
//- Wie heisst unser Plugin? 
bool AGain::getProductString (char* text)
{
vst_strncpy (text, "Gain", kVstMaxProductStrLen);
return true;
}


//------------------------------------------------------------------------

//- DEIN Firmenname steht hier
bool AGain::getVendorString (char* text)
{
vst_strncpy (text, "YOUR COMPANY", kVstMaxVendorStrLen);
return true;
}

//-----------------------------------------------------------------------------------------

//- Unsere Produktversion. Wenn du z.B. an Version 1.1 arbeitest, gibst du bei return einfach "1100" ein! So einfach ist das ;)

VstInt32 AGain::getVendorVersion ()
return 1000; 
}

//-----------------------------------------------------------------------------------------

//- Jetzt kommen wir zum interessanten Teil, nachdem wir schon jede Menge an Code geschrieben haben. die Funktion processReplacing! 
void AGain::processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames)
{
    float* in1  =  inputs[0]; //- Zeiger in1 auf input1
    float* in2  =  inputs[1]; //- das selbe auf in2
    float* out1 = outputs[0]; //- und auf output1
    float* out2 = outputs[1]; //- das selbe


//- Nun gibt es viele Möglichkeiten wie man die Daten verarbeitet. Hier verwenden wir ma eine while Schleife. Diese sagt folgendes (ungefähr): Während unsere Samples größer oder gleich 0 sind berechne den Inhalt! 

    while (--sampleFrames >= 0)
    {
        //- In der Schleife multiplizieren wir nun unsere Eingänge mit unserem Gain Fader. Vergesst nicht: hier geht es um einsen und nullen. Das heisst, unser Eingang ist immer EINS. Wenn  wir nun unseren Gain Fader auf die Hälfte einstellen, dann gibt er 0.5 zurück. Das hat nichts mit unserer Dezibel Klasse zu tun. Diese ist nur für unser Display verantwortlich! Wir bekommen also den Wert 1*0.5 = 0.5 zurück. 
        (*out1++) = (*in1++) * fGain;
        (*out2++) = (*in2++) * fGain;
    }
}




So das wars eigentlich schon. Wenn ihr nun in eurer IDE alles richtig gemacht habt, dann könnt ihr das mal kompilieren und das Plugin in euren VST Ordner kopieren.

Im nächsten Tutorial basteln wir uns eine kleine Stereoverbreiterung mit Phasenkorrektur!


Chris