1 /*
2  * Copyright (c) 2015-2017 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 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <inttypes.h>
22 
23 #include <espressivo.h>
24 #include <osc.lv2/util.h>
25 #include <props.h>
26 
27 #define MAX_NPROPS 6
28 #define MAX_STRLEN 128
29 
30 typedef struct _pos_t pos_t;
31 typedef struct _targetO_t targetO_t;
32 typedef struct _plugstate_t plugstate_t;
33 typedef struct _plughandle_t plughandle_t;
34 
35 struct _pos_t {
36 	uint64_t stamp;
37 
38 	float x;
39 	float z;
40 	float a;
41 	struct {
42 		float f1;
43 		float f11;
44 	} vx;
45 	struct {
46 		float f1;
47 		float f11;
48 	} vz;
49 	float v;
50 	float A;
51 	float m;
52 	float R;
53 };
54 
55 struct _targetO_t {
56 	uint32_t sid;
57 	uint32_t gid;
58 	uint32_t tuid;
59 
60 	bool active;
61 	pos_t pos;
62 };
63 
64 struct _plugstate_t {
65 	int32_t device_width;
66 	int32_t device_height;
67 	char device_name [MAX_STRLEN];
68 	int32_t octave;
69 	int32_t sensors_per_semitone;
70 	int32_t filter_stiffness;
71 };
72 
73 struct _plughandle_t {
74 	LV2_URID_Map *map;
75 	LV2_Atom_Forge forge;
76 	LV2_OSC_URID osc_urid;
77 
78 	LV2_Log_Log *log;
79 	LV2_Log_Logger logger;
80 
81 	XPRESS_T(xpressO, MAX_NVOICES);
82 	targetO_t targetO [MAX_NVOICES];
83 
84 	float rate;
85 	float s;
86 	float sm1;
87 
88 	int64_t frames;
89 
90 	float bot;
91 	float ran;
92 
93 	struct {
94 		uint32_t fid;
95 		uint64_t last;
96 		int32_t missed;
97 		uint16_t width;
98 		uint16_t height;
99 		bool ignore;
100 	} tuio2;
101 
102 	const LV2_Atom_Sequence *osc_in;
103 	LV2_Atom_Sequence *event_out;
104 
105 	LV2_Atom_Forge_Ref ref;
106 
107 	struct {
108 		LV2_URID device_width;
109 		LV2_URID device_height;
110 		LV2_URID device_name;
111 	} urid;
112 
113 	PROPS_T(props, MAX_NPROPS);
114 
115 	plugstate_t state;
116 	plugstate_t stash;
117 };
118 
119 static const targetO_t targetO_vanilla;
120 static const pos_t pos_vanilla;
121 
122 static inline void
_stiffness_set(plughandle_t * handle,int32_t stiffness)123 _stiffness_set(plughandle_t *handle, int32_t stiffness)
124 {
125 	handle->s = 1.f / stiffness;
126 	handle->sm1 = 1.f - handle->s;
127 	handle->s *= 0.5;
128 }
129 
130 static void
_intercept_filter_stiffness(void * data,int64_t frames,props_impl_t * impl)131 _intercept_filter_stiffness(void *data, int64_t frames, props_impl_t *impl)
132 {
133 	plughandle_t *handle = data;
134 
135 	_stiffness_set(handle, handle->state.filter_stiffness);
136 }
137 
138 static const props_def_t defs [MAX_NPROPS] = {
139 	{
140 		.property = ESPRESSIVO_URI"#tuio2_deviceWidth",
141 		.offset = offsetof(plugstate_t, device_width),
142 		.access = LV2_PATCH__readable,
143 		.type = LV2_ATOM__Int,
144 	},
145 	{
146 		.property = ESPRESSIVO_URI"#tuio2_deviceHeight",
147 		.offset = offsetof(plugstate_t, device_height),
148 		.access = LV2_PATCH__readable,
149 		.type = LV2_ATOM__Int,
150 	},
151 	{
152 		.property = ESPRESSIVO_URI"#tuio2_deviceName",
153 		.offset = offsetof(plugstate_t, device_name),
154 		.access = LV2_PATCH__readable,
155 		.type = LV2_ATOM__String,
156 		.max_size = MAX_STRLEN
157 	},
158 	{
159 		.property = ESPRESSIVO_URI"#tuio2_octave",
160 		.offset = offsetof(plugstate_t, octave),
161 		.type = LV2_ATOM__Int,
162 	},
163 	{
164 		.property = ESPRESSIVO_URI"#tuio2_sensorsPerSemitone",
165 		.offset = offsetof(plugstate_t, sensors_per_semitone),
166 		.type = LV2_ATOM__Int,
167 	},
168 	{
169 		.property = ESPRESSIVO_URI"#tuio2_filterStiffness",
170 		.offset = offsetof(plugstate_t, filter_stiffness),
171 		.type = LV2_ATOM__Int,
172 		.event_cb = _intercept_filter_stiffness
173 	}
174 };
175 
176 static inline void
_pos_init(pos_t * dst,uint64_t stamp)177 _pos_init(pos_t *dst, uint64_t stamp)
178 {
179 	*dst = pos_vanilla;
180 	dst->stamp = stamp;
181 }
182 
183 static inline void
_pos_clone(pos_t * dst,pos_t * src)184 _pos_clone(pos_t *dst, pos_t *src)
185 {
186 	*dst = *src;
187 }
188 
189 static inline void
_pos_deriv(plughandle_t * handle,pos_t * neu,pos_t * old)190 _pos_deriv(plughandle_t *handle, pos_t *neu, pos_t *old)
191 {
192 	if(neu->stamp <= old->stamp)
193 	{
194 		neu->stamp = old->stamp;
195 		neu->vx.f1 = old->vx.f1;
196 		neu->vx.f11 = old->vx.f11;
197 		neu->vz.f1 = old->vz.f1;
198 		neu->vz.f11 = old->vz.f11;
199 		neu->v = old->v;
200 		neu->A = old->A;
201 		neu->m = old->m;
202 		neu->R = old->R;
203 	}
204 	else
205 	{
206 		const uint32_t sec0 = old->stamp >> 32;
207 		const uint32_t frc0 = old->stamp & 0xffffffff;
208 		const uint32_t sec1 = neu->stamp >> 32;
209 		const uint32_t frc1 = neu->stamp & 0xffffffff;
210 		const double diff = (sec1 - sec0) + (frc1 - frc0) * 0x1p-32;
211 		const float rate = 1.0 / diff;
212 
213 		const float dx = neu->x - old->x;
214 		neu->vx.f1 = dx * rate;
215 
216 		const float dz = neu->z - old->z;
217 		neu->vz.f1 = dz * rate;
218 
219 		// first-order IIR filter
220 		neu->vx.f11 = handle->s*(neu->vx.f1 + old->vx.f1) + old->vx.f11*handle->sm1;
221 		neu->vz.f11 = handle->s*(neu->vz.f1 + old->vz.f1) + old->vz.f11*handle->sm1;
222 
223 		neu->v = sqrtf(neu->vx.f11 * neu->vx.f11 + neu->vz.f11 * neu->vz.f11);
224 
225 		const float dv =  neu->v - old->v;
226 		neu->A = 0.f;
227 		neu->m = dv * rate;
228 		neu->R = 0.f;
229 	}
230 }
231 
232 static targetO_t *
_tuio2_get(plughandle_t * handle,uint32_t sid,xpress_uuid_t * uuid)233 _tuio2_get(plughandle_t *handle, uint32_t sid, xpress_uuid_t *uuid)
234 {
235 	XPRESS_VOICE_FOREACH(&handle->xpressO, voice)
236 	{
237 		targetO_t *dst = voice->target;
238 
239 		if(dst->sid == sid)
240 		{
241 			*uuid = voice->uuid;
242 			return dst;
243 		}
244 	}
245 
246 	*uuid = 0;
247 	return NULL;
248 }
249 
250 static void
_tuio2_reset(plughandle_t * handle)251 _tuio2_reset(plughandle_t *handle)
252 {
253 	XPRESS_VOICE_FREE(&handle->xpressO, voice)
254 	{
255 		// nothing
256 	}
257 
258 	handle->tuio2.fid = 0;
259 	handle->tuio2.last = 0;
260 	handle->tuio2.missed = 0;
261 	handle->tuio2.width = 0;
262 	handle->tuio2.height = 0;
263 	handle->tuio2.ignore = false;
264 }
265 
266 // rt
267 static int
_tuio2_frm(const char * path,const LV2_Atom_Tuple * args,void * data)268 _tuio2_frm(const char *path, const LV2_Atom_Tuple *args,
269 	void *data)
270 {
271 	plughandle_t *handle = data;
272 	LV2_OSC_URID *osc_urid= &handle->osc_urid;
273 	LV2_Atom_Forge *forge = &handle->forge;
274 
275 	const LV2_Atom *ptr = lv2_atom_tuple_begin(args);
276 	uint32_t fid;
277 	uint64_t last;
278 
279 	ptr = lv2_osc_int32_get(osc_urid, ptr, (int32_t *)&fid);
280 	LV2_OSC_Timetag stamp;
281 	ptr = lv2_osc_timetag_get(osc_urid, ptr, &stamp);
282 	last = lv2_osc_timetag_parse(&stamp);
283 
284 	if(!ptr)
285 		return 1;
286 
287 	if(fid > handle->tuio2.fid)
288 	{
289 		if(last < handle->tuio2.last)
290 		{
291 			if(handle->log)
292 				lv2_log_trace(&handle->logger, "time warp: %08"PRIx64" must not be smaller than %08"PRIx64,
293 					last, handle->tuio2.last);
294 		}
295 
296 		if( (fid > handle->tuio2.fid + 1) && (handle->tuio2.fid > 0) )
297 		{
298 			// we have missed one or several bundles
299 			handle->tuio2.missed += fid - 1 - handle->tuio2.fid;
300 
301 			if(handle->log)
302 				lv2_log_trace(&handle->logger, "missed events: %"PRIu32" .. %"PRIu32" (missing: %"PRIi32")",
303 					handle->tuio2.fid + 1, fid - 1, handle->tuio2.missed);
304 		}
305 
306 		uint32_t dim;
307 		const char *source;
308 
309 		handle->tuio2.fid = fid;
310 		handle->tuio2.last = last;
311 
312 		ptr = lv2_osc_int32_get(osc_urid, ptr, (int32_t *)&dim);
313 		if(ptr)
314 		{
315 			handle->tuio2.width = dim >> 16;
316 			handle->tuio2.height = dim & 0xffff;
317 
318 			if(handle->state.device_width != handle->tuio2.width)
319 			{
320 				handle->state.device_width = handle->tuio2.width;
321 				props_set(&handle->props, forge, handle->frames, handle->urid.device_width, &handle->ref);
322 			}
323 
324 			if(handle->state.device_height != handle->tuio2.height)
325 			{
326 				handle->state.device_height = handle->tuio2.height;
327 				props_set(&handle->props, forge, handle->frames, handle->urid.device_height, &handle->ref);
328 			}
329 
330 			const int n = handle->tuio2.width;
331 			const float oct = handle->state.octave;
332 			const int sps = handle->state.sensors_per_semitone;
333 
334 			handle->ran = (float)n / sps;
335 			handle->bot = oct*12.f - 0.5 - (n % (6*sps) / (2.f*sps));
336 		}
337 
338 		ptr = lv2_osc_string_get(osc_urid, ptr, &source);
339 		if(ptr && strcmp(handle->state.device_name, source))
340 		{
341 			strncpy(handle->state.device_name, source, MAX_STRLEN - 1);
342 			props_set(&handle->props, forge, handle->frames, handle->urid.device_name, &handle->ref);
343 		}
344 
345 		// process this bundle
346 		handle->tuio2.ignore = false;
347 	}
348 	else // fid <= handle->tuio2.fid
349 	{
350 		// we have found a previously missed bundle
351 		handle->tuio2.missed -= 1;
352 
353 		if(handle->log)
354 			lv2_log_trace(&handle->logger, "found event: %"PRIu32" (missing: %"PRIi32")",
355 				fid, handle->tuio2.missed);
356 
357 		if(handle->tuio2.missed < 0)
358 		{
359 			// we must assume that the peripheral has been reset
360 			_tuio2_reset(handle);
361 
362 			if(handle->log)
363 				lv2_log_trace(&handle->logger, "reset");
364 		}
365 		else
366 		{
367 			// ignore this bundle
368 			handle->tuio2.ignore = true;
369 		}
370 	}
371 
372 	XPRESS_VOICE_FOREACH(&handle->xpressO, voice)
373 	{
374 		targetO_t *dst = voice->target;
375 
376 		dst->active = false; // reset active flag
377 	}
378 
379 	return 1;
380 }
381 
382 // rt
383 static int
_tuio2_tok(const char * path,const LV2_Atom_Tuple * args,void * data)384 _tuio2_tok(const char *path, const LV2_Atom_Tuple *args,
385 	void *data)
386 {
387 	plughandle_t *handle = data;
388 	LV2_OSC_URID *osc_urid= &handle->osc_urid;
389 	LV2_Atom_Forge *forge = &handle->forge;
390 
391 	pos_t pos;
392 	_pos_init(&pos, handle->tuio2.last);
393 
394 	unsigned n = 0;
395 	LV2_ATOM_TUPLE_FOREACH(args, atom)
396 		n++;
397 	const bool has_derivatives = n == 1;
398 
399 	const LV2_Atom *ptr = lv2_atom_tuple_begin(args);
400 
401 	if(handle->tuio2.ignore)
402 		return 1;
403 
404 	uint32_t sid;
405 	ptr = lv2_osc_int32_get(osc_urid, ptr, (int32_t *)&sid);
406 	if(!ptr)
407 		return 1;
408 
409 	xpress_uuid_t uuid;
410 	targetO_t *dst = _tuio2_get(handle, sid, &uuid);
411 	if(!dst)
412 	{
413 		if((dst = xpress_create(&handle->xpressO, &uuid)))
414 		{
415 			*dst = targetO_vanilla;
416 			dst->sid = sid;
417 		}
418 	}
419 	if(!dst)
420 		return 1; // failed to register
421 
422 	ptr = lv2_osc_int32_get(osc_urid, ptr, (int32_t *)&dst->tuid);
423 	ptr = lv2_osc_int32_get(osc_urid, ptr, (int32_t *)&dst->gid);
424 	ptr = lv2_osc_float_get(osc_urid, ptr, &pos.x);
425 	ptr = lv2_osc_float_get(osc_urid, ptr, &pos.z);
426 	ptr = lv2_osc_float_get(osc_urid, ptr, &pos.a);
427 
428 	if(has_derivatives)
429 	{
430 		ptr = lv2_osc_float_get(osc_urid, ptr, &pos.vx.f11);
431 		ptr = lv2_osc_float_get(osc_urid, ptr, &pos.vz.f11);
432 		ptr = lv2_osc_float_get(osc_urid, ptr, &pos.A);
433 		ptr = lv2_osc_float_get(osc_urid, ptr, &pos.m);
434 		ptr = lv2_osc_float_get(osc_urid, ptr, &pos.R);
435 		(void)ptr;
436 	}
437 	else // !has_derivatives
438 	{
439 		_pos_deriv(handle, &pos, &dst->pos);
440 	}
441 
442 	_pos_clone(&dst->pos, &pos);
443 
444 	const xpress_state_t state = {
445 		.zone = dst->gid,
446 		.pitch = (dst->pos.x * handle->ran + handle->bot) / 0x7f,
447 		.pressure = dst->pos.z,
448 		.timbre = 0.f, //TODO compare with dst->tuid
449 		.dPitch = dst->pos.vx.f11,
450 		.dPressure = dst->pos.vz.f11,
451 		.dTimbre = 0.f
452 	};
453 
454 	if(handle->ref)
455 		handle->ref = xpress_token(&handle->xpressO, forge, handle->frames, uuid, &state);
456 
457 	return 1;
458 }
459 
460 // rt
461 static int
_tuio2_alv(const char * path,const LV2_Atom_Tuple * args,void * data)462 _tuio2_alv(const char *path, const LV2_Atom_Tuple *args,
463 	void *data)
464 {
465 	plughandle_t *handle = data;
466 	LV2_OSC_URID *osc_urid= &handle->osc_urid;
467 	LV2_Atom_Forge *forge = &handle->forge;
468 
469 	if(handle->tuio2.ignore)
470 		return 1;
471 
472 	unsigned n = 0;
473 	LV2_ATOM_TUPLE_FOREACH(args, atom)
474 		n++;
475 
476 	const LV2_Atom *ptr = lv2_atom_tuple_begin(args);
477 	for(unsigned i=0; (i<n) && ptr; i++)
478 	{
479 		uint32_t sid;
480 
481 		ptr = lv2_osc_int32_get(osc_urid, ptr, (int32_t *)&sid);
482 
483 		// already registered in this step?
484 		xpress_uuid_t uuid;
485 		targetO_t *dst = _tuio2_get(handle, sid, &uuid);
486 		if(!dst)
487 		{
488 			if((dst = xpress_create(&handle->xpressO, &uuid)))
489 			{
490 				*dst = targetO_vanilla;
491 				dst->sid = sid;
492 			}
493 		}
494 		if(!dst)
495 			continue; // failed to register
496 
497 		dst->active = true; // set active state
498 	}
499 
500 	// iterate over inactive blobs
501 	unsigned freed = 0;
502 
503 	XPRESS_VOICE_FOREACH(&handle->xpressO, voice)
504 	{
505 		targetO_t *dst = voice->target;
506 
507 		// is is active?
508 		if(dst->active)
509 			continue;
510 
511 		// has it disappeared?
512 		voice->uuid = 0; // mark for removal
513 		freed += 1;
514 	}
515 
516 	if(freed > 0)
517 	{
518 		if(handle->ref)
519 			handle->ref = xpress_alive(&handle->xpressO, forge, handle->frames);
520 	}
521 
522 	return 1;
523 }
524 
525 static const xpress_iface_t ifaceO = {
526 	.size = sizeof(targetO_t)
527 };
528 
529 static LV2_Handle
instantiate(const LV2_Descriptor * descriptor,double rate,const char * bundle_path,const LV2_Feature * const * features)530 instantiate(const LV2_Descriptor* descriptor, double rate,
531 	const char *bundle_path, const LV2_Feature *const *features)
532 {
533 	plughandle_t *handle = calloc(1, sizeof(plughandle_t));
534 	if(!handle)
535 		return NULL;
536 
537 	handle->rate = rate;
538 	_stiffness_set(handle, 32);
539 
540 	xpress_map_t *voice_map = NULL;
541 
542 	for(unsigned i=0; features[i]; i++)
543 	{
544 		if(!strcmp(features[i]->URI, LV2_URID__map))
545 			handle->map = features[i]->data;
546 		else if(!strcmp(features[i]->URI, LV2_LOG__log))
547 			handle->log = features[i]->data;
548 		else if(!strcmp(features[i]->URI, XPRESS__voiceMap))
549 			voice_map = features[i]->data;
550 	}
551 
552 	if(!handle->map)
553 	{
554 		fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
555 		free(handle);
556 		return NULL;
557 	}
558 
559 	lv2_atom_forge_init(&handle->forge, handle->map);
560 	lv2_osc_urid_init(&handle->osc_urid, handle->map);
561 
562 	if(handle->log)
563 		lv2_log_logger_init(&handle->logger, handle->map, handle->log);
564 
565 	if(  !xpress_init(&handle->xpressO, MAX_NVOICES, handle->map, voice_map,
566 			XPRESS_EVENT_NONE, &ifaceO, handle->targetO, handle) )
567 	{
568 		free(handle);
569 		return NULL;
570 	}
571 
572 	if(!props_init(&handle->props, descriptor->URI,
573 		defs, MAX_NPROPS, &handle->state, &handle->stash,
574 		handle->map, handle))
575 	{
576 		fprintf(stderr, "failed to allocate property structure\n");
577 		free(handle);
578 		return NULL;
579 	}
580 
581 	handle->urid.device_width = props_map(&handle->props, ESPRESSIVO_URI"#tuio2_deviceWidth");
582 	handle->urid.device_height = props_map(&handle->props, ESPRESSIVO_URI"#tuio2_deviceHeight");
583 	handle->urid.device_name = props_map(&handle->props, ESPRESSIVO_URI"#tuio2_deviceName");
584 
585 	return handle;
586 }
587 
588 static void
connect_port(LV2_Handle instance,uint32_t port,void * data)589 connect_port(LV2_Handle instance, uint32_t port, void *data)
590 {
591 	plughandle_t *handle = (plughandle_t *)instance;
592 
593 	switch(port)
594 	{
595 		case 0:
596 			handle->osc_in = (const LV2_Atom_Sequence *)data;
597 			break;
598 		case 1:
599 			handle->event_out = (LV2_Atom_Sequence *)data;
600 			break;
601 		default:
602 			break;
603 	}
604 }
605 
606 static void
activate(LV2_Handle instance)607 activate(LV2_Handle instance)
608 {
609 	plughandle_t *handle = (plughandle_t *)instance;
610 
611 	handle->state.device_width = 1;
612 	handle->state.device_height = 1;
613 	handle->state.device_name[0] = '\0';
614 }
615 
616 typedef int (*osc_method_func_t)(const char *path,
617 	const LV2_Atom_Tuple *arguments, void *data);
618 typedef struct _method_t method_t;
619 
620 struct _method_t {
621 	const char *path;
622 	osc_method_func_t cb;
623 };
624 
625 static const method_t methods [] = {
626 	{"/tuio2/frm", _tuio2_frm},
627 	{"/tuio2/tok", _tuio2_tok},
628 	{"/tuio2/alv", _tuio2_alv},
629 
630 	{NULL, NULL}
631 };
632 
633 static void
_message_cb(const char * path,const LV2_Atom_Tuple * args,void * data)634 _message_cb(const char *path, const LV2_Atom_Tuple *args, void *data)
635 {
636 	for(const method_t *meth = methods; meth->cb; meth++)
637 	{
638 		if(path && (!meth->path || !strcmp(meth->path, path)) )
639 		{
640 			if(meth->cb(path, args, data))
641 				break; // event handled, break
642 		}
643 	}
644 }
645 
646 static void
run(LV2_Handle instance,uint32_t nsamples)647 run(LV2_Handle instance, uint32_t nsamples)
648 {
649 	plughandle_t *handle = (plughandle_t *)instance;
650 
651 	LV2_Atom_Forge *forge = &handle->forge;
652 	uint32_t capacity = handle->event_out->atom.size;
653 	LV2_Atom_Forge_Frame frame;
654 	lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->event_out, capacity);
655 	handle->ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
656 
657 	props_idle(&handle->props, forge, 0, &handle->ref);
658 	xpress_rst(&handle->xpressO);
659 
660 	// read incoming OSC
661 	LV2_ATOM_SEQUENCE_FOREACH(handle->osc_in, ev)
662 	{
663 		const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
664 		handle->frames = ev->time.frames;
665 
666 		if(!props_advance(&handle->props, forge, handle->frames, obj, &handle->ref))
667 			lv2_osc_unroll(&handle->osc_urid, obj, _message_cb, handle);
668 	}
669 
670 	if(handle->ref && !xpress_synced(&handle->xpressO))
671 		handle->ref = xpress_alive(&handle->xpressO, forge, nsamples-1);
672 
673 	if(handle->ref)
674 		lv2_atom_forge_pop(forge, &frame);
675 	else
676 		lv2_atom_sequence_clear(handle->event_out);
677 }
678 
679 static void
cleanup(LV2_Handle instance)680 cleanup(LV2_Handle instance)
681 {
682 	plughandle_t *handle = (plughandle_t *)instance;
683 
684 	if(handle)
685 	{
686 		xpress_deinit(&handle->xpressO);
687 		free(handle);
688 	}
689 }
690 
691 static LV2_State_Status
_state_save(LV2_Handle instance,LV2_State_Store_Function store,LV2_State_Handle state,uint32_t flags,const LV2_Feature * const * features)692 _state_save(LV2_Handle instance, LV2_State_Store_Function store,
693 	LV2_State_Handle state, uint32_t flags,
694 	const LV2_Feature *const *features)
695 {
696 	plughandle_t *handle = instance;
697 
698 	return props_save(&handle->props, store, state, flags, features);
699 }
700 
701 static LV2_State_Status
_state_restore(LV2_Handle instance,LV2_State_Retrieve_Function retrieve,LV2_State_Handle state,uint32_t flags,const LV2_Feature * const * features)702 _state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
703 	LV2_State_Handle state, uint32_t flags,
704 	const LV2_Feature *const *features)
705 {
706 	plughandle_t *handle = instance;
707 
708 	return props_restore(&handle->props, retrieve, state, flags, features);
709 }
710 
711 static const LV2_State_Interface state_iface = {
712 	.save = _state_save,
713 	.restore = _state_restore
714 };
715 
716 static const void *
extension_data(const char * uri)717 extension_data(const char *uri)
718 {
719 	if(!strcmp(uri, LV2_STATE__interface))
720 		return &state_iface;
721 	return NULL;
722 }
723 
724 const LV2_Descriptor tuio2_in = {
725 	.URI						= ESPRESSIVO_TUIO2_IN_URI,
726 	.instantiate		= instantiate,
727 	.connect_port		= connect_port,
728 	.activate				= activate,
729 	.run						= run,
730 	.deactivate			= NULL,
731 	.cleanup				= cleanup,
732 	.extension_data	= extension_data
733 };
734