oscparser 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscparser/__init__.py +70 -0
- oscparser/ctx.py +17 -0
- oscparser/decode.py +82 -0
- oscparser/encode.py +78 -0
- oscparser/framing/__init__.py +6 -0
- oscparser/framing/framer.py +18 -0
- oscparser/framing/fullframer.py +42 -0
- oscparser/framing/osc10.py +70 -0
- oscparser/framing/osc11.py +208 -0
- oscparser/processing/args/args.py +550 -0
- oscparser/processing/args/proccessing.py +29 -0
- oscparser/processing/osc/handlers.py +165 -0
- oscparser/processing/osc/processing.py +46 -0
- oscparser/types.py +263 -0
- oscparser-1.1.0.dist-info/METADATA +60 -0
- oscparser-1.1.0.dist-info/RECORD +19 -0
- oscparser-1.1.0.dist-info/WHEEL +5 -0
- oscparser-1.1.0.dist-info/licenses/LICENSE +21 -0
- oscparser-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from oscparser.ctx import DataBuffer
|
|
5
|
+
from oscparser.processing.args.proccessing import ArgDispatcher, ArgHandler
|
|
6
|
+
from oscparser.types import (
|
|
7
|
+
OSCRGBA,
|
|
8
|
+
OSCArray,
|
|
9
|
+
OSCBlob,
|
|
10
|
+
OSCChar,
|
|
11
|
+
OSCDouble,
|
|
12
|
+
OSCFalse,
|
|
13
|
+
OSCFloat,
|
|
14
|
+
OSCImpulse,
|
|
15
|
+
OSCInt,
|
|
16
|
+
OSCInt64,
|
|
17
|
+
OSCMidi,
|
|
18
|
+
OSCNil,
|
|
19
|
+
OSCString,
|
|
20
|
+
OSCSymbol,
|
|
21
|
+
OSCTimeTag,
|
|
22
|
+
OSCTrue,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _pad_to_multiple_of_4(length: int) -> int:
|
|
27
|
+
"""Return padding bytes needed to align to 4-byte boundary."""
|
|
28
|
+
remainder = length % 4
|
|
29
|
+
return 0 if remainder == 0 else 4 - remainder
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _encode_string(s: str) -> bytes:
|
|
33
|
+
"""Encode a string with null terminator and padding."""
|
|
34
|
+
encoded = s.encode("utf-8") + b"\x00"
|
|
35
|
+
padding = _pad_to_multiple_of_4(len(encoded))
|
|
36
|
+
return encoded + b"\x00" * padding
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _decode_string(ctx: DataBuffer) -> str:
|
|
40
|
+
"""Decode a null-terminated string from context."""
|
|
41
|
+
result = b""
|
|
42
|
+
while True:
|
|
43
|
+
byte = ctx.read(1)
|
|
44
|
+
if byte == b"\x00":
|
|
45
|
+
break
|
|
46
|
+
result += byte
|
|
47
|
+
# Skip padding
|
|
48
|
+
padding = _pad_to_multiple_of_4(len(result) + 1)
|
|
49
|
+
if padding > 0:
|
|
50
|
+
ctx.read(padding)
|
|
51
|
+
return result.decode("utf-8")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _encode_blob(data: bytes) -> bytes:
|
|
55
|
+
"""Encode a blob with 4-byte size prefix and padding."""
|
|
56
|
+
size = struct.pack(">I", len(data))
|
|
57
|
+
padding = _pad_to_multiple_of_4(len(data))
|
|
58
|
+
return size + data + b"\x00" * padding
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _decode_blob(ctx: DataBuffer) -> bytes:
|
|
62
|
+
"""Decode a blob from context."""
|
|
63
|
+
size = struct.unpack(">I", ctx.read(4))[0]
|
|
64
|
+
data = ctx.read(size)
|
|
65
|
+
padding = _pad_to_multiple_of_4(size)
|
|
66
|
+
if padding > 0:
|
|
67
|
+
ctx.read(padding)
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _datetime_to_timetag(dt: datetime) -> int:
|
|
72
|
+
"""Convert datetime to NTP timetag (64-bit)."""
|
|
73
|
+
# NTP epoch is 1900-01-01, Unix epoch is 1970-01-01
|
|
74
|
+
NTP_DELTA = 2208988800
|
|
75
|
+
timestamp = dt.timestamp()
|
|
76
|
+
seconds = int(timestamp) + NTP_DELTA
|
|
77
|
+
fraction = int((timestamp % 1) * (2**32))
|
|
78
|
+
return (seconds << 32) | fraction
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _timetag_to_datetime(timetag: int) -> datetime:
|
|
82
|
+
"""Convert NTP timetag to datetime."""
|
|
83
|
+
NTP_DELTA = 2208988800
|
|
84
|
+
seconds = (timetag >> 32) - NTP_DELTA
|
|
85
|
+
fraction = (timetag & 0xFFFFFFFF) / (2**32)
|
|
86
|
+
return datetime.fromtimestamp(seconds + fraction)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ============================================================================
|
|
90
|
+
# OSCInt Handler
|
|
91
|
+
# ============================================================================
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OSCIntHandler(ArgHandler[OSCInt]):
|
|
95
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
96
|
+
self.dispatcher = dispatcher
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCIntHandler":
|
|
100
|
+
return cls(dispatcher)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def handles(self) -> type[OSCInt]:
|
|
104
|
+
return OSCInt
|
|
105
|
+
|
|
106
|
+
def encode(self, arg: OSCInt, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
107
|
+
typetag.write(b"i")
|
|
108
|
+
message_body.write(struct.pack(">i", arg.value))
|
|
109
|
+
|
|
110
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCInt:
|
|
111
|
+
value = struct.unpack(">i", message_body.read(4))[0]
|
|
112
|
+
return OSCInt(value=value)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ============================================================================
|
|
116
|
+
# OSCFloat Handler
|
|
117
|
+
# ============================================================================
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class OSCFloatHandler(ArgHandler[OSCFloat]):
|
|
121
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
122
|
+
self.dispatcher = dispatcher
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCFloatHandler":
|
|
126
|
+
return cls(dispatcher)
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def handles(self) -> type[OSCFloat]:
|
|
130
|
+
return OSCFloat
|
|
131
|
+
|
|
132
|
+
def encode(self, arg: OSCFloat, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
133
|
+
typetag.write(b"f")
|
|
134
|
+
message_body.write(struct.pack(">f", arg.value))
|
|
135
|
+
|
|
136
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCFloat:
|
|
137
|
+
value = struct.unpack(">f", message_body.read(4))[0]
|
|
138
|
+
return OSCFloat(value=value)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ============================================================================
|
|
142
|
+
# OSCString Handler
|
|
143
|
+
# ============================================================================
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class OSCStringHandler(ArgHandler[OSCString]):
|
|
147
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
148
|
+
self.dispatcher = dispatcher
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCStringHandler":
|
|
152
|
+
return cls(dispatcher)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def handles(self) -> type[OSCString]:
|
|
156
|
+
return OSCString
|
|
157
|
+
|
|
158
|
+
def encode(self, arg: OSCString, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
159
|
+
typetag.write(b"s")
|
|
160
|
+
message_body.write(_encode_string(arg.value))
|
|
161
|
+
|
|
162
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCString:
|
|
163
|
+
value = _decode_string(message_body)
|
|
164
|
+
return OSCString(value=value)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ============================================================================
|
|
168
|
+
# OSCBlob Handler
|
|
169
|
+
# ============================================================================
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class OSCBlobHandler(ArgHandler[OSCBlob]):
|
|
173
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
174
|
+
self.dispatcher = dispatcher
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCBlobHandler":
|
|
178
|
+
return cls(dispatcher)
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def handles(self) -> type[OSCBlob]:
|
|
182
|
+
return OSCBlob
|
|
183
|
+
|
|
184
|
+
def encode(self, arg: OSCBlob, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
185
|
+
typetag.write(b"b")
|
|
186
|
+
message_body.write(_encode_blob(arg.value))
|
|
187
|
+
|
|
188
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCBlob:
|
|
189
|
+
value = _decode_blob(message_body)
|
|
190
|
+
return OSCBlob(value=value)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ============================================================================
|
|
194
|
+
# OSCTrue Handler
|
|
195
|
+
# ============================================================================
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class OSCTrueHandler(ArgHandler[OSCTrue]):
|
|
199
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
200
|
+
self.dispatcher = dispatcher
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCTrueHandler":
|
|
204
|
+
return cls(dispatcher)
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def handles(self) -> type[OSCTrue]:
|
|
208
|
+
return OSCTrue
|
|
209
|
+
|
|
210
|
+
def encode(self, arg: OSCTrue, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
211
|
+
typetag.write(b"T")
|
|
212
|
+
# No payload
|
|
213
|
+
|
|
214
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCTrue:
|
|
215
|
+
return OSCTrue()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ============================================================================
|
|
219
|
+
# OSCFalse Handler
|
|
220
|
+
# ============================================================================
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class OSCFalseHandler(ArgHandler[OSCFalse]):
|
|
224
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
225
|
+
self.dispatcher = dispatcher
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCFalseHandler":
|
|
229
|
+
return cls(dispatcher)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def handles(self) -> type[OSCFalse]:
|
|
233
|
+
return OSCFalse
|
|
234
|
+
|
|
235
|
+
def encode(self, arg: OSCFalse, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
236
|
+
typetag.write(b"F")
|
|
237
|
+
# No payload
|
|
238
|
+
|
|
239
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCFalse:
|
|
240
|
+
return OSCFalse()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ============================================================================
|
|
244
|
+
# OSCNil Handler
|
|
245
|
+
# ============================================================================
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class OSCNilHandler(ArgHandler[OSCNil]):
|
|
249
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
250
|
+
self.dispatcher = dispatcher
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCNilHandler":
|
|
254
|
+
return cls(dispatcher)
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def handles(self) -> type[OSCNil]:
|
|
258
|
+
return OSCNil
|
|
259
|
+
|
|
260
|
+
def encode(self, arg: OSCNil, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
261
|
+
typetag.write(b"N")
|
|
262
|
+
# No payload
|
|
263
|
+
|
|
264
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCNil:
|
|
265
|
+
return OSCNil()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ============================================================================
|
|
269
|
+
# OSCInt64 Handler
|
|
270
|
+
# ============================================================================
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class OSCInt64Handler(ArgHandler[OSCInt64]):
|
|
274
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
275
|
+
self.dispatcher = dispatcher
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCInt64Handler":
|
|
279
|
+
return cls(dispatcher)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def handles(self) -> type[OSCInt64]:
|
|
283
|
+
return OSCInt64
|
|
284
|
+
|
|
285
|
+
def encode(self, arg: OSCInt64, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
286
|
+
typetag.write(b"h")
|
|
287
|
+
message_body.write(struct.pack(">q", arg.value))
|
|
288
|
+
|
|
289
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCInt64:
|
|
290
|
+
value = struct.unpack(">q", message_body.read(8))[0]
|
|
291
|
+
return OSCInt64(value=value)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ============================================================================
|
|
295
|
+
# OSCDouble Handler
|
|
296
|
+
# ============================================================================
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class OSCDoubleHandler(ArgHandler[OSCDouble]):
|
|
300
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
301
|
+
self.dispatcher = dispatcher
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCDoubleHandler":
|
|
305
|
+
return cls(dispatcher)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def handles(self) -> type[OSCDouble]:
|
|
309
|
+
return OSCDouble
|
|
310
|
+
|
|
311
|
+
def encode(self, arg: OSCDouble, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
312
|
+
typetag.write(b"d")
|
|
313
|
+
message_body.write(struct.pack(">d", arg.value))
|
|
314
|
+
|
|
315
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCDouble:
|
|
316
|
+
value = struct.unpack(">d", message_body.read(8))[0]
|
|
317
|
+
return OSCDouble(value=value)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# ============================================================================
|
|
321
|
+
# OSCTimeTag Handler
|
|
322
|
+
# ============================================================================
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class OSCTimeTagHandler(ArgHandler[OSCTimeTag]):
|
|
326
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
327
|
+
self.dispatcher = dispatcher
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCTimeTagHandler":
|
|
331
|
+
return cls(dispatcher)
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def handles(self) -> type[OSCTimeTag]:
|
|
335
|
+
return OSCTimeTag
|
|
336
|
+
|
|
337
|
+
def encode(self, arg: OSCTimeTag, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
338
|
+
typetag.write(b"t")
|
|
339
|
+
timetag = _datetime_to_timetag(arg.value)
|
|
340
|
+
message_body.write(struct.pack(">Q", timetag))
|
|
341
|
+
|
|
342
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCTimeTag:
|
|
343
|
+
timetag = struct.unpack(">Q", message_body.read(8))[0]
|
|
344
|
+
value = _timetag_to_datetime(timetag)
|
|
345
|
+
return OSCTimeTag(value=value)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ============================================================================
|
|
349
|
+
# OSCChar Handler
|
|
350
|
+
# ============================================================================
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class OSCCharHandler(ArgHandler[OSCChar]):
|
|
354
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
355
|
+
self.dispatcher = dispatcher
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCCharHandler":
|
|
359
|
+
return cls(dispatcher)
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def handles(self) -> type[OSCChar]:
|
|
363
|
+
return OSCChar
|
|
364
|
+
|
|
365
|
+
def encode(self, arg: OSCChar, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
366
|
+
typetag.write(b"c")
|
|
367
|
+
# Encode as 4-byte ASCII value
|
|
368
|
+
char_byte = arg.value.encode("utf-8")[0] if arg.value else 0
|
|
369
|
+
message_body.write(struct.pack(">I", char_byte))
|
|
370
|
+
|
|
371
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCChar:
|
|
372
|
+
char_value = struct.unpack(">I", message_body.read(4))[0]
|
|
373
|
+
value = chr(char_value) if char_value > 0 else ""
|
|
374
|
+
return OSCChar(value=value)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ============================================================================
|
|
378
|
+
# OSCSymbol Handler
|
|
379
|
+
# ============================================================================
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class OSCSymbolHandler(ArgHandler[OSCSymbol]):
|
|
383
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
384
|
+
self.dispatcher = dispatcher
|
|
385
|
+
|
|
386
|
+
@classmethod
|
|
387
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCSymbolHandler":
|
|
388
|
+
return cls(dispatcher)
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def handles(self) -> type[OSCSymbol]:
|
|
392
|
+
return OSCSymbol
|
|
393
|
+
|
|
394
|
+
def encode(self, arg: OSCSymbol, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
395
|
+
typetag.write(b"S")
|
|
396
|
+
message_body.write(_encode_string(arg.value))
|
|
397
|
+
|
|
398
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCSymbol:
|
|
399
|
+
value = _decode_string(message_body)
|
|
400
|
+
return OSCSymbol(value=value)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ============================================================================
|
|
404
|
+
# OSCRGBA Handler
|
|
405
|
+
# ============================================================================
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class OSCRGBAHandler(ArgHandler[OSCRGBA]):
|
|
409
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
410
|
+
self.dispatcher = dispatcher
|
|
411
|
+
|
|
412
|
+
@classmethod
|
|
413
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCRGBAHandler":
|
|
414
|
+
return cls(dispatcher)
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def handles(self) -> type[OSCRGBA]:
|
|
418
|
+
return OSCRGBA
|
|
419
|
+
|
|
420
|
+
def encode(self, arg: OSCRGBA, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
421
|
+
typetag.write(b"r")
|
|
422
|
+
message_body.write(struct.pack(">BBBB", arg.r, arg.g, arg.b, arg.a))
|
|
423
|
+
|
|
424
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCRGBA:
|
|
425
|
+
r, g, b, a = struct.unpack(">BBBB", message_body.read(4))
|
|
426
|
+
return OSCRGBA(r=r, g=g, b=b, a=a)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# ============================================================================
|
|
430
|
+
# OSCMidi Handler
|
|
431
|
+
# ============================================================================
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class OSCMidiHandler(ArgHandler[OSCMidi]):
|
|
435
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
436
|
+
self.dispatcher = dispatcher
|
|
437
|
+
|
|
438
|
+
@classmethod
|
|
439
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCMidiHandler":
|
|
440
|
+
return cls(dispatcher)
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def handles(self) -> type[OSCMidi]:
|
|
444
|
+
return OSCMidi
|
|
445
|
+
|
|
446
|
+
def encode(self, arg: OSCMidi, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
447
|
+
typetag.write(b"m")
|
|
448
|
+
message_body.write(struct.pack(">BBBB", arg.port_id, arg.status, arg.data1, arg.data2))
|
|
449
|
+
|
|
450
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCMidi:
|
|
451
|
+
port_id, status, data1, data2 = struct.unpack(">BBBB", message_body.read(4))
|
|
452
|
+
return OSCMidi(port_id=port_id, status=status, data1=data1, data2=data2)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# ============================================================================
|
|
456
|
+
# OSCImpulse Handler
|
|
457
|
+
# ============================================================================
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class OSCImpulseHandler(ArgHandler[OSCImpulse]):
|
|
461
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
462
|
+
self.dispatcher = dispatcher
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCImpulseHandler":
|
|
466
|
+
return cls(dispatcher)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def handles(self) -> type[OSCImpulse]:
|
|
470
|
+
return OSCImpulse
|
|
471
|
+
|
|
472
|
+
def encode(self, arg: OSCImpulse, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
473
|
+
typetag.write(b"I")
|
|
474
|
+
# No payload
|
|
475
|
+
|
|
476
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCImpulse:
|
|
477
|
+
return OSCImpulse()
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
# ============================================================================
|
|
481
|
+
# OSCArray Handler
|
|
482
|
+
# ============================================================================
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class OSCArrayHandler(ArgHandler[OSCArray]):
|
|
486
|
+
def __init__(self, dispatcher: ArgDispatcher):
|
|
487
|
+
self.dispatcher = dispatcher
|
|
488
|
+
|
|
489
|
+
@classmethod
|
|
490
|
+
def from_dispatcher(cls, dispatcher: ArgDispatcher) -> "OSCArrayHandler":
|
|
491
|
+
return cls(dispatcher)
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def handles(self) -> type[OSCArray]:
|
|
495
|
+
return OSCArray
|
|
496
|
+
|
|
497
|
+
def encode(self, arg: OSCArray, message_body: DataBuffer, typetag: DataBuffer) -> None:
|
|
498
|
+
typetag.write(b"[")
|
|
499
|
+
for item in arg.items:
|
|
500
|
+
handler = self.dispatcher.get_handler_by_object(type(item))
|
|
501
|
+
handler.encode(item, message_body, typetag)
|
|
502
|
+
typetag.write(b"]")
|
|
503
|
+
|
|
504
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> OSCArray:
|
|
505
|
+
items = []
|
|
506
|
+
while True:
|
|
507
|
+
tag = typetag.read(1)
|
|
508
|
+
if tag == b"]":
|
|
509
|
+
break
|
|
510
|
+
handler = self.dispatcher.get_handler_by_tag(tag)
|
|
511
|
+
item = handler.decode(message_body, typetag)
|
|
512
|
+
items.append(item)
|
|
513
|
+
return OSCArray(items=tuple(items))
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# ============================================================================
|
|
517
|
+
# Registry
|
|
518
|
+
# ============================================================================
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def register_all_handlers(dispatcher: ArgDispatcher) -> None:
|
|
522
|
+
"""Register all OSC type handlers with the dispatcher."""
|
|
523
|
+
handlers = [
|
|
524
|
+
(OSCInt, b"i", OSCIntHandler),
|
|
525
|
+
(OSCFloat, b"f", OSCFloatHandler),
|
|
526
|
+
(OSCString, b"s", OSCStringHandler),
|
|
527
|
+
(OSCBlob, b"b", OSCBlobHandler),
|
|
528
|
+
(OSCTrue, b"T", OSCTrueHandler),
|
|
529
|
+
(OSCFalse, b"F", OSCFalseHandler),
|
|
530
|
+
(OSCNil, b"N", OSCNilHandler),
|
|
531
|
+
(OSCInt64, b"h", OSCInt64Handler),
|
|
532
|
+
(OSCDouble, b"d", OSCDoubleHandler),
|
|
533
|
+
(OSCTimeTag, b"t", OSCTimeTagHandler),
|
|
534
|
+
(OSCChar, b"c", OSCCharHandler),
|
|
535
|
+
(OSCSymbol, b"S", OSCSymbolHandler),
|
|
536
|
+
(OSCRGBA, b"r", OSCRGBAHandler),
|
|
537
|
+
(OSCMidi, b"m", OSCMidiHandler),
|
|
538
|
+
(OSCImpulse, b"I", OSCImpulseHandler),
|
|
539
|
+
(OSCArray, b"[", OSCArrayHandler),
|
|
540
|
+
]
|
|
541
|
+
|
|
542
|
+
for obj_type, tag, handler_cls in handlers:
|
|
543
|
+
dispatcher.register_handler(obj_type, tag, handler_cls)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def create_arg_dispatcher() -> ArgDispatcher:
|
|
547
|
+
"""Create and return a fully configured argument dispatcher."""
|
|
548
|
+
dispatcher = ArgDispatcher()
|
|
549
|
+
register_all_handlers(dispatcher)
|
|
550
|
+
return dispatcher
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any, Protocol, cast
|
|
2
|
+
|
|
3
|
+
from oscparser.ctx import DataBuffer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ArgHandler[T: object = object](Protocol):
|
|
7
|
+
@classmethod
|
|
8
|
+
def from_dispatcher(cls, dispatcher: "ArgDispatcher") -> "ArgHandler[T]": ...
|
|
9
|
+
|
|
10
|
+
def encode(self, arg: T, message_body: DataBuffer, typetag: DataBuffer): ...
|
|
11
|
+
|
|
12
|
+
def decode(self, message_body: DataBuffer, typetag: DataBuffer) -> T: ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArgDispatcher:
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._tag_handlers: dict[bytes, ArgHandler[Any]] = {}
|
|
18
|
+
self._object_handlers: dict[type, ArgHandler[Any]] = {}
|
|
19
|
+
|
|
20
|
+
def register_handler[T: type](self, obj: T, tag: bytes, handler: type[ArgHandler[T]]) -> None:
|
|
21
|
+
handler_inst = handler.from_dispatcher(self)
|
|
22
|
+
self._tag_handlers[tag] = handler_inst
|
|
23
|
+
self._object_handlers[obj] = handler_inst
|
|
24
|
+
|
|
25
|
+
def get_handler_by_tag(self, tag: bytes) -> ArgHandler:
|
|
26
|
+
return self._tag_handlers[tag]
|
|
27
|
+
|
|
28
|
+
def get_handler_by_object[T](self, obj: type[T]) -> ArgHandler[T]:
|
|
29
|
+
return cast(ArgHandler[T], self._object_handlers[obj])
|