Lesson 1: Patching - npisanti/ofxPDSP GitHub Wiki

DISCLAIMER: I'm taking for granted you have a good knowledge of subtractive synthesis concepts and nomenclature, as learning it while writing and compiling code would be difficult. So if things like "ADSR" and "oscillator" doesn't mean anything to you you should start somewhere else, sorry.

Ok, first thing first be shure to read about the openFrameworks sound stream works, and also have a working oF example sound/audioOutputExample.

Always remember that the oF audio callback runs in another thread of you app, so sometimes i will talk about what methods are thread-safe and what not in the next lessons.

Now modify yourofApp.h and add those lines:

    // pdsp modules
    ofxPDSPEngine engine;

    pdsp::VAOscillator osc;
    pdsp::LFO lfo;

In pdsp we are always connecting objects using the >> operator (also called "patch" operator). A ofxPDSPEngine will always be the end of the chain, it will start and stop the input and output audio streams and it will fill the audio buffers.

In your setup method you should set the right device ID for the ofxPDSPEngine and then start it with the right sample rate and buffer size. Device IDs will be listed in the console when you start your app, so if you ear no sound check out all the available devices and set the right one (also be shure it is not muted by your system).

void ofApp::setup(){
    
    ofSetWindowShape(640, 360); // we don't need fancy graphics at the moment
    
    //--------PATCHING-------
    // we will make our patching here
    
    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3); 
}

You can use the patch operator >> to connect pdsp objects (that we will call "modules"). All the modules have some input and outputs, when you use the >> operator you are connecting one of those outputs to one of those inputs. The ofxPDSPEngine has an 'audio_out(int channel)' method for patching modules to the system output. For example we could write:

void ofApp::setup(){
    ofSetWindowShape(640, 360);
    
    //--------PATCHING-------
    osc >> engine.audio_out(0);

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

we have just patched the oscillator output to our system left channel. Lower your system volume out, compile and run. You should hear a buzzing pitched sound from your left speaker / headphone.

When you are patching something you could use the * operator to scale the signal, for example:

void ofApp::setup(){
    ofSetWindowShape(640, 360); 
    
    //--------PATCHING-------
    osc * 0.25f >> engine.audio_out(0);
    osc * 0.50f >> engine.audio_out(1);

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

Compile and run. Now you should ear the same sound but lower in volume and slightly panned to the right.

When you are using the patch operator on the modules you are patching the default outputs / inputs. You could access other inputs and outputs with some in____() and out____() or in("tagname") and out("tagname") methods. What ins/outs are available and what are the default is described in the documentation. For example we could take a look at the VAOscillator page for knowing what outputs we have. We have outputs for all the four basic waveform, so we could try:

void ofApp::setup(){
    ofSetWindowShape(640, 360); // we don't need fancy graphics at the moment
    
    //--------PATCHING-------
    osc.out_sine() * 0.25f >> engine.audio_out(0);
    osc.out_sine() * 0.25f >> engine.audio_out(1);

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

Compile and run. You should hear a sine wave with no panning. Now our code is mostly the same of example-hello_world.

Looking at the VAOscillator documentation you should have seen that there were many inputs, but we haven't patched anything to them. When an input is not patched it usually takes a default value, for example in our case in_pitch() will have the default value of 69, that is A4 = 440hz (so you could use it to tune your instruments).

We can patch a float number right into osc.in_pitch(). When something is connected to an input the default value will be ignored.

void ofApp::setup(){
    ofSetWindowShape(640, 360); // we don't need fancy graphics at the moment
    
    //--------PATCHING-------
    osc.out_sine() * 0.25f >> engine.audio_out(0);
    osc.out_sine() * 0.25f >> engine.audio_out(1);
    81.0f >> osc.in_pitch();

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

Compile and run. Now the sine frequency should be an octave higher.
REMEMBER: when you are patching a float to an input you are not setting the default value, you are connecting a "number" to the input. You can have only one float number connected to each input so you could think it is the same, but the difference is that when you are patching another module to the input the module signal will override the default input, but it will be summed to connected floats.

void ofApp::setup(){
    ofSetWindowShape(640, 360); // we don't need fancy graphics at the moment
    
    //--------PATCHING-------
    osc.out_sine() * 0.25f >> engine.audio_out(0);
    osc.out_sine() * 0.25f >> engine.audio_out(1);
    43.0f >> osc; // now the pitch is 43 ( in_pitch() is the default input, so calling it is optional )
    60.0f >> osc.in_pitch(); // now the pitch is 60 (C4), the floats are not summed!

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

You could patch multiple signal to an input, and they will be summed. It's time to patch the LFO:

void ofApp::setup(){
    ofSetWindowShape(640, 360); // we don't need fancy graphics at the moment
    
    //--------PATCHING-------
    osc.out_sine() * 0.25f >> engine.audio_out(0);
    osc.out_sine() * 0.25f >> engine.audio_out(1);
                         60.0f >> osc.in_pitch(); 
    lfo.out_triangle() * 24.0f >> osc.in_pitch(); 

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

Compile and run, you should hear the sine wave modulated by the LFO.

You could use also the + operator to patch multiple modules with one >> operation, so the following code will produce the same result (remember only that the float has to be the first argument of the sum, because: c++ reasons):

void ofApp::setup(){
    ofSetWindowShape(640, 360); // we don't need fancy graphics at the moment
    
    //--------PATCHING-------
    osc.out_sine() * 0.25f >> engine.audio_out(0);
    osc.out_sine() * 0.25f >> engine.audio_out(1);
    60.0f + lfo.out_triangle() * 24.0f >> osc.in_pitch(); 

    //------------SETUPS AND START AUDIO-------------
    engine.listDevices();
    engine.setDeviceID(0); // REMEMBER TO SET THIS AT THE RIGHT INDEX!!!!
    engine.setup( 44100, 512, 3);    
}

Compile and run, the result will be the same.

It isn't thread-safe to patch something when the audio callback is running, but (most of the time)* it is thread safe to patch floats, so we can add a bit of interaction:

void ofApp::mouseMoved(int x, int y ){
    float pitch = ofMap(x, 0, ofGetWidth(), 48.0f, 72.0f);
    float lfoFreq = ofMap(y, 0, ofGetHeight(), 1.5f, 0.25f);
    pitch >> osc.in_pitch();
    lfoFreq >> lfo.in_freq();
}

Compile and run. Now you can control the pitch and the lfo speed by hovering the mouse in the app window. The change of those patched values is not smoothed out so if you move the mouse fast in the x axis you should ear some "grainy" noise. For a smoother value change you could patch floats to a pdsp::CRSlew and then patch this pdsp::CRSlew to the desired input (more on this later).

You can find a copy of the code we created here.

* patching floats to an input it is not an atomic operation, but usually you don't patch floats in the audio thread so there will be no other code to cause race conditions. Patching Units and module to an input while the audio thread is running instead is always a bad idea. Also in some cases patching float is always thread safe: for example patching floats to a pdsp::ValueNode, to a pdsp::CRSlew or to a pdsp::PatchNode.