From acbda29f838280ba98cf9e9e539e9d8a6e8fc6ad Mon Sep 17 00:00:00 2001 From: David Robillard Date: Fri, 9 Jun 2006 15:07:31 +0000 Subject: Added Om aka Graph aka god knows what git-svn-id: http://svn.drobilla.net/lad/grauph@9 a436a847-0d15-0410-975c-d299462d15a1 --- src/clients/supercollider/Makefile.am | 2 + src/clients/supercollider/Om.sc | 746 ++++++++++++++++++++++++++++++++++ src/clients/supercollider/README | 11 + src/clients/supercollider/example.sc | 27 ++ 4 files changed, 786 insertions(+) create mode 100644 src/clients/supercollider/Makefile.am create mode 100644 src/clients/supercollider/Om.sc create mode 100644 src/clients/supercollider/README create mode 100644 src/clients/supercollider/example.sc (limited to 'src/clients/supercollider') diff --git a/src/clients/supercollider/Makefile.am b/src/clients/supercollider/Makefile.am new file mode 100644 index 00000000..69661c73 --- /dev/null +++ b/src/clients/supercollider/Makefile.am @@ -0,0 +1,2 @@ +EXTRA_DIST = Om.sc example.sc + diff --git a/src/clients/supercollider/Om.sc b/src/clients/supercollider/Om.sc new file mode 100644 index 00000000..cb366d58 --- /dev/null +++ b/src/clients/supercollider/Om.sc @@ -0,0 +1,746 @@ +// TODO: +// * Keep track of established connections. +Om : Model { + classvar <>program = "om", <>patchLoader = "om_patch_loader"; + classvar <>oscURL, uiClass; + var loadIntoJack = true; + var allocator, requestResponders, requestHandlers, notificationResponders; + var creatingNode, newNodeEnd; + var 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 diff --git a/src/clients/supercollider/example.sc b/src/clients/supercollider/example.sc new file mode 100644 index 00000000..6186e433 --- /dev/null +++ b/src/clients/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) +) -- cgit v1.2.1