1 |
# ====================================================================== |
---|
2 |
# file: OSC.py |
---|
3 |
# author: stefan kersten <steve@k-hornz.de> |
---|
4 |
# contents: OSC client module for python |
---|
5 |
# license: public domain |
---|
6 |
# ====================================================================== |
---|
7 |
# $Id: osc.py,v 1.1 2005/03/01 14:36:21 nebogeo Exp $ |
---|
8 |
# ====================================================================== |
---|
9 |
# copyright (c) 2000 stefan kersten |
---|
10 |
# ====================================================================== |
---|
11 |
# this module provides simple OSC client functionality |
---|
12 |
# usage examples down at the end of the file |
---|
13 |
# ====================================================================== |
---|
14 |
|
---|
15 |
__revision__ = "$Revision: 1.1 $" |
---|
16 |
|
---|
17 |
# ====================================================================== |
---|
18 |
# imports |
---|
19 |
|
---|
20 |
import cStringIO, exceptions, math, socket, struct, time, types |
---|
21 |
|
---|
22 |
# ====================================================================== |
---|
23 |
# constants |
---|
24 |
|
---|
25 |
SECONDS_UTC_TO_UNIX_EPOCH = 2208988800.0 |
---|
26 |
FLOAT_TO_INT_SCALE = pow(2.0, 32.0) |
---|
27 |
|
---|
28 |
# ====================================================================== |
---|
29 |
# types |
---|
30 |
|
---|
31 |
class Value: |
---|
32 |
"""Abstract OSC value.""" |
---|
33 |
def __init__(self, value): |
---|
34 |
self.value = value |
---|
35 |
|
---|
36 |
def binary_value(self): |
---|
37 |
pass |
---|
38 |
|
---|
39 |
def type_tag(self): |
---|
40 |
pass |
---|
41 |
|
---|
42 |
class Int(Value): |
---|
43 |
"""32 bit integer value.""" |
---|
44 |
def __init__(self, value): |
---|
45 |
Value.__init__(self, long(value)) |
---|
46 |
|
---|
47 |
def binary_value(self): |
---|
48 |
return struct.pack('!l', self.value) |
---|
49 |
|
---|
50 |
def type_tag(self): |
---|
51 |
return 'i' |
---|
52 |
|
---|
53 |
class Float(Value): |
---|
54 |
"""32 bit floating point value.""" |
---|
55 |
def __init__(self, value): |
---|
56 |
Value.__init__(self, float(value)) |
---|
57 |
|
---|
58 |
def binary_value(self): |
---|
59 |
return struct.pack('!f', self.value) |
---|
60 |
|
---|
61 |
def type_tag(self): |
---|
62 |
return 'f' |
---|
63 |
|
---|
64 |
class String(Value): |
---|
65 |
"""Null-terminated string padded to multiples of 4 byte.""" |
---|
66 |
def __init__(self, value): |
---|
67 |
Value.__init__(self, str(value)) |
---|
68 |
|
---|
69 |
def binary_value(self): |
---|
70 |
v = self.value |
---|
71 |
l = len(v) |
---|
72 |
return struct.pack('%ds%dx' % (l, self.pad_amount(l)), v) |
---|
73 |
|
---|
74 |
def type_tag(self): |
---|
75 |
return 's' |
---|
76 |
|
---|
77 |
def pad_amount(self, len): |
---|
78 |
return 4 - (len % 4) |
---|
79 |
|
---|
80 |
class Time(Value): |
---|
81 |
"""64 bit timetag in NTP format.""" |
---|
82 |
def __init__(self, value): |
---|
83 |
Value.__init__(self, float(value)) |
---|
84 |
|
---|
85 |
def __add__(self, time): |
---|
86 |
return Time(float(self.value + time.value)) |
---|
87 |
|
---|
88 |
def binary_value(self): |
---|
89 |
t = self.value |
---|
90 |
# FIXME: how to convert without overflows? |
---|
91 |
s = long(t) |
---|
92 |
f = long(math.fmod(t, 1.0)*FLOAT_TO_INT_SCALE) |
---|
93 |
return struct.pack('!LL', s, f) |
---|
94 |
|
---|
95 |
# ====================================================================== |
---|
96 |
# utilities |
---|
97 |
|
---|
98 |
time_module = time |
---|
99 |
def time(): |
---|
100 |
"""Return current time as float in OSC format.""" |
---|
101 |
return SECONDS_UTC_TO_UNIX_EPOCH + time_module.time() |
---|
102 |
|
---|
103 |
# ====================================================================== |
---|
104 |
# classes |
---|
105 |
|
---|
106 |
class Packet: |
---|
107 |
"""Abstract base class for all OSC-related containers. |
---|
108 |
|
---|
109 |
Has methods for retrieving the proper binary representation |
---|
110 |
and its size. |
---|
111 |
""" |
---|
112 |
def __init__(self, packets): |
---|
113 |
stream = cStringIO.StringIO() |
---|
114 |
self._write_contents(packets, stream) |
---|
115 |
self._data = stream.getvalue() |
---|
116 |
|
---|
117 |
def get_packet(self): |
---|
118 |
"""Return the binary representation of the receiver's contents. |
---|
119 |
|
---|
120 |
This data is in the proper OSC format and can be sent over a |
---|
121 |
socket. |
---|
122 |
""" |
---|
123 |
return self._data |
---|
124 |
|
---|
125 |
def get_size(self): |
---|
126 |
"""Return the size of the receiver's binary data.""" |
---|
127 |
return len(self._data) |
---|
128 |
|
---|
129 |
def _write_contents(self, packets, stream): |
---|
130 |
"""Write packets on stream. |
---|
131 |
|
---|
132 |
Private. |
---|
133 |
|
---|
134 |
Override in subclasses for specific behavior. |
---|
135 |
""" |
---|
136 |
pass |
---|
137 |
|
---|
138 |
def __repr__(self): |
---|
139 |
return '<' + \ |
---|
140 |
str(self.__class__.__name__) + \ |
---|
141 |
' instance, size=' + \ |
---|
142 |
str(self.get_size()) + \ |
---|
143 |
'>' |
---|
144 |
|
---|
145 |
def sendto(self, host, port): |
---|
146 |
"""Send the receiver's data through a UDP socket.""" |
---|
147 |
s = socket.socket(socket.SOCK_DGRAM, socket.AF_INET) |
---|
148 |
packet = self.get_packet() |
---|
149 |
s.sendto(packet, (host, port)) |
---|
150 |
s.close() |
---|
151 |
|
---|
152 |
def sendlocal(self, port): |
---|
153 |
"""Send the receiver's data through a UDP socket locally.""" |
---|
154 |
self.sendto('localhost', port) |
---|
155 |
|
---|
156 |
def _value(x): |
---|
157 |
"""Convert x(int, float or string) to an OSC object.""" |
---|
158 |
t = type(x) |
---|
159 |
if t == types.FloatType: |
---|
160 |
return Float(x) |
---|
161 |
if t == types.IntType or t == types.LongType: |
---|
162 |
return Int(x) |
---|
163 |
# return string representation as default |
---|
164 |
return String(str(x)) |
---|
165 |
|
---|
166 |
class Message(Packet): |
---|
167 |
"""Single OSC message with arguments. |
---|
168 |
|
---|
169 |
Message(address, *args) -> Message |
---|
170 |
|
---|
171 |
address -- OSC address string |
---|
172 |
*args -- message argument list |
---|
173 |
""" |
---|
174 |
def __init__(self, address, args=[]): |
---|
175 |
Packet.__init__(self, [String(address)] + map(lambda x: _value(x), args)) |
---|
176 |
|
---|
177 |
def _write_contents(self, args, stream): |
---|
178 |
t_stream = cStringIO.StringIO() # tag stream |
---|
179 |
v_stream = cStringIO.StringIO() # value stream |
---|
180 |
# open signature string |
---|
181 |
t_stream.write(',') |
---|
182 |
# collect tags and arguments |
---|
183 |
for v in args[1:]: |
---|
184 |
t_stream.write(v.type_tag()) |
---|
185 |
v_stream.write(v.binary_value()) |
---|
186 |
# write address |
---|
187 |
stream.write(args[0].binary_value()) |
---|
188 |
# write signature |
---|
189 |
stream.write(String(t_stream.getvalue()).binary_value()) |
---|
190 |
# write arguments |
---|
191 |
stream.write(v_stream.getvalue()) |
---|
192 |
|
---|
193 |
class Bundle(Packet): |
---|
194 |
"""OSC container type with timing information. |
---|
195 |
|
---|
196 |
Bundle(time, packets) -> Bundle |
---|
197 |
|
---|
198 |
time -- floating point timetag in OSC units |
---|
199 |
packets -- array of Packet(s) |
---|
200 |
""" |
---|
201 |
def __init__(self, time, packets): |
---|
202 |
Packet.__init__(self, [Time(time)] + packets) |
---|
203 |
|
---|
204 |
def _write_contents(self, args, stream): |
---|
205 |
# write '#bundle' preamble |
---|
206 |
stream.write(String('#bundle').binary_value()) |
---|
207 |
# write timetag |
---|
208 |
stream.write(args[0].binary_value()) |
---|
209 |
# write packets, prefixed with a byte count |
---|
210 |
for packet in args[1:]: |
---|
211 |
data = packet.get_packet() |
---|
212 |
size = len(data) |
---|
213 |
stream.write(Int(size).binary_value()) |
---|
214 |
stream.write(data) |
---|
215 |
|
---|
216 |
def test(port): |
---|
217 |
"""Some example messages and bundles, sent to port.""" |
---|
218 |
Message("/filter/cutoff", [145.1232]).sendlocal(port) |
---|
219 |
Message("/http", ["www dot k-hornz dot de", 12, 3.41, "bulb"]).sendlocal(port) |
---|
220 |
# print Int(len(Message("/msg").get_packet())).binary_value() |
---|
221 |
Bundle(0.1, [Message("/fubar")]).sendlocal(port) |
---|
222 |
Bundle(time(), [Message("/msg", [1.0, "+", 1, 61, "0"]), Message("/bang!")]).sendlocal(port) |
---|
223 |
|
---|
224 |
def test2(): |
---|
225 |
"""Some example messages and bundles, sent to port.""" |
---|
226 |
Message("/noisepattern/start", ["hello"]).sendlocal(9898989) |
---|
227 |
Message("/noisepattern/modify", [1, "hello", "two"]).sendlocal(9898989) |
---|
228 |
print("/noisepattern/start") |
---|
229 |
|
---|
230 |
if __name__ == "__main__": |
---|
231 |
"""Run dump on port 10000.""" |
---|
232 |
#test(10000) |
---|
233 |
test2() |
---|
234 |
|
---|
235 |
# EOF |
---|
236 |
# ====================================================================== |
---|