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.
@@ -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])