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