summaryrefslogtreecommitdiffstats
path: root/src/clients/python
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2006-06-09 15:07:31 +0000
committerDavid Robillard <d@drobilla.net>2006-06-09 15:07:31 +0000
commitacbda29f838280ba98cf9e9e539e9d8a6e8fc6ad (patch)
treee31b37a2456e6d1e564c9a7146c88be259d338b0 /src/clients/python
downloadingen-acbda29f838280ba98cf9e9e539e9d8a6e8fc6ad.tar.gz
ingen-acbda29f838280ba98cf9e9e539e9d8a6e8fc6ad.tar.bz2
ingen-acbda29f838280ba98cf9e9e539e9d8a6e8fc6ad.zip
Added Om aka Graph aka god knows what
git-svn-id: http://svn.drobilla.net/lad/grauph@9 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/clients/python')
-rw-r--r--src/clients/python/Makefile.am4
-rwxr-xr-xsrc/clients/python/OSC.py374
-rw-r--r--src/clients/python/OSC.pycbin0 -> 13627 bytes
-rw-r--r--src/clients/python/omecho.py40
-rw-r--r--src/clients/python/omsynth.py635
-rw-r--r--src/clients/python/omsynth.pycbin0 -> 33761 bytes
-rw-r--r--src/clients/python/scripts/Makefile.am2
-rwxr-xr-xsrc/clients/python/scripts/flatten.py232
-rw-r--r--src/clients/python/scripts/sillysinepatch.py41
9 files changed, 1328 insertions, 0 deletions
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 <dholth@stetson.edu> 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
--- /dev/null
+++ b/src/clients/python/OSC.pyc
Binary files 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
--- /dev/null
+++ b/src/clients/python/omsynth.pyc
Binary files 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)