1 /* dssi_analyse_plugin.c
2 *
3 * Written by Sean Bolton, with inspiration from Richard Furse's analyseplugin
4 * from the LADSPA SDK.
5 *
6 * This program is in the public domain.
7 *
8 * This program expects the name of a DSSI plugin to be provided on the
9 * command line, in the form '[<path>]<so-name>[:<label>]' (e.g.
10 * '/usr/lib/dssi/xsynth-dssi.so:Xsynth-DSSI'). It then lists various
11 * information about the plugin. If <path> is omitted, then the plugin
12 * shared library is searched for in the colon-separated list of directories
13 * specified in the environment variable DSSI_PATH. If <label> is omitted,
14 * all plugins in the shared library are shown.
15 *
16 * $Id: dssi_analyse_plugin.c,v 1.1 2010/06/27 19:30:09 smbolton Exp $
17 */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <dirent.h>
25 #include <errno.h>
26 #include <dlfcn.h>
27
28 #include <ladspa.h>
29 #include "dssi.h"
30 #include <alsa/asoundef.h>
31
32 int verbose = 0;
33
_strdupn(const char * s,size_t n)34 static char *_strdupn(const char *s, size_t n)
35 {
36 char *t = malloc(n + 1);
37 if (t) {
38 memcpy(t, s, n);
39 t[n] = 0;
40 }
41 return t;
42 }
43
_isnonnull(void * p,int ought)44 static const char *_isnonnull(void *p, int ought)
45 {
46 return (p ? "yes" : (ought ? "NO!" : "no "));
47 }
48
49 static void
print_cc(int cc)50 print_cc(int cc)
51 {
52 const char *s = NULL;
53
54 printf(" CC%d", cc);
55 switch (cc) {
56 case MIDI_CTL_MSB_BANK: s = "Error: bank select MSB should not be mapped!"; break;
57 case MIDI_CTL_MSB_MODWHEEL: s = "modwheel"; break;
58 case MIDI_CTL_MSB_BREATH: s = "breath"; break;
59 case MIDI_CTL_MSB_FOOT: s = "foot"; break;
60 case MIDI_CTL_MSB_PORTAMENTO_TIME: s = "portamento time"; break;
61 case MIDI_CTL_MSB_DATA_ENTRY: s = "data entry"; break;
62 case MIDI_CTL_MSB_MAIN_VOLUME: s = "volume"; break;
63 case MIDI_CTL_MSB_BALANCE: s = "balance"; break;
64 case MIDI_CTL_MSB_PAN: s = "pan"; break;
65 case MIDI_CTL_MSB_EXPRESSION: s = "expression"; break;
66 case MIDI_CTL_MSB_EFFECT1: s = "effect 1"; break;
67 case MIDI_CTL_MSB_EFFECT2: s = "effect 2"; break;
68 case MIDI_CTL_MSB_GENERAL_PURPOSE1: s = "general purpose control 1"; break;
69 case MIDI_CTL_MSB_GENERAL_PURPOSE2: s = "general purpose control 2"; break;
70 case MIDI_CTL_MSB_GENERAL_PURPOSE3: s = "general purpose control 3"; break;
71 case MIDI_CTL_MSB_GENERAL_PURPOSE4: s = "general purpose control 4"; break;
72 case MIDI_CTL_LSB_BANK: s = "Error: bank select LSB should not be mapped!"; break;
73 case MIDI_CTL_SUSTAIN: s = "sustain"; break;
74 case MIDI_CTL_PORTAMENTO: s = "portamento"; break;
75 case MIDI_CTL_SUSTENUTO: s = "sostenuto"; break;
76 case MIDI_CTL_SOFT_PEDAL: s = "soft pedal"; break;
77 case MIDI_CTL_LEGATO_FOOTSWITCH: s = "legato footswitch"; break;
78 case MIDI_CTL_HOLD2: s = "hold 2 pedal"; break;
79 case MIDI_CTL_SC1_SOUND_VARIATION: s = "sc1 sound variation"; break;
80 case MIDI_CTL_SC2_TIMBRE: s = "sc2 timbre"; break;
81 case MIDI_CTL_SC3_RELEASE_TIME: s = "sc3 release time"; break;
82 case MIDI_CTL_SC4_ATTACK_TIME: s = "sc4 attack time"; break;
83 case MIDI_CTL_SC5_BRIGHTNESS: s = "sc5 brightness"; break;
84 case MIDI_CTL_SC6: s = "sound control 6"; break;
85 case MIDI_CTL_SC7: s = "sound control 7"; break;
86 case MIDI_CTL_SC8: s = "sound control 8"; break;
87 case MIDI_CTL_SC9: s = "sound control 9"; break;
88 case MIDI_CTL_SC10: s = "sound control 10"; break;
89 case MIDI_CTL_GENERAL_PURPOSE5: s = "general purpose button 1"; break;
90 case MIDI_CTL_GENERAL_PURPOSE6: s = "general purpose button 2"; break;
91 case MIDI_CTL_GENERAL_PURPOSE7: s = "general purpose button 3"; break;
92 case MIDI_CTL_GENERAL_PURPOSE8: s = "general purpose button 4"; break;
93 case MIDI_CTL_PORTAMENTO_CONTROL: s = "portamento control"; break;
94 case MIDI_CTL_E1_REVERB_DEPTH: s = "effects level"; break;
95 case MIDI_CTL_E2_TREMOLO_DEPTH: s = "tremelo level"; break;
96 case MIDI_CTL_E3_CHORUS_DEPTH: s = "chorus level"; break;
97 case MIDI_CTL_E4_DETUNE_DEPTH: s = "celeste level"; break;
98 case MIDI_CTL_E5_PHASER_DEPTH: s = "phaser level"; break;
99 }
100 if (s) printf(" (%s)", s);
101 }
102
103 void
describe_plugin(const DSSI_Descriptor * descriptor,const char * so_directory,const char * so_name,const char * label)104 describe_plugin(const DSSI_Descriptor *descriptor, const char *so_directory,
105 const char *so_name, const char *label)
106 {
107 int i;
108 const DSSI_Descriptor *dd = descriptor;
109 const LADSPA_Descriptor *ld = descriptor->LADSPA_Plugin;
110 LADSPA_Handle instance;
111
112 printf("Label: %s\n", ld->Label);
113 printf("Name: %s\n", ld->Name);
114 printf("Maker: %s\n", ld->Maker);
115 printf("Copyright: %s\n", ld->Copyright);
116
117 printf("Properties:");
118 if (LADSPA_IS_REALTIME(ld->Properties))
119 printf(" REALTIME");
120 else
121 printf(" (not REALTIME)");
122 if (LADSPA_IS_INPLACE_BROKEN(ld->Properties))
123 printf(" INPLACE_BROKEN");
124 else
125 printf(" (not INPLACE_BROKEN)");
126 if (LADSPA_IS_HARD_RT_CAPABLE(ld->Properties))
127 printf(" HARD_RT_CAPABLE\n");
128 else
129 printf(" (not HARD_RT_CAPABLE)\n");
130
131 if (dd->DSSI_API_Version != 1)
132 printf("Put on your future-proof bat-panties, Robin! This plugin reports DSSI API\n"
133 "version %d! Proceeding anyway....\n", dd->DSSI_API_Version);
134 else
135 printf("DSSI API Version: 1\n");
136
137 printf("Functions available:\n");
138 printf(" %s instantiate() %s configure()\n",
139 _isnonnull(ld->instantiate, 1),
140 _isnonnull(dd->configure, 0));
141 printf(" %s connect_port() %s get_program()\n",
142 _isnonnull(ld->connect_port, 1),
143 _isnonnull(dd->get_program, 0));
144 printf(" %s activate() %s select_program()\n",
145 _isnonnull(ld->activate, 0),
146 _isnonnull(dd->select_program, 0));
147 printf(" %s run() %s get_midi_controller_for_port()\n",
148 _isnonnull(ld->run, 0),
149 _isnonnull(dd->get_midi_controller_for_port, 0));
150 printf(" %s run_adding() %s run_synth()\n",
151 _isnonnull(ld->run_adding, (ld->set_run_adding_gain != NULL)),
152 _isnonnull(dd->run_synth, 0));
153 printf(" %s set_run_adding_gain() %s run_synth_adding()\n",
154 _isnonnull(ld->set_run_adding_gain,
155 (ld->run_adding != NULL ||
156 dd->run_synth_adding != NULL ||
157 dd->run_multiple_synths_adding != NULL)),
158 _isnonnull(dd->run_synth_adding, (ld->set_run_adding_gain != NULL)));
159 printf(" %s deactivate() %s run_multiple_synths()\n",
160 _isnonnull(ld->deactivate, 0),
161 _isnonnull(dd->run_multiple_synths, 0));
162 printf(" %s cleanup() %s run_multiple_synths_adding()\n",
163 _isnonnull(ld->cleanup, 1),
164 _isnonnull(dd->run_multiple_synths_adding, (ld->set_run_adding_gain != NULL)));
165
166 if (ld->PortCount) {
167 printf("Ports (%lu):\n", ld->PortCount);
168 /* -------------------------------------------------------------------------------- 80 chars */
169 printf(" --- name---------------- type------- min----- max----- default flags---------\n");
170 /* 1 OSC1 Pitch control in 0.25 4 max *srate log int */
171 } else
172 printf("Error: plugin has no ports!\n");
173
174 for (i = 0; i < ld->PortCount; i++) {
175 LADSPA_PortDescriptor pd = ld->PortDescriptors[i];
176 const LADSPA_PortRangeHint *prh = &ld->PortRangeHints[i];
177 LADSPA_PortRangeHintDescriptor hd = prh->HintDescriptor;
178
179 printf(" %3d %-20s", i, ld->PortNames[i]);
180 if ((pd & (LADSPA_PORT_CONTROL | LADSPA_PORT_AUDIO)) == LADSPA_PORT_CONTROL) {
181 printf(" control");
182 } else if ((pd & (LADSPA_PORT_CONTROL | LADSPA_PORT_AUDIO)) == LADSPA_PORT_AUDIO) {
183 printf(" audio ");
184 } else {
185 printf(" 0x%02x??", (pd & (LADSPA_PORT_CONTROL | LADSPA_PORT_AUDIO)));
186 }
187 if ((pd & (LADSPA_PORT_INPUT | LADSPA_PORT_OUTPUT)) == LADSPA_PORT_INPUT) {
188 printf(" in ");
189 } else if ((pd & (LADSPA_PORT_INPUT | LADSPA_PORT_OUTPUT)) == LADSPA_PORT_OUTPUT) {
190 printf(" out");
191 } else {
192 printf(" %02x?", (pd & (LADSPA_PORT_INPUT | LADSPA_PORT_OUTPUT)));
193 }
194
195 /* PortRangeHints */
196 if (LADSPA_IS_HINT_TOGGLED(hd)) {
197 printf(" toggled ");
198 } else {
199 if (LADSPA_IS_HINT_BOUNDED_BELOW(hd))
200 printf("%9g", prh->LowerBound);
201 else
202 printf(" ");
203 if (LADSPA_IS_HINT_BOUNDED_ABOVE(hd))
204 printf("%9g", prh->UpperBound);
205 else
206 printf(" ");
207 }
208 if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hd)) printf(" min ");
209 else if (LADSPA_IS_HINT_DEFAULT_LOW(hd)) printf(" low ");
210 else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hd)) printf(" mid ");
211 else if (LADSPA_IS_HINT_DEFAULT_HIGH(hd)) printf(" hi ");
212 else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hd)) printf(" max ");
213 else if (LADSPA_IS_HINT_DEFAULT_0(hd)) printf(" 0 ");
214 else if (LADSPA_IS_HINT_DEFAULT_1(hd)) printf(" 1 ");
215 else if (LADSPA_IS_HINT_DEFAULT_100(hd)) printf(" 100 ");
216 else if (LADSPA_IS_HINT_DEFAULT_440(hd)) printf(" 440 ");
217 else if (!LADSPA_IS_HINT_HAS_DEFAULT(hd)) printf(" ");
218 else printf(" invalid?");
219 if (LADSPA_IS_HINT_SAMPLE_RATE(hd)) printf(" *srate");
220 if (LADSPA_IS_HINT_LOGARITHMIC(hd)) printf(" log");
221 if (LADSPA_IS_HINT_INTEGER(hd)) printf(" int");
222
223 putchar('\n');
224
225 if (LADSPA_IS_HINT_TOGGLED(hd)) {
226 /* "LADSPA_HINT_TOGGLED may not be used in conjunction with any
227 * other hint except LADSPA_HINT_DEFAULT_0 or LADSPA_HINT_DEFAULT_1." */
228 if ((hd | LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_DEFAULT_1) !=
229 (LADSPA_HINT_TOGGLED | LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_DEFAULT_1))
230 printf(" Error: 'toggled' is incompatible with other supplied hints.\n");
231 }
232 }
233
234 instance = NULL;
235 if (ld->instantiate && (dd->get_midi_controller_for_port ||
236 (verbose && dd->get_program))) {
237 instance = ld->instantiate(ld, 44100L);
238 if (!instance)
239 printf("Error: unable to instantiate plugin!\n");
240 }
241
242 if (instance) {
243
244 if (dd->get_midi_controller_for_port) {
245 int map;
246 int found = 0;
247
248 printf("Requested MIDI Control Change/NRPN Mappings:\n");
249 for (i = 0; i < ld->PortCount; i++) {
250 LADSPA_PortDescriptor pd = ld->PortDescriptors[i];
251 if (LADSPA_IS_PORT_INPUT(pd) && LADSPA_IS_PORT_CONTROL(pd)) {
252 map = dd->get_midi_controller_for_port(instance, i);
253 if (DSSI_CONTROLLER_IS_SET(map)) {
254 printf(" %3d %-20s ", i, ld->PortNames[i]);
255 if (DSSI_IS_CC(map))
256 print_cc(DSSI_CC_NUMBER(map));
257 if (DSSI_IS_NRPN(map))
258 printf(" NRPN%d", DSSI_NRPN_NUMBER(map));
259 if (!DSSI_IS_CC(map) && !DSSI_IS_NRPN(map))
260 printf(" Error: no valid mappings returned!");
261 putchar('\n');
262 found = 1;
263 }
264 }
265 }
266 if (!found) printf(" (none)\n");
267 }
268
269 if (verbose && dd->get_program) {
270 const DSSI_Program_Descriptor *pd;
271 int found = 0;
272
273 printf("Default Programs:\n");
274 printf(" bank num name----------------------------------------\n");
275
276 for (i = 0; (pd = dd->get_program(instance, i)) != NULL; i++) {
277 printf(" %4lu %3lu %s\n", pd->Bank, pd->Program, pd->Name);
278 found = 1;
279 }
280 if (!found) printf(" (none)\n");
281 }
282
283 if (ld->cleanup)
284 ld->cleanup(instance);
285 }
286
287 { /* user interfaces */
288 int so_len, label_len;
289 char *so_base, *ui_dir;
290 DIR *dir;
291 struct dirent *dirent;
292 struct stat statbuf;
293 int found = 0;
294
295 printf("Available User Interfaces:\n");
296
297 so_len = strlen(so_name);
298 if (so_len > 3 && !strcmp(so_name + so_len - 3, ".so")) {
299 so_len -= 3;
300 so_base = _strdupn(so_name, so_len);
301 } else
302 so_base = strdup(so_name); /* okay, so what are you, a .dll, a .dylib? */
303
304 ui_dir = malloc(strlen(so_directory) + strlen(so_base) + 2);
305 sprintf(ui_dir, "%s/%s", so_directory, so_base);
306
307 label_len = strlen(label);
308 if ((dir = opendir(ui_dir)) != NULL) {
309 while ((dirent = readdir(dir)) != NULL) {
310 if ((!strncmp(dirent->d_name, so_base, so_len) &&
311 dirent->d_name[so_len] == '_') ||
312 (!strncmp(dirent->d_name, label, label_len) &&
313 dirent->d_name[label_len] == '_')) {
314
315 char *ui_path = malloc(strlen(ui_dir) + strlen(dirent->d_name) + 2);
316
317 sprintf(ui_path, "%s/%s", ui_dir, dirent->d_name);
318
319 if (stat(ui_path, &statbuf)) {
320 printf(" (found %s, but could not stat it: %s)\n",
321 ui_path, strerror(errno));
322
323 } else if ((S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) &&
324 (statbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
325 printf(" %s\n", ui_path);
326 found = 1;
327 } else {
328 printf(" (found %s, but it is not an executable file)\n", ui_path);
329 }
330
331 free(ui_path);
332 }
333 }
334 }
335 free(ui_dir);
336 free(so_base);
337
338 if (!found) printf(" (none)\n");
339 }
340 }
341
342 void
usage(const char * program_name)343 usage(const char *program_name)
344 {
345 fprintf(stderr, "usage: %s [-v|--verbose] [<path>]<DSSI-so-name>[:<label>]\n\n", program_name);
346 fprintf(stderr, "Example: %s -v /usr/lib/dssi/xsynth-dssi.so:Xsynth-DSSI\n", program_name);
347 fprintf(stderr, "If <path> is omitted, then the plugin library is searched for in the colon-\n");
348 fprintf(stderr, "list of directories specified in the environment variable DSSI_PATH. If\n");
349 fprintf(stderr, "<label> is omitted, all plugins in the shared library are shown.\n");
350 fprintf(stderr, "Optional argument:\n\n");
351 fprintf(stderr, " -v, --verbose Provide additional information, including default program\n");
352 fprintf(stderr, " names.\n");
353
354 exit(1);
355 }
356
357 int
main(int argc,char * argv[])358 main(int argc, char *argv[])
359 {
360 char *so_path, *so_directory, *so_name, *label;
361 char *tmp;
362 void *handle = 0;
363 DSSI_Descriptor_Function dssi_descriptor_function;
364 LADSPA_Descriptor_Function ladspa_descriptor_function;
365 int id;
366 const DSSI_Descriptor *descriptor;
367 int label_found;
368
369 if (argc < 2 || argc > 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
370 usage(argv[0]); /* does not return */
371 }
372
373 if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--verbose"))
374 verbose = 1;
375
376 /* if plugin label was given, separate it from shared library name */
377 tmp = strrchr(argv[argc - 1], ':');
378 if (tmp) {
379 so_name = _strdupn(argv[argc - 1], tmp - argv[argc - 1]);
380 label = strdup(tmp + 1);
381 } else {
382 so_name = strdup(argv[argc - 1]);
383 label = NULL;
384 }
385
386 /* if absolute path was given, separate directory from basename */
387 tmp = strrchr(so_name, '/');
388 if (tmp) {
389 so_path = so_name;
390 so_directory = _strdupn(so_name, tmp - so_name);
391 so_name = strdup(tmp + 1);
392 } else {
393 so_path = NULL;
394 so_directory = NULL;
395 }
396
397 if (so_path) { /* absolute path */
398
399 if ((handle = dlopen(so_path, RTLD_LAZY)) == NULL) {
400 fprintf(stderr, "Error: can't load DSSI or LADSPA plugin from '%s': %s\n",
401 so_path, dlerror());
402 exit(1);
403 }
404
405 } else { /* no path given, search over DSSI_PATH */
406
407 char *dssi_path = getenv("DSSI_PATH");
408 char *pathtmp, *element;
409
410 if (!dssi_path) {
411 dssi_path = "/usr/local/lib/dssi:/usr/lib/dssi";
412 fprintf(stderr, "Warning: DSSI_PATH not set, defaulting to '%s'\n", dssi_path);
413 } else if (verbose) {
414 printf("Searching DSSI_PATH '%s'...\n", dssi_path);
415 }
416
417 pathtmp = strdup(dssi_path);
418 tmp = pathtmp;
419 while ((element = strtok(tmp, ":")) != 0) {
420 tmp = NULL;
421
422 so_path = malloc(strlen(element) + strlen(so_name) + 2);
423 sprintf(so_path, "%s/%s", element, so_name);
424
425 if ((handle = dlopen(so_path, RTLD_LAZY))) {
426 so_directory = strdup(element);
427 break;
428 }
429
430 free(so_path);
431 so_path = NULL;
432 }
433
434 free(pathtmp);
435
436 if (!handle) {
437 fprintf(stderr, "Error: couldn't locate DSSI or LADSPA plugin library '%s' on path '%s'\n",
438 so_name, dssi_path);
439 exit(1);
440 }
441 }
442
443 dssi_descriptor_function = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor");
444
445 if (!dssi_descriptor_function) {
446 fprintf(stderr, "Error: shared library '%s' is not a DSSI plugin library\n", so_path);
447 exit(1);
448 }
449
450 printf("Examining DSSI plugin library %s:\n", so_path);
451
452 label_found = 0;
453 for (id = 0; (descriptor = dssi_descriptor_function(id)); id++) {
454 if (label) {
455 if (!strcmp(label, descriptor->LADSPA_Plugin->Label)) {
456 label_found = 1;
457 describe_plugin(descriptor, so_directory, so_name, label);
458 break;
459 }
460 } else {
461 if (id > 0) putchar('\n');
462 describe_plugin(descriptor, so_directory, so_name, descriptor->LADSPA_Plugin->Label);
463 label_found++;
464 }
465 }
466
467 if (!label_found) {
468 if (label)
469 fprintf(stderr, "Error: could not find plugin '%s' in DSSI library '%s'\n", label, so_path);
470 else
471 printf("(Hmm, this DSSI plugin library reports no DSSI plugins -- perhaps something\n"
472 " wrong with its initialization code?)\n");
473 }
474
475 ladspa_descriptor_function = (LADSPA_Descriptor_Function)dlsym(handle, "ladspa_descriptor");
476 if (verbose && ladspa_descriptor_function) {
477 if (label)
478 printf("This library exports a LADSPA descriptor function as well.\n");
479 else {
480 const LADSPA_Descriptor *ldescriptor;
481
482 printf("This library exports a LADSPA descriptor function as well. LADSPA plugins are:\n");
483 for (id = 0; (ldescriptor = ladspa_descriptor_function(id)); id++)
484 printf(" %-16s (%lu) %s\n", ldescriptor->Label, ldescriptor->UniqueID, ldescriptor->Name);
485 if (id == 0)
486 printf(" (Oddly, this library has a LADSPA descriptor function but reports no plugins.)\n");
487 }
488 }
489
490 dlclose(handle);
491 free(label);
492 free(so_name);
493 free(so_directory);
494 free(so_path);
495
496 return 0;
497 }
498
499