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