1 /*
2 * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
3 *
4 * This is free software: you can redistribute it and/or modify
5 * it under the terms of the Artistic License 2.0 as published by
6 * The Perl Foundation.
7 *
8 * This source is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * Artistic License 2.0 for more details.
12 *
13 * You should have received a copy of the Artistic License 2.0
14 * along the source as a COPYING file. If not, obtain it from
15 * http://www.perlfoundation.org/artistic_license_2_0.
16 */
17
18 #ifndef LV2_OSC_UTIL_H
19 #define LV2_OSC_UTIL_H
20
21 #include <assert.h>
22 #include <ctype.h>
23 #include <inttypes.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #if !defined(_WIN32)
27 # include <fnmatch.h>
28 #endif
29
30 #include <osc.lv2/osc.h>
31
32 #include <lv2/lv2plug.in/ns/ext/atom/util.h>
33
34 #ifdef __cplusplus
35 extern "C" {
36 #endif
37
38 #ifndef __unused
39 # define __unused __attribute__((unused))
40 #endif
41
42 #undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
43 #define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
44 for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
45 !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
46 (iter) = lv2_atom_tuple_next(iter))
47
48 typedef void (*LV2_OSC_Method)(const char *path,
49 const LV2_Atom_Tuple *arguments, void *data);
50
51 typedef struct _LV2_OSC_Hook LV2_OSC_Hook;
52
53 struct _LV2_OSC_Hook {
54 const char *name;
55 const LV2_OSC_Hook *hooks;
56 LV2_OSC_Method method;
57 void *data;
58 };
59
60 // characters not allowed in OSC path string
61 static const char invalid_path_chars [] = {
62 ' ', '#',
63 '\0'
64 };
65
66 // allowed characters in OSC format string
67 static const char valid_format_chars [] = {
68 LV2_OSC_INT32, LV2_OSC_FLOAT, LV2_OSC_STRING, LV2_OSC_BLOB,
69 LV2_OSC_TRUE, LV2_OSC_FALSE, LV2_OSC_NIL, LV2_OSC_IMPULSE,
70 LV2_OSC_INT64, LV2_OSC_DOUBLE, LV2_OSC_TIMETAG,
71 LV2_OSC_SYMBOL, LV2_OSC_MIDI,
72 '\0'
73 };
74
75 static inline bool
lv2_osc_pattern_match(const char * from,const char * name,size_t len)76 lv2_osc_pattern_match(const char *from, const char *name, size_t len)
77 {
78 #if !defined(_WIN32)
79 size_t nbrace = 0;
80
81 # if defined(FNM_EXTMATCH)
82 // count opening curly braces
83 for(size_t i = 0; i < len; i++)
84 {
85 if(from[i] == '{')
86 {
87 nbrace++;
88 }
89 }
90 # endif
91
92 // allocate temporary pattern buffer
93 char *pattern = alloca(len + nbrace + 1);
94
95 if(!pattern)
96 {
97 return false;
98 }
99
100 # if defined(FNM_EXTMATCH)
101 // convert {x,y} to @(x|y) for extended fnmatch
102 if(nbrace)
103 {
104 char *ptr = pattern;
105
106 for(size_t i = 0; i < len; i++)
107 {
108 switch(from[i])
109 {
110 case '{':
111 {
112 *ptr++ = '@';
113 *ptr++ = '(';
114 } break;
115 case ',':
116 {
117 *ptr++ = '|';
118 } break;
119 case '}':
120 {
121 *ptr++ = ')';
122 } break;
123 default:
124 {
125 *ptr++ = from[i];
126 } break;
127 }
128 }
129 }
130 else
131 # endif
132 {
133 memcpy(pattern, from, len);
134 }
135
136 // terminate pattern string with null terminator
137 pattern[len + nbrace] = '\0';
138
139 # if defined(FNM_EXTMATCH)
140 return fnmatch(pattern, name, FNM_NOESCAPE | FNM_EXTMATCH) == 0 ? true : false;
141 # else
142 return fnmatch(pattern, name, FNM_NOESCAPE) == 0 ? true : false;
143 # endif
144 #else
145 return strncmp(from, name, len) == 0 ? true : false;
146 #endif
147 }
148
149 static inline void
_lv2_osc_hooks_internal(const char * path,const char * from,const LV2_Atom_Tuple * arguments,const LV2_OSC_Hook * hooks)150 _lv2_osc_hooks_internal(const char *path, const char *from,
151 const LV2_Atom_Tuple *arguments, const LV2_OSC_Hook *hooks)
152 {
153 const char *ptr = strchr(from, '/');
154
155 const size_t len = ptr
156 ? (size_t)(ptr - from)
157 : strlen(from);
158
159 for(const LV2_OSC_Hook *hook = hooks; hook && hook->name; hook++)
160 {
161 if(lv2_osc_pattern_match(from, hook->name, len))
162 {
163 if(hook->hooks && ptr)
164 {
165 from = &ptr[1];
166
167 _lv2_osc_hooks_internal(path, from, arguments, hook->hooks);
168 }
169 else if(hook->method && !ptr)
170 {
171 hook->method(path, arguments, hook->data);
172 }
173 }
174 }
175 }
176
177 /**
178 TODO
179 */
180 static inline void
lv2_osc_hooks(const char * path,const LV2_Atom_Tuple * arguments,void * data)181 lv2_osc_hooks(const char *path, const LV2_Atom_Tuple *arguments, void *data)
182 {
183 const LV2_OSC_Hook *hooks = data;
184 const char *from = &path[1];
185
186 _lv2_osc_hooks_internal(path, from, arguments, hooks);
187 }
188
189 /**
190 TODO
191 */
192 static inline bool
lv2_osc_check_path(const char * path)193 lv2_osc_check_path(const char *path)
194 {
195 assert(path);
196
197 if(path[0] != '/')
198 return false;
199
200 for(const char *ptr=path+1; *ptr!='\0'; ptr++)
201 if( (isprint(*ptr) == 0) || (strchr(invalid_path_chars, *ptr) != NULL) )
202 return false;
203
204 return true;
205 }
206
207 /**
208 TODO
209 */
210 static inline bool
lv2_osc_check_fmt(const char * format,int offset)211 lv2_osc_check_fmt(const char *format, int offset)
212 {
213 assert(format);
214
215 if(offset && (format[0] != ',') )
216 return false;
217
218 for(const char *ptr=format+offset; *ptr!='\0'; ptr++)
219 if(strchr(valid_format_chars, *ptr) == NULL)
220 return false;
221
222 return true;
223 }
224
225 /**
226 TODO
227 */
228 static inline uint64_t
lv2_osc_timetag_parse(const LV2_OSC_Timetag * timetag)229 lv2_osc_timetag_parse(const LV2_OSC_Timetag *timetag)
230 {
231 return ((uint64_t)timetag->integral << 32) | timetag->fraction;
232 }
233
234 /**
235 TODO
236 */
237 static inline LV2_OSC_Timetag *
lv2_osc_timetag_create(LV2_OSC_Timetag * timetag,uint64_t tt)238 lv2_osc_timetag_create(LV2_OSC_Timetag *timetag, uint64_t tt)
239 {
240 timetag->integral = tt >> 32;
241 timetag->fraction = tt & 0xffffffff;
242
243 return timetag;
244 }
245
246 #define LV2_OSC_TIMETAG_CREATE(tt) \
247 lv2_osc_timetag_create(&(LV2_OSC_Timetag){.integral = 0, .fraction = 0}, (tt))
248
249 /**
250 TODO
251 */
252 static inline bool
lv2_osc_is_packet_type(LV2_OSC_URID * osc_urid,LV2_URID type)253 lv2_osc_is_packet_type(LV2_OSC_URID *osc_urid, LV2_URID type)
254 {
255 return type == osc_urid->OSC_Packet;
256 }
257
258 /**
259 TODO
260 */
261 static inline bool
lv2_osc_is_bundle_type(LV2_OSC_URID * osc_urid,LV2_URID type)262 lv2_osc_is_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
263 {
264 return type == osc_urid->OSC_Bundle;
265 }
266
267 /**
268 TODO
269 */
270 static inline bool
lv2_osc_is_message_type(LV2_OSC_URID * osc_urid,LV2_URID type)271 lv2_osc_is_message_type(LV2_OSC_URID *osc_urid, LV2_URID type)
272 {
273 return type == osc_urid->OSC_Message;
274 }
275
276 /**
277 TODO
278 */
279 static inline bool
lv2_osc_is_message_or_bundle_type(LV2_OSC_URID * osc_urid,LV2_URID type)280 lv2_osc_is_message_or_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
281 {
282 return lv2_osc_is_message_type(osc_urid, type)
283 || lv2_osc_is_bundle_type(osc_urid, type);
284 }
285
286 static inline LV2_OSC_Type
lv2_osc_argument_type(LV2_OSC_URID * osc_urid,const LV2_Atom * atom)287 lv2_osc_argument_type(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
288 {
289 const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
290
291 if(atom->type == osc_urid->ATOM_Int)
292 return LV2_OSC_INT32;
293 else if(atom->type == osc_urid->ATOM_Float)
294 return LV2_OSC_FLOAT;
295 else if(atom->type == osc_urid->ATOM_String)
296 return LV2_OSC_STRING;
297 else if(atom->type == osc_urid->ATOM_Chunk)
298 return LV2_OSC_BLOB;
299
300 else if(atom->type == osc_urid->ATOM_Long)
301 return LV2_OSC_INT64;
302 else if(atom->type == osc_urid->ATOM_Double)
303 return LV2_OSC_DOUBLE;
304 else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
305 return LV2_OSC_TIMETAG;
306
307 else if(atom->type == osc_urid->ATOM_Bool)
308 {
309 if(((const LV2_Atom_Bool *)atom)->body)
310 return LV2_OSC_TRUE;
311 else
312 return LV2_OSC_FALSE;
313 }
314 else if(atom->type == osc_urid->ATOM_Literal)
315 {
316 const LV2_Atom_Literal *lit = (const LV2_Atom_Literal *)atom;
317 if(lit->body.datatype == osc_urid->OSC_Nil)
318 return LV2_OSC_NIL;
319 else if(lit->body.datatype == osc_urid->OSC_Impulse)
320 return LV2_OSC_IMPULSE;
321 else if(lit->body.datatype == osc_urid->OSC_Char)
322 return LV2_OSC_CHAR;
323 else if(lit->body.datatype == osc_urid->OSC_RGBA)
324 return LV2_OSC_RGBA;
325 }
326
327 else if(atom->type == osc_urid->ATOM_URID)
328 return LV2_OSC_SYMBOL;
329 else if(atom->type == osc_urid->MIDI_MidiEvent)
330 return LV2_OSC_MIDI;
331
332 return '\0';
333 }
334
335 static inline const LV2_Atom *
lv2_osc_int32_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,int32_t * i)336 lv2_osc_int32_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
337 int32_t *i)
338 {
339 assert(i);
340 *i = ((const LV2_Atom_Int *)atom)->body;
341
342 return lv2_atom_tuple_next(atom);
343 }
344
345 static inline const LV2_Atom *
lv2_osc_float_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,float * f)346 lv2_osc_float_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
347 float *f)
348 {
349 assert(f);
350 *f = ((const LV2_Atom_Float *)atom)->body;
351
352 return lv2_atom_tuple_next(atom);
353 }
354
355 static inline const LV2_Atom *
lv2_osc_string_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,const char ** s)356 lv2_osc_string_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
357 const char **s)
358 {
359 assert(s);
360 *s = LV2_ATOM_BODY_CONST(atom);
361
362 return lv2_atom_tuple_next(atom);
363 }
364
365 static inline const LV2_Atom *
lv2_osc_blob_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,uint32_t * size,const uint8_t ** b)366 lv2_osc_blob_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
367 uint32_t *size, const uint8_t **b)
368 {
369 assert(size && b);
370 *size = atom->size;
371 *b = LV2_ATOM_BODY_CONST(atom);
372
373 return lv2_atom_tuple_next(atom);
374 }
375
376 static inline const LV2_Atom *
lv2_osc_int64_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,int64_t * h)377 lv2_osc_int64_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
378 int64_t *h)
379 {
380 assert(h);
381 *h = ((const LV2_Atom_Long *)atom)->body;
382
383 return lv2_atom_tuple_next(atom);
384 }
385
386 static inline const LV2_Atom *
lv2_osc_double_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,double * d)387 lv2_osc_double_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
388 double *d)
389 {
390 assert(d);
391 *d = ((const LV2_Atom_Double *)atom)->body;
392
393 return lv2_atom_tuple_next(atom);
394 }
395
396 static inline const LV2_Atom *
lv2_osc_timetag_get(LV2_OSC_URID * osc_urid,const LV2_Atom * atom,LV2_OSC_Timetag * timetag)397 lv2_osc_timetag_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom,
398 LV2_OSC_Timetag *timetag)
399 {
400 assert(timetag);
401
402 const LV2_Atom_Long *integral = NULL;
403 const LV2_Atom_Long *fraction = NULL;
404
405 lv2_atom_object_get((const LV2_Atom_Object *)atom,
406 osc_urid->OSC_timetagIntegral, &integral,
407 osc_urid->OSC_timetagFraction, &fraction,
408 0);
409
410 if( integral && (integral->atom.type == osc_urid->ATOM_Long)
411 && fraction && (fraction->atom.type == osc_urid->ATOM_Long) )
412 {
413 timetag->integral = integral->body;
414 timetag->fraction = fraction->body;
415 }
416 else
417 {
418 // set to immediate
419 timetag->integral = 0;
420 timetag->fraction = 1;
421 }
422
423 return lv2_atom_tuple_next(atom);
424 }
425
426 static inline const LV2_Atom *
lv2_osc_true_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom)427 lv2_osc_true_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
428 {
429 return lv2_atom_tuple_next(atom);
430 }
431
432 static inline const LV2_Atom *
lv2_osc_false_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom)433 lv2_osc_false_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
434 {
435 return lv2_atom_tuple_next(atom);
436 }
437
438 static inline const LV2_Atom *
lv2_osc_nil_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom)439 lv2_osc_nil_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
440 {
441 return lv2_atom_tuple_next(atom);
442 }
443
444 static inline const LV2_Atom *
lv2_osc_impulse_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom)445 lv2_osc_impulse_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
446 {
447 return lv2_atom_tuple_next(atom);
448 }
449
450 static inline const LV2_Atom *
lv2_osc_symbol_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,LV2_URID * S)451 lv2_osc_symbol_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
452 LV2_URID *S)
453 {
454 assert(S);
455 *S = ((const LV2_Atom_URID *)atom)->body;
456
457 return lv2_atom_tuple_next(atom);
458 }
459
460 static inline const LV2_Atom *
lv2_osc_midi_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,uint32_t * size,const uint8_t ** m)461 lv2_osc_midi_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
462 uint32_t *size, const uint8_t **m)
463 {
464 assert(size && m);
465 *size = atom->size;
466 *m = LV2_ATOM_BODY_CONST(atom);
467
468 return lv2_atom_tuple_next(atom);
469 }
470
471 static inline const LV2_Atom *
lv2_osc_char_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,char * c)472 lv2_osc_char_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom, char *c)
473 {
474 assert(c);
475 const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
476 *c = str[0];
477
478 return lv2_atom_tuple_next(atom);
479 }
480
481 static inline const LV2_Atom *
lv2_osc_rgba_get(LV2_OSC_URID * osc_urid __unused,const LV2_Atom * atom,uint8_t * r,uint8_t * g,uint8_t * b,uint8_t * a)482 lv2_osc_rgba_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
483 uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
484 {
485 assert(r && g && b && a);
486 const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
487
488 uint8_t *key [4] = {
489 r, g, b, a
490 };
491
492 const char *pos = str;
493 char *endptr;
494
495 for(unsigned count = 0; count < 4; count++, pos += 2)
496 {
497 char buf [5] = {'0', 'x', pos[0], pos[1], '\0'};
498
499 *key[count] = strtol(buf, &endptr, 16);
500 }
501
502 return lv2_atom_tuple_next(atom);
503 }
504
505 /**
506 TODO
507 */
508 static inline bool
lv2_osc_bundle_body_get(LV2_OSC_URID * osc_urid,uint32_t size,const LV2_Atom_Object_Body * body,const LV2_Atom_Object ** timetag,const LV2_Atom_Tuple ** items)509 lv2_osc_bundle_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
510 const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
511 {
512 assert(timetag && items);
513
514 *timetag = NULL;
515 *items = NULL;
516
517 lv2_atom_object_body_get(size, body,
518 osc_urid->OSC_bundleTimetag, timetag,
519 osc_urid->OSC_bundleItems, items,
520 0);
521
522 if(!*timetag || ((*timetag)->atom.type != osc_urid->ATOM_Object) || ((*timetag)->body.otype != osc_urid->OSC_Timetag))
523 return false;
524 if(!*items || ((*items)->atom.type != osc_urid->ATOM_Tuple))
525 return false;
526
527 return true;
528 }
529
530 /**
531 TODO
532 */
533 static inline bool
lv2_osc_bundle_get(LV2_OSC_URID * osc_urid,const LV2_Atom_Object * obj,const LV2_Atom_Object ** timetag,const LV2_Atom_Tuple ** items)534 lv2_osc_bundle_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
535 const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
536 {
537 return lv2_osc_bundle_body_get(osc_urid, obj->atom.size, &obj->body,
538 timetag, items);
539 }
540
541 /**
542 TODO
543 */
544 static inline bool
lv2_osc_message_body_get(LV2_OSC_URID * osc_urid,uint32_t size,const LV2_Atom_Object_Body * body,const LV2_Atom_String ** path,const LV2_Atom_Tuple ** arguments)545 lv2_osc_message_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
546 const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
547 {
548 assert(path && arguments);
549
550 *path = NULL;
551 *arguments = NULL;
552
553 lv2_atom_object_body_get(size, body,
554 osc_urid->OSC_messagePath, path,
555 osc_urid->OSC_messageArguments, arguments,
556 0);
557
558 if(!*path || ((*path)->atom.type != osc_urid->ATOM_String))
559 return false;
560 // message without arguments is valid
561 if( *arguments && ((*arguments)->atom.type != osc_urid->ATOM_Tuple))
562 return false;
563
564 return true;
565 }
566
567 /**
568 TODO
569 */
570 static inline bool
lv2_osc_message_get(LV2_OSC_URID * osc_urid,const LV2_Atom_Object * obj,const LV2_Atom_String ** path,const LV2_Atom_Tuple ** arguments)571 lv2_osc_message_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
572 const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
573 {
574 return lv2_osc_message_body_get(osc_urid, obj->atom.size, &obj->body,
575 path, arguments);
576 }
577
578 static inline bool
lv2_osc_body_unroll(LV2_OSC_URID * osc_urid,uint32_t size,const LV2_Atom_Object_Body * body,LV2_OSC_Method method,void * data)579 lv2_osc_body_unroll(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
580 LV2_OSC_Method method, void *data)
581 {
582 if(body->otype == osc_urid->OSC_Bundle)
583 {
584 const LV2_Atom_Object *timetag = NULL;
585 const LV2_Atom_Tuple *items = NULL;
586
587 if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
588 return false;
589
590 LV2_OSC_Timetag tt;
591 lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
592
593 LV2_ATOM_TUPLE_FOREACH(items, atom)
594 {
595 const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
596
597 if(!lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data))
598 return false;
599 }
600
601 return true;
602 }
603 else if(body->otype == osc_urid->OSC_Message)
604 {
605 const LV2_Atom_String *path = NULL;
606 const LV2_Atom_Tuple *arguments = NULL;
607
608 if(!lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
609 return false;
610
611 if(method)
612 method(LV2_ATOM_BODY_CONST(path), arguments, data);
613
614 return true;
615 }
616
617 return false;
618 }
619
620 static inline bool
lv2_osc_unroll(LV2_OSC_URID * osc_urid,const LV2_Atom_Object * obj,LV2_OSC_Method method,void * data)621 lv2_osc_unroll(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
622 LV2_OSC_Method method, void *data)
623 {
624 return lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data);
625 }
626
627 #ifdef __cplusplus
628 } // extern "C"
629 #endif
630
631 #endif // LV2_OSC_UTIL_H
632