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