| /* | |
| ** Copyright (C) 1999-2019 Erik de Castro Lopo <erikd@mega-nerd.com> | |
| ** | |
| ** All rights reserved. | |
| ** | |
| ** Redistribution and use in source and binary forms, with or without | |
| ** modification, are permitted provided that the following conditions are | |
| ** met: | |
| ** | |
| ** * Redistributions of source code must retain the above copyright | |
| ** notice, this list of conditions and the following disclaimer. | |
| ** * Redistributions in binary form must reproduce the above copyright | |
| ** notice, this list of conditions and the following disclaimer in | |
| ** the documentation and/or other materials provided with the | |
| ** distribution. | |
| ** * Neither the author nor the names of any contributors may be used | |
| ** to endorse or promote products derived from this software without | |
| ** specific prior written permission. | |
| ** | |
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
| ** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| ** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
| ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
| ** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
| ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
| ** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
| ** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <inttypes.h> | |
| #include <ctype.h> | |
| #include <math.h> | |
| #include <sndfile.h> | |
| #include "common.h" | |
| #define BUFFER_LEN (1 << 16) | |
| #if (defined (WIN32) || defined (_WIN32)) | |
| #include <windows.h> | |
| #endif | |
| static void usage_exit (const char *progname) ; | |
| static void info_dump (const char *filename) ; | |
| static int instrument_dump (const char *filename) ; | |
| static int broadcast_dump (const char *filename) ; | |
| static int chanmap_dump (const char *filename) ; | |
| static int cart_dump (const char *filename) ; | |
| static void total_dump (void) ; | |
| static double total_seconds = 0.0 ; | |
| int | |
| main (int argc, char *argv []) | |
| { int k ; | |
| if (argc < 2 || strcmp (argv [1], "--help") == 0 || strcmp (argv [1], "-h") == 0) | |
| usage_exit (program_name (argv [0])) ; | |
| if (strcmp (argv [1], "--instrument") == 0) | |
| { int error = 0 ; | |
| for (k = 2 ; k < argc ; k++) | |
| error += instrument_dump (argv [k]) ; | |
| return error ; | |
| } ; | |
| if (strcmp (argv [1], "--broadcast") == 0) | |
| { int error = 0 ; | |
| for (k = 2 ; k < argc ; k++) | |
| error += broadcast_dump (argv [k]) ; | |
| return error ; | |
| } ; | |
| if (strcmp (argv [1], "--channel-map") == 0) | |
| { int error = 0 ; | |
| for (k = 2 ; k < argc ; k++) | |
| error += chanmap_dump (argv [k]) ; | |
| return error ; | |
| } ; | |
| if (strcmp (argv [1], "--cart") == 0) | |
| { int error = 0 ; | |
| for (k = 2 ; k < argc ; k++) | |
| error += cart_dump (argv [k]) ; | |
| return error ; | |
| } ; | |
| for (k = 1 ; k < argc ; k++) | |
| info_dump (argv [k]) ; | |
| if (argc > 2) | |
| total_dump () ; | |
| return 0 ; | |
| } /* main */ | |
| /*============================================================================== | |
| ** Print version and usage. | |
| */ | |
| static void | |
| usage_exit (const char *progname) | |
| { printf ("Usage :\n %s <file> ...\n", progname) ; | |
| printf (" Prints out information about one or more sound files.\n\n") ; | |
| printf (" %s --instrument <file>\n", progname) ; | |
| printf (" Prints out the instrument data for the given file.\n\n") ; | |
| printf (" %s --broadcast <file>\n", progname) ; | |
| printf (" Prints out the broadcast WAV info for the given file.\n\n") ; | |
| printf (" %s --channel-map <file>\n", progname) ; | |
| printf (" Prints out the channel map for the given file.\n\n") ; | |
| printf (" %s --cart <file>\n", progname) ; | |
| printf (" Prints out the cart chunk WAV info for the given file.\n\n") ; | |
| printf ("Using %s.\n\n", sf_version_string ()) ; | |
| #if (defined (_WIN32) || defined (WIN32)) | |
| printf ("This is a Unix style command line application which\n" | |
| "should be run in a MSDOS box or Command Shell window.\n\n") ; | |
| printf ("Sleeping for 5 seconds before exiting.\n\n") ; | |
| fflush (stdout) ; | |
| Sleep (5 * 1000) ; | |
| #endif | |
| exit (1) ; | |
| } /* usage_exit */ | |
| /*============================================================================== | |
| ** Dumping of sndfile info. | |
| */ | |
| static double data [BUFFER_LEN] ; | |
| static double | |
| calc_decibels (SF_INFO * sfinfo, double max) | |
| { double decibels ; | |
| switch (sfinfo->format & SF_FORMAT_SUBMASK) | |
| { case SF_FORMAT_PCM_U8 : | |
| case SF_FORMAT_PCM_S8 : | |
| decibels = max / 0x80 ; | |
| break ; | |
| case SF_FORMAT_PCM_16 : | |
| decibels = max / 0x8000 ; | |
| break ; | |
| case SF_FORMAT_PCM_24 : | |
| decibels = max / 0x800000 ; | |
| break ; | |
| case SF_FORMAT_PCM_32 : | |
| decibels = max / 0x80000000 ; | |
| break ; | |
| case SF_FORMAT_FLOAT : | |
| case SF_FORMAT_DOUBLE : | |
| case SF_FORMAT_VORBIS : | |
| case SF_FORMAT_OPUS : | |
| decibels = max / 1.0 ; | |
| break ; | |
| default : | |
| decibels = max / 0x8000 ; | |
| break ; | |
| } ; | |
| return 20.0 * log10 (decibels) ; | |
| } /* calc_decibels */ | |
| static const char * | |
| format_duration_str (double seconds) | |
| { static char str [128] ; | |
| int hrs, min ; | |
| double sec ; | |
| memset (str, 0, sizeof (str)) ; | |
| hrs = (int) (seconds / 3600.0) ; | |
| min = (int) ((seconds - (hrs * 3600.0)) / 60.0) ; | |
| sec = seconds - (hrs * 3600.0) - (min * 60.0) ; | |
| snprintf (str, sizeof (str) - 1, "%02d:%02d:%06.3f", hrs, min, sec) ; | |
| return str ; | |
| } /* format_duration_str */ | |
| static const char * | |
| generate_duration_str (SF_INFO *sfinfo) | |
| { | |
| double seconds ; | |
| if (sfinfo->samplerate < 1) | |
| return NULL ; | |
| if (sfinfo->frames / sfinfo->samplerate > 0x7FFFFFFF) | |
| return "unknown" ; | |
| seconds = (1.0 * sfinfo->frames) / sfinfo->samplerate ; | |
| /* Accumulate the total of all known file durations */ | |
| total_seconds += seconds ; | |
| return format_duration_str (seconds) ; | |
| } /* generate_duration_str */ | |
| static void | |
| info_dump (const char *filename) | |
| { static char strbuffer [BUFFER_LEN] ; | |
| SNDFILE *file ; | |
| SF_INFO sfinfo ; | |
| double signal_max, decibels ; | |
| memset (&sfinfo, 0, sizeof (sfinfo)) ; | |
| if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL) | |
| { printf ("Error : Not able to open input file %s.\n", filename) ; | |
| fflush (stdout) ; | |
| memset (data, 0, sizeof (data)) ; | |
| sf_command (file, SFC_GET_LOG_INFO, strbuffer, BUFFER_LEN) ; | |
| puts (strbuffer) ; | |
| puts (sf_strerror (NULL)) ; | |
| return ; | |
| } ; | |
| printf ("========================================\n") ; | |
| sf_command (file, SFC_GET_LOG_INFO, strbuffer, BUFFER_LEN) ; | |
| puts (strbuffer) ; | |
| printf ("----------------------------------------\n") ; | |
| printf ("Sample Rate : %d\n", sfinfo.samplerate) ; | |
| if (sfinfo.frames == SF_COUNT_MAX) | |
| printf ("Frames : unknown\n") ; | |
| else | |
| printf ("Frames : %" PRId64 "\n", sfinfo.frames) ; | |
| printf ("Channels : %d\n", sfinfo.channels) ; | |
| printf ("Format : 0x%08X\n", sfinfo.format) ; | |
| printf ("Sections : %d\n", sfinfo.sections) ; | |
| printf ("Seekable : %s\n", (sfinfo.seekable ? "TRUE" : "FALSE")) ; | |
| printf ("Duration : %s\n", generate_duration_str (&sfinfo)) ; | |
| if (sfinfo.frames < 100 * 1024 * 1024) | |
| { /* Do not use sf_signal_max because it doesn't work for non-seekable files . */ | |
| sf_command (file, SFC_CALC_SIGNAL_MAX, &signal_max, sizeof (signal_max)) ; | |
| decibels = calc_decibels (&sfinfo, signal_max) ; | |
| printf ("Signal Max : %g (%4.2f dB)\n", signal_max, decibels) ; | |
| } ; | |
| putchar ('\n') ; | |
| sf_close (file) ; | |
| } /* info_dump */ | |
| /*============================================================================== | |
| ** Dumping of SF_INSTRUMENT data. | |
| */ | |
| static const char * | |
| str_of_type (int mode) | |
| { switch (mode) | |
| { case SF_LOOP_NONE : return "none" ; | |
| case SF_LOOP_FORWARD : return "fwd " ; | |
| case SF_LOOP_BACKWARD : return "back" ; | |
| case SF_LOOP_ALTERNATING : return "alt " ; | |
| default : break ; | |
| } ; | |
| return "????" ; | |
| } /* str_of_mode */ | |
| static int | |
| instrument_dump (const char *filename) | |
| { SNDFILE *file ; | |
| SF_INFO sfinfo ; | |
| SF_INSTRUMENT inst ; | |
| int got_inst, k ; | |
| memset (&sfinfo, 0, sizeof (sfinfo)) ; | |
| if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL) | |
| { printf ("Error : Not able to open input file %s.\n", filename) ; | |
| fflush (stdout) ; | |
| memset (data, 0, sizeof (data)) ; | |
| puts (sf_strerror (NULL)) ; | |
| return 1 ; | |
| } ; | |
| got_inst = sf_command (file, SFC_GET_INSTRUMENT, &inst, sizeof (inst)) ; | |
| sf_close (file) ; | |
| if (got_inst == SF_FALSE) | |
| { printf ("Error : File '%s' does not contain instrument data.\n\n", filename) ; | |
| return 1 ; | |
| } ; | |
| printf ("Instrument : %s\n\n", filename) ; | |
| printf (" Gain : %d\n", inst.gain) ; | |
| printf (" Base note : %d\n", inst.basenote) ; | |
| printf (" Velocity : %d - %d\n", (int) inst.velocity_lo, (int) inst.velocity_hi) ; | |
| printf (" Key : %d - %d\n", (int) inst.key_lo, (int) inst.key_hi) ; | |
| printf (" Loop points : %d\n", inst.loop_count) ; | |
| for (k = 0 ; k < inst.loop_count ; k++) | |
| printf (" %-2d Mode : %s Start : %6" PRIu32 " End : %6" PRIu32 | |
| " Count : %6" PRIu32 "\n", k, str_of_type (inst.loops [k].mode), | |
| inst.loops [k].start, inst.loops [k].end, inst.loops [k].count) ; | |
| putchar ('\n') ; | |
| return 0 ; | |
| } /* instrument_dump */ | |
| static int | |
| broadcast_dump (const char *filename) | |
| { SNDFILE *file ; | |
| SF_INFO sfinfo ; | |
| SF_BROADCAST_INFO_2K bext ; | |
| double time_ref_sec ; | |
| int got_bext ; | |
| memset (&sfinfo, 0, sizeof (sfinfo)) ; | |
| if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL) | |
| { printf ("Error : Not able to open input file %s.\n", filename) ; | |
| fflush (stdout) ; | |
| memset (data, 0, sizeof (data)) ; | |
| puts (sf_strerror (NULL)) ; | |
| return 1 ; | |
| } ; | |
| memset (&bext, 0, sizeof (SF_BROADCAST_INFO_2K)) ; | |
| got_bext = sf_command (file, SFC_GET_BROADCAST_INFO, &bext, sizeof (bext)) ; | |
| sf_close (file) ; | |
| if (got_bext == SF_FALSE) | |
| { printf ("Error : File '%s' does not contain broadcast information.\n\n", filename) ; | |
| return 1 ; | |
| } ; | |
| /* | |
| ** From : http://www.ebu.ch/en/technical/publications/userguides/bwf_user_guide.php | |
| ** | |
| ** Time Reference: | |
| ** This field is a count from midnight in samples to the first sample | |
| ** of the audio sequence. | |
| */ | |
| time_ref_sec = ((pow (2.0, 32) * bext.time_reference_high) + (1.0 * bext.time_reference_low)) / sfinfo.samplerate ; | |
| printf ("Description : %.*s\n", (int) sizeof (bext.description), bext.description) ; | |
| printf ("Originator : %.*s\n", (int) sizeof (bext.originator), bext.originator) ; | |
| printf ("Origination ref : %.*s\n", (int) sizeof (bext.originator_reference), bext.originator_reference) ; | |
| printf ("Origination date : %.*s\n", (int) sizeof (bext.origination_date), bext.origination_date) ; | |
| printf ("Origination time : %.*s\n", (int) sizeof (bext.origination_time), bext.origination_time) ; | |
| if (bext.time_reference_high == 0 && bext.time_reference_low == 0) | |
| printf ("Time ref : 0\n") ; | |
| else | |
| printf ("Time ref : 0x%x%08x (%.6f seconds)\n", bext.time_reference_high, bext.time_reference_low, time_ref_sec) ; | |
| printf ("BWF version : %d\n", bext.version) ; | |
| if (bext.version >= 1) | |
| printf ("UMID : %.*s\n", (int) sizeof (bext.umid), bext.umid) ; | |
| if (bext.version >= 2) | |
| { /* 0x7fff shall be used to designate an unused value */ | |
| /* valid range: -99.99 .. 99.99 */ | |
| printf ("Loudness value : %6.2f LUFS\n", bext.loudness_value / 100.0) ; | |
| /* valid range: 0.00 .. 99.99 */ | |
| printf ("Loudness range : %6.2f LU\n", bext.loudness_range / 100.0) ; | |
| /* valid range: -99.99 .. 99.99 */ | |
| printf ("Max. true peak level : %6.2f dBTP\n", bext.max_true_peak_level / 100.0) ; | |
| printf ("Max. momentary loudness : %6.2f LUFS\n", bext.max_momentary_loudness / 100.0) ; | |
| printf ("Max. short term loudness : %6.2f LUFS\n", bext.max_shortterm_loudness / 100.0) ; | |
| } ; | |
| printf ("Coding history : %.*s\n", bext.coding_history_size, bext.coding_history) ; | |
| return 0 ; | |
| } /* broadcast_dump */ | |
| static int | |
| chanmap_dump (const char *filename) | |
| { SNDFILE *file ; | |
| SF_INFO sfinfo ; | |
| int * channel_map ; | |
| int got_chanmap, k ; | |
| memset (&sfinfo, 0, sizeof (sfinfo)) ; | |
| if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL) | |
| { printf ("Error : Not able to open input file %s.\n", filename) ; | |
| fflush (stdout) ; | |
| memset (data, 0, sizeof (data)) ; | |
| puts (sf_strerror (NULL)) ; | |
| return 1 ; | |
| } ; | |
| if ((channel_map = calloc (sfinfo.channels, sizeof (int))) == NULL) | |
| { printf ("Error : malloc failed.\n\n") ; | |
| return 1 ; | |
| } ; | |
| got_chanmap = sf_command (file, SFC_GET_CHANNEL_MAP_INFO, channel_map, sfinfo.channels * sizeof (int)) ; | |
| sf_close (file) ; | |
| if (got_chanmap == SF_FALSE) | |
| { printf ("Error : File '%s' does not contain channel map information.\n\n", filename) ; | |
| free (channel_map) ; | |
| return 1 ; | |
| } ; | |
| printf ("File : %s\n\n", filename) ; | |
| puts (" Chan Position") ; | |
| for (k = 0 ; k < sfinfo.channels ; k ++) | |
| { const char * name ; | |
| #define CASE_NAME(x) case x : name = #x ; break ; | |
| switch (channel_map [k]) | |
| { CASE_NAME (SF_CHANNEL_MAP_INVALID) ; | |
| CASE_NAME (SF_CHANNEL_MAP_MONO) ; | |
| CASE_NAME (SF_CHANNEL_MAP_LEFT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_RIGHT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_FRONT_LEFT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_FRONT_RIGHT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_FRONT_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_REAR_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_REAR_LEFT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_REAR_RIGHT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_LFE) ; | |
| CASE_NAME (SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_SIDE_LEFT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_SIDE_RIGHT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_FRONT_LEFT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_FRONT_RIGHT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_FRONT_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_REAR_LEFT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_REAR_RIGHT) ; | |
| CASE_NAME (SF_CHANNEL_MAP_TOP_REAR_CENTER) ; | |
| CASE_NAME (SF_CHANNEL_MAP_MAX) ; | |
| default : name = "default" ; | |
| break ; | |
| } ; | |
| printf (" %3d %s\n", k, name) ; | |
| } ; | |
| putchar ('\n') ; | |
| free (channel_map) ; | |
| return 0 ; | |
| } /* chanmap_dump */ | |
| static int | |
| cart_dump (const char *filename) | |
| { SNDFILE *file ; | |
| SF_INFO sfinfo ; | |
| SF_CART_INFO_VAR (1024) cart ; | |
| int got_cart, k ; | |
| memset (&sfinfo, 0, sizeof (sfinfo)) ; | |
| memset (&cart, 0, sizeof (cart)) ; | |
| if ((file = sf_open (filename, SFM_READ, &sfinfo)) == NULL) | |
| { printf ("Error : Not able to open input file %s.\n", filename) ; | |
| fflush (stdout) ; | |
| memset (data, 0, sizeof (data)) ; | |
| puts (sf_strerror (NULL)) ; | |
| return 1 ; | |
| } ; | |
| got_cart = sf_command (file, SFC_GET_CART_INFO, &cart, sizeof (cart)) ; | |
| sf_close (file) ; | |
| if (got_cart == SF_FALSE) | |
| { printf ("Error : File '%s' does not contain cart information.\n\n", filename) ; | |
| return 1 ; | |
| } ; | |
| printf ("Version : %.*s\n", (int) sizeof (cart.version), cart.version) ; | |
| printf ("Title : %.*s\n", (int) sizeof (cart.title), cart.title) ; | |
| printf ("Artist : %.*s\n", (int) sizeof (cart.artist), cart.artist) ; | |
| printf ("Cut id : %.*s\n", (int) sizeof (cart.cut_id), cart.cut_id) ; | |
| printf ("Category : %.*s\n", (int) sizeof (cart.category), cart.category) ; | |
| printf ("Classification : %.*s\n", (int) sizeof (cart.classification), cart.classification) ; | |
| printf ("Out cue : %.*s\n", (int) sizeof (cart.out_cue), cart.out_cue) ; | |
| printf ("Start date : %.*s\n", (int) sizeof (cart.start_date), cart.start_date) ; | |
| printf ("Start time : %.*s\n", (int) sizeof (cart.start_time), cart.start_time) ; | |
| printf ("End date : %.*s\n", (int) sizeof (cart.end_date), cart.end_date) ; | |
| printf ("End time : %.*s\n", (int) sizeof (cart.end_time), cart.end_time) ; | |
| printf ("App id : %.*s\n", (int) sizeof (cart.producer_app_id), cart.producer_app_id) ; | |
| printf ("App version : %.*s\n", (int) sizeof (cart.producer_app_version), cart.producer_app_version) ; | |
| printf ("User defined : %.*s\n", (int) sizeof (cart.user_def), cart.user_def) ; | |
| printf ("Level ref. : %d\n", cart.level_reference) ; | |
| printf ("Post timers :\n") ; | |
| for (k = 0 ; k < ARRAY_LEN (cart.post_timers) ; k++) | |
| if (cart.post_timers [k].usage [0]) | |
| printf (" %d %.*s %d\n", k, (int) sizeof (cart.post_timers [k].usage), cart.post_timers [k].usage, cart.post_timers [k].value) ; | |
| printf ("Reserved : %.*s\n", (int) sizeof (cart.reserved), cart.reserved) ; | |
| printf ("Url : %.*s\n", (int) sizeof (cart.url), cart.url) ; | |
| printf ("Tag text : %.*s\n", cart.tag_text_size, cart.tag_text) ; | |
| return 0 ; | |
| } /* cart_dump */ | |
| static void | |
| total_dump (void) | |
| { printf ("========================================\n") ; | |
| printf ("Total Duration : %s\n", format_duration_str (total_seconds)) ; | |
| } /* total_dump */ |