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