source: trunk/OSC(no_struct).py @ 6

Revision 6, 11.9 KB checked in by st8, 16 months ago (diff)

Added custom osc.py (no req for struct)

Line 
1#!/usr/bin/python
2#
3# Open SoundControl for Python
4# Copyright (C) 2002 Daniel Holth, Clinton McChesney
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19#
20# For questions regarding this module contact
21# Daniel Holth <dholth@stetson.edu> or visit
22# http://www.stetson.edu/~ProctoLogic/
23#
24# Changelog:
25# 15 Nov. 2001:
26#   Removed dependency on Python 2.0 features.
27#   - dwh
28# 13 Feb. 2002:
29#   Added a generic callback handler.
30#   - dwh
31# 8th April 2009
32#   Now struct free - thanks ableton
33#   - st8
34
35import math
36import sys
37import string
38
39#################################################################
40# Functions for handling binary conversion
41def int2byte(i, neg = 0):
42    # Converts an int to 4 bytes
43    ex = neg == 1 and 128 or 0
44    return chr(((i >> 24) & 255) + ex) + chr((i >> 16) & 255) + chr((i >> 8) & 255) + chr(i & 255);
45
46def byte2int(b):
47    # Converts 4 bytes to an int
48    return ord(b[3]) | (ord(b[2]) << 8) | (ord(b[1]) << 16) | (ord(b[0]) << 24)
49
50def i2bin(n):
51    # Converts an int to binary notation
52    hdigits = ('0000','0001','0010','0011','0100','0101','0110','0111',
53               '1000','1001','1010','1011','1100','1101','1110','1111')
54
55    a = [hdigits[int(d,16)] for d in hex(n)[2:]]
56    return ''.join(a)
57
58def float2byte(fl):
59    # Converts a float to 4 bytes
60    i = abs(int(fl))
61    f  = abs(float(fl) - i)
62   
63    rem = f
64    fb = ''
65    while (rem != 0 and len(fb) < 32):
66        rem *= 2
67        fb = fb + (rem >= 1 and '1' or '0')
68       
69        rem -= int(rem)
70       
71    ib = i2bin(i).lstrip('0')
72       
73    exp = i2bin(len(ib) - 1 + 127).lstrip('0')
74    sig = (ib[1:] + fb)[0:23]
75   
76    if len(sig) < 23:
77        for i in range(23 - len(sig)):
78            sig = sig + '0'
79
80    bytes =  int2byte(int(exp + sig,2), fl < 0)
81   
82    return bytes
83   
84def byte2float(b):
85    # Converts 4 bytes to a float
86    binary = i2bin(byte2int(b))
87   
88    sign = binary[0]
89    exp  = int(binary[1:9], 2) - 127
90    sig  = binary[9:]
91   
92    fr = 0
93    for i in range(1,len(sig)):
94        if sig[i-1] == '1':
95            fr += pow(2, -i)
96
97    num = fr + 1
98    for i in range(exp):
99        num *= 2
100           
101    return sign == '1' and -num or num
102
103#################################################################
104   
105def hexDump(bytes):
106    """Useful utility; prints the string in hexadecimal"""
107    for i in range(len(bytes)):
108        sys.stdout.write("%2x " % (ord(bytes[i])))
109        if (i+1) % 8 == 0:
110            print repr(bytes[i-7:i+1])
111
112    if(len(bytes) % 8 != 0):
113        print string.rjust("", 11), repr(bytes[i-7:i+1])
114
115#################################################################
116
117class OSCMessage:
118    """Builds typetagged OSC messages."""
119    def __init__(self):
120        self.address  = ""
121        self.typetags = ","
122        self.message  = ""
123
124    def setAddress(self, address):
125        self.address = address
126
127    def setMessage(self, message):
128        self.message = message
129
130    def setTypetags(self, typetags):
131        self.typetags = typetags
132
133    def clear(self):
134        self.address  = ""
135        self.clearData()
136
137    def clearData(self):
138        self.typetags = ","
139        self.message  = ""
140
141    def append(self, argument, typehint = None):
142        """Appends data to the message,
143        updating the typetags based on
144        the argument's type.
145        If the argument is a blob (counted string)
146        pass in 'b' as typehint."""
147
148        if typehint == 'b':
149            binary = OSCBlob(argument)
150        else:
151            binary = OSCArgument(argument)
152
153        self.typetags = self.typetags + binary[0]
154        self.rawAppend(binary[1])
155
156    def rawAppend(self, data):
157        """Appends raw data to the message.  Use append()."""
158        self.message = self.message + data
159
160    def getBinary(self):
161        """Returns the binary message (so far) with typetags."""
162        address  = OSCArgument(self.address)[1]
163        typetags = OSCArgument(self.typetags)[1]
164        return address + typetags + self.message
165
166    def __repr__(self):
167        return self.getBinary()
168
169def readString(data):
170    length   = string.find(data,"\0")
171    nextData = int(math.ceil((length+1) / 4.0) * 4)
172    return (data[0:length], data[nextData:])
173
174
175def readBlob(data):
176    length   = byte2int(data[0:4])
177    nextData = int(math.ceil((length) / 4.0) * 4) + 4   
178    return (data[4:length+4], data[nextData:])
179
180
181def readInt(data):
182    if(len(data)<4):
183        print "Error: too few bytes for int", data, len(data)
184        rest = data
185        integer = 0
186    else:
187        integer = byte2int(data[0:4])
188        rest    = data[4:]
189       
190    return (integer, rest)
191
192
193
194def readLong(data):
195    """Tries to interpret the next 8 bytes of the data
196    as a 64-bit signed integer."""
197   
198    high = byte2int(data[0:4])
199    low  = byte2int(data[4:9])
200   
201    big = (long(high) << 32) + low
202    rest = data[8:]
203    return (big, rest)
204
205
206
207def readFloat(data):
208    if(len(data)<4):
209        print "Error: too few bytes for float", data, len(data)
210        rest = data
211        float = 0
212    else:
213        float = byte2float(data[0:4])
214        rest  = data[4:]
215
216    return (float, rest)
217
218
219def OSCBlob(next):
220    """Convert a string into an OSC Blob,
221    returning a (typetag, data) tuple."""
222
223    if type(next) == type(""):
224        length = len(next)
225        padded = math.ceil((len(next)) / 4.0) * 4
226       
227        if len(next) < padded:
228            for i in range((padded - len(next))):
229                next = next + "\x00"
230       
231        binary = int2byte(length) + next
232        tag    = 'b'
233    else:
234        tag    = ''
235        binary = ''
236   
237    return (tag, binary)
238
239
240def OSCArgument(next):
241    """Convert some Python types to their
242    OSC binary representations, returning a
243    (typetag, data) tuple."""
244   
245    if type(next) == type(""):       
246        OSCstringLength = int(math.ceil((len(next)+1) / 4.0) * 4)
247       
248        binary = next
249        if len(next) < OSCstringLength:
250            for i in range((OSCstringLength - len(next))):
251                binary = binary + "\x00"
252           
253        tag = "s"
254    elif type(next) == type(42.5):
255        binary = float2byte(next)
256        tag = "f"
257    elif type(next) == type(13):
258        binary  = int2byte(next)
259        tag = "i"
260    else:
261        binary  = ""
262        tag = ""
263
264    return (tag, binary)
265
266def parseArgs(args):
267    """Given a list of strings, produces a list
268    where those strings have been parsed (where
269    possible) as floats or integers."""
270    parsed = []
271    for arg in args:
272        print arg
273        arg = arg.strip()
274        interpretation = None
275        try:
276            interpretation = float(arg)
277            if string.find(arg, ".") == -1:
278                interpretation = int(interpretation)
279        except:
280            # Oh - it was a string.
281            interpretation = arg
282            pass
283        parsed.append(interpretation)
284    return parsed
285
286
287
288def decodeOSC(data):
289    """Converts a typetagged OSC message to a Python list."""
290    table = {"i":readInt, "f":readFloat, "s":readString, "b":readBlob}
291    decoded = []
292    address,  rest = readString(data)
293    typetags = ""
294
295    if address == "#bundle":
296        time, rest = readLong(rest)
297        decoded.append(address)
298        decoded.append(time)
299        while len(rest)>0:
300            length, rest = readInt(rest)
301            decoded.append(decodeOSC(rest[:length]))
302            rest = rest[length:]
303
304    elif len(rest)>0:
305        typetags, rest = readString(rest)
306        decoded.append(address)
307        decoded.append(typetags)
308        if(typetags[0] == ","):
309            for tag in typetags[1:]:
310                value, rest = table[tag](rest)               
311                decoded.append(value)
312        else:
313            print "Oops, typetag lacks the magic ,"
314
315    # return only the data
316    return decoded
317
318
319class CallbackManager:
320    """This utility class maps OSC addresses to callables.
321
322    The CallbackManager calls its callbacks with a list
323    of decoded OSC arguments, including the address and
324    the typetags as the first two arguments."""
325
326    def __init__(self):
327        self.callbacks = {}
328        self.add(self.unbundler, "#bundle")
329
330    def handle(self, data, source = None):
331        """Given OSC data, tries to call the callback with the
332        right address."""
333        decoded = decodeOSC(data)
334        self.dispatch(decoded)
335
336    def dispatch(self, message):
337        """Sends decoded OSC data to an appropriate calback"""
338        try:
339            address = message[0]
340            self.callbacks[address](message)
341        except KeyError, e:
342            print "key not found"
343            # address not found
344            pass
345        except None, e:
346            print "Exception in", address, "callback :", e
347       
348        return
349
350    def add(self, callback, name):
351        """Adds a callback to our set of callbacks,
352        or removes the callback with name if callback
353        is None."""
354        if callback == None:
355            del self.callbacks[name]
356        else:
357            self.callbacks[name] = callback
358
359    def unbundler(self, messages):
360        """Dispatch the messages in a decoded bundle."""
361        # first two elements are #bundle and the time tag, rest are messages.
362        for message in messages[2:]:
363            self.dispatch(message)
364
365
366if __name__ == "__main__":
367    hexDump("Welcome to the OSC testing program.")
368    print
369    message = OSCMessage()
370    message.setAddress("/foo/play")
371    message.append(44)
372    message.append(11)
373    message.append(4.5)
374    message.append("the white cliffs of dover")
375    hexDump(message.getBinary())
376
377    print "Making and unmaking a message.."
378
379    strings = OSCMessage()
380    strings.append("Mary had a little lamb")
381    strings.append("its fleece was white as snow")
382    strings.append("and everywhere that Mary went,")
383    strings.append("the lamb was sure to go.")
384    strings.append(14.5)
385    strings.append(14.5)
386    strings.append(-400)
387
388    raw  = strings.getBinary()
389
390    hexDump(raw)
391   
392    print "Retrieving arguments..."
393    data = raw
394    for i in range(6):
395        text, data = readString(data)
396        print text
397
398    number, data = readFloat(data)
399    print number
400
401    number, data = readFloat(data)
402    print number
403
404    number, data = readInt(data)
405    print number
406
407    hexDump(raw)
408    print decodeOSC(raw)
409    print decodeOSC(message.getBinary())
410
411    print "Testing Blob types."
412   
413    blob = OSCMessage() 
414    blob.append("","b")
415    blob.append("b","b")
416    blob.append("bl","b")
417    blob.append("blo","b")
418    blob.append("blob","b")
419    blob.append("blobs","b")
420    blob.append(42)
421
422    hexDump(blob.getBinary())
423
424    print decodeOSC(blob.getBinary())
425
426    def printingCallback(stuff):
427        sys.stdout.write("Got: ")
428        for i in stuff:
429            sys.stdout.write(str(i) + " ")
430        sys.stdout.write("\n")
431
432    print "Testing the callback manager."
433   
434    c = CallbackManager()
435    c.add(printingCallback, "/print")
436   
437    c.handle(message.getBinary())
438    message.setAddress("/print")
439    c.handle(message.getBinary())
440   
441    print1 = OSCMessage()
442    print1.setAddress("/print")
443    print1.append("Hey man, that's cool.")
444    print1.append(42)
445    print1.append(3.1415926)
446
447    c.handle(print1.getBinary())
448
449    bundle = OSCMessage()
450    bundle.setAddress("")
451    bundle.append("#bundle")
452    bundle.append(0)
453    bundle.append(0)
454    bundle.append(print1.getBinary(), 'b')
455    bundle.append(print1.getBinary(), 'b')
456
457    bundlebinary = bundle.message
458
459    print "sending a bundle to the callback manager"
460    c.handle(bundlebinary)
Note: See TracBrowser for help on using the repository browser.