1 /*
2 ** Copyright (C) 1999-2019 Erik de Castro Lopo <erikd@mega-nerd.com>
3 **
4 ** All rights reserved.
5 **
6 ** Redistribution and use in source and binary forms, with or without
7 ** modification, are permitted provided that the following conditions are
8 ** met:
9 **
10 **     * Redistributions of source code must retain the above copyright
11 **       notice, this list of conditions and the following disclaimer.
12 **     * Redistributions in binary form must reproduce the above copyright
13 **       notice, this list of conditions and the following disclaimer in
14 **       the documentation and/or other materials provided with the
15 **       distribution.
16 **     * Neither the author nor the names of any contributors may be used
17 **       to endorse or promote products derived from this software without
18 **       specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 ** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 ** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27 ** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
29 ** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
30 ** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 
33 #include	<stdio.h>
34 #include	<stdlib.h>
35 #include	<string.h>
36 #include	<inttypes.h>
37 #include	<ctype.h>
38 #include	<math.h>
39 
40 #include	<sndfile.h>
41 
42 #include "common.h"
43 
44 #define	BUFFER_LEN		(1 << 16)
45 
46 #if (defined (WIN32) || defined (_WIN32))
47 #include <windows.h>
48 #endif
49 
50 static void usage_exit (const char *progname) ;
51 
52 static void info_dump (const char *filename) ;
53 static int	instrument_dump (const char *filename) ;
54 static int	broadcast_dump (const char *filename) ;
55 static int	chanmap_dump (const char *filename) ;
56 static int	cart_dump (const char *filename) ;
57 static void total_dump (void) ;
58 
59 static double total_seconds = 0.0 ;
60 
61 int
main(int argc,char * argv[])62 main (int argc, char *argv [])
63 {	int	k ;
64 
65 	if (argc < 2 || strcmp (argv [1], "--help") == 0 || strcmp (argv [1], "-h") == 0)
66 		usage_exit (program_name (argv [0])) ;
67 
68 	if (strcmp (argv [1], "--instrument") == 0)
69 	{	int error = 0 ;
70 
71 		for (k = 2 ; k < argc ; k++)
72 			error += instrument_dump (argv [k]) ;
73 		return error ;
74 		} ;
75 
76 	if (strcmp (argv [1], "--broadcast") == 0)
77 	{	int error = 0 ;
78 
79 		for (k = 2 ; k < argc ; k++)
80 			error += broadcast_dump (argv [k]) ;
81 		return error ;
82 		} ;
83 
84 	if (strcmp (argv [1], "--channel-map") == 0)
85 	{	int error = 0 ;
86 
87 		for (k = 2 ; k < argc ; k++)
88 			error += chanmap_dump (argv [k]) ;
89 		return error ;
90 		} ;
91 
92 	if (strcmp (argv [1], "--cart") == 0)
93 	{	int error = 0 ;
94 
95 		for (k = 2 ; k < argc ; k++)
96 			error += cart_dump (argv [k]) ;
97 		return error ;
98 		} ;
99 
100 	for (k = 1 ; k < argc ; k++)
101 		info_dump (argv [k]) ;
102 
103 	if (argc > 2)
104 		total_dump () ;
105 
106 	return 0 ;
107 } /* main */
108 
109 /*==============================================================================
110 **	Print version and usage.
111 */
112 
113 static double	data [BUFFER_LEN] ;
114 
115 static void
usage_exit(const char * progname)116 usage_exit (const char *progname)
117 {	printf ("Usage :\n  %s <file> ...\n", progname) ;
118 	printf ("    Prints out information about one or more sound files.\n\n") ;
119 	printf ("  %s --instrument <file>\n", progname) ;
120 	printf ("    Prints out the instrument data for the given file.\n\n") ;
121 	printf ("  %s --broadcast <file>\n", progname) ;
122 	printf ("    Prints out the broadcast WAV info for the given file.\n\n") ;
123 	printf ("  %s --channel-map <file>\n", progname) ;
124 	printf ("    Prints out the channel map for the given file.\n\n") ;
125 	printf ("  %s --cart <file>\n", progname) ;
126 	printf ("    Prints out the cart chunk WAV info for the given file.\n\n") ;
127 
128 	printf ("Using %s.\n\n", sf_version_string ()) ;
129 #if (defined (_WIN32) || defined (WIN32))
130 		printf ("This is a Unix style command line application which\n"
131 				"should be run in a MSDOS box or Command Shell window.\n\n") ;
132 		printf ("Sleeping for 5 seconds before exiting.\n\n") ;
133 		fflush (stdout) ;
134 
135 		Sleep (5 * 1000) ;
136 #endif
137 	exit (1) ;
138 } /* usage_exit */
139 
140 /*==============================================================================
141 **	Dumping of sndfile info.
142 */
143 
144 static double	data [BUFFER_LEN] ;
145 
146 static double
calc_decibels(SF_INFO * sfinfo,double max)147 calc_decibels (SF_INFO * sfinfo, double max)
148 {	double decibels ;
149 
150 	switch (sfinfo->format & SF_FORMAT_SUBMASK)
151 	{	case SF_FORMAT_PCM_U8 :
152 		case SF_FORMAT_PCM_S8 :
153 			decibels = max / 0x80 ;
154 			break ;
155 
156 		case SF_FORMAT_PCM_16 :
157 			decibels = max / 0x8000 ;
158 			break ;
159 
160 		case SF_FORMAT_PCM_24 :
161 			decibels = max / 0x800000 ;
162 			break ;
163 
164 		case SF_FORMAT_PCM_32 :
165 			decibels = max / 0x80000000 ;
166 			break ;
167 
168 		case SF_FORMAT_FLOAT :
169 		case SF_FORMAT_DOUBLE :
170 			decibels = max / 1.0 ;
171 			break ;
172 
173 		default :
174 			decibels = max / 0x8000 ;
175 			break ;
176 		} ;
177 
178 	return 20.0 * log10 (decibels) ;
179 } /* calc_decibels */
180 
181 static const char *
format_duration_str(double seconds)182 format_duration_str (double seconds)
183 {	static char str [128] ;
184 	int hrs, min ;
185 	double sec ;
186 
187 	memset (str, 0, sizeof (str)) ;
188 
189 	hrs = (int) (seconds / 3600.0) ;
190 	min = (int) ((seconds - (hrs * 3600.0)) / 60.0) ;
191 	sec = seconds - (hrs * 3600.0) - (min * 60.0) ;
192 
193 	snprintf (str, sizeof (str) - 1, "%02d:%02d:%06.3f", hrs, min, sec) ;
194 
195 	return str ;
196 } /* format_duration_str */
197 
198 static const char *
generate_duration_str(SF_INFO * sfinfo)199 generate_duration_str (SF_INFO *sfinfo)
200 {
201 	double seconds ;
202 
203 	if (sfinfo->samplerate < 1)
204 		return NULL ;
205 
206 	if (sfinfo->frames / sfinfo->samplerate > 0x7FFFFFFF)
207 		return "unknown" ;
208 
209 	seconds = (1.0 * sfinfo->frames) / sfinfo->samplerate ;
210 
211 	/* Accumulate the total of all known file durations */
212 	total_seconds += seconds ;
213 
214 	return format_duration_str (seconds) ;
215 } /* generate_duration_str */
216 
217 static void
info_dump(const char * filename)218 info_dump (const char *filename)
219 {	static	char	strbuffer [BUFFER_LEN] ;
220 	SNDFILE		*file ;
221 	SF_INFO		sfinfo ;
222 	double		signal_max, decibels ;
223 
224 	memset (&sfinfo, 0, sizeof (sfinfo)) ;
225 
226 	if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
227 	{	printf ("Error : Not able to open input file %s.\n", filename) ;
228 		fflush (stdout) ;
229 		memset (data, 0, sizeof (data)) ;
230 		sf_command (file, SFC_GET_LOG_INFO, strbuffer, BUFFER_LEN) ;
231 		puts (strbuffer) ;
232 		puts (sf_strerror (NULL)) ;
233 		return ;
234 		} ;
235 
236 	printf ("========================================\n") ;
237 	sf_command (file, SFC_GET_LOG_INFO, strbuffer, BUFFER_LEN) ;
238 	puts (strbuffer) ;
239 	printf ("----------------------------------------\n") ;
240 
241 	printf ("Sample Rate : %d\n", sfinfo.samplerate) ;
242 
243 	if (sfinfo.frames == SF_COUNT_MAX)
244 		printf ("Frames      : unknown\n") ;
245 	else
246 		printf ("Frames      : %" PRId64 "\n", sfinfo.frames) ;
247 
248 	printf ("Channels    : %d\n", sfinfo.channels) ;
249 	printf ("Format      : 0x%08X\n", sfinfo.format) ;
250 	printf ("Sections    : %d\n", sfinfo.sections) ;
251 	printf ("Seekable    : %s\n", (sfinfo.seekable ? "TRUE" : "FALSE")) ;
252 	printf ("Duration    : %s\n", generate_duration_str (&sfinfo)) ;
253 
254 	if (sfinfo.frames < 100 * 1024 * 1024)
255 	{	/* Do not use sf_signal_max because it doesn't work for non-seekable files . */
256 		sf_command (file, SFC_CALC_SIGNAL_MAX, &signal_max, sizeof (signal_max)) ;
257 		decibels = calc_decibels (&sfinfo, signal_max) ;
258 		printf ("Signal Max  : %g (%4.2f dB)\n", signal_max, decibels) ;
259 		} ;
260 	putchar ('\n') ;
261 
262 	sf_close (file) ;
263 
264 } /* info_dump */
265 
266 /*==============================================================================
267 **	Dumping of SF_INSTRUMENT data.
268 */
269 
270 static const char *
str_of_type(int mode)271 str_of_type (int mode)
272 {	switch (mode)
273 	{	case SF_LOOP_NONE : return "none" ;
274 		case SF_LOOP_FORWARD : return "fwd " ;
275 		case SF_LOOP_BACKWARD : return "back" ;
276 		case SF_LOOP_ALTERNATING : return "alt " ;
277 		default : break ;
278 		} ;
279 
280 	return "????" ;
281 } /* str_of_mode */
282 
283 static int
instrument_dump(const char * filename)284 instrument_dump (const char *filename)
285 {	SNDFILE	*file ;
286 	SF_INFO	sfinfo ;
287 	SF_INSTRUMENT inst ;
288 	int got_inst, k ;
289 
290 	memset (&sfinfo, 0, sizeof (sfinfo)) ;
291 
292 	if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
293 	{	printf ("Error : Not able to open input file %s.\n", filename) ;
294 		fflush (stdout) ;
295 		memset (data, 0, sizeof (data)) ;
296 		puts (sf_strerror (NULL)) ;
297 		return 1 ;
298 		} ;
299 
300 	got_inst = sf_command (file, SFC_GET_INSTRUMENT, &inst, sizeof (inst)) ;
301 	sf_close (file) ;
302 
303 	if (got_inst == SF_FALSE)
304 	{	printf ("Error : File '%s' does not contain instrument data.\n\n", filename) ;
305 		return 1 ;
306 		} ;
307 
308 	printf ("Instrument : %s\n\n", filename) ;
309 	printf ("  Gain        : %d\n", inst.gain) ;
310 	printf ("  Base note   : %d\n", inst.basenote) ;
311 	printf ("  Velocity    : %d - %d\n", (int) inst.velocity_lo, (int) inst.velocity_hi) ;
312 	printf ("  Key         : %d - %d\n", (int) inst.key_lo, (int) inst.key_hi) ;
313 	printf ("  Loop points : %d\n", inst.loop_count) ;
314 
315 	for (k = 0 ; k < inst.loop_count ; k++)
316 		printf ("  %-2d    Mode : %s    Start : %6d   End : %6d   Count : %6d\n", k, str_of_type (inst.loops [k].mode), inst.loops [k].start, inst.loops [k].end, inst.loops [k].count) ;
317 
318 	putchar ('\n') ;
319 	return 0 ;
320 } /* instrument_dump */
321 
322 static int
broadcast_dump(const char * filename)323 broadcast_dump (const char *filename)
324 {	SNDFILE	*file ;
325 	SF_INFO	sfinfo ;
326 	SF_BROADCAST_INFO_2K bext ;
327 	double time_ref_sec ;
328 	int got_bext ;
329 
330 	memset (&sfinfo, 0, sizeof (sfinfo)) ;
331 
332 	if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
333 	{	printf ("Error : Not able to open input file %s.\n", filename) ;
334 		fflush (stdout) ;
335 		memset (data, 0, sizeof (data)) ;
336 		puts (sf_strerror (NULL)) ;
337 		return 1 ;
338 		} ;
339 
340 	memset (&bext, 0, sizeof (SF_BROADCAST_INFO_2K)) ;
341 
342 	got_bext = sf_command (file, SFC_GET_BROADCAST_INFO, &bext, sizeof (bext)) ;
343 	sf_close (file) ;
344 
345 	if (got_bext == SF_FALSE)
346 	{	printf ("Error : File '%s' does not contain broadcast information.\n\n", filename) ;
347 		return 1 ;
348 		} ;
349 
350 	/*
351 	**	From : http://www.ebu.ch/en/technical/publications/userguides/bwf_user_guide.php
352 	**
353 	**	Time Reference:
354 	**		This field is a count from midnight in samples to the first sample
355 	**		of the audio sequence.
356 	*/
357 
358 	time_ref_sec = ((pow (2.0, 32) * bext.time_reference_high) + (1.0 * bext.time_reference_low)) / sfinfo.samplerate ;
359 
360 	printf ("Description              : %.*s\n", (int) sizeof (bext.description), bext.description) ;
361 	printf ("Originator               : %.*s\n", (int) sizeof (bext.originator), bext.originator) ;
362 	printf ("Origination ref          : %.*s\n", (int) sizeof (bext.originator_reference), bext.originator_reference) ;
363 	printf ("Origination date         : %.*s\n", (int) sizeof (bext.origination_date), bext.origination_date) ;
364 	printf ("Origination time         : %.*s\n", (int) sizeof (bext.origination_time), bext.origination_time) ;
365 
366 	if (bext.time_reference_high == 0 && bext.time_reference_low == 0)
367 		printf ("Time ref                 : 0\n") ;
368 	else
369 		printf ("Time ref                 : 0x%x%08x (%.6f seconds)\n", bext.time_reference_high, bext.time_reference_low, time_ref_sec) ;
370 
371 	printf ("BWF version              : %d\n", bext.version) ;
372 
373 	if (bext.version >= 1)
374 		printf ("UMID                     : %.*s\n", (int) sizeof (bext.umid), bext.umid) ;
375 
376 	if (bext.version >= 2)
377 	{	/* 0x7fff shall be used to designate an unused value */
378 		/* valid range: -99.99 .. 99.99 */
379 		printf ("Loudness value           : %6.2f LUFS\n", bext.loudness_value / 100.0) ;
380 		/* valid range: 0.00 .. 99.99 */
381 		printf ("Loudness range           : %6.2f LU\n", bext.loudness_range / 100.0) ;
382 		/* valid range: -99.99 .. 99.99 */
383 		printf ("Max. true peak level     : %6.2f dBTP\n", bext.max_true_peak_level / 100.0) ;
384 		printf ("Max. momentary loudness  : %6.2f LUFS\n", bext.max_momentary_loudness / 100.0) ;
385 		printf ("Max. short term loudness : %6.2f LUFS\n", bext.max_shortterm_loudness / 100.0) ;
386 		} ;
387 
388 	printf ("Coding history           : %.*s\n", bext.coding_history_size, bext.coding_history) ;
389 
390 	return 0 ;
391 } /* broadcast_dump */
392 
393 static int
chanmap_dump(const char * filename)394 chanmap_dump (const char *filename)
395 {	SNDFILE	*file ;
396 	SF_INFO	sfinfo ;
397 	int * channel_map ;
398 	int got_chanmap, k ;
399 
400 	memset (&sfinfo, 0, sizeof (sfinfo)) ;
401 
402 	if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
403 	{	printf ("Error : Not able to open input file %s.\n", filename) ;
404 		fflush (stdout) ;
405 		memset (data, 0, sizeof (data)) ;
406 		puts (sf_strerror (NULL)) ;
407 		return 1 ;
408 		} ;
409 
410 	if ((channel_map = calloc (sfinfo.channels, sizeof (int))) == NULL)
411 	{	printf ("Error : malloc failed.\n\n") ;
412 		return 1 ;
413 		} ;
414 
415 	got_chanmap = sf_command (file, SFC_GET_CHANNEL_MAP_INFO, channel_map, sfinfo.channels * sizeof (int)) ;
416 	sf_close (file) ;
417 
418 	if (got_chanmap == SF_FALSE)
419 	{	printf ("Error : File '%s' does not contain channel map information.\n\n", filename) ;
420 		free (channel_map) ;
421 		return 1 ;
422 		} ;
423 
424 	printf ("File : %s\n\n", filename) ;
425 
426 	puts ("    Chan    Position") ;
427 	for (k = 0 ; k < sfinfo.channels ; k ++)
428 	{	const char * name ;
429 
430 #define CASE_NAME(x)	case x : name = #x ; break ;
431 		switch (channel_map [k])
432 		{	CASE_NAME (SF_CHANNEL_MAP_INVALID) ;
433 			CASE_NAME (SF_CHANNEL_MAP_MONO) ;
434 			CASE_NAME (SF_CHANNEL_MAP_LEFT) ;
435 			CASE_NAME (SF_CHANNEL_MAP_RIGHT) ;
436 			CASE_NAME (SF_CHANNEL_MAP_CENTER) ;
437 			CASE_NAME (SF_CHANNEL_MAP_FRONT_LEFT) ;
438 			CASE_NAME (SF_CHANNEL_MAP_FRONT_RIGHT) ;
439 			CASE_NAME (SF_CHANNEL_MAP_FRONT_CENTER) ;
440 			CASE_NAME (SF_CHANNEL_MAP_REAR_CENTER) ;
441 			CASE_NAME (SF_CHANNEL_MAP_REAR_LEFT) ;
442 			CASE_NAME (SF_CHANNEL_MAP_REAR_RIGHT) ;
443 			CASE_NAME (SF_CHANNEL_MAP_LFE) ;
444 			CASE_NAME (SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER) ;
445 			CASE_NAME (SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER) ;
446 			CASE_NAME (SF_CHANNEL_MAP_SIDE_LEFT) ;
447 			CASE_NAME (SF_CHANNEL_MAP_SIDE_RIGHT) ;
448 			CASE_NAME (SF_CHANNEL_MAP_TOP_CENTER) ;
449 			CASE_NAME (SF_CHANNEL_MAP_TOP_FRONT_LEFT) ;
450 			CASE_NAME (SF_CHANNEL_MAP_TOP_FRONT_RIGHT) ;
451 			CASE_NAME (SF_CHANNEL_MAP_TOP_FRONT_CENTER) ;
452 			CASE_NAME (SF_CHANNEL_MAP_TOP_REAR_LEFT) ;
453 			CASE_NAME (SF_CHANNEL_MAP_TOP_REAR_RIGHT) ;
454 			CASE_NAME (SF_CHANNEL_MAP_TOP_REAR_CENTER) ;
455 			CASE_NAME (SF_CHANNEL_MAP_MAX) ;
456 			default : name = "default" ;
457 				break ;
458 			} ;
459 
460 		printf ("    %3d     %s\n", k, name) ;
461 		} ;
462 
463 	putchar ('\n') ;
464 	free (channel_map) ;
465 
466 	return 0 ;
467 } /* chanmap_dump */
468 
469 static int
cart_dump(const char * filename)470 cart_dump (const char *filename)
471 {	SNDFILE	*file ;
472 	SF_INFO	sfinfo ;
473 	SF_CART_INFO_VAR (1024) cart ;
474 	int got_cart, k ;
475 
476 	memset (&sfinfo, 0, sizeof (sfinfo)) ;
477 	memset (&cart, 0, sizeof (cart)) ;
478 
479 	if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
480 	{	printf ("Error : Not able to open input file %s.\n", filename) ;
481 		fflush (stdout) ;
482 		memset (data, 0, sizeof (data)) ;
483 		puts (sf_strerror (NULL)) ;
484 		return 1 ;
485 		} ;
486 
487 	got_cart = sf_command (file, SFC_GET_CART_INFO, &cart, sizeof (cart)) ;
488 	sf_close (file) ;
489 
490 	if (got_cart == SF_FALSE)
491 	{	printf ("Error : File '%s' does not contain cart information.\n\n", filename) ;
492 		return 1 ;
493 		} ;
494 
495 	printf ("Version        : %.*s\n", (int) sizeof (cart.version), cart.version) ;
496 	printf ("Title          : %.*s\n", (int) sizeof (cart.title), cart.title) ;
497 	printf ("Artist         : %.*s\n", (int) sizeof (cart.artist), cart.artist) ;
498 	printf ("Cut id         : %.*s\n", (int) sizeof (cart.cut_id), cart.cut_id) ;
499 	printf ("Category       : %.*s\n", (int) sizeof (cart.category), cart.category) ;
500 	printf ("Classification : %.*s\n", (int) sizeof (cart.classification), cart.classification) ;
501 	printf ("Out cue        : %.*s\n", (int) sizeof (cart.out_cue), cart.out_cue) ;
502 	printf ("Start date     : %.*s\n", (int) sizeof (cart.start_date), cart.start_date) ;
503 	printf ("Start time     : %.*s\n", (int) sizeof (cart.start_time), cart.start_time) ;
504 	printf ("End date       : %.*s\n", (int) sizeof (cart.end_date), cart.end_date) ;
505 	printf ("End time       : %.*s\n", (int) sizeof (cart.end_time), cart.end_time) ;
506 	printf ("App id         : %.*s\n", (int) sizeof (cart.producer_app_id), cart.producer_app_id) ;
507 	printf ("App version    : %.*s\n", (int) sizeof (cart.producer_app_version), cart.producer_app_version) ;
508 	printf ("User defined   : %.*s\n", (int) sizeof (cart.user_def), cart.user_def) ;
509 	printf ("Level ref.     : %d\n", cart.level_reference) ;
510 	printf ("Post timers    :\n") ;
511 
512 	for (k = 0 ; k < ARRAY_LEN (cart.post_timers) ; k++)
513 		if (cart.post_timers [k].usage [0])
514 			printf ("  %d   %.*s    %d\n", k, (int) sizeof (cart.post_timers [k].usage), cart.post_timers [k].usage, cart.post_timers [k].value) ;
515 
516 	printf ("Reserved       : %.*s\n", (int) sizeof (cart.reserved), cart.reserved) ;
517 	printf ("Url            : %.*s\n", (int) sizeof (cart.url), cart.url) ;
518 	printf ("Tag text       : %.*s\n", cart.tag_text_size, cart.tag_text) ;
519 
520 	return 0 ;
521 } /* cart_dump */
522 
523 static void
total_dump(void)524 total_dump (void)
525 {	printf ("========================================\n") ;
526 	printf ("Total Duration : %s\n", format_duration_str (total_seconds)) ;
527 } /* total_dump */
528