Skip to content

Lesson 3: making Modules

Nicola Pisanti edited this page Apr 10, 2016 · 22 revisions

In this lesson we will learn about making new modules out of basic elements. Start again from scratch. Add a theremin.h file to you /src folder. We will write our new class in the .h code (really bad coding practice, but we are just sketching). In theremin.h

#pragma once
#include "ofxPDSP.h"

class Theremin : public pdsp::Patchable{ 
public:
    Theremin() {
        //add inputs / outputs with these methods 
        addModuleInput("amp", amp.in_mod()); // arguments are tag and the Unit in/out to link to that tag
        addModuleInput("pitch", osc.in_pitch());
        addModuleOutput("signal", amp ); 
        //patching
        osc >> amp;
    }
private:
    pdsp::Amp amp;
    pdsp::FMOperator osc;
};

pdsp::Patchable is the base class you have to extend for creating new modules. Our amp and osc objects are private, but using the methods addModuleInput(const char *tag, Patchable &unit) and addModuleOutput(const char *tag, Patchable &unit) we are making some of those objects inputs/outputs available for patching outside the class using the in("tag") and out("tag") methods. When you are adding an input you can set its tag name as first argument and you can select the input/output to assign as second argument ( if you are not selecting an in/out defaults are used). The first input added will become the default input, and the first output added will become the default output.

So in our ofApp.h, on the top add

#include "theremin.h"

and on the bottom:

    // pdsp modules
    pdsp::Processor engine;
    Theremin theremin; //our new class

in ofApp.cpp

void ofApp::setup(){
    ofSetWindowShape(640, 360);

    //--------PATCHING-------
    theremin * 0.5f >> engine.audio_out(0); // the amp output is default in Theremin
    theremin * 0.5f >> 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); 
}

void ofApp::mouseMoved(int x, int y ){
    float gain = ofMap(y, 0.0f, ofGetHeight(), 1.0f, 0.0f );
    float p = ofMap(x, 0.0f, ofGetWidth(), 48.f, 84.f ); //three octave
    gain >> theremin.in("amp");
    p >> theremin.in("pitch");
}

Now compile and run. By hovering your mouse on the window you should be able to control both the synth amp and pitch.

There is still the problem of some audible zipper noise caused by the control being updated in steps. We can use pdsp::CRSlew to smoothen signals, so modify theremin.h:

#pragma once
#include "ofxPDSP.h"

class Theremin : public pdsp::Patchable{ 
public:
    Theremin() {
        //add inputs / outputs with this methods 
        addModuleInput("amp", ampSlew); // arguments are tag and the Unit in/out to link to that tag
        addModuleInput("pitch", pitchSlew);
        addModuleOutput("signal", amp ); 
        
        //patching
        pitchSlew.set(250.0f) >> osc.in_pitch(); // 250 millisecond slew
        ampSlew.set(100.0f)  >> amp.in_mod(); // 100 millisecond slew
        osc >> amp;
    }
    
private:
    pdsp::Amp amp;
    pdsp::FMOperator osc;
    pdsp::CRSlew ampSlew;
    pdsp::CRSlew pitchSlew;
};

You don't need to modify the other files. Notice we have substituted our pdsp::CRSlew in the addModuleInput() calls and patched them to the amp mod and pitch inputs. Compile and run. Now you can try again and everything will be nicely smoothen out.

Notice that we patched our module using a string passed to the in() method. Basically it's what happens inside all the in_tagname() and out_tagname() methods, they're just handy methods if your IDE has autocompletion. We can add them to our class too:

#pragma once
#include "ofxPDSP.h"

class Theremin : public pdsp::Patchable{ 
public:
    Theremin() {
        //add inputs / outputs with this methods 
        addModuleInput("amp", ampSlew); // arguments are tag and the Unit in/out to link to that tag
        addModuleInput("pitch", pitchSlew);
        addModuleOutput("signal", amp ); 
        
        //patching
        pitchSlew.set(250.0f) >> osc.in_pitch(); // 250 millisecond slew
        ampSlew.set(100.0f)  >> amp.in_mod(); // 100 millisecond slew
        osc >> amp;
    }
    
    pdsp::Patchable& in_amp(){
        return in("amp");
    }
    pdsp::Patchable& in_pitch(){
        return in("pitch");
    }
    pdsp::Patchable& out_signal(){
        return out("signal");
    }
    
private:
    pdsp::Amp amp;
    pdsp::FMOperator osc;
    pdsp::CRSlew ampSlew;
    pdsp::CRSlew pitchSlew;
};

Now this module will work perfectly fine until you use some kind of data structure of Theremin, like a vector for having multiple voices. Then the compiler will spit out lot of errors, as some of the classes we are using cannot create default copy constructors, so we have to refactor our code like this:

#pragma once
#include "ofxPDSP.h"

class Theremin : public pdsp::Patchable{ 
public:
    Theremin() { patch(); }
    Theremin(const Theremin & other) { patch(); cout<<"warning! copy constructing modules is bad!!!\n"; } 

    void patch(){
        //add inputs / outputs with this methods 
        addModuleInput("amp", ampSlew); // arguments are tag and the Unit in/out to link to that tag
        addModuleInput("pitch", pitchSlew);
        addModuleOutput("signal", amp ); 
        
        //patching
        pitchSlew.set(250.0f) >> osc.in_pitch(); // 250 millisecond slew
        ampSlew.set(100.0f)  >> amp.in_mod(); // 100 millisecond slew
        osc >> amp;
    }
    
    pdsp::Patchable& in_amp(){
        return in("amp");
    }
    pdsp::Patchable& in_pitch(){
        return in("pitch");
    }
    pdsp::Patchable& out_signal(){
        return out("signal");
    }
    
private:
    pdsp::Amp amp;
    pdsp::FMOperator osc;
    pdsp::CRSlew ampSlew;
    pdsp::CRSlew pitchSlew;
};

You can find also this lesson finished code in the lessons repo.