API
a lightweight Application Programming Interface
An API is a way to encapsulate all of the publically callable functions for a single application, composition, piece, class, service etc. in one universally accessible place.
API can be used to register simple functions or can register methods on objects. These registered functions can then be called through the API object or by making an API call using the API class.
(
a = API('myApp');
// define
a.add('wiggle',{ arg numTimes;
"------commencing wiggling------".postln;
numTimes.do({
"wiggling".postln;
});
"------wiggling completed------".postln;
"".postln;
"this is the return value" // return value
});
)
// wiggle it
a.wiggle(3)
OSC
The API can also easily be mounted to OSC and the piece can be easily controlled via Processing, Quartz Composer etc. Since the interface of the API is queryable it would be easily possible to auto-configure MIDI devices.
a.mountOSC;
// send a message to self (the supercollider client)
NetAddr.localAddr.sendMsg( '/myApp/wiggle', 4 )
But notice that we didn't listen for a response, so we didn't see the return value.
Let's listen:
(
o = OSCresponderNode(NetAddr.localAddr,'/response',
{ |time, responder, msg|
("time:" + time).postln;
("msg:" + msg).postln;
}).add;
// and send the message to self again
NetAddr.localAddr.sendMsg( '/myApp/wiggle', 4 )
)
o.remove
See the OSC second below for more
Other Benefits
Even when not working with external applications, its beneficial to use an API to keep the primary functions of your application separate from the particulars of your GUI, key commands, MIDI controllers, OSC and other input devices. This means you Don't Repeat Yourself (DRY) by having a view action and a key command do the same thing. You also keep your your data (whether in variables or model classes) separate from your application (actions on that data).
button.action = {
a.wiggle(1);
}
// make a function
slider.action = a.func('wiggle')
// call the API using lookup by API name
NoteOnResponder({ |src,chan,note,vel| API('myApp').call('wiggle',vel) });
If a new device (midi/keyboard) comes along you can quickly attach it to your musical code and play around with it.
Tools for configuring MIDI, touch, arduinio devices etc could configure themselves automatically or using a GUI by querying APIs.
When the time comes to hook it up to OSC your application is easily mounted.
It also allows a simple way for your application to resuse functionality in other SC applications by calling commands via the API rather than using global or environment variables.
Creating an API
You specify an API by name and it is created or if an API has already been created by that name it is fetched from the API registry.
a = API('myApp');
b = API('myApp');
// true
a === b
Defining functions
add(selector,function)
// define a single function
a.add('wiggle',{
"wiggling"
});
// with as many arguments as wanted
a.add('viggle',{ arg one,two;
"vee bitte ?" + (one * two)
});
addAll(dictionary)
// add several methods using a dictionary
a.addAll(IdentityDictionary[
'woggle' -> {
"woggling".postln;
}
]);
// add several methods using an Event
// note that ( ) creates an Event which is often abused for its laconic syntax
a.addAll((
worgle: {
"worgling"
}
));
make(makeFunction)
// add several methods using a make function (see Environment-make)
a.make({
~waggle = { arg one,two;
one.do({
"waggling".postln;
});
two
};
});
exposeMethods(object,methodNameList)
for each method name in the list add an API entry that will call that method on the object passing in args
// expose methods on an object
b = Pseq([1,2,3,4],inf).asStream;
a.exposeMethods(b,['next','reset']);
s.boot;
p = Pbind(\freq, Prand([300, 500, 231.2, 399.2], inf), \dur, Prand([0.1, 0.3], inf));
a = API('p').exposeMethods(p,[\play]);
// 6 methods
a.functionNames
// I have no default synth def, no idea why
a.play
exposeAllExcept(object,methodNamesToExclude)
Useful for full blown Application classes where most every method should be callable through the API
TrApp {
var model;
*new { arg model;
^super.newCopyArgs(model)
}
publish {
API('total').exposeAllExcept(this,[\publish,\mount,\prFindMode ]);
}
mount {
API('total').mountOSC
}
// public applications methods ...
play {
}
stop {
}
etc.
}
trapp = TrApp(tracker);
trapp.publish.mount;
or take any object you are fond of and expose it.
// should be methods native to Pbind only
// may change this, but its a safety/cleanliness issue
// maybe expose can find any responding method
s.boot;
p = Pbind(\freq, Prand([300, 500, 231.2, 399.2], inf), \dur, Prand([0.1, 0.3], inf));
a = API('p').exposeAllExcept(p); // exposes all because there are no methods excluded
// 6 methods
a.functionNames
// not a method of Pbind
a.play
Calling API functions
API calls return the value from the function. The examples here assume you executed the code in the examples above, or they will complain that the api call was not recognized.
API('myApp').call('wiggle')
// passing in arguments
API('myApp').call('waggle',5,"done")
Functions may also be called directly on the API object as though they were native methods of the API object
a = API('myApp');
a.waggle(16)
But only if the API class (or Object) does not already implement a method by that name
// Pseq has next and reset methods
b = Pseq([1,2,3,4],inf).asStream;
a.exposeMethods(b,['next','reset']);
// but so does Object
// so this calls Object-next rather than looking up a responding API function
a.next
// solution: be explicit by using call
// this finds the stream's next and reset defined above
a.call('next')
a.call('reset')
Functions for use in view actions etc.
func(selector, ... presetArgs)
returns a function that will call the API when valued.
presetArgs if supplied allow you to "bake in" arguments to the API call
a = API('myApp');
a.add("funcMe",{ arg b,c;
[b,c].postln;
nil
});
// create a function
f = a.func("funcMe");
// execute it
f.value("like","you did")
// baking in arguments
g = a.func("funcMe","arguments defined at func creation time precede arguments supplied when valued");
g.value("like","you did");
// notice that in this case funcMe only takes 2 arguments so "you did" is not handled by the function
(
w = Window.new.front;
l = Slider(w, Rect(20, 60, 150, 20));
l.action = a.func("funcMe");
l.keyDownAction = g;
)
OSC
a.mountOSC(baseCmdName,addr)
mounts the commands on OSCresponders so that the whole API is available at baseCmdName.
functions are passed the OSC message as args. time and addr are not passed through as
the functions are assumed to be device neutral.
baseCmdName
by default will be mounted at the name of the API
eg. '/myApp/method'
if set to "" then all commands will be available at the root:
"/method"
addr
nil by default meaning that it will respond to messages from any IP address
(
a.mountOSC
)
Results of the function will be returned to the address.
TODO: decide how the baseCmdName for the response is best specified.
provisional:
If called via OSC the return value is treated as a list: ['/response' ] ++ result.asArray
so your function may return a single item or an array.
NOTE: processing sketch in help file does not get the response. not sure where the problem is yet.
A better solution is required.
Exceptions will result in an "/exception meaCulpaText callingMsg" response
and API functions will be allowed to throw a malformed input exception that returns "/error scoldingText callingMsg"
a.unmountOSC
unmounts all previously mounted OSCresponders
(
a.unmountOSC
)
Processing Example
A basic OSC receiving processing sketch is included in the Help folder that works with this basic example.
You need the http://www.sojamo.de/oscP5 library.
(
a = API('osctest');
// define
a.add('wiggle',{ arg x,y;
("wiggling" + x + y).postln;
"wiggled" + Main.elapsedTime // return value
});
a.add('click',{ arg x,y;
[x,y].postln;
"".postln;
"You clicked" + x + y // return value
});
a.mountOSC
)
Open Help/osctest.pde and run it.
The Processing osc lib appears to send from a different port each time it starts the applet.
The sketch sends a /API/registerListener command so that SC knows the addr/port to reply to
// this is processing code
(
OscMessage msg = new OscMessage("/API/registerListener");
msg.add(listeningPort);
oscP5.send(msg, sc);
)
Or you can register from the SC side:
API.registerListener( callsFromNetAddr,sendResponseToNetAddr,responseCmdName)
callsFromNetAddr: calls from this address
sendResponseToNetAddr: responses will be sent here
responseCmdName: optional response OSC path (default /response)
// or just set it up from SC
// all requests will respond to /response on port 12000 locally
API.routeResponses( '/response/, nil, NetAddr(nil,12000) )
For the other app to get a response sent to a path other than the default responseCmdName:
/API/call /pathToSendReturnValue /myApp/funcName arg1 arg2
note: this may change to be within the specific API's directory
An Example
s.boot;
(
var seq,numSteps,step,patt,pbind,playing,api;
// SOUND APPLICATION
numSteps = 1024;
seq = Array.fill(4, {Array.fill(numSteps,0)});
step = 0;
patt = Pfunc({
var ev;
step = step.clip(0,numSteps-1);
ev = (degree: seq[0][step],nharms: seq[1][step],amp:seq[2][step],pan:seq[3][step],instrument:\tinker,dur:0.125);
step = (step + 1).wrap(0,numSteps-1);
ev
});
SynthDef(\tinker, { | out, freq = 440, amp = 0.1, nharms = 10, pan = 0, gate = 1 |
var audio = Blip.ar(freq, nharms,amp);
var env = Linen.kr(gate, doneAction: 2);
OffsetOut.ar(out, Pan2.ar(audio, pan, env) );
}).add;
// I never use patterns. I don't get why amp doesn't work or why <> doesn't work
pbind = patt; //patt <> Pbind(\instrument,\tinker,\dur,0.125)
// API
api = API('stepper');
api.make({
~play = {
if(playing.isNil,{
playing = pbind.play;
});
};
~stop = {
if(playing.notNil,{
playing.stop;
playing = nil;
});
};
~isPlaying = {
playing.notNil
};
~setNumSteps = { arg ns;
numSteps = ns;
};
~numSteps = {
numSteps
};
~setStep = { arg st;
step = st.clip(0,numSteps-1);
};
~resize = {
if(seq[0].size > numSteps,{
seq = seq.collect({ arg sq; sq.copyRange(0,numSteps-1) });
},{
if(seq[0].size < numSteps,{
seq = seq.collect({ arg sq; sq.extend(numSteps,0) });
});
});
};
~put = { arg parami,i,val;
if(i < numSteps,{
seq[parami][i] = val;
true
},{
false
});
};
~rand = {
seq = [Array.rand(numSteps,0,12),Array.rand(numSteps,0,12),Array.rand(numSteps,-1,1),Array.rand(numSteps,0.0,1.0)];
};
});
api.rand()
)
(
// a boring gui
a = API('stepper');
w = Window.new.front;
w.flow({ arg l;
Button(l,80@17)
.states_([
["stop", Color.black, Color.red]
])
.action_(a.func('stop'));
Button(l,80@17)
.states_([
["play", Color.black, Color.green]
])
.action_(a.func('play'));
l.startRow;
EZSlider(l,200@17,
"set numSteps:",
StaticIntegerSpec(1,1024),
{ arg nb;
a.setNumSteps(nb.value.asInteger);
a.resize;
},a.numSteps);
l.startRow;
Button(l,80@17)
.states_([
["randomize", Color.black, Color.blue]
])
.action_(a.func('rand'));
},Rect(10,10,400,400));
)
Note that this GUI has no dependency relationship on anything. It addresses the stepper API by name.
Map Images to sequence data
OS X only I think
Open an image file, scale it down to 1 row (letting the graphics system smudge and average each column).
Maps the pixels of the image onto the sequence above using the API
(
File.openDialog("Choose an image file...",{ arg path;
var img,numSteps,scale;
a = API('stepper');
img = Image(path);
img.scalesWhenResized = true;
img.setSize( img.width, 1 );
v = img.plot("Scaled down to one row",Rect(100,100,img.width,100));
//a.setNumSteps( min(img.width,1024));
numSteps = a.numSteps;
scale = img.width.asFloat / numSteps.asFloat;
a.numSteps.do({ arg i;
var c,hue,sat,val,alpha;
p = img.getPixel( (i * scale).asInteger, 0 );
c = Color.new255(p.red,p.green,p.blue,p.alpha);
# hue,sat,val,alpha = c.asHSV;
[hue,sat,val,alpha].debug;
a.put(0, i, (hue * 32).asInteger.debug("degree") ); // hue to degree
a.put( 1, i, (val * 10).debug("nharms") );// value to nharms
a.put( 3, i, (sat * 0.1).debug("amp") );// saturation to amp
//a.put( 1, i, val * 10.postln );// value to pan
"".debug;
});
// click on the image to move the sequence play head
v.view.children[0].mouseDownAction = { arg view,x,y;
var step;
step = (x / scale).asInteger;
step.debug("play from step");
a.setStep( step );
};
if(a.isPlaying.not,{
a.play
});
});
)
A note on the use of Application classes
MVC would have a Controller to coordinate communication between the Model and the View(s). In larger systems this often leaves a confusion about where "state" is stored. Some state is musical/data and some state has to do with how the application is being used. Ideally a controller should only coordinate communication between views and model and should have no state. In practice they tend to swell into applications. A controller starts off just creating views and specifying the actions, but as complexity grows it starts to store state in its own variables and gradually becomes an application in itself. But it is an application that is tied to a specific GUI. If key commands, MIDI or even OSC are attached to that controller/application, it quickly becomes limiting and the controller has exceeded its scope. The controller is GUI-centric.
The solution to this bloat is to create an application class that encapsulates state regardless of which interface system is used to change that state. This class has intimate knowledge of the Model(s) but should not need to know anything about the views, controllers or external devices that connect to it. The API exposes that application class' methods. A controller creates views and connects their actions to call the API. Any musical state (eg. currently selected pattern) or data would be stored in the model, and any application state (related to your current session of using the music, its editor etc.) is stored in the application object.