1 /*
2 * NASPRO - The NASPRO Architecture for Sound PROcessing
3 * A simple command line effect processor using LV2 plugins
4 *
5 * Copyright (C) 2011-2014 Stefano D'Angelo
6 *
7 * See the COPYING file for license conditions.
8 */
9
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <errno.h>
14 #include <locale.h>
15 #include <inttypes.h>
16 #include <stdbool.h>
17 #include <time.h>
18
19 #include <math.h>
20
21 #include <sndfile.h>
22
23 #include <lilv/lilv.h>
24
25 #include "config.h"
26
27 /* Utils */
28
29 static const char *argv0;
30
31 #define PRINT_ERROR(fmt) \
32 fprintf(stderr, "%s: error: " fmt "\n", argv0);
33 #define PRINTF_ERROR(fmt, ...) \
34 fprintf(stderr, "%s: error: " fmt "\n", argv0, __VA_ARGS__);
35 #define PRINT_WARNING(fmt) \
36 fprintf(stderr, "%s: warning: " fmt "\n", argv0);
37 #define PRINTF_WARNING(fmt, ...) \
38 fprintf(stderr, "%s: warning: " fmt "\n", argv0, __VA_ARGS__);
39
40 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
41
42 static bool
strtoul_nl(unsigned long * v,const char * s)43 strtoul_nl(unsigned long *v, const char *s)
44 {
45 char *c, *locale;
46 int err, errsv;
47
48 errsv = errno;
49
50 locale = setlocale(LC_NUMERIC, NULL);
51 setlocale(LC_NUMERIC, "C");
52
53 errno = 0;
54 *v = strtoul(s, &c, 10);
55 err = errno;
56
57 setlocale(LC_NUMERIC, locale);
58
59 errno = (err == 0) ? errsv : err;
60
61 return (err == 0) && (*c == '\0');
62 }
63
64 static bool
strtof_nl(float * v,const char * s)65 strtof_nl(float *v, const char *s)
66 {
67 char *c, *locale;
68 int err, errsv;
69
70 errsv = errno;
71
72 locale = setlocale(LC_NUMERIC, NULL);
73 setlocale(LC_NUMERIC, "C");
74
75 errno = 0;
76 *v = strtof(s, &c);
77 err = errno;
78
79 setlocale(LC_NUMERIC, locale);
80
81 errno = (err == 0) ? errsv : err;
82
83 return (err == 0) && (*c == '\0');
84 }
85
86 static bool
id_is_valid(const char * id)87 id_is_valid(const char *id)
88 {
89 const char *c = id;
90
91 if (((*c < 'A') || ((*c > 'Z') && (*c < 'a'))
92 || (*c > 'z')) && (*c != '_'))
93 return 0;
94
95 for (; *c != '\0'; c++)
96 if (((*c < '0') || ((*c > '9') && (*c < 'A'))
97 || ((*c > 'Z') && (*c < 'a'))
98 || (*c > 'z')) && (*c != '_'))
99 return 0;
100
101 return 1;
102 }
103
104 /* Command line arguments */
105
106 #define FRAMES_N_DEFAULT 512
107
108 typedef struct
109 {
110 char *symbol;
111 float value;
112 const LilvPort *port;
113 }
114 connection_t;
115
116 static const char *in_filename = NULL;
117 static const char *out_filename = NULL;
118
119 static unsigned long frames_n = 0; // 0 == unspecified
120
121 static connection_t *connections = NULL;
122 static size_t connections_n = 0;
123
124 static bool latency_do = true;
125
126 static bool timing_do = false;
127
128 static bool normalize_do = false;
129
130 static char *plugin_uri = NULL;
131
132 static connection_t *
cmd_connection_get_by_symbol(const char * symbol)133 cmd_connection_get_by_symbol(const char *symbol)
134 {
135 if (connections == NULL)
136 return NULL;
137
138 for (size_t i = 0; i < connections_n; i++)
139 if (!strcmp(connections[i].symbol, symbol))
140 return connections + i;
141
142 return NULL;
143 }
144
145 static connection_t *
cmd_connection_get_by_port(const LilvPort * port)146 cmd_connection_get_by_port(const LilvPort *port)
147 {
148 for (size_t i = 0; i < connections_n; i++)
149 if (connections[i].port == port)
150 return connections + i;
151
152 return NULL;
153 }
154
155 static int
cmd_parse(int argc,char * argv[])156 cmd_parse(int argc, char *argv[])
157 {
158 for (int i = 1; i < argc; i++)
159 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
160 {
161 int argv0_len = strlen(argv[0]);
162
163 printf("Usage: %s [options] -i input -o output "
164 "plugin\n", argv[0]);
165 printf(" %*s -h|--help\n", argv0_len, " ");
166 printf(" %*s --version\n", argv0_len, " ");
167 printf("Command line arguments:\n");
168 printf(" -h, --help "
169 "Display this information\n");
170 printf(" --version "
171 "Display version information\n");
172 printf(" -i input "
173 "Input sound file\n");
174 printf(" -o output "
175 "Output sound file\n");
176 printf(" plugin "
177 "LV2 plugin URI\n");
178 printf(" -n frames "
179 "Number of frames per cycle [default=%d]\n",
180 FRAMES_N_DEFAULT);
181 printf(" -c port:value "
182 "Set value for control in port\n");
183 printf(" --with-latency "
184 "Disable latency compensation\n");
185 printf(" --timing "
186 "Report timing statistics\n");
187 printf(" --normalize "
188 "Normalize output\n");
189
190 return 1;
191 }
192
193 for (int i = 1; i < argc; i++)
194 if (!strcmp(argv[i], "--version"))
195 {
196 printf(PACKAGE " (" PACKAGE_NAME ") " VERSION "\n");
197 printf("Copyright (C) 2011-2014 Stefano D'Angelo\n");
198 printf("This is free software; see the source for "
199 "copying conditions. There is NO\n");
200 printf("warranty; not even for MERCHANTABILITY or "
201 "FITNESS FOR A PARTICULAR PURPOSE.\n\n");
202
203 return 1;
204 }
205
206 for (int i = 1; i < argc; i++)
207 {
208 if (!strcmp(argv[i], "-i"))
209 {
210 if (i == (argc - 1))
211 {
212 PRINT_ERROR("missing filename after '-i'");
213 goto cmd_parse_err;
214 }
215
216 if (in_filename != NULL)
217 {
218 PRINT_ERROR("input file specified more than "
219 "once");
220 goto cmd_parse_err;
221 }
222
223 in_filename = argv[++i];
224 continue;
225 }
226
227 if (!strcmp(argv[i], "-o"))
228 {
229 if (i == (argc - 1))
230 {
231 PRINT_ERROR("missing filename after '-o'");
232 goto cmd_parse_err;
233 }
234
235 if (out_filename != NULL)
236 {
237 PRINT_ERROR("output file specified more than "
238 "once");
239 goto cmd_parse_err;
240 }
241
242 out_filename = argv[++i];
243 continue;
244 }
245
246 if (!strcmp(argv[i], "-n"))
247 {
248 if (i == (argc - 1))
249 {
250 PRINT_ERROR("missing number of frames after "
251 "'-n'");
252 goto cmd_parse_err;
253 }
254
255 if (frames_n != 0)
256 {
257 PRINT_ERROR("number of frames specified more "
258 "than once");
259 goto cmd_parse_err;
260 }
261
262 bool err = !strtoul_nl(&frames_n, argv[++i]);
263 if (err || (frames_n == 0))
264 {
265 PRINT_ERROR("invalid number of frames");
266 goto cmd_parse_err;
267 }
268
269 continue;
270 }
271
272 if (!strcmp(argv[i], "-c"))
273 {
274 if (i == (argc - 1))
275 {
276 PRINT_ERROR("missing <port>:<value> after "
277 "'-c'");
278 goto cmd_parse_err;
279 }
280
281 char *v = strchr(argv[++i], ':');
282 if (v == NULL)
283 {
284 PRINT_ERROR("invalid <port>:<value> argument");
285 goto cmd_parse_err;
286 }
287 *v = '\0';
288
289 if (!id_is_valid(argv[i]))
290 {
291 PRINTF_ERROR("invalid port identifier '%s'",
292 argv[i]);
293 goto cmd_parse_err;
294 }
295
296 float val;
297
298 if (!strtof_nl(&val, ++v))
299 {
300 PRINTF_ERROR("invalid value for port '%s'",
301 argv[i]);
302 goto cmd_parse_err;
303 }
304
305 connection_t *tmp = cmd_connection_get_by_symbol(
306 argv[i]);
307 if (tmp != NULL)
308 if (tmp->value != val)
309 {
310 PRINTF_ERROR("a different value for "
311 "port '%s' was already "
312 "specified", argv[i]);
313 goto cmd_parse_err;
314 }
315
316 connections_n++;
317 tmp = realloc(connections,
318 connections_n * sizeof(connection_t));
319 if (tmp == NULL)
320 {
321 PRINTF_ERROR("%s", strerror(ENOMEM));
322 goto cmd_parse_err;
323 }
324 connections = tmp;
325
326 connections[connections_n - 1].symbol = argv[i];
327 connections[connections_n - 1].value = val;
328 connections[connections_n - 1].port = NULL;
329
330 continue;
331 }
332
333 if (!strcmp(argv[i], "--with-latency"))
334 {
335 latency_do = false;
336 continue;
337 }
338
339 if (!strcmp(argv[i], "--timing"))
340 {
341 timing_do = true;
342 continue;
343 }
344
345 if (!strcmp(argv[i], "--normalize"))
346 {
347 normalize_do = true;
348 continue;
349 }
350
351 if (argv[i][0] == '-')
352 {
353 PRINTF_ERROR("unknown command line option '%s'",
354 argv[i]);
355 goto cmd_parse_err;
356 }
357
358 if (plugin_uri != NULL)
359 {
360 PRINT_ERROR("plugin URI specified more than once");
361 goto cmd_parse_err;
362 }
363
364 plugin_uri = argv[i];
365 }
366
367 if (in_filename == NULL)
368 {
369 PRINT_ERROR("no input file specified");
370 goto cmd_parse_err;
371 }
372
373 if (out_filename == NULL)
374 {
375 PRINT_ERROR("no output file specified");
376 goto cmd_parse_err;
377 }
378
379 if (plugin_uri == NULL)
380 {
381 PRINT_ERROR("no plugin URI specified");
382 goto cmd_parse_err;
383 }
384
385 if (frames_n == 0)
386 frames_n = FRAMES_N_DEFAULT;
387
388 return 0;
389
390 cmd_parse_err:
391 if (connections != NULL)
392 free(connections);
393
394 return -1;
395 }
396
397 static void
cmd_fini()398 cmd_fini()
399 {
400 if (connections != NULL)
401 free(connections);
402 }
403
404 /* LV2 */
405
406 static LilvWorld *lv2_world;
407 static const LilvPlugins *lv2_plugins;
408 static LilvNode *lv2_input_node, *lv2_output_node, *lv2_audio_node,
409 *lv2_ctrl_node, *lv2_conn_opt_node, *lv2_rep_lat_node,
410 *lv2_sample_rate_node;
411 static const LilvPort *lv2_latency_port = NULL;
412
413 static uint32_t audio_in_min = 0, audio_in_max = 0, audio_in_n, audio_out_n = 0,
414 ctrl_in_n = 0, ctrl_out_n = 0;
415
416 static int
lv2_init()417 lv2_init()
418 {
419 lv2_world = lilv_world_new();
420 if (lv2_world == NULL)
421 {
422 PRINT_ERROR("could not initialize Lilv");
423 return 0;
424 }
425
426 lilv_world_load_all(lv2_world);
427
428 lv2_plugins = lilv_world_get_all_plugins(lv2_world);
429
430 lv2_input_node = lilv_new_uri(lv2_world, LILV_URI_INPUT_PORT);
431 lv2_output_node = lilv_new_uri(lv2_world, LILV_URI_OUTPUT_PORT);
432 lv2_audio_node = lilv_new_uri(lv2_world, LILV_URI_AUDIO_PORT);
433 lv2_ctrl_node = lilv_new_uri(lv2_world, LILV_URI_CONTROL_PORT);
434 lv2_conn_opt_node = lilv_new_uri(lv2_world,
435 LILV_NS_LV2 "connectionOptional");
436 lv2_rep_lat_node = lilv_new_uri(lv2_world,
437 LILV_NS_LV2 "reportsLatency");
438 lv2_sample_rate_node = lilv_new_uri(lv2_world,
439 LILV_NS_LV2 "sampleRate");
440
441 return 1;
442 }
443
444 static const LilvPlugin *
lv2_plugin_get(const char * uri)445 lv2_plugin_get(const char *uri)
446 {
447 LilvNode *uri_node;
448 const LilvPlugin *plugin;
449
450 uri_node = lilv_new_uri(lv2_world, uri);
451 plugin = lilv_plugins_get_by_uri(lv2_plugins, uri_node);
452 lilv_node_free(uri_node);
453
454 return plugin;
455 }
456
457 static bool
lv2_plugin_check_features(const LilvPlugin * plugin)458 lv2_plugin_check_features(const LilvPlugin *plugin)
459 {
460 LilvNodes *features;
461 bool ret = true;
462
463 features = lilv_plugin_get_required_features(plugin);
464 if (lilv_nodes_size(features))
465 ret = false;
466 lilv_nodes_free(features);
467
468 return ret;
469 }
470
471 static bool
lv2_plugin_inspect_ports(const LilvPlugin * plugin)472 lv2_plugin_inspect_ports(const LilvPlugin *plugin)
473 {
474 uint32_t n = lilv_plugin_get_num_ports(plugin);
475
476 for (uint32_t i = 0; i < n; i++)
477 {
478 const LilvPort *port = lilv_plugin_get_port_by_index(plugin, i);
479 const char *symbol = lilv_node_as_string(
480 lilv_port_get_symbol(plugin, port));
481
482 if (lilv_port_is_a(plugin, port, lv2_audio_node))
483 {
484 if (lilv_port_is_a(plugin, port, lv2_input_node))
485 {
486 if (cmd_connection_get_by_symbol(symbol)
487 != NULL)
488 {
489 PRINTF_ERROR("cannot assign a value to "
490 "port '%s' (it is an "
491 "audio in port)", symbol);
492 return false;
493 }
494
495 audio_in_max++;
496 if (!lilv_port_has_property(plugin, port,
497 lv2_conn_opt_node))
498 audio_in_min++;
499 }
500 else if (lilv_port_is_a(plugin, port, lv2_output_node))
501 {
502 if (cmd_connection_get_by_symbol(symbol)
503 != NULL)
504 {
505 PRINTF_ERROR("cannot assign a value to "
506 "port '%s' (it is an "
507 "audio out port)", symbol);
508 return false;
509 }
510
511 if (!lilv_port_has_property(plugin, port,
512 lv2_conn_opt_node))
513 audio_out_n++;
514 }
515 else
516 {
517 PRINTF_ERROR("port '%s' has a port type this "
518 "program does not understand",
519 symbol);
520 return false;
521 }
522 }
523 else if (lilv_port_is_a(plugin, port, lv2_ctrl_node))
524 {
525 if (lilv_port_is_a(plugin, port, lv2_input_node))
526 {
527 connection_t *conn =
528 cmd_connection_get_by_symbol(symbol);
529
530 if (conn != NULL)
531 {
532 conn->port = port;
533 ctrl_in_n++;
534 }
535 else if (!lilv_port_has_property(plugin, port,
536 lv2_conn_opt_node))
537 ctrl_in_n++;
538 }
539 else if (lilv_port_is_a(plugin, port, lv2_output_node))
540 {
541 if (cmd_connection_get_by_symbol(symbol)
542 != NULL)
543 {
544 PRINTF_ERROR("cannot assign a value to "
545 "port '%s' (it is a "
546 "control out port)",
547 symbol);
548 return false;
549 }
550
551 if (latency_do && lilv_port_is_a(plugin, port,
552 lv2_rep_lat_node))
553 {
554 lv2_latency_port = port;
555 ctrl_out_n++;
556 }
557 else if (!lilv_port_has_property(plugin, port,
558 lv2_conn_opt_node))
559 ctrl_out_n++;
560 }
561 else
562 {
563 PRINTF_ERROR("port '%s' has a port type this "
564 "program does not understand",
565 symbol);
566 return false;
567 }
568 }
569 else
570 {
571 PRINTF_ERROR("port '%s' has a port type this program "
572 "does not understand", symbol);
573 return false;
574 }
575 }
576
577 for (size_t i = 0; i < connections_n; i++)
578 if (connections[i].port == NULL)
579 {
580 PRINTF_ERROR("port '%s' not found",
581 connections[i].symbol);
582 return false;
583 }
584
585 if (audio_in_max == 0)
586 {
587 PRINT_ERROR("the plugin has no audio in ports");
588 return false;
589 }
590
591 if (audio_out_n == 0)
592 {
593 PRINT_ERROR("the plugin has no audio out ports");
594 return false;
595 }
596
597 return true;
598 }
599
600 static void
lv2_fini()601 lv2_fini()
602 {
603 lilv_node_free(lv2_sample_rate_node);
604 lilv_node_free(lv2_rep_lat_node);
605 lilv_node_free(lv2_conn_opt_node);
606 lilv_node_free(lv2_ctrl_node);
607 lilv_node_free(lv2_audio_node);
608 lilv_node_free(lv2_output_node);
609 lilv_node_free(lv2_input_node);
610
611 lilv_world_free(lv2_world);
612 }
613
614 /* Sound processing */
615
616 static float *in_buf, *out_buf;
617 static float **audio_in_bufs, **audio_out_bufs;
618 static float *ctrl_in_values = NULL, *ctrl_out_values = NULL;
619 static float **norm_bufs = NULL;
620 static float latency;
621
622 static bool
proc_bufs_alloc(sf_count_t frames)623 proc_bufs_alloc(sf_count_t frames)
624 {
625 in_buf = malloc(audio_in_n * frames_n * sizeof(float));
626 if (in_buf == NULL)
627 goto in_err;
628
629 out_buf = malloc(audio_out_n * frames_n * sizeof(float));
630 if (out_buf == NULL)
631 goto out_err;
632
633 audio_in_bufs = malloc(audio_in_n * sizeof(float *));
634 if (audio_in_bufs == NULL)
635 goto audio_in_err;
636
637 for (uint32_t i = 0; i < audio_in_n; i++)
638 {
639 audio_in_bufs[i] = malloc(frames_n * sizeof(float));
640 if (audio_in_bufs[i] == NULL)
641 {
642 for (uint32_t j = 0; j < i; j++)
643 free(audio_in_bufs[j]);
644 free(audio_in_bufs);
645 goto audio_in_err;
646 }
647 }
648
649 audio_out_bufs = malloc(audio_out_n * sizeof(float *));
650 if (audio_out_bufs == NULL)
651 goto audio_out_err;
652
653 for (uint32_t i = 0; i < audio_out_n; i++)
654 {
655 audio_out_bufs[i] = malloc(frames_n * sizeof(float));
656 if (audio_out_bufs[i] == NULL)
657 {
658 for (uint32_t j = 0; j < i; j++)
659 free(audio_out_bufs[j]);
660 free(audio_out_bufs);
661 goto audio_out_err;
662 }
663 }
664
665 if (ctrl_in_n > connections_n)
666 {
667 ctrl_in_values = malloc((ctrl_in_n - connections_n)
668 * sizeof(float));
669 if (ctrl_in_values == NULL)
670 goto ctrl_in_err;
671 }
672
673 if (ctrl_out_n > 0)
674 {
675 ctrl_out_values = malloc(ctrl_out_n * sizeof(float));
676 if (ctrl_out_values == NULL)
677 goto ctrl_out_err;
678 }
679
680 if (normalize_do)
681 {
682 norm_bufs = malloc(audio_out_n * sizeof(float *));
683 if (norm_bufs == NULL)
684 goto norm_err;
685 for (uint32_t i = 0; i < audio_out_n; i++)
686 {
687 norm_bufs[i] = malloc(frames * sizeof(float));
688 if (norm_bufs[i] == NULL)
689 {
690 for (uint32_t j = 0; j < i; j++)
691 free(norm_bufs[j]);
692 free(norm_bufs);
693 goto norm_err;
694 }
695 }
696 }
697
698 return true;
699
700 norm_err:
701 if (ctrl_out_values != NULL)
702 free(ctrl_out_values);
703 ctrl_out_err:
704 if (ctrl_in_values != NULL)
705 free(ctrl_in_values);
706 ctrl_in_err:
707 for (uint32_t i = 0; i < audio_out_n; i++)
708 free(audio_out_bufs[i]);
709 free(audio_out_bufs);
710 audio_out_err:
711 for (uint32_t i = 0; i < audio_in_n; i++)
712 free(audio_in_bufs[i]);
713 free(audio_in_bufs);
714 audio_in_err:
715 free(out_buf);
716 out_err:
717 free(in_buf);
718 in_err:
719 return false;
720 }
721
722 static void
proc_bufs_connect(const LilvPlugin * plugin,LilvInstance * instance,double sample_rate)723 proc_bufs_connect(const LilvPlugin *plugin, LilvInstance *instance,
724 double sample_rate)
725 {
726 uint32_t audio_in_i = 0, audio_out_i = 0, ctrl_in_i = 0, ctrl_out_i = 0;
727
728 uint32_t n = lilv_plugin_get_num_ports(plugin);
729 for (uint32_t i = 0; i < n; i++)
730 {
731 const LilvPort *port = lilv_plugin_get_port_by_index(plugin, i);
732
733 if (lilv_port_is_a(plugin, port, lv2_audio_node))
734 {
735 if (lilv_port_has_property(plugin, port,
736 lv2_conn_opt_node))
737 continue;
738
739 if (lilv_port_is_a(plugin, port, lv2_input_node))
740 {
741 lilv_instance_connect_port(instance, i,
742 audio_in_bufs[audio_in_i]);
743 audio_in_i++;
744 }
745 else
746 {
747 lilv_instance_connect_port(instance, i,
748 audio_out_bufs[audio_out_i]);
749 audio_out_i++;
750 }
751 }
752 else if (lilv_port_is_a(plugin, port, lv2_input_node))
753 {
754 connection_t *conn = cmd_connection_get_by_port(port);
755
756 if ((conn == NULL)
757 && lilv_port_has_property(plugin, port,
758 lv2_conn_opt_node))
759 continue;
760
761 LilvNode *min, *max, *deflt;
762 lilv_port_get_range(plugin, port, &deflt, &min, &max);
763
764 if (conn != NULL)
765 {
766 if ((min != NULL) && (max != NULL))
767 {
768 double amin = lilv_node_as_float(min);
769 double amax = lilv_node_as_float(max);
770
771 if (lilv_port_has_property(plugin, port,
772 lv2_sample_rate_node))
773 {
774 amin *= sample_rate;
775 amax *= sample_rate;
776 }
777
778 if ((conn->value < amin)
779 || (conn->value > amax))
780 PRINTF_WARNING("out of range "
781 "value for port '%s' "
782 "(min: %g, max: %g)",
783 lilv_node_as_string(
784 lilv_port_get_symbol(
785 plugin, port)),
786 amin, amax);
787 }
788 else if (min != NULL)
789 {
790 double amin = lilv_node_as_float(min);
791
792 if (lilv_port_has_property(plugin, port,
793 lv2_sample_rate_node))
794 amin *= sample_rate;
795
796 if (conn->value < amin)
797 PRINTF_WARNING("out of range "
798 "value for port '%s' "
799 "(min: %g)",
800 lilv_node_as_string(
801 lilv_port_get_symbol(
802 plugin, port)),
803 amin);
804 }
805 else if (max != NULL)
806 {
807 double amax = lilv_node_as_float(max);
808
809 if (lilv_port_has_property(plugin, port,
810 lv2_sample_rate_node))
811 amax *= sample_rate;
812
813 if (conn->value > amax)
814 PRINTF_WARNING("out of range "
815 "value for port '%s' "
816 "(max: %g)",
817 lilv_node_as_string(
818 lilv_port_get_symbol(
819 plugin, port)),
820 amax);
821 }
822
823 lilv_instance_connect_port(instance, i,
824 &conn->value);
825 }
826 else
827 {
828 if (deflt != NULL)
829 ctrl_in_values[ctrl_in_i] =
830 lilv_node_as_float(deflt);
831 else if (min != NULL)
832 ctrl_in_values[ctrl_in_i] =
833 lilv_node_as_float(min);
834 else if (max != NULL)
835 ctrl_in_values[ctrl_in_i] =
836 lilv_node_as_float(max);
837 else
838 ctrl_in_values[ctrl_in_i] = 0.0;
839
840 lilv_instance_connect_port(instance, i,
841 ctrl_in_values
842 + ctrl_in_i);
843 ctrl_in_i++;
844 }
845
846 if (deflt != NULL)
847 lilv_node_free(deflt);
848 if (min != NULL)
849 lilv_node_free(min);
850 if (max != NULL)
851 lilv_node_free(max);
852 }
853 else
854 {
855 if (port == lv2_latency_port)
856 lilv_instance_connect_port(instance, i,
857 &latency);
858 else if (!lilv_port_has_property(plugin, port,
859 lv2_conn_opt_node))
860 {
861 lilv_instance_connect_port(instance, i,
862 ctrl_out_values + ctrl_out_i);
863 ctrl_out_i++;
864 }
865 }
866 }
867
868 if (audio_in_i == audio_in_n)
869 return;
870
871 n = lilv_plugin_get_num_ports(plugin);
872 for (uint32_t i = 0; (i < n) && (audio_in_i < audio_in_n); i++)
873 {
874 const LilvPort *port = lilv_plugin_get_port_by_index(plugin, i);
875
876 if (!lilv_port_has_property(plugin, port, lv2_conn_opt_node))
877 continue;
878
879 if (!lilv_port_is_a(plugin, port, lv2_audio_node))
880 continue;
881
882 if (!lilv_port_is_a(plugin, port, lv2_input_node))
883 continue;
884
885 lilv_instance_connect_port(instance, i,
886 audio_out_bufs[audio_in_i]);
887 audio_in_i++;
888 }
889 }
890
891 static void
proc_demux_in(sf_count_t frames)892 proc_demux_in(sf_count_t frames)
893 {
894 for (sf_count_t i = 0; i < frames; i++)
895 for (uint32_t j = 0; j < audio_in_n; j++)
896 audio_in_bufs[j][i] = in_buf[i * audio_in_n + j];
897 }
898
899 static void
proc_mux_out(float ** src_bufs,sf_count_t frames,sf_count_t in_offset,sf_count_t out_offset)900 proc_mux_out(float **src_bufs, sf_count_t frames, sf_count_t in_offset,
901 sf_count_t out_offset)
902 {
903 for (sf_count_t i = out_offset; i < frames; i++)
904 for (uint32_t j = 0; j < audio_out_n; j++)
905 out_buf[(i - out_offset) * audio_out_n + j] =
906 src_bufs[j][i + in_offset];
907 }
908
909 static void
proc_out_checks(sf_count_t frames,bool * nan_warning,bool * inf_warning,bool * clip_warning,float * out_max)910 proc_out_checks(sf_count_t frames, bool *nan_warning, bool *inf_warning,
911 bool *clip_warning, float *out_max)
912 {
913 for (uint32_t i = 0; i < audio_out_n; i++)
914 for (sf_count_t j = 0; j < frames; j++)
915 {
916 if (isnan(audio_out_bufs[i][j]))
917 {
918 if (!*nan_warning)
919 {
920 PRINT_WARNING("NaN detected in audio "
921 "output");
922 *nan_warning = true;
923 }
924 audio_out_bufs[i][j] = 0.0;
925 }
926 else if (isinf(audio_out_bufs[i][j]))
927 {
928 if (!*inf_warning)
929 {
930 PRINT_WARNING("infinity value detected "
931 "in audio output");
932 *inf_warning = true;
933 }
934
935 if (normalize_do)
936 continue;
937 }
938
939 if (normalize_do)
940 {
941 float aval = fabsf(audio_out_bufs[i][j]);
942 if (aval > *out_max)
943 *out_max = aval;
944
945 continue;
946 }
947
948 if (audio_out_bufs[i][j] < -1.0)
949 {
950 if (!*clip_warning)
951 {
952 PRINT_WARNING("out of range value "
953 "detected in audio "
954 "output, clipping");
955 *clip_warning = true;
956 }
957 audio_out_bufs[i][j] = -1.0;
958 }
959 else if (audio_out_bufs[i][j] > 1.0)
960 {
961 if (!*clip_warning)
962 {
963 PRINT_WARNING("out of range value "
964 "detected in audio "
965 "output, clipping");
966 *clip_warning = true;
967 }
968 audio_out_bufs[i][j] = 1.0;
969 }
970 }
971 }
972
973 static void
proc_run(SNDFILE * in,SNDFILE * out,LilvInstance * instance)974 proc_run(SNDFILE *in, SNDFILE *out, LilvInstance *instance)
975 {
976 sf_count_t frames, out_offset, norm_i;
977 float latency_left = -1.0;
978 bool nan_warning = false, inf_warning = false, clip_warning = false;
979 clock_t total_time = 0, process_time = 0, pre_time = 0; // gcc happy
980 float out_max = 0.0;
981
982 if (timing_do)
983 total_time = clock();
984
985 if (normalize_do)
986 norm_i = 0;
987
988 while ((frames = sf_readf_float(in, in_buf, frames_n)) > 0)
989 {
990 out_offset = 0;
991
992 proc_demux_in(frames);
993
994 if (timing_do)
995 pre_time = clock();
996
997 lilv_instance_run(instance, frames);
998
999 if (timing_do)
1000 process_time += clock() - pre_time;
1001
1002 proc_out_checks(frames, &nan_warning, &inf_warning,
1003 &clip_warning, &out_max);
1004
1005 // Compensate latency
1006 if (lv2_latency_port != NULL)
1007 {
1008 if (latency_left < 0.0)
1009 latency_left = latency;
1010 if (latency_left > 0.0)
1011 {
1012 if (latency_left >= (float)frames)
1013 {
1014 latency_left -= (float)frames;
1015 continue;
1016 }
1017 out_offset = roundf(latency_left);
1018 latency_left = 0.0;
1019 }
1020 }
1021
1022 if (normalize_do)
1023 {
1024 for (uint32_t i = 0; i < audio_out_n; i++)
1025 memcpy(norm_bufs[i] + norm_i, audio_out_bufs[i],
1026 (frames - out_offset) * sizeof(float));
1027 norm_i += frames - out_offset;
1028 }
1029 else
1030 {
1031 proc_mux_out(audio_out_bufs, frames, 0, out_offset);
1032 sf_writef_float(out, out_buf, (frames - out_offset));
1033 }
1034 }
1035
1036 // Extra silence processing for latency compensation
1037 if (lv2_latency_port != NULL)
1038 {
1039 out_offset = roundf(latency);
1040 while ((frames = MIN(frames_n, out_offset)) > 0)
1041 {
1042 proc_demux_in(frames);
1043
1044 if (timing_do)
1045 pre_time = clock();
1046
1047 lilv_instance_run(instance, frames);
1048
1049 if (timing_do)
1050 process_time += clock() - pre_time;
1051
1052 proc_out_checks(frames, &nan_warning, &inf_warning,
1053 &clip_warning, &out_max);
1054
1055 if (normalize_do)
1056 {
1057 for (uint32_t i = 0; i < audio_out_n; i++)
1058 memcpy(norm_bufs[i] + norm_i,
1059 audio_out_bufs[i],
1060 frames * sizeof(float));
1061 norm_i += frames;
1062 }
1063 else
1064 {
1065 proc_mux_out(audio_out_bufs, frames, 0, 0);
1066 sf_writef_float(out, out_buf, frames);
1067 }
1068
1069 out_offset -= frames;
1070 }
1071 }
1072
1073 if (normalize_do)
1074 {
1075 if (out_max == 0.0)
1076 {
1077 PRINT_WARNING("the plugin produced silent output, "
1078 "cannot normalize");
1079 }
1080 else
1081 {
1082 out_max = 1 / out_max;
1083 printf("Applying %gx gain (%g dB) to all output "
1084 "channels\n", out_max, 20.0 * logf(out_max));
1085
1086 for (uint32_t i = 0; i < audio_out_n; i++)
1087 for (sf_count_t j = 0; j < norm_i; j++)
1088 if (!isinf(norm_bufs[i][j]))
1089 norm_bufs[i][j] *= out_max;
1090 else if (signbit(norm_bufs[i][j]))
1091 norm_bufs[i][j] = -1.0;
1092 else
1093 norm_bufs[i][j] = 1.0;
1094 }
1095
1096 sf_count_t i = 0;
1097 while ((frames = MIN(frames_n, norm_i - i)) > 0)
1098 {
1099 proc_mux_out(norm_bufs, frames, i, 0);
1100 sf_writef_float(out, out_buf, frames);
1101 i += frames;
1102 }
1103 }
1104
1105 if (timing_do)
1106 {
1107 total_time = clock() - total_time;
1108 printf("Total time : %g s\n",
1109 (double)total_time / CLOCKS_PER_SEC);
1110 printf("Processing time: %g s\n",
1111 (double)process_time / CLOCKS_PER_SEC);
1112 }
1113 }
1114
1115 static void
proc_bufs_free()1116 proc_bufs_free()
1117 {
1118 free(in_buf);
1119 free(out_buf);
1120
1121 for (uint32_t i = 0; i < audio_in_n; i++)
1122 free(audio_in_bufs[i]);
1123 free(audio_in_bufs);
1124
1125 for (uint32_t i = 0; i < audio_out_n; i++)
1126 free(audio_out_bufs[i]);
1127 free(audio_out_bufs);
1128
1129 if (ctrl_in_values != NULL)
1130 free(ctrl_in_values);
1131
1132 if (ctrl_out_values != NULL)
1133 free(ctrl_out_values);
1134
1135 if (norm_bufs != NULL)
1136 free(norm_bufs);
1137 }
1138
1139 /* Main */
1140
1141 int
main(int argc,char * argv[])1142 main(int argc, char *argv[])
1143 {
1144 int exit_code = EXIT_SUCCESS;
1145
1146 argv0 = argv[0];
1147
1148 int err = cmd_parse(argc, argv);
1149 if (err > 0)
1150 return EXIT_SUCCESS;
1151 else if (err < 0)
1152 return EXIT_FAILURE;
1153
1154 if (!lv2_init())
1155 {
1156 exit_code = EXIT_FAILURE;
1157 goto lv2_init_err;
1158 }
1159
1160 const LilvPlugin *plugin = lv2_plugin_get(plugin_uri);
1161 if (plugin == NULL)
1162 {
1163 PRINTF_ERROR("plugin '%s' not found", plugin_uri);
1164 exit_code = EXIT_FAILURE;
1165 goto lv2_plugin_err;
1166 }
1167
1168 if (!lv2_plugin_check_features(plugin))
1169 {
1170 PRINT_ERROR("the plugin requires features that this program "
1171 "does not support, sorry");
1172 exit_code = EXIT_FAILURE;
1173 goto lv2_plugin_err;
1174 }
1175
1176 if (!lv2_plugin_inspect_ports(plugin))
1177 {
1178 exit_code = EXIT_FAILURE;
1179 goto lv2_plugin_err;
1180 }
1181
1182 SNDFILE *in;
1183 SF_INFO in_info;
1184
1185 in_info.format = 0;
1186 in = sf_open(in_filename, SFM_READ, &in_info);
1187 if (in == NULL)
1188 {
1189 PRINTF_ERROR("could not open input file: %s",
1190 sf_strerror(NULL));
1191 exit_code = EXIT_FAILURE;
1192 goto in_open_err;
1193 }
1194
1195 if ((in_info.channels < audio_in_min)
1196 || (in_info.channels > audio_in_max))
1197 {
1198 PRINTF_ERROR("the number of channels of the input file (%d) "
1199 "and the number of input ports of the plugin (%"
1200 PRIu32 " to %" PRIu32 ") do not match",
1201 in_info.channels, audio_in_min, audio_in_max);
1202 exit_code = EXIT_FAILURE;
1203 goto in_channels_err;
1204 }
1205
1206 audio_in_n = in_info.channels;
1207
1208 LilvInstance *instance = lilv_plugin_instantiate(plugin,
1209 in_info.samplerate,
1210 NULL);
1211 if (instance == NULL)
1212 {
1213 PRINT_ERROR("failed to instantiate the plugin");
1214 exit_code = EXIT_FAILURE;
1215 goto instantiate_err;
1216 }
1217
1218 if (!proc_bufs_alloc(in_info.frames))
1219 {
1220 PRINTF_ERROR("%s", strerror(ENOMEM));
1221 exit_code = EXIT_FAILURE;
1222 goto bufs_err;
1223 }
1224
1225 SNDFILE *out;
1226 SF_INFO out_info;
1227
1228 out_info.samplerate = in_info.samplerate;
1229 out_info.channels = audio_out_n;
1230 out_info.format = in_info.format;
1231
1232 out = sf_open(out_filename, SFM_WRITE, &out_info);
1233 if (out == NULL)
1234 {
1235 PRINTF_ERROR("could not open output file: %s",
1236 sf_strerror(NULL));
1237 exit_code = EXIT_FAILURE;
1238 goto out_open_err;
1239 }
1240
1241 proc_bufs_connect(plugin, instance, in_info.samplerate);
1242
1243 lilv_instance_activate(instance);
1244
1245 proc_run(in, out, instance);
1246
1247 lilv_instance_deactivate(instance);
1248
1249 if (sf_close(out) != 0)
1250 PRINTF_WARNING("could not close output file: %s",
1251 sf_strerror(out));
1252
1253 out_open_err:
1254 proc_bufs_free();
1255
1256 bufs_err:
1257 lilv_instance_free(instance);
1258
1259 instantiate_err:
1260 in_channels_err:
1261 if (sf_close(in) != 0)
1262 PRINTF_WARNING("could not close input file: %s",
1263 sf_strerror(in));
1264
1265 in_open_err:
1266 lv2_plugin_err:
1267 lv2_fini();
1268
1269 lv2_init_err:
1270 cmd_fini();
1271
1272 return exit_code;
1273 }
1274