FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
python-osc/pythonosc/parsing/osc_types.py at main · ZipFile/python-osc · GitHub
ZipFile
/
python-osc
Public
forked from
attwad/python-osc
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
python-osc
/
pythonosc
/
parsing
/
osc_types.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
457 lines (353 loc) · 14.5 KB
Breadcrumbs
python-osc
/
pythonosc
/
parsing
/
osc_types.py
Copy path
File metadata and controls
457 lines (353 loc) · 14.5 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
"""Functions to get OSC types from datagrams and vice versa"""
import
struct
from
pythonosc
.
parsing
import
ntp
from
datetime
import
datetime
,
timedelta
from
typing
import
Union
,
Tuple
,
cast
MidiPacket
=
Tuple
[
int
,
int
,
int
,
int
]
class
ParseError
(
Exception
):
"""Base exception for when a datagram parsing error occurs."""
class
BuildError
(
Exception
):
"""Base exception for when a datagram building error occurs."""
# Constant for special ntp datagram sequences that represent an immediate time.
IMMEDIATELY
=
0
# Datagram length in bytes for types that have a fixed size.
_INT_DGRAM_LEN
=
4
_INT64_DGRAM_LEN
=
8
_UINT64_DGRAM_LEN
=
8
_FLOAT_DGRAM_LEN
=
4
_DOUBLE_DGRAM_LEN
=
8
_TIMETAG_DGRAM_LEN
=
8
# Strings and blob dgram length is always a multiple of 4 bytes.
_STRING_DGRAM_PAD
=
4
_BLOB_DGRAM_PAD
=
4
_EMPTY_STR_DGRAM
=
b"
\x00
\x00
\x00
\x00
"
def
write_string
(
val
:
str
)
->
bytes
:
"""Returns the OSC string equivalent of the given python string.
Raises:
- BuildError if the string could not be encoded.
"""
try
:
dgram
=
val
.
encode
(
"utf-8"
)
# Default, but better be explicit.
except
(
UnicodeEncodeError
,
AttributeError
)
as
e
:
raise
BuildError
(
f"Incorrect string, could not encode
{
e
}
"
)
diff
=
_STRING_DGRAM_PAD
-
(
len
(
dgram
)
%
_STRING_DGRAM_PAD
)
dgram
+=
b"
\x00
"
*
diff
return
dgram
def
get_string
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
str
,
int
]:
"""Get a python string from the datagram, starting at pos start_index.
According to the specifications, a string is:
"A sequence of non-null ASCII characters followed by a null,
followed by 0-3 additional null characters to make the total number
of bits a multiple of 32".
Args:
dgram: A datagram packet.
start_index: An index where the string starts in the datagram.
Returns:
A tuple containing the string and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
if
start_index
<
0
:
raise
ParseError
(
"start_index < 0"
)
offset
=
0
try
:
while
dgram
[
start_index
+
offset
]
!=
0
:
offset
+=
1
# OSC spec: "followed by a null, followed by 0-3 additional null characters
# to make the total number of bits a multiple of 32"
# This means the total length (including the first null) must be a multiple of 4.
total_len
=
offset
+
1
if
total_len
%
4
!=
0
:
total_len
+=
4
-
(
total_len
%
4
)
if
start_index
+
total_len
>
len
(
dgram
):
raise
ParseError
(
"Datagram is too short"
)
data_str
=
dgram
[
start_index
:
start_index
+
offset
]
return
data_str
.
decode
(
"utf-8"
),
start_index
+
total_len
except
IndexError
as
ie
:
raise
ParseError
(
f"Could not parse datagram
{
ie
}
"
)
except
TypeError
as
te
:
raise
ParseError
(
f"Could not parse datagram
{
te
}
"
)
def
write_int
(
val
:
int
)
->
bytes
:
"""Returns the datagram for the given integer parameter value
Raises:
- BuildError if the int could not be converted.
"""
try
:
return
struct
.
pack
(
">i"
,
val
)
except
struct
.
error
as
e
:
raise
BuildError
(
f"Wrong argument value passed:
{
e
}
"
)
def
get_int
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
int
,
int
]:
"""Get a 32-bit big-endian two's complement integer from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the integer starts in the datagram.
Returns:
A tuple containing the integer and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_INT_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
return
(
struct
.
unpack
(
">i"
,
dgram
[
start_index
:
start_index
+
_INT_DGRAM_LEN
])[
0
],
start_index
+
_INT_DGRAM_LEN
,
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
write_int64
(
val
:
int
)
->
bytes
:
"""Returns the datagram for the given 64-bit big-endian signed parameter value
Raises:
- BuildError if the int64 could not be converted.
"""
try
:
return
struct
.
pack
(
">q"
,
val
)
except
struct
.
error
as
e
:
raise
BuildError
(
f"Wrong argument value passed:
{
e
}
"
)
def
get_int64
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
int
,
int
]:
"""Get a 64-bit big-endian signed integer from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the 64-bit integer starts in the datagram.
Returns:
A tuple containing the 64-bit integer and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_INT64_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
return
(
struct
.
unpack
(
">q"
,
dgram
[
start_index
:
start_index
+
_INT64_DGRAM_LEN
])[
0
],
start_index
+
_INT64_DGRAM_LEN
,
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
get_uint64
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
int
,
int
]:
"""Get a 64-bit big-endian unsigned integer from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the integer starts in the datagram.
Returns:
A tuple containing the integer and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_UINT64_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
return
(
struct
.
unpack
(
">Q"
,
dgram
[
start_index
:
start_index
+
_UINT64_DGRAM_LEN
])[
0
],
start_index
+
_UINT64_DGRAM_LEN
,
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
get_timetag
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
Tuple
[
datetime
,
int
],
int
]:
"""Get a 64-bit OSC time tag from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the osc time tag starts in the datagram.
Returns:
A tuple containing the tuple of time of sending in utc as datetime and the
fraction of the current second and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_TIMETAG_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
timetag
,
_
=
get_uint64
(
dgram
,
start_index
)
seconds
,
fraction
=
ntp
.
parse_timestamp
(
timetag
)
utc
=
datetime
.
combine
(
ntp
.
_NTP_EPOCH
,
datetime
.
min
.
time
())
+
timedelta
(
seconds
=
seconds
)
return
(
utc
,
fraction
),
start_index
+
_TIMETAG_DGRAM_LEN
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
write_float
(
val
:
float
)
->
bytes
:
"""Returns the datagram for the given float parameter value
Raises:
- BuildError if the float could not be converted.
"""
try
:
return
struct
.
pack
(
">f"
,
val
)
except
struct
.
error
as
e
:
raise
BuildError
(
f"Wrong argument value passed:
{
e
}
"
)
def
get_float
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
float
,
int
]:
"""Get a 32-bit big-endian IEEE 754 floating point number from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the float starts in the datagram.
Returns:
A tuple containing the float and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_FLOAT_DGRAM_LEN
:
# Noticed that Reaktor doesn't send the last bunch of \x00 needed to make
# the float representation complete in some cases, thus we pad here to
# account for that.
dgram
=
dgram
+
b"
\x00
"
*
(
_FLOAT_DGRAM_LEN
-
len
(
dgram
[
start_index
:]))
return
(
struct
.
unpack
(
">f"
,
dgram
[
start_index
:
start_index
+
_FLOAT_DGRAM_LEN
])[
0
],
start_index
+
_FLOAT_DGRAM_LEN
,
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
write_double
(
val
:
float
)
->
bytes
:
"""Returns the datagram for the given double parameter value
Raises:
- BuildError if the double could not be converted.
"""
try
:
return
struct
.
pack
(
">d"
,
val
)
except
struct
.
error
as
e
:
raise
BuildError
(
f"Wrong argument value passed:
{
e
}
"
)
def
get_double
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
float
,
int
]:
"""Get a 64-bit big-endian IEEE 754 floating point number from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the double starts in the datagram.
Returns:
A tuple containing the double and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_DOUBLE_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
return
(
struct
.
unpack
(
">d"
,
dgram
[
start_index
:
start_index
+
_DOUBLE_DGRAM_LEN
])[
0
],
start_index
+
_DOUBLE_DGRAM_LEN
,
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
get_blob
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
bytes
,
int
]:
"""Get a blob from the datagram.
According to the specifications, a blob is made of
"an int32 size count, followed by that many 8-bit bytes of arbitrary
binary data, followed by 0-3 additional zero bytes to make the total
number of bits a multiple of 32".
Args:
dgram: A datagram packet.
start_index: An index where the float starts in the datagram.
Returns:
A tuple containing the blob and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
size
,
int_offset
=
get_int
(
dgram
,
start_index
)
# Make the size a multiple of 32 bits.
total_size
=
size
+
(
-
size
%
_BLOB_DGRAM_PAD
)
end_index
=
int_offset
+
size
if
end_index
-
start_index
>
len
(
dgram
[
start_index
:]):
raise
ParseError
(
"Datagram is too short."
)
return
dgram
[
int_offset
:
int_offset
+
size
],
int_offset
+
total_size
def
write_blob
(
val
:
bytes
)
->
bytes
:
"""Returns the datagram for the given blob parameter value.
Raises:
- BuildError if the value was empty or if its size didn't fit an OSC int.
"""
if
not
val
:
raise
BuildError
(
"Blob value cannot be empty"
)
dgram
=
write_int
(
len
(
val
))
dgram
+=
val
while
len
(
dgram
)
%
_BLOB_DGRAM_PAD
!=
0
:
dgram
+=
b"
\x00
"
return
dgram
def
get_date
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
float
,
int
]:
"""Get a 64-bit big-endian fixed-point time tag as a date from the datagram.
According to the specifications, a date is represented as is:
"the first 32 bits specify the number of seconds since midnight on
January 1, 1900, and the last 32 bits specify fractional parts of a second
to a precision of about 200 picoseconds".
Args:
dgram: A datagram packet.
start_index: An index where the date starts in the datagram.
Returns:
A tuple containing the system date and the new end index.
returns osc_immediately (0) if the corresponding OSC sequence was found.
Raises:
ParseError if the datagram could not be parsed.
"""
# Check for the special case first.
if
dgram
[
start_index
:
start_index
+
_TIMETAG_DGRAM_LEN
]
==
ntp
.
IMMEDIATELY
:
return
IMMEDIATELY
,
start_index
+
_TIMETAG_DGRAM_LEN
if
len
(
dgram
[
start_index
:])
<
_TIMETAG_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
timetag
,
start_index
=
get_uint64
(
dgram
,
start_index
)
seconds
=
timetag
*
ntp
.
_NTP_TIMESTAMP_TO_SECONDS
return
ntp
.
ntp_time_to_system_epoch
(
seconds
),
start_index
def
write_date
(
system_time
:
Union
[
int
,
float
])
->
bytes
:
if
system_time
==
IMMEDIATELY
:
return
ntp
.
IMMEDIATELY
try
:
return
ntp
.
system_time_to_ntp
(
system_time
)
except
ntp
.
NtpError
as
ntpe
:
raise
BuildError
(
ntpe
)
def
write_rgba
(
val
:
bytes
)
->
bytes
:
"""Returns the datagram for the given rgba32 parameter value
Raises:
- BuildError if the int could not be converted.
"""
try
:
return
struct
.
pack
(
">I"
,
val
)
except
struct
.
error
as
e
:
raise
BuildError
(
f"Wrong argument value passed:
{
e
}
"
)
def
get_rgba
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
bytes
,
int
]:
"""Get an rgba32 integer from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the integer starts in the datagram.
Returns:
A tuple containing the integer and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_INT_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
return
(
struct
.
unpack
(
">I"
,
dgram
[
start_index
:
start_index
+
_INT_DGRAM_LEN
])[
0
],
start_index
+
_INT_DGRAM_LEN
,
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
def
write_midi
(
val
:
MidiPacket
)
->
bytes
:
"""Returns the datagram for the given MIDI message parameter value
A valid MIDI message: (port id, status byte, data1, data2).
Raises:
- BuildError if the MIDI message could not be converted.
"""
if
len
(
val
)
!=
4
:
raise
BuildError
(
"MIDI message length is invalid"
)
try
:
value
=
sum
((
value
&
0xFF
)
<<
8
*
(
3
-
pos
)
for
pos
,
value
in
enumerate
(
val
))
return
struct
.
pack
(
">I"
,
value
)
except
struct
.
error
as
e
:
raise
BuildError
(
f"Wrong argument value passed:
{
e
}
"
)
def
get_midi
(
dgram
:
bytes
,
start_index
:
int
)
->
Tuple
[
MidiPacket
,
int
]:
"""Get a MIDI message (port id, status byte, data1, data2) from the datagram.
Args:
dgram: A datagram packet.
start_index: An index where the MIDI message starts in the datagram.
Returns:
A tuple containing the MIDI message and the new end index.
Raises:
ParseError if the datagram could not be parsed.
"""
try
:
if
len
(
dgram
[
start_index
:])
<
_INT_DGRAM_LEN
:
raise
ParseError
(
"Datagram is too short"
)
val
=
struct
.
unpack
(
">I"
,
dgram
[
start_index
:
start_index
+
_INT_DGRAM_LEN
])[
0
]
midi_msg
=
cast
(
MidiPacket
,
tuple
((
val
&
0xFF
<<
8
*
i
)
>>
8
*
i
for
i
in
range
(
3
,
-
1
,
-
1
))
)
return
(
midi_msg
,
start_index
+
_INT_DGRAM_LEN
)
except
(
struct
.
error
,
TypeError
)
as
e
:
raise
ParseError
(
f"Could not parse datagram
{
e
}
"
)
Back
|
FazBrowse Home
|
New Git URL