// TODO: // * Keep track of established connections. Ingen : Model { classvar <>program = "om", <>patchLoader = "om_patch_loader"; classvar <>oscURL, uiClass; var loadIntoJack = true; var allocator, requestResponders, requestHandlers, notificationResponders; var creatingNode, newNodeEnd; var IngenInternalNode, \LADSPA -> IngenLADSPANode, \DSSI -> IngenDSSINode ]; uiClass = IngenEmacsUI } *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); ("Ingen"+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]) { ("Ingen sent an existng node with differing type"+path).warn } } } { ("Ingen 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 = IngenPort.new(portName, parent, type, dir, hint, def, min, max); parent.ports[portName.asSymbol] = port; parent.changed(\newPort, port) } { if (parent.getPort(portName).porttype != type) { ("Ingen tried to create an already existing port with differing type" +path).warn } } } { ("Ingen 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 } { ("Ingen 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 } { ("Ingen 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 } { ("Ingen 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) } { ("Ingen 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); "Ingen 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| ^Ingen.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+"Ingen"+"om").unixCmd } { (program+"-p"+addr.port).unixCmd } } { "You have to manually boot Ingen now".postln } } loadPatch {|patchPath| (patchLoader + patchPath).unixCmd } activate { | handler | this.sendReq("engine/activate", { root = IngenPatch("",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"+"Ingen").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) } } IngenMetadata { 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) } } IngenObject : Model { var