1 /* avldrums -- simple & robust x-platform fluidsynth LV2
2  *
3  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifndef _GNU_SOURCE
20 #define _GNU_SOURCE
21 #endif
22 
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <math.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 
30 #include <lv2/lv2plug.in/ns/ext/atom/util.h>
31 #include <lv2/lv2plug.in/ns/ext/log/logger.h>
32 #include <lv2/lv2plug.in/ns/ext/worker/worker.h>
33 
34 #include "midnam_lv2.h"
35 
36 #include "fluidsynth.h"
37 #include "avldrums.h"
38 
39 #ifdef _WIN32
40 #define PATH_SEP "\\"
41 #else
42 #define PATH_SEP "/"
43 #endif
44 
45 enum {
46   CMD_APPLY    = 0,
47   CMD_FREE     = 1,
48 };
49 
50 typedef struct {
51 	/* ports */
52 	const LV2_Atom_Sequence* control;
53 	LV2_Atom_Sequence*       notify;
54 
55 	float* p_ports[AVL_PORT_LAST];
56 
57 	/* fluid synth */
58 	fluid_settings_t* settings;
59 	fluid_synth_t*    synth;
60 	int               synthId;
61 
62 	/* lv2 URIDs */
63 	AVLLV2URIs uris;
64 
65 	/* lv2 extensions */
66 	LV2_Log_Log*         log;
67 	LV2_Log_Logger       logger;
68 	LV2_Worker_Schedule* schedule;
69 	LV2_Midnam*          midnam;
70 	LV2_Atom_Forge       forge;
71 	LV2_Atom_Forge_Frame frame;
72 
73 	/* state */
74 	bool panic;
75 	bool initialized;
76 	bool multi_out;
77 	bool inform_ui;
78 	bool ui_active;
79 
80 	char current_sf2_file_path[1024];
81 	char queue_sf2_file_path[1024];
82 	bool reinit_in_progress; // set in run, cleared in work_response
83 	bool queue_reinit; // set in restore, cleared in work_response
84 
85 	fluid_midi_event_t* fmidi_event;
86 
87 } AVLSynth;
88 
89 /* *****************************************************************************
90  * helpers
91  */
92 
93 static bool
load_sf2(AVLSynth * self,const char * fn)94 load_sf2 (AVLSynth* self, const char* fn)
95 {
96 	const int synth_id = fluid_synth_sfload (self->synth, fn, 1);
97 
98 	if (synth_id == FLUID_FAILED) {
99 		return false;
100 	}
101 
102 	fluid_sfont_t* const sfont = fluid_synth_get_sfont_by_id (self->synth, synth_id);
103 	if (!sfont) {
104 		return false;
105 	}
106 
107 	int chn = 0;
108 	fluid_preset_t* preset;
109 	fluid_sfont_iteration_start (sfont);
110 	if ((preset = fluid_sfont_iteration_next (sfont))) {
111 		for (chn = 0; chn < 16; ++chn) {
112 			fluid_synth_program_select (self->synth, chn, synth_id,
113 					fluid_preset_get_banknum (preset), fluid_preset_get_num (preset));
114 		}
115 	}
116 
117 	if (chn == 0) {
118 		return false;
119 	}
120 
121 	if (self->multi_out) {
122 		/* pan mono outs hard left */
123 		fluid_midi_event_set_value (self->fmidi_event, 0);
124 
125 		for (uint8_t chn = 0; chn < 5; ++chn) {
126 			fluid_midi_event_set_type (self->fmidi_event, 0xb0 | chn);
127 			fluid_midi_event_set_key (self->fmidi_event, 0x0a);
128 			fluid_synth_handle_midi_event (self->synth, self->fmidi_event);
129 
130 			fluid_midi_event_set_type (self->fmidi_event, 0xb0 | chn);
131 			fluid_midi_event_set_key (self->fmidi_event, 0x2a);
132 			fluid_synth_handle_midi_event (self->synth, self->fmidi_event);
133 		}
134 	}
135 
136 	return true;
137 }
138 
139 static void
synth_sound(AVLSynth * self,uint32_t n_samples,uint32_t offset)140 synth_sound (AVLSynth* self, uint32_t n_samples, uint32_t offset)
141 {
142 	if (self->multi_out) {
143 		while (n_samples > 0) {
144 			uint32_t n = n_samples > 8192 ? 8192 : n_samples;
145 			float* out[14];
146 
147 			out[0]  = &self->p_ports[AVL_PORT_OUT_Kick][offset];
148 			out[1]  = NULL;
149 			out[2]  = &self->p_ports[AVL_PORT_OUT_Snare][offset];
150 			out[3]  = NULL;
151 			out[4]  = &self->p_ports[AVL_PORT_OUT_HiHat][offset];
152 			out[5]  = NULL;
153 			out[6]  = &self->p_ports[AVL_PORT_OUT_Tom][offset];
154 			out[7]  = NULL;
155 			out[8]  = &self->p_ports[AVL_PORT_OUT_FloorTom][offset];
156 			out[9]  = NULL;
157 			out[10] = &self->p_ports[AVL_PORT_OUT_Overheads_L][offset];
158 			out[11] = &self->p_ports[AVL_PORT_OUT_Overheads_R][offset];
159 			out[12] = &self->p_ports[AVL_PORT_OUT_Percussion_L][offset];
160 			out[13] = &self->p_ports[AVL_PORT_OUT_Percussion_R][offset];
161 
162 			fluid_synth_process (self->synth, n, 0, NULL, 14, out);
163 			n_samples -= n;
164 			offset += n;
165 		}
166 	} else {
167 		fluid_synth_write_float (
168 				self->synth,
169 				n_samples,
170 				&self->p_ports[AVL_PORT_OUT_L][offset], 0, 1,
171 				&self->p_ports[AVL_PORT_OUT_R][offset], 0, 1);
172 	}
173 }
174 
175 static uint8_t
assign_channel(uint8_t note)176 assign_channel (uint8_t note)
177 {
178 	switch (note) {
179 		case 36:
180 			return 0; // Kick
181 		case 37:
182 		case 38:
183 		case 40:
184 			return 1; // Snare
185 		case 42:
186 		case 44:
187 		case 46:
188 		case 48:
189 			return 2; // HiHat
190 		case 45:
191 		case 47:
192 			return 3; // Tom
193 		case 41:
194 		case 43:
195 			return 4; // Floor Tom
196 		case 39: // Hand Clap
197 		case 54: // Tambourine
198 		case 56: // Cowbell
199 		case 61: // Maracas
200 			return 6; // Percussions etc
201 		default:
202 			break;
203 	}
204 	return 5; // Cymbals
205 }
206 
207 static void
inform_ui(AVLSynth * self)208 inform_ui (AVLSynth* self)
209 {
210 	LV2_Atom_Forge_Frame frame;
211 	lv2_atom_forge_frame_time (&self->forge, 0);
212 	x_forge_object (&self->forge, &frame, 1, self->uris.drumkit);
213 	lv2_atom_forge_property_head (&self->forge, self->uris.loaded, 0);
214 	lv2_atom_forge_bool (&self->forge, strlen (self->current_sf2_file_path) > 0);
215 	lv2_atom_forge_pop (&self->forge, &frame);
216 }
217 
218 static void
kick_ui(AVLSynth * self,int * n)219 kick_ui (AVLSynth* self, int* n)
220 {
221 	LV2_Atom_Forge_Frame frame;
222 	lv2_atom_forge_frame_time (&self->forge, 0);
223 	x_forge_object (&self->forge, &frame, 1, self->uris.drumhit);
224 	lv2_atom_forge_property_head (&self->forge, self->uris.drumhits, 0);
225 	lv2_atom_forge_vector (&self->forge, sizeof(int32_t), self->uris.atom_Int, DRUM_PCS, n);
226 	lv2_atom_forge_pop (&self->forge, &frame);
227 }
228 
file_exists(const char * filename)229 static int file_exists (const char *filename) {
230 	struct stat s;
231 	if (!filename || strlen(filename) < 1) return 0;
232 	int result= stat (filename, &s);
233 	if (result != 0) return 0; /* stat() failed */
234 	if (S_ISREG(s.st_mode)) return 1; /* is a regular file - ok */
235 	return 0;
236 }
237 
238 /* *****************************************************************************
239  * LV2 Plugin
240  */
241 
242 static LV2_Handle
instantiate(const LV2_Descriptor * descriptor,double rate,const char * bundle_path,const LV2_Feature * const * features)243 instantiate (const LV2_Descriptor*     descriptor,
244              double                    rate,
245              const char*               bundle_path,
246              const LV2_Feature* const* features)
247 {
248 	static const char* kits [] = {
249 		"Black_Pearl_4_LV2.sf2",
250 		"Red_Zeppelin_4_LV2.sf2"
251 	};
252 
253 	int kit = -1;
254 	bool multi_out = false;
255 
256 	if (!strcmp (descriptor->URI, AVL_URI "BlackPearl")) {
257 		kit = 0;
258 	} else if (!strcmp (descriptor->URI, AVL_URI "BlackPearlMulti")) {
259 		kit = 0; multi_out = true;
260 	} else if (!strcmp (descriptor->URI, AVL_URI "RedZeppelin")) {
261 		kit = 1;
262 	} else if (!strcmp (descriptor->URI, AVL_URI "RedZeppelinMulti")) {
263 		kit = 1; multi_out = true;
264 	}
265 
266 	AVLSynth* self = (AVLSynth*)calloc (1, sizeof (AVLSynth));
267 
268 	if (!self || !bundle_path || kit < 0) {
269 		return NULL;
270 	}
271 
272 	LV2_URID_Map* map = NULL;
273 
274 	for (int i=0; features[i] != NULL; ++i) {
275 		if (!strcmp (features[i]->URI, LV2_URID__map)) {
276 			map = (LV2_URID_Map*)features[i]->data;
277 		} else if (!strcmp (features[i]->URI, LV2_LOG__log)) {
278 			self->log = (LV2_Log_Log*)features[i]->data;
279 		} else if (!strcmp (features[i]->URI, LV2_WORKER__schedule)) {
280 			self->schedule = (LV2_Worker_Schedule*)features[i]->data;
281 		} else if (!strcmp (features[i]->URI, LV2_MIDNAM__update)) {
282 			self->midnam = (LV2_Midnam*)features[i]->data;
283 		}
284 	}
285 
286 	lv2_log_logger_init (&self->logger, map, self->log);
287 
288 	if (!map) {
289 		lv2_log_error (&self->logger, "avldrums.lv2: Host does not support urid:map\n");
290 		free (self);
291 		return NULL;
292 	}
293 
294 	if (!self->schedule) {
295 		lv2_log_error (&self->logger, "avldrums.lv2: Host does not support worker:schedule\n");
296 		free (self);
297 		return NULL;
298 	}
299 
300 	snprintf (self->queue_sf2_file_path, sizeof (self->queue_sf2_file_path), "%s" PATH_SEP "%s",
301 			bundle_path, kits[kit]);
302 	self->queue_sf2_file_path[sizeof(self->queue_sf2_file_path) - 1] = '\0';
303 
304 	if (!file_exists (self->queue_sf2_file_path)) {
305 		lv2_log_error (&self->logger, "avldrums.lv2: Cannot find drumkit soundfont: '%s'\n", self->queue_sf2_file_path);
306 		free (self);
307 		return NULL;
308 	}
309 
310 	/* initialize fluid synth */
311 	self->settings = new_fluid_settings ();
312 
313 	if (!self->settings) {
314 		lv2_log_error (&self->logger, "avldrums.lv2: cannot allocate Fluid Settings\n");
315 		free (self);
316 		return NULL;
317 	}
318 
319 	fluid_settings_setnum (self->settings, "synth.sample-rate", rate);
320 	fluid_settings_setint (self->settings, "synth.threadsafe-api", 0);
321 
322 	if (multi_out) {
323 		self->multi_out = true;
324 		fluid_settings_setint (self->settings, "synth.audio-channels", 7);
325 		fluid_settings_setint (self->settings, "synth.audio-groups", 7);
326 	} else {
327 		self->multi_out = false;
328 		fluid_settings_setint (self->settings, "synth.audio-channels", 1); // stereo pairs
329 	}
330 
331 	self->synth = new_fluid_synth (self->settings);
332 
333 	if (!self->synth) {
334 		lv2_log_error (&self->logger, "avldrums.lv2: cannot allocate Fluid Synth\n");
335 		delete_fluid_settings (self->settings);
336 		free (self);
337 		return NULL;
338 	}
339 
340 	fluid_synth_set_gain (self->synth, 1.0f);
341 	fluid_synth_set_polyphony (self->synth, DRUM_PCS);
342 	fluid_synth_set_sample_rate (self->synth, (float)rate);
343 
344 	self->fmidi_event = new_fluid_midi_event ();
345 
346 	if (!self->fmidi_event) {
347 		lv2_log_error (&self->logger, "avldrums.lv2: cannot allocate Fluid Event\n");
348 		delete_fluid_synth (self->synth);
349 		delete_fluid_settings (self->settings);
350 		free (self);
351 		return NULL;
352 	}
353 
354 	/* initialize plugin state */
355 
356 	self->panic = false;
357 	self->inform_ui = false;
358 	self->ui_active = false;
359 	self->initialized = false;
360 	self->reinit_in_progress = false;
361 	self->queue_reinit = true;
362 
363 	lv2_atom_forge_init (&self->forge, map);
364 	map_avldrums_uris (map, &self->uris);
365 
366 	return (LV2_Handle)self;
367 }
368 
369 static void
connect_port(LV2_Handle instance,uint32_t port,void * data)370 connect_port (LV2_Handle instance,
371 			  uint32_t   port,
372 			  void*      data)
373 {
374 	AVLSynth* self = (AVLSynth*)instance;
375 
376 	switch (port) {
377 		case AVL_PORT_CONTROL:
378 			self->control = (const LV2_Atom_Sequence*)data;
379 			break;
380 		case AVL_PORT_NOTIFY:
381 			self->notify = (LV2_Atom_Sequence*)data;
382 			break;
383 		default:
384 			if (port < AVL_PORT_LAST) {
385 				self->p_ports[port] = (float*)data;
386 			}
387 			break;
388 	}
389 }
390 
391 static void
deactivate(LV2_Handle instance)392 deactivate (LV2_Handle instance)
393 {
394 	AVLSynth* self = (AVLSynth*)instance;
395 	self->panic = true;
396 }
397 
398 static void
run(LV2_Handle instance,uint32_t n_samples)399 run (LV2_Handle instance, uint32_t n_samples)
400 {
401 	AVLSynth* self = (AVLSynth*)instance;
402 
403 	if (!self->control || !self->notify) {
404 		return;
405 	}
406 
407 	const uint32_t capacity = self->notify->atom.size;
408 	lv2_atom_forge_set_buffer (&self->forge, (uint8_t*)self->notify, capacity);
409 	lv2_atom_forge_sequence_head (&self->forge, &self->frame, 0);
410 
411 	if (self->multi_out || !self->initialized || self->reinit_in_progress) {
412 		memset (self->p_ports[AVL_PORT_OUT_L], 0, n_samples * sizeof (float));
413 		memset (self->p_ports[AVL_PORT_OUT_R], 0, n_samples * sizeof (float));
414 		if (self->multi_out) {
415 			for (uint32_t cc = AVL_PORT_OUT_HiHat; cc < AVL_PORT_LAST; ++cc) {
416 				memset (self->p_ports[cc], 0, n_samples * sizeof (float));
417 			}
418 		}
419 	} else if (self->panic) {
420 		fluid_synth_all_notes_off (self->synth, -1);
421 		fluid_synth_all_sounds_off (self->synth, -1);
422 		self->panic = false;
423 	}
424 
425 	uint32_t offset = 0;
426 
427 	bool hit_event = false;
428 	int drum_hits[DRUM_PCS];
429 	memset (drum_hits, 0, sizeof (drum_hits));
430 
431 	LV2_ATOM_SEQUENCE_FOREACH (self->control, ev) {
432 		if (ev->body.type == self->uris.atom_Blank || ev->body.type == self->uris.atom_Object) {
433 			const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body;
434 			if (obj->body.otype == self->uris.ui_off) {
435 				self->ui_active = false;
436 			} else if (obj->body.otype == self->uris.ui_on) {
437 				self->ui_active = true;
438 				self->inform_ui = true;
439 			}
440 		} else if (ev->body.type == self->uris.midi_MidiEvent &&
441 				   self->initialized && !self->reinit_in_progress) {
442 			if (ev->body.size != 3 || ev->time.frames > n_samples) { // XXX should be >= n_samples
443 				continue;
444 			}
445 			// work-around jalv sending GUI -> DSP messages @ n_samples
446 			if (ev->time.frames == n_samples) {
447 				ev->time.frames = n_samples -1;
448 			}
449 
450 			if (ev->time.frames > offset) {
451 				synth_sound (self, ev->time.frames - offset, offset);
452 			}
453 
454 			offset = ev->time.frames;
455 
456 			const uint8_t* const data = (const uint8_t*)(ev + 1);
457 			fluid_midi_event_set_type (self->fmidi_event, data[0] & 0xf0);
458 
459 			if (fluid_midi_event_get_type(self->fmidi_event) == 0xc0 /*PROGRAM_CHANGE*/) {
460 				continue;
461 			}
462 			if (fluid_midi_event_get_type(self->fmidi_event) == 0xa0 /*KEY_PRESSURE*/) {
463 				continue;
464 			}
465 			if (fluid_midi_event_get_type(self->fmidi_event) == 0xd0 /*CHANNEL_PRESSURE*/) {
466 				continue;
467 			}
468 			if (fluid_midi_event_get_type(self->fmidi_event) == 0xe0 /*PITCH_BEND*/) {
469 				continue;
470 			}
471 			if (fluid_midi_event_get_type(self->fmidi_event) == 0xf0 /*MIDI_SYSEX*/) {
472 				continue;
473 			}
474 
475 			if (fluid_midi_event_get_type(self->fmidi_event) == 0xb0 /*CC*/) {
476 				switch (data[1]) {
477 					case 0x0a: // pan
478 					case 0x2a: // pan
479 						continue;
480 					default:
481 						break;
482 				}
483 			}
484 
485 			if ((fluid_midi_event_get_type(self->fmidi_event) & 0xe0) == 0x80 /*NOTE*/) {
486 				fluid_midi_event_set_channel (self->fmidi_event, assign_channel (data[1]));
487 			}
488 
489 			fluid_midi_event_set_key (self->fmidi_event, data[1]);
490 			fluid_midi_event_set_value (self->fmidi_event, data[2]);
491 			fluid_synth_handle_midi_event (self->synth, self->fmidi_event);
492 
493 			if (fluid_midi_event_get_type(self->fmidi_event) == 0x90 /*NOTE_ON*/) {
494 				int dp = data[1] - 36; // base-note
495 				if (dp >= 0 && dp < DRUM_PCS) {
496 					drum_hits[dp] = data[2];
497 					hit_event = true;
498 				}
499 			}
500 		}
501 	}
502 
503 	if (self->queue_reinit && !self->reinit_in_progress) {
504 		self->reinit_in_progress = true;
505 		int magic = 0x47110815;
506 		self->schedule->schedule_work (self->schedule->handle, sizeof (int), &magic);
507 	}
508 
509 	/* inform the GUI */
510 	if (self->ui_active) {
511 		if (self->inform_ui) {
512 			self->inform_ui = false;
513 			inform_ui (self);
514 		}
515 		if (hit_event) {
516 			kick_ui (self, drum_hits);
517 		}
518 	}
519 
520 	if (n_samples > offset && self->initialized && !self->reinit_in_progress) {
521 		synth_sound (self, n_samples - offset, offset);
522 	}
523 }
524 
cleanup(LV2_Handle instance)525 static void cleanup (LV2_Handle instance)
526 {
527 	AVLSynth* self = (AVLSynth*)instance;
528 	delete_fluid_synth (self->synth);
529 	delete_fluid_settings (self->settings);
530 	delete_fluid_midi_event (self->fmidi_event);
531 	free (self);
532 }
533 
534 /* *****************************************************************************
535  * LV2 Extensions
536  */
537 
538 static LV2_Worker_Status
work(LV2_Handle instance,LV2_Worker_Respond_Function respond,LV2_Worker_Respond_Handle handle,uint32_t size,const void * data)539 work (LV2_Handle                  instance,
540       LV2_Worker_Respond_Function respond,
541       LV2_Worker_Respond_Handle   handle,
542       uint32_t                    size,
543       const void*                 data)
544 {
545 	AVLSynth* self = (AVLSynth*)instance;
546 
547 	if (size != sizeof (int)) {
548 		return LV2_WORKER_ERR_UNKNOWN;
549 	}
550 	int magic = *((const int*)data);
551 	if (magic != 0x47110815) {
552 		return LV2_WORKER_ERR_UNKNOWN;
553 	}
554 
555 	self->initialized = load_sf2 (self, self->queue_sf2_file_path);
556 
557 	if (self->initialized) {
558 		fluid_synth_all_notes_off (self->synth, -1);
559 		fluid_synth_all_sounds_off (self->synth, -1);
560 		self->panic = false;
561 		// boostrap synth engine.
562 		float b[1024];
563 		fluid_synth_write_float (self->synth, 1024, b, 0, 1, b, 0, 1);
564 	}
565 
566 	respond (handle, 1, "");
567 	return LV2_WORKER_SUCCESS;
568 }
569 
570 static LV2_Worker_Status
work_response(LV2_Handle instance,uint32_t size,const void * data)571 work_response (LV2_Handle  instance,
572                uint32_t    size,
573                const void* data)
574 {
575 	AVLSynth* self = (AVLSynth*)instance;
576 
577 	if (self->initialized) {
578 		strcpy (self->current_sf2_file_path, self->queue_sf2_file_path);
579 	} else {
580 		self->current_sf2_file_path[0] = 0;
581 	}
582 
583 	self->reinit_in_progress = false;
584 	self->inform_ui = true;
585 	self->queue_reinit = false;
586 	return LV2_WORKER_SUCCESS;
587 }
588 
589 static char*
mn_file(LV2_Handle instance)590 mn_file (LV2_Handle instance)
591 {
592 #include "midnam.h"
593 	AVLSynth* self = (AVLSynth*)instance;
594 	char* mn = strdup (AVL_Drumkits_midnam);
595 	char inst[11];
596 	snprintf (inst, 10, "%p", self);
597 	inst[10] = '\0';
598 	memcpy (&mn[339], inst, strlen(inst));
599 	return mn;
600 }
601 
602 static char*
mn_model(LV2_Handle instance)603 mn_model (LV2_Handle instance)
604 {
605 	AVLSynth* self = (AVLSynth*)instance;
606 	char* rv = strdup ("AVL-Drumkits-LV2:0xXXXXXXXX");
607 	sprintf (rv, "AVL-Drumkits-LV2:0xXXXXXXXX");
608 	char inst[11];
609 	snprintf (inst, 10, "%p", self);
610 	inst[10] = '\0';
611 	memcpy (&rv[17], inst, strlen(inst));
612 	return rv;
613 }
614 
615 static void
mn_free(char * v)616 mn_free (char* v)
617 {
618 	free (v);
619 }
620 
621 static const void*
extension_data(const char * uri)622 extension_data (const char* uri)
623 {
624 	static const LV2_Worker_Interface worker = { work, work_response, NULL };
625 	static const LV2_Midnam_Interface midnam = { mn_file, mn_model, mn_free };
626 	if (!strcmp (uri, LV2_WORKER__interface)) {
627 		return &worker;
628 	}
629 	if (!strcmp (uri, LV2_MIDNAM__interface)) {
630 		return &midnam;
631 	}
632 	return NULL;
633 }
634 
635 #define mkdesc(ID, NAME) \
636 static const LV2_Descriptor descriptor ## ID = { \
637 	AVL_URI NAME, \
638 	instantiate, \
639 	connect_port, \
640 	NULL, \
641 	run, \
642 	deactivate, \
643 	cleanup, \
644 	extension_data \
645 };
646 
647 mkdesc(0, "BlackPearl");
648 mkdesc(1, "BlackPearlMulti");
649 mkdesc(2, "RedZeppelin");
650 mkdesc(3, "RedZeppelinMulti");
651 
652 #undef LV2_SYMBOL_EXPORT
653 #ifdef _WIN32
654 #    define LV2_SYMBOL_EXPORT __declspec(dllexport)
655 #else
656 #    define LV2_SYMBOL_EXPORT  __attribute__ ((visibility ("default")))
657 #endif
658 LV2_SYMBOL_EXPORT
659 const LV2_Descriptor*
lv2_descriptor(uint32_t index)660 lv2_descriptor (uint32_t index)
661 {
662 	switch (index) {
663 	case 0: return &descriptor0;
664 	case 1: return &descriptor1;
665 	case 2: return &descriptor2;
666 	case 3: return &descriptor3;
667 	default:
668 		return NULL;
669 	}
670 }
671