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/python/Makefile.am | 4 + src/clients/python/OSC.py | 374 ++++++++++++++++ src/clients/python/OSC.pyc | Bin 0 -> 13627 bytes src/clients/python/omecho.py | 40 ++ src/clients/python/omsynth.py | 635 +++++++++++++++++++++++++++ src/clients/python/omsynth.pyc | Bin 0 -> 33761 bytes src/clients/python/scripts/Makefile.am | 2 + src/clients/python/scripts/flatten.py | 232 ++++++++++ src/clients/python/scripts/sillysinepatch.py | 41 ++ 9 files changed, 1328 insertions(+) create mode 100644 src/clients/python/Makefile.am create mode 100755 src/clients/python/OSC.py create mode 100644 src/clients/python/OSC.pyc create mode 100644 src/clients/python/omecho.py create mode 100644 src/clients/python/omsynth.py create mode 100644 src/clients/python/omsynth.pyc create mode 100644 src/clients/python/scripts/Makefile.am create mode 100755 src/clients/python/scripts/flatten.py create mode 100644 src/clients/python/scripts/sillysinepatch.py (limited to 'src/clients/python') diff --git a/src/clients/python/Makefile.am b/src/clients/python/Makefile.am new file mode 100644 index 00000000..017b1f4b --- /dev/null +++ b/src/clients/python/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = scripts + +EXTRA_DIST = omecho.py omsynth.py OSC.py + diff --git a/src/clients/python/OSC.py b/src/clients/python/OSC.py new file mode 100755 index 00000000..74eb5880 --- /dev/null +++ b/src/clients/python/OSC.py @@ -0,0 +1,374 @@ +#!/usr/bin/python +# +# Open SoundControl for Python +# Copyright (C) 2002 Daniel Holth, Clinton McChesney +# Modified by Leonard Ritter +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# For questions regarding this module contact +# Daniel Holth or visit +# http://www.stetson.edu/~ProctoLogic/ + +import socket +import struct +import math +import sys +import string + + +def hexDump(bytes): + """Useful utility; prints the string in hexadecimal""" + for i in range(len(bytes)): + sys.stdout.write("%2x " % (ord(bytes[i]))) + if (i+1) % 8 == 0: + print repr(bytes[i-7:i+1]) + + if(len(bytes) % 8 != 0): + print string.rjust("", 11), repr(bytes[i-7:i+1]) + + +class OSCMessage: + """Builds typetagged OSC messages.""" + def __init__(self): + self.address = "" + self.typetags = "," + self.message = "" + + def setAddress(self, address): + self.address = address + + def setMessage(self, message): + self.message = message + + def setTypetags(self, typetags): + self.typetags = typetags + + def clear(self): + self.address = "" + self.clearData() + + def clearData(self): + self.typetags = "," + self.message = "" + + def append(self, argument, typehint = None): + """Appends data to the message, + updating the typetags based on + the argument's type. + If the argument is a blob (counted string) + pass in 'b' as typehint.""" + + if typehint == 'b': + binary = OSCBlob(argument) + else: + binary = OSCArgument(argument) + + self.typetags = self.typetags + binary[0] + self.rawAppend(binary[1]) + + def rawAppend(self, data): + """Appends raw data to the message. Use append().""" + self.message = self.message + data + + def getBinary(self): + """Returns the binary message (so far) with typetags.""" + address = OSCArgument(self.address)[1] + typetags = OSCArgument(self.typetags)[1] + return address + typetags + self.message + + def __repr__(self): + return self.getBinary() + +def readString(data): + length = string.find(data,"\0") + nextData = int(math.ceil((length+1) / 4.0) * 4) + return (data[0:length], data[nextData:]) + + +def readBlob(data): + length = struct.unpack(">i", data[0:4])[0] + nextData = int(math.ceil((length) / 4.0) * 4) + 4 + return (data[4:length+4], data[nextData:]) + + +def readInt(data): + if(len(data)<4): + print "Error: too few bytes for int", data, len(data) + rest = data + integer = 0 + else: + integer = struct.unpack(">i", data[0:4])[0] + rest = data[4:] + + return (integer, rest) + + + +def readLong(data): + """Tries to interpret the next 8 bytes of the data + as a 64-bit signed integer.""" + high, low = struct.unpack(">ll", data[0:8]) + big = (long(high) << 32) + low + rest = data[8:] + return (big, rest) + + + +def readFloat(data): + if(len(data)<4): + print "Error: too few bytes for float", data, len(data) + rest = data + float = 0 + else: + float = struct.unpack(">f", data[0:4])[0] + rest = data[4:] + + return (float, rest) + + +def OSCBlob(next): + """Convert a string into an OSC Blob, + returning a (typetag, data) tuple.""" + + if type(next) == type(""): + length = len(next) + padded = math.ceil((len(next)) / 4.0) * 4 + binary = struct.pack(">i%ds" % (padded), length, next) + tag = 'b' + else: + tag = '' + binary = '' + + return (tag, binary) + + +def OSCArgument(next): + """Convert some Python types to their + OSC binary representations, returning a + (typetag, data) tuple.""" + + if type(next) == type(""): + OSCstringLength = math.ceil((len(next)+1) / 4.0) * 4 + binary = struct.pack(">%ds" % (OSCstringLength), next) + tag = "s" + elif type(next) == type(42.5): + binary = struct.pack(">f", next) + tag = "f" + elif type(next) == type(13): + binary = struct.pack(">i", next) + tag = "i" + else: + binary = "" + tag = "" + + return (tag, binary) + + +def parseArgs(args): + """Given a list of strings, produces a list + where those strings have been parsed (where + possible) as floats or integers.""" + parsed = [] + for arg in args: + print arg + arg = arg.strip() + interpretation = None + try: + interpretation = float(arg) + if string.find(arg, ".") == -1: + interpretation = int(interpretation) + except: + # Oh - it was a string. + interpretation = arg + pass + parsed.append(interpretation) + return parsed + + + +def decodeOSC(data): + """Converts a typetagged OSC message to a Python list.""" + table = {"i":readInt, "f":readFloat, "s":readString, "b":readBlob} + decoded = [] + address, rest = readString(data) + typetags = "" + + if address == "#bundle": + time, rest = readLong(rest) + decoded.append(address) + decoded.append(time) + while len(rest)>0: + length, rest = readInt(rest) + decoded.append(decodeOSC(rest[:length])) + rest = rest[length:] + + elif len(rest)>0: + typetags, rest = readString(rest) + decoded.append(address) + decoded.append(typetags) + if(typetags[0] == ","): + for tag in typetags[1:]: + value, rest = table[tag](rest) + decoded.append(value) + else: + print "Oops, typetag lacks the magic ," + + # return only the data + return address,decoded[2:] + + +class CallbackManager: + """This utility class maps OSC addresses to callables. + + The CallbackManager calls its callbacks with a list + of decoded OSC arguments, including the address and + the typetags as the first two arguments.""" + + def __init__(self): + self.callbacks = {} + self.addCallback(self.unbundler, "#bundle") + + def handle(self, data, source = None): + """Given OSC data, tries to call the callback with the + right address.""" + decoded = decodeOSC(data) + self.dispatch(decoded) + + def dispatch(self, message): + """Sends decoded OSC data to an appropriate calback""" + try: + address = message[0] + self.callbacks[address](message) + except KeyError, e: + # address not found + print "no handler for '%s'" % address + pass + except None, e: + print "Exception in", address, "callback :", e + + return + + def addCallback(self, callback, name): + """Adds a callback to our set of callbacks, + or removes the callback with name if callback + is None.""" + if callback == None: + del self.callbacks[name] + else: + self.callbacks[name] = callback + + def unbundler(self, messages): + """Dispatch the messages in a decoded bundle.""" + # first two elements are #bundle and the time tag, rest are messages. + for message in messages[2:]: + self.dispatch(message) + + +if __name__ == "__main__": + hexDump("Welcome to the OSC testing program.") + print + message = OSCMessage() + message.setAddress("/foo/play") + message.append(44) + message.append(11) + message.append(4.5) + message.append("the white cliffs of dover") + hexDump(message.getBinary()) + + print "Making and unmaking a message.." + + strings = OSCMessage() + strings.append("Mary had a little lamb") + strings.append("its fleece was white as snow") + strings.append("and everywhere that Mary went,") + strings.append("the lamb was sure to go.") + strings.append(14.5) + strings.append(14.5) + strings.append(-400) + + raw = strings.getBinary() + + hexDump(raw) + + print "Retrieving arguments..." + data = raw + for i in range(6): + text, data = readString(data) + print text + + number, data = readFloat(data) + print number + + number, data = readFloat(data) + print number + + number, data = readInt(data) + print number + + hexDump(raw) + print decodeOSC(raw) + print decodeOSC(message.getBinary()) + + print "Testing Blob types." + + blob = OSCMessage() + blob.append("","b") + blob.append("b","b") + blob.append("bl","b") + blob.append("blo","b") + blob.append("blob","b") + blob.append("blobs","b") + blob.append(42) + + hexDump(blob.getBinary()) + + print decodeOSC(blob.getBinary()) + + def printingCallback(stuff): + sys.stdout.write("Got: ") + for i in stuff: + sys.stdout.write(str(i) + " ") + sys.stdout.write("\n") + + print "Testing the callback manager." + + c = CallbackManager() + c.add(printingCallback, "/print") + + c.handle(message.getBinary()) + message.setAddress("/print") + c.handle(message.getBinary()) + + print1 = OSCMessage() + print1.setAddress("/print") + print1.append("Hey man, that's cool.") + print1.append(42) + print1.append(3.1415926) + + c.handle(print1.getBinary()) + + bundle = OSCMessage() + bundle.setAddress("") + bundle.append("#bundle") + bundle.append(0) + bundle.append(0) + bundle.append(print1.getBinary(), 'b') + bundle.append(print1.getBinary(), 'b') + + bundlebinary = bundle.message + + print "sending a bundle to the callback manager" + c.handle(bundlebinary) diff --git a/src/clients/python/OSC.pyc b/src/clients/python/OSC.pyc new file mode 100644 index 00000000..3f2be1bc Binary files /dev/null and b/src/clients/python/OSC.pyc differ diff --git a/src/clients/python/omecho.py b/src/clients/python/omecho.py new file mode 100644 index 00000000..c0d2d3b1 --- /dev/null +++ b/src/clients/python/omecho.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# +# Python bindings for Om +# Copyright (C) 2005 Leonard Ritter +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import omsynth +import os,time,sys + +def main(om): + om.setEnvironment(omsynth.Environment()) + om.engine.activate() + om.engine.register_client(om.getAddressAsString()) + om.request.all_objects(om.getAddressAsString()) + + om.request.all_objects() + time.sleep(3) + om.getEnvironment().printPatch() + om.getEnvironment().printConnections() + print "omecho will now monitor and mirror changes in the structure" + print "hit return to exit when youre done" + sys.stdin.readline() + om.engine.unregister_client(om.getAddressAsString()) + os._exit(0) + +if __name__ == "__main__": + omsynth.startClient(main) diff --git a/src/clients/python/omsynth.py b/src/clients/python/omsynth.py new file mode 100644 index 00000000..d7cfa5ab --- /dev/null +++ b/src/clients/python/omsynth.py @@ -0,0 +1,635 @@ +#!/usr/bin/python +# +# Python bindings for Om +# Copyright (C) 2005 Leonard Ritter +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os,sys,thread,time + +from OSC import OSCMessage, decodeOSC + +from twisted.internet.protocol import DatagramProtocol +from twisted.internet import reactor + +OM_CALL_TIMEOUT = 5 +OM_CALL_POLLTIME = 0.1 + +class TreeElement: + def __init__(self,environment,parent,name): + self.environment = environment + self.parent = parent + self.name = name + self.metadata = {} + + def __del__(self): + print "'%s': gone" % self.name + + def removeChild(self, child): + pass + + def remove(self): + self.parent.removeChild(self.name) + self.parent = None + del self + + def getParent(self): + return self.parent + + def getName(self): + return self.name + + def getPath(self): + if self.parent: + return self.parent.getPath() + "/" + self.name + else: + return self.name + + def getDepth(self): + if self.parent: + return self.parent.getDepth() + 1 + else: + return 0 + + def setMetaData(self,key,value): + if (not self.metadata.has_key(value)) or (self.metadata[key] != value): + print "||| '%s': '%s' = '%s'" % (self.getPath(), key, value) + self.metadata[key] = value + +class Port(TreeElement): + def __init__(self,environment,parent,name): + TreeElement.__init__(self,environment,parent,name) + self.porttype = "" + self.direction = "" + self.hint = "" + self.defaultvalue = 0.0 + self.minvalue = 0.0 + self.maxvalue = 0.0 + self.value = 0.0 + self.connections = {} + print "*** '%s': new port" % self.getPath() + + def remove(self): + for connection in self.getConnections(): + connection.remove() + TreeElement.remove(self) + + def getConnections(self): + return self.connections + + def addConnection(self,target,connection): + self.connections[target] = connection + + def removeConnection(self,target): + del self.connections[target] + + def setPortType(self,porttype): + if self.porttype != porttype: + print "*** '%s': changing porttype from '%s' to '%s'" % (self.getPath(), self.porttype, porttype) + self.porttype = porttype + + def setDirection(self,direction): + if self.direction != direction: + print "*** '%s': changing direction from '%s' to '%s'" % (self.getPath(), self.direction, direction) + self.direction = direction + + def setHint(self,hint): + if self.hint != hint: + print "*** '%s': changing hint from '%s' to '%s'" % (self.getPath(), self.hint, hint) + self.hint = hint + + def setDefaultValue(self,defaultvalue): + if self.defaultvalue != defaultvalue: + print "*** '%s': changing defaultvalue from '%.3f' to '%.3f'" % (self.getPath(), self.defaultvalue, defaultvalue) + self.defaultvalue = defaultvalue + + def setMinValue(self,minvalue): + if self.minvalue != minvalue: + print "*** '%s': changing minvalue from '%.3f' to '%.3f'" % (self.getPath(), self.minvalue, minvalue) + self.minvalue = minvalue + + def setMaxValue(self,maxvalue): + if self.maxvalue != maxvalue: + print "*** '%s': changing maxvalue from '%.3f' to '%.3f'" % (self.getPath(), self.maxvalue, maxvalue) + self.maxvalue = maxvalue + + def setValue(self,value): + if self.value != value: + print "*** '%s': changing value from '%.3f' to '%.3f'" % (self.getPath(), self.value, value) + self.value = value + +class Node(TreeElement): + def __init__(self,environment,parent,name): + TreeElement.__init__(self,environment,parent,name) + self.ports = {} + self.polyphonic = 0 + self.plugintype = "" + self.libname = "" + self.pluginlabel = "" + print "+++ '%s': new node" % self.getPath() + + def remove(self): + for port in self.getPorts(): + port.remove() + TreeElement.remove(self) + + def removeChild(self, child): + del self.ports[child] + + def getPorts(self): + return self.ports.values() + + def setPluginLabel(self,pluginlabel): + if pluginlabel != self.pluginlabel: + print "+++ '%s': changing pluginlabel from '%s' to '%s'" % (self.getPath(), self.pluginlabel, pluginlabel) + self.pluginlabel = pluginlabel + + def setLibName(self,libname): + if libname != self.libname: + print "+++ '%s': changing libname from '%s' to '%s'" % (self.getPath(), self.libname, libname) + self.libname = libname + + def setPluginType(self,plugintype): + if plugintype != self.plugintype: + print "+++ '%s': changing plugintype from '%s' to '%s'" % (self.getPath(), self.plugintype, plugintype) + self.plugintype = plugintype + + def setPolyphonic(self,polyphonic): + if polyphonic != self.polyphonic: + print "+++ '%s': changing polyphony from %i to %i" % (self.getPath(), self.polyphonic, polyphonic) + self.polyphonic = polyphonic + + def hasPort(self,name): + return self.ports.has_key(name) + + def getPort(self,name,mustexist=False): + if not self.hasPort(name): + if mustexist: + return None + self.ports[name] = self.environment.getPortClass()(self.environment,self,name) + return self.ports[name] + +class Patch(Node): + def __init__(self,environment,parent,name): + Node.__init__(self,environment,parent,name) + self.nodes = {} + self.patches = {} + self.poly = 0 + self.enabled = False + print "### '%s': new patch" % self.getPath() + + def remove(self): + for patch in self.getPatches(): + patch.remove() + for node in self.getNodes(): + node.remove() + Node.remove(self) + + def removeChild(self, child): + if self.hasNode(child): + del self.nodes[child] + elif self.hasPatch(child): + del self.patches[child] + else: + Node.removeChild(self,child) + + def getPatches(self): + return self.patches.values() + + def getNodes(self): + return self.nodes.values() + + def getEnabled(self): + return self.enabled + + def setEnabled(self,enabled): + if enabled != self.enabled: + print "### '%s': changing enabled from %s to %s" % (self.getPath(), str(self.enabled), str(enabled)) + enabled = self.enabled + + def getPoly(self): + return self.poly + + def setPoly(self,poly): + if poly != self.poly: + print "### '%s': changing polyphony from %i to %i" % (self.getPath(), self.poly, poly) + self.poly = poly + + def hasNode(self,name): + return self.nodes.has_key(name) + + def getNode(self,name,mustexist=False): + if not self.hasNode(name): + if mustexist: + return None + self.nodes[name] = self.environment.getNodeClass()(self.environment,self,name) + return self.nodes[name] + + def hasPatch(self,name): + return self.patches.has_key(name) + + def getPatch(self,name,mustexist=False): + if not self.hasPatch(name): + if mustexist: + return None + self.patches[name] = self.environment.getPatchClass()(self.environment,self,name) + return self.patches[name] + +class Connection: + def __init__(self,environment,srcport,dstport): + self.environment = environment + self.srcport = srcport + self.dstport = dstport + self.srcport.addConnection(self.dstport,self) + self.dstport.addConnection(self.srcport,self) + print ">>> '%s'->'%s': new connection" % (self.srcport.getPath(),self.dstport.getPath()) + + def __del__(self): + print "connection gone" + + def remove(self): + self.srcport.removeConnection(self.dstport) + self.dstport.removeConnection(self.srcport) + del self + + def getSrcPort(self): + return self.srcport + + def getDstPort(self): + return self.dstport + + def getPortPair(self): + return self.srcport, self.dstport + +class Environment: + def __init__(self): + self.omPatch = self.getPatchClass()(self,None,"") + self.enabled = False + self.connections = {} + + def getConnectionClass(self): + return Connection + + def getPatchClass(self): + return Patch + + def getNodeClass(self): + return Node + + def getPortClass(self): + return Port + + def getConnection(self,srcportpath,dstportpath,mustexist=False): + srcport = self.getPort(srcportpath,True) + if not srcport: + return None + dstport = self.getPort(dstportpath,True) + if not dstport: + return None + if not self.connections.has_key((srcport,dstport)): + if mustexist: + return None + self.connections[(srcport,dstport)] = self.getConnectionClass()(self,srcport,dstport) + return self.connections[(srcport,dstport)] + + def getConnections(self): + return self.connections.values() + + def getPatch(self,path,mustexist=False): + elements = path.split("/") + currentPatch = None + for element in elements: + if element == "": + currentPatch = self.omPatch + else: + currentPatch = currentPatch.getPatch(element,mustexist) + if not currentPatch: + break + return currentPatch + + def getNode(self,path,mustexist=False): + elements = path.split("/") + basepath = "/".join(elements[:-1]) + nodename = elements[-1] + patch = self.getPatch(basepath,True) + if patch: + return patch.getNode(nodename,mustexist) + return None + + def getPort(self,path,mustexist=False): + elements = path.split("/") + basepath = "/".join(elements[:-1]) + portname = elements[-1] + node = self.getNode(basepath,True) + if node: + return node.getPort(portname,mustexist) + patch = self.getPatch(basepath,True) + if patch: + return patch.getPort(portname,mustexist) + return None + + def getObject(self,path): + patch = self.getPatch(path,True) + if patch: + return patch + node = self.getNode(path,True) + if node: + return node + return self.getPort(path,True) + + def printPatch(self,patch=None): + if not patch: + patch = self.omPatch + print patch.getDepth()*' ' + "### " + patch.getPath() + for node in patch.getNodes(): + print node.getDepth()*' ' + "+++ " + node.getPath() + for port in node.getPorts(): + print port.getDepth()*' ' + "*** " + port.getPath() + for port in patch.getPorts(): + print port.getDepth()*' ' + "*** " + port.getPath() + for subpatch in patch.getPatches(): + self.printPatch(subpatch) + + def printConnections(self): + for connection in self.getConnections(): + print ">>> %s -> %s" % (connection.getSrcPort().getPath(), connection.getDstPort().getPath()) + + #~ /om/engine_enabled - Notification engine's DSP has been enabled. + def __om__engine_enabled(self): + self.enabled = True + + #~ /om/engine_disabled - Notification engine's DSP has been disabled. + def __om__engine_disabled(self): + self.enabled = False + + #~ /om/new_node - Notification of a new node's creation. + #~ * path (string) - Path of the new node + #~ * polyphonic (integer-boolean) - Node is polyphonic (1 for yes, 0 for no) + #~ * type (string) - Type of plugin (LADSPA, DSSI, Internal, Patch) + #~ * lib-name (string) - Name of library if a plugin (ie cmt.so) + #~ * plug-label (string) - Label of plugin in library (ie adsr_env) + + #~ * New nodes are sent as a blob. The first message in the blob will be this one (/om/new_node), followed by a series of /om/new_port commands, followed by /om/new_node_end. + def __om__new_node(self,path,polyphonic,plugintype,libname,pluginlabel): + node = self.getNode(path) + node.setPolyphonic(polyphonic) + node.setPluginType(plugintype) + node.setLibName(libname) + node.setPluginLabel(pluginlabel) + + def __om__new_node_end(self): + pass + + #~ /om/node_removal - Notification of a node's destruction. + #~ * path (string) - Path of node (which no longer exists) + def __om__node_removal(self,path): + node = self.getNode(path) + node.remove() + + #~ /om/new_port - Notification of a node's destruction. + + #~ * path (string) - Path of new port + #~ * type (string) - Type of port (CONTROL or AUDIO) + #~ * direction (string) - Direction of data flow (INPUT or OUTPUT) + #~ * hint (string) - Hint (INTEGER, LOGARITHMIC, TOGGLE, or NONE) + #~ * default-value (float) - Default (initial) value + #~ * min-value (float) - Suggested minimum value + #~ * min-value (float) - Suggested maximum value + + #~ * Note that in the event of loading a patch, this message could be followed immediately by a control change, meaning the default-value is not actually the current value of the port (ahem, Lachlan). + #~ * The minimum and maximum values are suggestions only, they are not enforced in any way, and going outside them is perfectly fine. Also note that the port ranges in om_gtk are not these ones! Those ranges are set as metadata. + def __om__new_port(self,path,porttype,direction,hint,defaultvalue,minvalue,maxvalue): + port = self.getPort(path) + port.setPortType(porttype) + port.setDirection(direction) + port.setHint(hint) + port.setDefaultValue(defaultvalue) + port.setMinValue(minvalue) + port.setMaxValue(maxvalue) + + #~ /om/port_removal - Notification of a port's destruction. + #~ * path (string) - Path of port (which no longer exists) + def __om__port_removal(self,path): + port = self.getPort(path) + port.remove() + + #~ /om/patch_destruction - Notification of a patch's destruction. + #~ * path (string) - Path of patch (which no longer exists) + def __om__patch_destruction(self,path): + patch = self.getPatch(path) + patch.remove() + + #~ /om/patch_enabled - Notification a patch's DSP processing has been enabled. + #~ * path (string) - Path of enabled patch + def __om__patch_enabled(self,path): + patch = self.getPatch(path) + patch.setEnabled(True) + + #~ /om/patch_disabled - Notification a patch's DSP processing has been disabled. + #~ * path (string) - Path of disabled patch + def __om__patch_disabled(self,path): + patch = self.getPatch(path) + patch.setEnabled(False) + + #~ /om/new_connection - Notification a new connection has been made. + #~ * src-path (string) - Path of the source port + #~ * dst-path (string) - Path of the destination port + def __om__new_connection(self,srcpath,dstpath): + self.getConnection(srcpath,dstpath) + + #~ /om/disconnection - Notification a connection has been unmade. + #~ * src-path (string) - Path of the source port + #~ * dst-path (string) - Path of the destination port + def __om__disconnection(self,srcpath,dstpath): + connection = self.getConnection(srcpath,dstpath) + portpair = connection.getPortPair() + connection.remove() + del self.connections[portpair] + + #~ /om/metadata/update - Notification of a piece of metadata. + #~ * path (string) - Path of the object associated with metadata (can be a node, patch, or port) + #~ * key (string) + #~ * value (string) + def __om__metadata__update(self,path,key,value): + object = self.getObject(path) + object.setMetaData(key,value) + + #~ /om/control_change - Notification the value of a port has changed + #~ * path (string) - Path of port + #~ * value (float) - New value of port + #~ * This will only send updates for values set by clients of course - not values changing because of connections to other ports! + def __om__control_change(self,path,value): + port = self.getPort(path) + port.setValue(value) + + #~ /om/new_patch - Notification of a new patch + #~ * path (string) - Path of new patch + #~ * poly (int) - Polyphony of new patch (not a boolean like new_node) + def __om__new_patch(self,path,poly): + patch = self.getPatch(path) + patch.setPoly(poly) + +class SocketError: + pass + +class Call: + pass + +class ClientProxy: + def __init__(self, om, name, is_async = False): + self.name = name + self.om = om + self.is_async = is_async + + def __call__(self, *args): + if (self.is_async): + self.om.sendMsg(self.name, *args) + return True + + result = self.om.sendMsgBlocking(self.name, *args) + if not result: + return None + if result[0] == "/om/response/ok": + return True + print "ERROR: %s" % result[1][1] + return False + + def __getattr__(self, name): + if (name[:2] == "__") and (name[-2:] == "__"): # special function + raise AttributeError, name + if name in self.__dict__: + raise AttributeError, name + if name == 'async': + return ClientProxy(self.om, self.name, True) + else: + return ClientProxy(self.om, self.name + '/' + name) + +class Client(DatagramProtocol, ClientProxy): + def __init__(self): + ClientProxy.__init__(self, self, "/om") + + def startProtocol(self): + self.transport.connect("127.0.0.1", 16180) + host = self.transport.getHost() + self.host = host.host + self.port = host.port + self.environment = None + self.handlers = {} + self.calls = {} + #print "opened port at %s" % (str((self.host,self.port))) + self.nextPacketNumber = 1 + + def setEnvironment(self, environment): + self.handlers = {} + self.environment = environment + for name in dir(self.environment): + element = getattr(self.environment,name) + if (type(element).__name__ == 'instancemethod') and (element.__name__[:6] == "__om__"): + handlername = element.__name__.replace("__","/") + print "registering handler for '%s'" % handlername + self.handlers[handlername] = element + + def getEnvironment(self): + return self.environment + + def connectionRefused(self): + print "Noone listening, aborting." + os._exit(-1) + + def messageReceived(self, (msg, args)): + if msg == "/om/error": + print "ERROR: %r" % args + return + if msg == "/om/response/ok": + omcall = self.calls[args[0]] + omcall.result = (msg,args) + omcall.done = True + return + if msg == "/om/response/error": + omcall = self.calls[args[0]] + omcall.result = (msg,args) + omcall.done = True + return + if msg == "#bundle": + for arg in args: + self.messageReceived(arg) + return + if self.handlers.has_key(msg): + try: + self.handlers[msg](*args) + except: + a,b,c = sys.exc_info() + sys.excepthook(a,b,c) + print "with '%s'" % repr((msg,args)) + return + print "no handler for '%s'" % repr((msg,args)) + + def datagramReceived(self, data, (host, port)): + self.messageReceived(decodeOSC(data)) + + def getPacketNumber(self): + packetNumber = self.nextPacketNumber + self.nextPacketNumber = (self.nextPacketNumber + 1) + return packetNumber + + def sendMsg(self,msg,*args): + packetNumber = self.getPacketNumber() + print "Sending %r (#%i)..." % (msg,packetNumber) + omcall = Call() + omcall.result = None + omcall.done = False + self.calls[packetNumber] = omcall + message = OSCMessage() + message.setAddress(msg) + message.append(packetNumber) + for arg in args: + message.append(arg) + self.transport.write(message.getBinary()) + time.sleep(0.01) + return True + + def sendMsgBlocking(self,msg,*args): + packetNumber = self.getPacketNumber() + print "Sending %r (#%i)..." % (msg,packetNumber) + omcall = Call() + omcall.result = None + omcall.done = False + self.calls[packetNumber] = omcall + message = OSCMessage() + message.setAddress(msg) + message.append(packetNumber) + for arg in args: + message.append(arg) + self.transport.write(message.getBinary()) + now = time.time() + while not omcall.done: + time.sleep(OM_CALL_POLLTIME) + distance = time.time() - now + if distance > OM_CALL_TIMEOUT: + print "timeout" + break + del self.calls[packetNumber] + return omcall.result + + def getAddressAsString(self): + return "osc.udp://%s:%d" % (self.host, self.port) + + + +def startClient(func): + om = Client() + reactor.listenUDP(0, om) + thread.start_new_thread(func,(om,)) + reactor.run() diff --git a/src/clients/python/omsynth.pyc b/src/clients/python/omsynth.pyc new file mode 100644 index 00000000..7cdd9cbe Binary files /dev/null and b/src/clients/python/omsynth.pyc differ diff --git a/src/clients/python/scripts/Makefile.am b/src/clients/python/scripts/Makefile.am new file mode 100644 index 00000000..3e070601 --- /dev/null +++ b/src/clients/python/scripts/Makefile.am @@ -0,0 +1,2 @@ +EXTRA_DIST = sillysinepatch.py + diff --git a/src/clients/python/scripts/flatten.py b/src/clients/python/scripts/flatten.py new file mode 100755 index 00000000..eaf8084d --- /dev/null +++ b/src/clients/python/scripts/flatten.py @@ -0,0 +1,232 @@ +#!/usr/bin/python + +############################################################################### +# +# flatten.py - a python script that merges all subpatches in an Om patch +# into the parent patch +# +# Copyright (C) 2005 Lars Luthman +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +############################################################################### + +import omsynth +import os,time,sys + + +def getPatchBounds(patch): + """Returns the smallest rectangle that contains all modules in the patch.""" + min_x = None + min_y = None + max_x = None + max_y = None + for node in patch.getNodes(): + x = node.metadata['module-x'] + y = node.metadata['module-y'] + if (x != None): + if (min_x == None or float(x) < min_x): + min_x = float(x) + if (max_x == None or float(x) > max_x): + max_x = float(x) + if (y != None): + if (min_y == None or float(y) < min_y): + min_y = float(y) + if (max_y == None or float(y) > max_y): + max_y = float(y) + if min_x == None: + min_x = 0 + max_x = 0 + if min_y == None: + min_y = 0 + max_y = 0 + + return (min_x, min_y, max_x, max_y) + + +def cloneNode(om, node, patch): + """Copy a node into a patch, return the new node's name.""" + + # create a node with a free name in the parent + names = [] + for node2 in patch.getNodes(): + names.append(node2.getName()) + for patch2 in patch.getPatches(): + names.append(patch2.getName()) + names.sort() + name = node.getName() + for name2 in names: + if name2 == name: + name = name + '_' + om.synth.create_node.async(patch.getPath() + '/' + name, + node.plugintype, node.libname, + node.pluginlabel, node.polyphonic) + + # copy port values + for port in node.getPorts(): + path = '%s/%s/%s' % (patch.getPath(), name, port.getName()) + om.synth.set_port_value_slow.async(path, port.value) + om.metadata.set.async(path, 'user-min', '%f' % port.minvalue) + om.metadata.set.async(path, 'user-max', '%f' % port.maxvalue) + return name + + +def flatten(om, patch): + """Merge all subpatches into the parent patch.""" + + # something is wrong, we don't have a patch + if patch == None: + return + + # iterate over all subpatches + for subpatch in patch.getPatches(): + flatten(om, subpatch) + lookup = {} + + # copy all nodes from the subpatch to the parent patch + for node in subpatch.getNodes(): + lookup[node.getName()] = cloneNode(om, node, patch) + + # copy all connections + for node in subpatch.getNodes(): + for port in node.getPorts(): + if port.direction == 'OUTPUT': + for target in port.getConnections().keys(): + targetname = '%s/%s' % (lookup[target.getParent().getName()], target.getName()) + om.synth.connect.async(patch.getPath() + '/' + + lookup[node.getName()] + '/' + + port.getName(), + patch.getPath() + '/' + + targetname) + + # make external connections + for node in subpatch.getNodes(): + if node.libname == '': + lbl = node.pluginlabel + + if lbl == 'audio_input' or lbl == 'control_input': + port1 = node.getPort('in') + for port2 in port1.getConnections().keys(): + dst = '%s/%s/%s' % (patch.getPath(), lookup[port2.getParent().getName()], port2.getName()) + port4 = subpatch.getPort(node.getName()) + conns = port4.getConnections().keys() + if len(conns) == 0: + portValue = port4.value + om.synth.set_port_value_slow.async(dst, portValue) + else: + for port3 in port4.getConnections().keys(): + src = port3.getPath() + om.synth.connect.async(src, dst) + + if lbl == 'audio_output' or lbl == 'control_output': + port2 = node.getPort('out', True) + for port1 in port2.getConnections().keys(): + src = '%s/%s/%s' % (patch.getPath(), lookup[port1.getParent().getName()], port1.getName()) + port3 = subpatch.getPort(node.getName()) + for port4 in port3.getConnections().keys(): + dst = port4.getPath() + om.synth.connect.async(src, dst) + + # destroy all input and output nodes from the subpatch + for node in subpatch.getNodes(): + if node.libname == '': + lbl = node.pluginlabel + if (lbl == 'audio_input' or lbl == 'control_input' or + lbl == 'audio_output' or lbl == 'control_output'): + om.synth.destroy_node.async('%s/%s' % (patch.getPath(), + lookup[node.getName()])) + + # calculate where to move all the new nodes + (min_x, min_y, max_x, max_y) = getPatchBounds(subpatch) + sub_x = subpatch.metadata['module-x'] + if sub_x == None: + sub_x = 0 + sub_y = subpatch.metadata['module-y'] + if sub_y == None: + sub_y = 0 + x_offset = float(sub_x) + if min_x != None: + x_offset = float(sub_x) - min_x + y_offset = float(sub_y) + if min_y != None: + y_offset = float(sub_y) - min_y + + # move the new nodes + for node in subpatch.getNodes(): + x = float(node.metadata['module-x']) + if x == None: + x = 0 + om.metadata.set.async('%s/%s' % (patch.getPath(), + lookup[node.getName()]), + 'module-x', '%f' % (x + x_offset)) + y = float(node.metadata['module-y']) + if y == None: + y = 0 + om.metadata.set.async('%s/%s' % (patch.getPath(), + lookup[node.getName()]), + 'module-y', '%f' % (y + y_offset)) + + # move the old nodes in the patch + x_offset = 0 + if min_x != None and max_x != None: + x_offset = max_x - min_x + y_offset = 0 + if min_y != None and max_y != None: + y_offset = max_y - min_y + for node in patch.getNodes(): + if node.getName() not in lookup.values(): + x = node.metadata['module-x'] + if x != None and float(x) > float(sub_x): + om.metadata.set.async(node.getPath(), 'module-x', + '%f' % (float(x) + x_offset)) + y = node.metadata['module-y'] + if y != None and float(y) > float(sub_y): + om.metadata.set.async(node.getPath(), 'module-y', + '%f' % (float(y) + y_offset)) + # destroy the subpatch + om.synth.destroy_patch(subpatch.getPath()) + + +def main(om): + om.setEnvironment(omsynth.Environment()) + om.engine.activate.async() + om.engine.load_plugins.async() + om.request.all_objects(om.getAddressAsString()) + + # wait for all the data to arrive (there should be a cleaner way) + time.sleep(3) + + patch = om.getEnvironment().getPatch(sys.argv[1], True); + flatten(om, patch) + + os._exit(0) + + +if len(sys.argv) > 1: + if sys.argv[1] == "--name": + print "Flatten patch" + os._exit(0) + elif sys.argv[1] == "--shortdesc": + print "Merge the contents of all subpatches into the parent patch"; + os._exit(0) + elif sys.argv[1] == "--signature": + print "%p"; + os._exit(0) + else: + omsynth.startClient(main) + +else: + print "Which patch do you want to flatten?" + os._exit(0) diff --git a/src/clients/python/scripts/sillysinepatch.py b/src/clients/python/scripts/sillysinepatch.py new file mode 100644 index 00000000..ac51a080 --- /dev/null +++ b/src/clients/python/scripts/sillysinepatch.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# +# Python bindings for Om +# Copyright (C) 2005 Leonard Ritter +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import omsynth +import os,time,sys + +def main(om): + om.setEnvironment(omsynth.Environment()) + om.engine.activate() + om.engine.load_plugins() + om.engine.register_client(om.getAddressAsString()) + om.request.all_objects(om.getAddressAsString()) + om.synth.create_patch("/silly_sine", 1) + om.synth.create_node("/silly_sine/output", "Internal", "", "audio_output", 0) + om.synth.create_node("/silly_sine/sine", "LADSPA", "cmt.so", "sine_fcac", 0) + om.synth.set_port_value("/silly_sine/sine/Frequency", 440.0) + om.synth.set_port_value("/silly_sine/sine/Amplitude", 1.0) + om.synth.connect("/silly_sine/sine/Output", "/silly_sine/output/out") + om.synth.enable_patch("/silly_sine") + om.engine.enable() + om.engine.unregister_client(om.getAddressAsString()) + os._exit(0) + +if __name__ == "__main__": + omsynth.startClient(main) -- cgit v1.2.1