Commission Music

Commission Music
Bespoke Noise!!

Thursday, 16 November 2006

How to write a SC plugin / Biquad filter!!!

The tutorials in the SC distro are not really great on how to do plugins, so here's my version. After you get the SC source code (an exercize left to the reader), you're going to want to build your own copy of SC. This should not be the same copy that you use for doing your music, because development tends to break things and you don't want to break your instrument. First build the Server, then the Plugins, then the Lang.

Once you've done that, open the plugin project with Xcode. In the Project Window, pick a target and ctrl-click on it to duplicate it. Then option click the new target to rename it to [whatever]. Double click on it to bring up the target inspector. In the summary, rename Base Product Name to [whatever].scx .  Then click on "Settings" in the list on the left. Change Product Name to [whatever].scx .  Close the target inspector menu and go back to the project menu. Drag your new target into the list for All, so it gets built when you build all.

Ctrl-click on your new target again to Add. Add a new C++ file. Don't generate a header file for it. This is my example, a biquad filter:

/*
 *  LesUGens.cpp
 *  xSC3plugins
 *
 *  Created by Celeste Hutchins on 16/10/06.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// all plugins should include SC_Plugin.h
#include "SC_PlugIn.h"

// and this line
static InterfaceTable *ft;

// here you define the data that your plugin will need to keep around
// biquads have two delayed samples, so that's what we save

struct Biquad : public Unit
{

    // delayed samples
    float m_sa1;
    float m_sa2;
};


// declare the functions that your UGen will need
extern "C"
{
 // this line is required
 void load(InterfaceTable *inTable);

 // calculate the next batch of samples
 void Biquad_next(Biquad *unit, int numsamples);
 // constructor
 void Biquad_Ctor(Biquad* unit);
 
}

//////////////////////////////////////////////////////////////////////////////////////////////////


// Calculation function for the UGen.  This gets called once per x samples (usually 66)

void Biquad_next(Biquad *unit, int numsamples) 
{

 // pointers to in and out
 float *out = ZOUT(0);
 float *in = ZIN(0);
    
 // load delayed samples from our struct
 float delay1 = unit->m_sa1;
 float delay2 = unit->m_sa2;

 // the filter co-efficients are passed in.  These might change at the control rate,
 // so we re-read them every time.
 // the optimizer will stick these in registers
 float amp0 = ZIN0(1);
 float amp1 = ZIN0(2);
 float amp2 = ZIN0(3);
 float amp3 = ZIN0(4);
 float amp4 = ZIN0(5);
 
 
 float next_delay;
 
 // This loop actually does the calculation
 LOOP(numsamples,

  // read in the next sample
     float samp = ZXP(in);

  // calculate
     next_delay = (amp0 * samp) + (amp1 * delay1) + (amp2 * delay2);

  //write out result
     ZXP(out) = next_delay - (amp3 *delay1) - (amp4 * delay2);

  // keep track of data
     delay2 = delay1;
     delay1 = next_delay;
     
 );
 
 // write data back into the struct for the next time
 unit->m_sa1 = delay1;
 unit->m_sa2 = delay2;
 
}


// The constructor function
// This only runs once
// It initializes the struct
// Sets the calculation function
// And, for reasons I don't understand, calculates one sample of output

void Biquad_Ctor(Biquad *unit)
{

 // set the calculation function
    SETCALC(Biquad_next);
    
 // initialize data
    unit->m_sa1 = 0.f;
    unit->m_sa2 = 0.f;

 // 1 sample of output
    Biquad_next(unit, 1);

}

// This function gets called when the plugin is loaded
void load(InterfaceTable *inTable)
{

 // don't forget this line
    ft = inTable;

 // Nor this line
    DefineSimpleUnit(Biquad);
}

Ok, when you build this, it will get copied into the plugin directory. But that's not enough. The SCLang also needs to know about your new UGen. Create a new class file called [whatever].sc .  You can stick this in your ~/Library, it won't mess up your other copies of SuperCollider. This is my file:

Biquad : UGen {

 *ar { arg in, a0 = 1, a1 = 0, a2 =0, a3 =0, a4 =0, mul = 1.0, add = 0.0;
  ^this.multiNew('audio', in, a0, a1, a2, a3, a4).madd(mul, add)
 }
 
 *kr { arg in, a0 = 1, a1 = 0, a2 =0, a3 =0, a4 =0, mul = 1.0, add = 0.0;
  ^this.multiNew('control', in, a0, a1, a2, a3, a4).madd(mul, add)
 }
 
}

The multiNew part handles multiple channel expansion for you. The .madd ads the convenience variables mul and add. Your users like to have those.

I don't know if a biquad filter comes with SC or not. I couldn't find one. They're useful for Karplus-Strong and a few other things. For more information, check out the wikipedia article on Filter Design

Tags: ,

No comments: