summaryrefslogtreecommitdiffstats
path: root/src/progs/supercollider
diff options
context:
space:
mode:
Diffstat (limited to 'src/progs/supercollider')
-rw-r--r--src/progs/supercollider/Makefile.am2
-rw-r--r--src/progs/supercollider/Om.sc746
-rw-r--r--src/progs/supercollider/README11
-rw-r--r--src/progs/supercollider/example.sc27
4 files changed, 786 insertions, 0 deletions
diff --git a/src/progs/supercollider/Makefile.am b/src/progs/supercollider/Makefile.am
new file mode 100644
index 00000000..69661c73
--- /dev/null
+++ b/src/progs/supercollider/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = Om.sc example.sc
+
diff --git a/src/progs/supercollider/Om.sc b/src/progs/supercollider/Om.sc
new file mode 100644
index 00000000..cb366d58
--- /dev/null
+++ b/src/progs/supercollider/Om.sc
@@ -0,0 +1,746 @@
+// TODO:
+// * Keep track of established connections.
+Om : Model {
+ classvar <>program = "om", <>patchLoader = "om_patch_loader";
+ classvar <>oscURL, <nodeTypeMap, <>uiClass;
+ var <addr;
+ var <>loadIntoJack = true;
+ var allocator, requestResponders, requestHandlers, notificationResponders;
+ var creatingNode, newNodeEnd;
+ var <registered = false, <booting = false;
+ var <root;
+ var <plugins, pluginParentEvent;
+ var onNewPatch;
+ *initClass {
+ Class.initClassTree(Event);
+ Class.initClassTree(NetAddr);
+ Event.parentEvents.default[\omcmd] = \noteOn;
+ Event.parentEvents.default[\omEventFunctions] = (
+ noteOn: #{ arg midinote=60, amp=0.1;
+ [midinote, asInteger((amp * 127).clip(0, 127)) ] },
+ noteOff: #{ arg midinote=60; [ midinote ] },
+ setPortValue: #{|portValue=0| [portValue] }
+ );
+ Event.parentEvents.default.eventTypes[\om]=#{|server|
+ var freqs, lag, dur, sustain, strum, target, bndl, omcmd;
+ freqs = ~freq = ~freq.value + ~detune;
+ if (freqs.isKindOf(Symbol).not) {
+ ~amp = ~amp.value;
+ strum = ~strum;
+ lag = ~lag + server.latency;
+ sustain = ~sustain = ~sustain.value;
+ omcmd = ~omcmd;
+ target = ~target;
+ ~midinote = ~midinote.value;
+ ~portValue = ~portValue.value;
+ bndl = ~omEventFunctions[omcmd].valueEnvir.asCollection;
+ bndl = bndl.flop;
+ bndl.do {|msgArgs, i|
+ var latency;
+ latency = i * strum + lag;
+ if(latency == 0.0) {
+ target.performList(omcmd, msgArgs)
+ } {
+ thisThread.clock.sched(latency, {
+ target.performList(omcmd, msgArgs);
+ })
+ };
+ if(omcmd === \noteOn) {
+ thisThread.clock.sched(sustain + latency, {
+ target.noteOff(msgArgs[0])
+ })
+ }
+ }
+ }
+ };
+ oscURL="osc.udp://"++NetAddr.localAddr.ip++$:++NetAddr.localAddr.port;
+ nodeTypeMap = IdentityDictionary[
+ \Internal -> OmInternalNode,
+ \LADSPA -> OmLADSPANode,
+ \DSSI -> OmDSSINode
+ ];
+ uiClass = OmEmacsUI
+ }
+ *new { | netaddr |
+ ^super.new.init(netaddr)
+ }
+ gui { ^uiClass.new(this) }
+ init { |netaddr|
+ addr = netaddr ? NetAddr("127.0.0.1", 16180);
+ onNewPatch = IdentityDictionary.new;
+ allocator = StackNumberAllocator(0,1024);
+ requestHandlers = IdentityDictionary.new;
+ requestResponders = [
+ "response/ok" -> {|id|
+ requestHandlers.removeAt(id).value; allocator.free(id) },
+ "response/error" -> {|id,text|
+ requestHandlers.removeAt(id);
+ allocator.free(id);
+ ("Om"+text).error }
+ ].collect({|a|
+ var func = a.value;
+ OSCresponder(addr, "/om/"++a.key, {|time,resp,msg|
+ func.value(*msg[1..])
+ })
+ });
+ notificationResponders = [
+ "new_patch" -> {|path,poly|
+ var func = onNewPatch.removeAt(path);
+ if (func.notNil) {
+ func.value(this.getPatch(path,false).prSetPoly(poly))
+ }
+ },
+ "metadata/update" -> {|path,key,value|
+ this.getObject(path).metadata.prSetMetadata(key, value) },
+ "new_node" -> {|path,poly,type,lib,label|
+ var patchPath, nodeName, patch, node;
+ var lastSlash = path.asString.inject(nil,{|last,char,i|
+ if(char==$/,i,last)
+ });
+ if (lastSlash.notNil) {
+ patchPath = path.asString.copyFromStart(lastSlash-1);
+ nodeName = path.asString.copyToEnd(lastSlash+1);
+ patch = this.getPatch(patchPath);
+ if (patch.notNil) {
+ if (patch.hasNode(nodeName).not) {
+ node = nodeTypeMap[type].new
+ (nodeName, patch, poly, label, lib);
+ creatingNode = node;
+ patch.nodes[nodeName.asSymbol] = node;
+ patch.changed(\newNode, node);
+ } {
+ if (patch.getNode(nodeName).class != nodeTypeMap[type]) {
+ ("Om sent an existng node with differing type"+path).warn
+ }
+ }
+ } {
+ ("Om tried to create node in non-existing patch"+patchPath).warn
+ }
+ } {
+ ("Invalid path in node creation"+path).warn
+ }
+ },
+ "new_node_end" -> {
+ newNodeEnd.value(creatingNode);
+ newNodeEnd = nil;
+ creatingNode = nil },
+ "new_port" -> {|path,type,dir,hint,def,min,max|
+ var basePath, portName, parent, port;
+ var lastSlash = path.asString.inject(nil,{|last,char,i|
+ if(char==$/,i,last)
+ });
+ if (lastSlash.notNil) {
+ basePath = path.asString.copyFromStart(lastSlash-1);
+ portName = path.asString.copyToEnd(lastSlash+1);
+ parent = this.getNode(basePath) ? this.getPatch(basePath);
+ if (parent.notNil) {
+ if (parent.hasPort(portName).not) {
+ port = OmPort.new(portName, parent, type, dir, hint, def, min, max);
+ parent.ports[portName.asSymbol] = port;
+ parent.changed(\newPort, port)
+ } {
+ if (parent.getPort(portName).porttype != type) {
+ ("Om tried to create an already existing port with differing type"
+ +path).warn
+ }
+ }
+ } {
+ ("Om tried to create port on non-existing object"+basePath).warn
+ }
+ } {
+ ("Invalid path in port creation"+path).warn
+ }
+ },
+ "control_change" -> {|path,value|
+ this.getPort(path).prSetValue(value) },
+ "patch_enabled" -> {|path| this.getPatch(path).prSetEnabled(true) },
+ "patch_disabled" -> {|path| this.getPatch(path).prSetEnabled(false) },
+ "plugin" -> {|lib,label,name,type|
+ plugins.add(Event.new(4,nil,pluginParentEvent).putAll(
+ (type:type, lib:lib, label:label, name:name))) },
+ "node_removal" -> {|path|
+ var node = this.getNode(path);
+ if (node.notNil) {
+ node.parent.nodes.removeAt(node.name.asSymbol).free
+ } {
+ ("Om attempting to remove non-existing node"+path).warn
+ }
+ },
+ "port_removal" -> {|path|
+ var port = this.getPort(path);
+ if (port.notNil) {
+ port.parent.ports.removeAt(port.name.asSymbol).free
+ } {
+ ("Om attempting to remove non-existing port"+path).warn
+ }
+ },
+ "patch_destruction" -> {|path|
+ var patch = this.getPatch(path);
+ if (patch.notNil) {
+ patch.parent.patches.removeAt(patch.name.asSymbol).free
+ } {
+ ("Om attempting to remove non-existing patch"+path).warn
+ }
+ },
+ "program_add" -> {|path,bank,program,name|
+ var node = this.getNode(path);
+ if (node.respondsTo(\prProgramAdd)) {
+ node.prProgramAdd(bank,program,name)
+ } {
+ ("Om tried to add program info to"+node).warn
+ }
+ }
+ ].collect({|a|
+ var func = a.value;
+ OSCresponder(addr, "/om/"++a.key, {|time,resp,msg|
+ func.value(*msg[1..])
+ })
+ });
+ pluginParentEvent = Event.new(2,nil,nil).putAll((
+ engine:this,
+ new:{|self,path,poly=1,handler|self.engine.createNode(path?("/"++self.name),self.type,self.lib,self.label,poly,created:handler)}
+ ));
+ }
+ *waitForBoot {|func| ^this.new.waitForBoot(func) }
+ waitForBoot {|func|
+ var r, id = 727;
+ requestHandlers[id] = {
+ r.stop;
+ booting=false;
+ this.changed(\running, true);
+ func.value(this)
+ };
+ if (booting.not) {this.boot};
+ r = Routine.run {
+ 50.do {
+ 0.1.wait;
+ addr.sendMsg("/om/ping", id)
+ };
+ requestHandlers.removeAt(id);
+ "Om engine boot failed".error;
+ }
+ }
+ getPatch {|path, mustExist=true|
+ var elements, currentPatch;
+ if (path.class == Array) { elements = path
+ } { elements = path.asString.split($/) };
+ elements.do{|elem|
+ if (elem=="") {
+ currentPatch = root
+ } {
+ currentPatch = currentPatch.getPatch(elem,mustExist);
+ if (currentPatch.isNil) { ^nil }
+ }
+ };
+ ^currentPatch;
+ }
+ getNode {|path|
+ var basePath, nodeName, patch;
+ if (path.class == Array) { basePath = path
+ } { basePath = path.asString.split($/) };
+ nodeName = basePath.pop;
+ patch = this.getPatch(basePath,true);
+ if (patch.notNil) {
+ ^patch.getNode(nodeName)
+ };
+ ^nil
+ }
+ getPort {|path|
+ var basePath, portName, node, patch;
+ basePath = path.asString.split($/);
+ portName = basePath.pop;
+ node = this.getNode(basePath.copy);
+ if (node.notNil) { ^node.getPort(portName) };
+ patch = this.getPatch(basePath,true);
+ if (patch.notNil) { ^patch.getPort(portName) };
+ ^nil
+ }
+ getObject {|path|
+ var patch,node,port;
+ patch = this.getPatch(path,true);
+ if (patch.notNil) { ^patch };
+ node = this.getNode(path);
+ if (node.notNil) { ^node };
+ port = this.getPort(path,true);
+ if (port.notNil) { ^port };
+ ^nil
+ }
+ at {|path|^this.getObject(path.asString)}
+ *boot {|func|
+ ^Om.new.waitForBoot {|e|
+ e.activate {
+ e.register {
+ e.loadPlugins {
+ e.requestPlugins {
+ e.requestAllObjects {
+ func.value(e)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ boot {
+ requestResponders.do({|resp| resp.add});
+ booting = true;
+ if (addr.addr == 2130706433) {
+ if (loadIntoJack) {
+ ("jack_load"+"-i"+addr.port+"Om"+"om").unixCmd
+ } {
+ (program+"-p"+addr.port).unixCmd
+ }
+ } {
+ "You have to manually boot Om now".postln
+ }
+ }
+ loadPatch {|patchPath| (patchLoader + patchPath).unixCmd }
+ activate { | handler |
+ this.sendReq("engine/activate", {
+ root = OmPatch("",nil,this);
+ this.changed(\newPatch, root);
+ handler.value
+ })
+ }
+ register { | handler |
+ this.sendReq("engine/register_client", {
+ registered=true;
+ notificationResponders.do({|resp| resp.add});
+ this.changed(\registered, registered);
+ handler.value(this)
+ })
+ }
+ unregister { | handler |
+ this.sendReq("engine/unregister_client", {
+ registered=false;
+ notificationResponders.do({|resp| resp.remove});
+ this.changed(\registered, registered);
+ handler.value(this)
+ })
+ }
+ registered_ {|flag|
+ if (flag and: registered.not) {
+ this.register
+ } {
+ if (flag.not and: registered) {
+ this.unregister
+ }
+ }
+ }
+ loadPlugins { | handler | this.sendReq("engine/load_plugins", handler) }
+ requestPlugins {|handler|
+ var startTime = Main.elapsedTime;
+ plugins = Set.new;
+ this.sendReq("request/plugins", {
+ ("Received info about"+plugins.size+"plugins in"+(Main.elapsedTime-startTime)+"seconds").postln;
+ this.changed(\plugins, plugins);
+ handler.value(this);
+ })
+ }
+ requestAllObjects { |handler|
+ this.sendReq("request/all_objects", handler)
+ }
+ createPatch { | path, poly=1, handler |
+ onNewPatch[path.asSymbol] = handler;
+ this.sendReq("synth/create_patch", nil, path.asString, poly.asInteger)
+ }
+ createNode { | path, type='LADSPA', lib, label, poly=1, created, handler |
+ newNodeEnd = created;
+ this.sendReq("synth/create_node",handler,path,type,lib,label,poly)
+ }
+ createAudioInput { | path, handler |
+ this.createNode(path,"Internal","","audio_input",0,handler)
+ }
+ createAudioOutput {|path,handler|
+ this.createNode(path,"Internal","","audio_output",0,handler)
+ }
+ createMIDIInput {|path,handler|
+ this.createNode(path,"Internal","","midi_input",1,handler)
+ }
+ createMIDIOutput {|path,handler|
+ this.createNode(path,"Internal","","midi_output",1,handler)
+ }
+ createNoteIn {|path| this.createNode(path,"Internal","","note_in") }
+ connect {|fromPath,toPath,handler|
+ this.sendReq("synth/connect",handler,fromPath.asString,toPath.asString)
+ }
+ disconnect { | fromPath, toPath, handler |
+ this.sendReq("synth/disconnect",handler,fromPath.asString,toPath.asString)
+ }
+ disconnectAll { | path, handler |
+ this.sendReq("synth/disconnect_all",handler,path);
+ }
+ sendReq { | path, handler...args |
+ var id = allocator.alloc;
+ requestHandlers[id] = handler;
+ addr.sendMsg("/om/"++path, id, *args)
+ }
+ quit {
+ if (loadIntoJack) {
+ ("jack_unload"+"Om").unixCmd;
+ booting=false;
+ requestResponders.do(_.remove);
+ notificationResponders.do(_.remove);
+ this.changed(\running, false);
+ } {
+ this.sendReq("engine/quit", {
+ booting=false;
+ requestResponders.do(_.remove);
+ notificationResponders.do(_.remove);
+ this.changed(\running, false);
+ })
+ }
+ }
+ ping {| n=1, func |
+ var id, result, start;
+ id = allocator.alloc;
+ result = 0;
+ requestHandlers[id] = {
+ var end;
+ end = Main.elapsedTime;
+ result=max((end-start).postln,result);
+ n=n-1;
+ if (n > 0) {
+ start = Main.elapsedTime;
+ addr.sendMsg("/om/ping", id)
+ } {
+ allocator.free(id);
+ func.value(result)
+ }
+ };
+ start = Main.elapsedTime;
+ addr.sendMsg("/om/ping", id)
+ }
+ setPortValue {|path, val| this.getPort(path.asString).value=val }
+ jackConnect {|path, jackPort|
+ this.getPort(path).jackConnect(jackPort)
+ }
+ noteOn {|path, note, vel|
+ var patch,node;
+ patch = this.getPatch(path,true);
+ if (patch.notNil) { patch.noteOn(note,vel) };
+ node = this.getNode(path);
+ if (node.notNil) { node.noteOn(note,vel) };
+ }
+ noteOff {|path, note|
+ var patch,node;
+ patch = this.getPatch(path,true);
+ if (patch.notNil) { patch.noteOff(note) };
+ node = this.getNode(path);
+ if (node.notNil) { node.noteOff(note) };
+ }
+ matchPlugins{ | label, lib, name, type |
+ ^plugins.select{ |p|
+ label.matchItem(p.label) and: {
+ lib.matchItem(p.lib) and: {
+ name.matchItem(p.name) and: {
+ type.matchItem(p.type)
+ }
+ }
+ }
+ }
+ }
+ dssiMsg {|path,reqType="program" ...args|
+ addr.sendMsg("/dssi"++path++$/++reqType,*args)
+ }
+}
+
+OmMetadata {
+ var object, dict;
+ *new {|obj|^super.new.metadataInit(obj)}
+ metadataInit {|obj|
+ dict=Dictionary.new;
+ object=obj
+ }
+ put {|key,val|
+ object.engine.sendReq("metadata/set", nil,
+ object.path, key.asString, val.asString)
+ }
+ at {|key|^dict.at(key.asSymbol)}
+ prSetMetadata {|key,val|
+ dict.put(key,val);
+ object.changed(\metadata, key, val)
+ }
+}
+
+OmObject : Model {
+ var <name, <parent, <metadata;
+ *new {|name, parent|
+ ^super.new.initOmObject(name,parent);
+ }
+ initOmObject {|argName, argParent|
+ name = argName;
+ parent = argParent;
+ metadata=OmMetadata(this)
+ }
+ path { ^parent.notNil.if({ parent.path ++ $/ ++ name }, name).asString }
+ depth { ^parent.notNil.if({ parent.depth + 1 }, 0) }
+ engine { ^parent.engine }
+}
+
+OmPort : OmObject {
+ var <porttype, <direction, <spec, <value, <connections;
+ *new {|name,parent,type,dir,hint,def,min,max|
+ ^super.new(name,parent).initPort(type,dir,hint,def,min,max)
+ }
+ initPort {|type,dir,hint,def,min,max|
+ porttype = type;
+ direction = dir;
+ spec = ControlSpec(min, max,
+ if (hint == 'LOGARITHMIC', 'exp', 'lin'),
+ if (hint == 'INTEGER', 1, 0),
+ def);
+ connections = IdentityDictionary.new;
+ }
+ jackConnect {|jackPort|
+ if (porttype.asSymbol != \AUDIO
+ || direction.asSymbol!=\OUTPUT) {
+ Error("Not a audio output port").throw
+ };
+ ("jack_connect" + "Om:"++(this.path) + jackPort).unixCmd
+ }
+ value_ {|val|
+ if (porttype == \CONTROL and: {direction == \INPUT}) {
+ this.engine.sendReq("synth/set_port_value", nil, this.path, val)
+ } {
+ Error("Not a input control port"+this.path).throw
+ }
+ }
+ connectTo {|destPort|
+ if (this.direction!=destPort.direction) {
+ this.engine.connect(this,destPort)
+ } {
+ Error("Unable to connect ports with same direction").throw
+ }
+ }
+
+ // Setters for OSC responders
+ prSetValue {|argValue|
+ if (value != argValue) {
+ value = argValue;
+ this.changed(\value, value)
+ }
+ }
+}
+
+OmNode : OmObject { // Abstract class
+ var <ports, <polyphonic;
+ *new {|name,parent,poly|
+ ^super.new(name,parent).initNode(poly)
+ }
+ initNode {|argPoly|
+ polyphonic = argPoly;
+ ports = IdentityDictionary.new;
+ }
+ hasPort {|name| ^ports[name.asSymbol].notNil }
+ getPort {|name| ^ports[name.asSymbol] }
+ portArray {|type=\AUDIO,dir=\OUTPUT|
+ var result = Array.new(8);
+ ports.do {|port|
+ if (port.porttype==type and: {port.direction==dir}) {
+ result=result.add(port)
+ }
+ };
+ ^result
+ }
+ audioOut {
+ ^this.portArray(\AUDIO, \OUTPUT)
+ }
+ audioIn {
+ ^this.portArray(\AUDIO, \INPUT)
+ }
+}
+
+OmInternalNode : OmNode {
+ var <pluginlabel;
+ *new {|name,parent,poly,label|
+ ^super.new(name,parent,poly).initInternalNode(label)
+ }
+ initInternalNode {|label|
+ pluginlabel = label
+ }
+ noteOn { |note,vel|
+ if (pluginlabel == \note_in or:
+ {pluginlabel == \trigger_in and: {this.value == note}}) {
+ this.engine.sendReq("synth/note_on", nil, this.path, note, vel)
+ } {
+ Error("Not a trigger_in or note_in node").throw
+ }
+ }
+ noteOff {|note|
+ if (pluginlabel == \note_in or:
+ {pluginlabel == \trigger_in and: {this.value == note}}) {
+ this.engine.sendReq("synth/note_off", nil, this.path, note)
+ } {
+ Error("Not a trigger_in or note_in node").throw
+ }
+ }
+}
+
+OmLADSPANode : OmNode {
+ var <pluginlabel, <libname;
+ *new {|name, parent, poly, label, lib|
+ ^super.new(name,parent,poly).initLADSPANode(label,lib)
+ }
+ initLADSPANode {|label,lib|
+ pluginlabel = label;
+ libname = lib
+ }
+}
+
+OmDSSINode : OmLADSPANode {
+ var programs;
+ *new {|name,parent,poly,label,lib|
+ ^super.new(name,parent,poly,label,lib).initDSSI
+ }
+ initDSSI {
+ programs = Set.new;
+ }
+ program {|bank, prog|
+ this.engine.dssiMsg(this.path,"program",bank.asInteger,prog.asInteger)
+ }
+ prProgramAdd {|bank,program,name|
+ var info = (bank:bank, program:program, name:name);
+ if (programs.includes(info).not) {
+ programs.add(info);
+ this.changed(\programAdded, info)
+ }
+ }
+}
+
+OmPatch : OmNode {
+ var <nodes, <patches, <poly, <enabled;
+ var om;
+ *new {|name,parent,engine|
+ ^super.new(name,parent).initPatch(engine);
+ }
+ initPatch {|argEngine|
+ nodes = IdentityDictionary.new;
+ patches = IdentityDictionary.new;
+ om = argEngine
+ }
+ hasNode {|name|
+ ^nodes[name.asSymbol].notNil
+ }
+ getNode {|name|
+ ^nodes[name.asSymbol]
+ }
+ hasPatch {|name|
+ ^patches[name.asSymbol].notNil
+ }
+ getPatch {|name,mustExist=false|
+ if (this.hasPatch(name).not) {
+ if (mustExist) { ^nil };
+ patches[name.asSymbol] = OmPatch(name,this);
+ };
+ ^patches[name.asSymbol]
+ }
+ engine {
+ if (om.notNil) { ^om } { ^parent.engine };
+ }
+ connect {|fromPort, toPort|
+ this.engine.connect(this.path++$/++fromPort, this.path++$/++toPort)
+ }
+ dumpX {
+ (String.fill(this.depth,$ )++"*"+this.path).postln;
+ nodes.do{|node|
+ (String.fill(node.depth,$ )++"**"+node.path).postln;
+ node.ports.do{|port|
+ (String.fill(port.depth,$ )++"***"+port.path).postln };
+ };
+ ports.do{|port| (String.fill(port.depth,$ )++"***"+port.path).postln };
+ patches.do(_.dump)
+ }
+ noteOn {|note,vel|
+ var targetNode;
+ this.nodes.do{|node|
+ if (node.class == OmInternalNode) {
+ node.pluginlabel.switch(
+ \trigger_in, {
+ if (node.ports['Note Number'].value == note) {
+ targetNode = node;
+ }
+ },
+ \note_in, {
+ targetNode = node;
+ }
+ )
+ }
+ };
+ if (targetNode.notNil) {
+ targetNode.noteOn(note, vel)
+ } {
+ Error("Unable to find trigger_in for note "++note).throw
+ }
+ }
+ noteOff {|note|
+ var targetNode;
+ this.nodes.do{|node|
+ if (node.class == OmInternalNode) {
+ node.pluginlabel.switch(
+ \trigger_in, {
+ if (node.ports['Note Number'].value == note) {
+ targetNode = node;
+ }
+ },
+ \note_in, {
+ targetNode = node;
+ }
+ )
+ }
+ };
+ if (targetNode.notNil) {
+ targetNode.noteOff(note)
+ } {
+ Error("Unable to find node for note_off "++note).throw
+ }
+ }
+ newPatch {|name, poly=1, handler|
+ this.engine.createPatch(this.path++$/++name, poly, handler)
+ }
+ newNode {|name, poly=1, type, lib, label, fullname, handler|
+ var candidates = this.engine.matchPlugins(label,lib,fullname,type);
+ if (candidates.size == 1) {
+ candidates.pop.new(this.path++"/"++name, poly, handler)
+ } {
+ if (candidates.size==0) {
+ Error("Plugin not found").throw
+ } {
+ Error("Plugin info not unique").throw
+ }
+ }
+ }
+
+ // Setters for OSC responders
+ prSetPoly {|argPoly| poly = argPoly }
+ prSetEnabled {|flag|
+ if (flag != enabled) {
+ enabled = flag;
+ this.changed(\enabled, flag)
+ }
+ }
+}
+
+
+OmEmacsUI {
+ var engine, window, bootBtn;
+ *new {|engine| ^super.newCopyArgs(engine).init }
+ init {
+ window = EmacsBuffer("*Om -"+engine.addr.ip++$:++engine.addr.port);
+ bootBtn = EmacsButton(window, ["Boot","Quit"], {|value|
+ if (value==1) {
+ engine.boot
+ } {
+ engine.quit
+ }
+ }).value=engine.registered.binaryValue;
+ engine.addDependant(this);
+ window.front;
+ }
+ update {|who, what ... args|
+ Emacs.message(who.asString+what+args);
+ if (what == \newPatch or: {what == \newNode or: {what == \newPort}}) {
+ args[0].addDependant(this)
+ };
+ }
+}
diff --git a/src/progs/supercollider/README b/src/progs/supercollider/README
new file mode 100644
index 00000000..020da8aa
--- /dev/null
+++ b/src/progs/supercollider/README
@@ -0,0 +1,11 @@
+Om bindings for SuperCollider
+-----------------------------
+
+Installation:
+Copy the Om.sc file into some directory in your SC class path and
+reboot sclang.
+
+Usage:
+See example.sc for inspiration.
+
+ -- Mario Lang <mlang@delysid.org>
diff --git a/src/progs/supercollider/example.sc b/src/progs/supercollider/example.sc
new file mode 100644
index 00000000..6186e433
--- /dev/null
+++ b/src/progs/supercollider/example.sc
@@ -0,0 +1,27 @@
+// Boot Om (inside SC) and the scsynth server
+(
+o=Om.boot {
+ o.loadPatch("/home/mlang/src/om-synth/src/clients/patches/303.om");
+ s.boot;
+}
+)
+// Connect patch output to one SC input
+o.jackConnect("/303/output", "SuperCollider:in_3");
+// Play this input on the default output bus (most simple postprocessor)
+{AudioIn.ar(3).dup}.play;
+// A simple 303 pattern
+(
+Tempo.bpm=170;
+Ppar([
+ Pbind(\type, \om, \target, o.getPatch("/303",true),
+ \dur, 1/1, \octave,2,\degree, Pseq([Pshuf([0,2,4,6],16)],2)),
+ Pbind(\type, \om, \target, o.getPatch("/303",true),
+ \dur, Prand([Pshuf([1/2,1/4,1/4],4),Pshuf([1/3,1/3,1/6,1/6],4)],inf),
+ \legato, Prand((0.5,0.55..0.9),inf),
+ \octave,3,\degree, Pseq([Pshuf([0,2,4,6],2)],40)),
+ Pbind(\type, \om, \omcmd, \setPortValue,
+ \target, o.getPort("/303/cutoff",true),
+ \dur, 1/10, \portValue, Pseq((0,0.005..1).mirror,2)),
+ Pbind(\type, \om, \omcmd, \setPortValue,
+ \target, o.getPort("/303/resonance",true), \portValue, 1)]).play(quant:4)
+)