Writing Hadron Plugins

All Hadron plugins are SuperCollider classes and they inherit a supplied class called HadronPlugin. If you are not familiar with writing classes in SC, the standard Writing-Classes help will accompany. Hopefully the info here will also give you some little insights on how SC classes work. If you have never written a class for SC, there is nothing to be afraid of, nothing is harder than dealing with the regular environment.

In this tutorial, we are going to create a plugin called HrDoNothing that will just pass its inputs to its outputs.

See also: Hadron HadronPlugin HadronModTargetControl

Inherit HadronPlugin Class to Make It Work For You

Your plugin needs to inherit the HadronPlugin class, so here is what we have at the beginning (with some necessary default code):

HrDoNothing : HadronPlugin

{

*initClass

{

this.addHadronPlugin;

}

}

You need to save this as a class file into the Hadron plugin directory (don't forget to save it as a Class file with .sc extension by selecting "Class" from the save dropdown menu). The *initClass method is necessary if you want to have your plugin recognized by the system.

Now if you launch Hadron:

Hadron.new;

You will see your plugin in the "New Inst." window list. But you can't create it yet, it will throw an error. We need to handle the initialization of the class by supplying necessary parameters. This is what it should look like, add this into your class:

*new

{|argParentApp, argIdent, argUniqueID, argExtraArgs, argCanvasXY|

var numIns = 2;

var numOuts = 2;

var bounds = Rect(200, 200, 350, 50);

var name = "HrDoNothing";

^super.new(argParentApp, name, argIdent, argUniqueID, argExtraArgs, bounds, numIns, numOuts, argCanvasXY).init;

}

This looks more or less the same for most plugins. You need to specify number of inputs you will have (2 in this example), number of outputs you will have (again, 2 in this example), default screen bounds for your plugin, and a name string that will be seen on the canvas and the GUI window title. The rest should stay intact for your plugin to work.

If you want your plugin to have arguments, you should parse the argExtraArgs argument passed into *new which is an Array of String arguments and use them. For example, if you want your plugin to have variable number of inputs and / or outputs whose count will be determined by arguments supplied by user from the Hadron GUI, you can grab the values here, and use them. To see an example of this in action, see the sources of HrStereoSplitter and HrStereoMixer. They get arguments to determine how many inlets or outlets they are going to have, and have default values employed if there are no arguments supplied. In your plugin implementation, you can always access these arguments from the variable "extraArgs".

The last line of the code (^super.new(...)) initializes the superclass first (HadronPlugin) and the chain of events call our "init" method. Init is the place where you define your GUI, define synths, possibly send them to server etc.

In your init method and possibly in other methods, you will be using some variables that are provided to you by the HadronPlugin interface, here is the list:

inBusses: This is an array of audio busses (see: Bus) that holds the input busses of your plugin. The size of the array is determined by the number of input channels you define in your *new class method. You should get audio from these busses (in our case, there are two inputs and they are inBusses[0] and inBusses[1]) using InFeedback. These busses should be sent as arguments to your synth.

outBusses: This is an array of audio busses (see: Bus) that holds the output busses of your plugin. The size of the array is determined by the number of output channels you define in your *new class method. You should write to those busses with Out. These busses should be sent as arguments to your synth.

uniqueID: An Integer between 0 and 65536 unique to the instances of your plugins. Every plugin active in a running Hadron system has a uniqueID. This number is provided to you by the HadronPlugin interface.

ident: The ident text as String, if supplied by the user.

window: This is an instance of CompositeView. You should use this as the parent view when drawing your widgets to your GUI. This window is embedded into the outerWindow (which you should not use) which shows the standard In/Outs, Kill and Hide buttons for all plugins. The bounds of the canvas are defined in your *new method.

extraArgs: If extra arguments for your objects are supplied by the user, they are stored in this variable. This is an Array of Strings. You might want to check out the source codes of HrStereoMixer and HrStereoSplitter to see them in action.

saveGets: An uninitialized Array you should fill with functions that will return the values you need to save when the user saves the state of his / her patch, when evaluated. For example, if you need to save the values of a NumberBox and a Slider, you need to say:

saveGets = [ { myNumBox.value; }, { mySlider.value; } ];

saveSets: An uninitialized Array you should fill with functions that will set the values of the parameters saved from obtaining the values of functions from the saveGets variable. The functions will be passed the saved value. The ordering must be same with the functions declared in the saveGets variable. So the companion for the above saveGets variable will be:

saveSets = [{|argVal| myNumBox.valueAction_(argVal);}, {|argVal| mySlider.valueAction_(argVal);} ];

modSets: This is an empty Dictionary. If you want any of the controls / values of your plugin to be modulatable, you should fill this dictionary with key - value pairs where keys will be the descriptive names you give to the modulatable parameter, and the values will be functions that will be passed values which should get the argument and do the relevant action for modulation. So if we have a level slider in our GUI and want it to be modulatable with the help of HadronModTargetControl (see its help for more info) we might put:

modSets.put(\level, {|argVal| myLevelSlider.valueAction_(argVal); });

modGets: This is essentially the same with modSets (a dictionary), but in the functions, you should return the value instead of setting one. So the complementary modGets for the above modSets would be:

modGets.put(\level, { myLevelSlider.value; }); //returns value

group: This is a Group reserved for your synth. Your Groups and Synths should have this group in their targets. See Synth and Group help for more info.

Now that we have more info about the variables provided for us, let's move on to the init method. We will have no GUI widgets for our HrDoNothing plugin so nothing fancy here. We will just change the background color of our view.

init

{

window.background_(Color.gray(0.8));

}

At this stage, if you save your code, recompile the lib, and add the plugin to Hadron.plugins, you should be able to crate the instance from the Hadron GUI, it will be an empty window, with the standard buttons at the bottom. And if you look at the canvas (or the In / Outs window), you will see that our plugin has 2 inputs and 2 outputs. Unfortunately, it doesn't do much yet. We need to define out synth and send it to server first. Here is the code:

init

{

window.background_(Color.gray(0.6));

fork

{

SynthDef("hrDoNothing"++uniqueID,

{

arg inBus0, inBus1, outBus0, outBus1;

var sound = [InFeedback.ar(inBus0), InFeedback.ar(inBus1)];

Out.ar(outBus0, sound[0]);

Out.ar(outBus1, sound[1]);

}).memStore;

Server.default.sync;

synthInstance =

Synth("hrDoNothing"++uniqueID,

[

\inBus0, inBusses[0],

\inBus1, inBusses[1],

\outBus0, outBusses[0],

\outBus1, outBusses[1]

], target: group);

}

}

fork{ } returns a Routine using the supplied function. And Routine is a function which can return in the moddle and resume where it left off later on. Why do we need this? In the fork block, we first define our synth with SynthDef.memStore, and this operation is asynchronous. Before we create the synth instance with Synth(...) we need to make sure that our SynthDef is compiled, sent to server and ready to be used. For this to happen, we need to issue "Server.default.sync;" which pauses the routine until the server finishes the tasks it has on its hands. This pause-continue operation is only possible in a Routine, so we are using a fork { } here. If you are not going to initiate your synth right away, you don't need to worry about this.

We give the name ("hrDoNothing"++uniqueID) to our synth, append the uniqueID to avoid possible name clashes between synths defined in plugins supplied by different people. This is just a convention for making sure Synth names between different plugins don't clash.

In the SynthDef, we read from the input bus arguments with InFeedback, and out to the output busses with Out. The synthInstance variable is not a standard variable provided by HadronPlugin, so we need to define it at the top of our class source, above the *new method:

HrDoNothing : HadronPlugin

{

var synthInstance;

*new

{....

Be sure to send the bus arguments when instantiating your synth, and set the target as the "group" that is supplied for you by HadronPlugin. You can also use your own Group but then, its target should be the group.

Now if you save your class and recompile the lang, when you run Hadron and add your plugin to the system, you can observe in your Server window that Synths will show 1 as count, and a new group will be added. When you kill your instance, they will be freed automatically for you. The HadronPlugin interface frees the supplied group automatically when the instance is killed.

There is one more step you need to take, to make this a complete plugin.

When you connect your instance to another plugin, your bus mappings change, so you need to update your running synth with the new bus mappings. To do this, you need to override a method called "updateBusConnections" inside your plugin. Here is what it looks like:

updateBusConnections

{

synthInstance.set

(

\inBus0, inBusses[0],

\inBus1, inBusses[1],

\outBus0, outBusses[0],

\outBus1, outBusses[1]

);

}

This method is called whenever there is a connection change relevant to your plugin occurs, so you need to update your synth(s) about the change.

Now your plugin is fully functional, here is the complete source:

HrDoNothing : HadronPlugin

{

var synthInstance;

*initClass

{

this.addHadronPlugin;

}

*new

{|argParentApp, argIdent, argUniqueID, argExtraArgs, argCanvasXY|

var numIns = 2;

var numOuts = 2;

var bounds = Rect(200, 200, 350, 50);

var name = "HrDoNothing";

^super.new(argParentApp, name, argIdent, argUniqueID, argExtraArgs, bounds, numIns, numOuts, argCanvasXY).init;

}

init

{

window.background_(Color.gray(0.8));

fork

{

SynthDef("hrDoNothing"++uniqueID,

{

arg inBus0, inBus1, outBus0, outBus1;

var sound = [InFeedback.ar(inBus0), InFeedback.ar(inBus1)];

Out.ar(outBus0, sound[0]);

Out.ar(outBus1, sound[1]);

}).memStore;

Server.default.sync;

synthInstance =

Synth("hrDoNothing"++uniqueID,

[

\inBus0, inBusses[0],

\inBus1, inBusses[1],

\outBus0, outBusses[0],

\outBus1, outBusses[1]

], target: group);

}

}

updateBusConnections

{

synthInstance.set

(

\inBus0, inBusses[0],

\inBus1, inBusses[1],

\outBus0, outBusses[0],

\outBus1, outBusses[1]

);

}

}

Save your plugin, recompile and:

Hadron.new;

You can now use your plugin and it will just pass its inputs to its outputs. You will want more functionality, a custom GUI etc. and there are some other methods supplied by HadronPlugin interface for you to help you get what you need, which you can read about in the HadronPlugin help.