2008-02-04 13:11:40 +01:00
|
|
|
#define POSIXLY_CORRECT
|
2015-08-30 21:37:34 +02:00
|
|
|
#include <getopt.h>
|
2008-01-31 07:15:03 +01:00
|
|
|
#include "../src/vgmstream.h"
|
2018-11-03 19:29:45 +01:00
|
|
|
#include "../src/plugins.h"
|
2008-01-31 07:15:03 +01:00
|
|
|
#include "../src/util.h"
|
2008-07-01 21:20:09 +02:00
|
|
|
#ifdef WIN32
|
|
|
|
#include <io.h>
|
|
|
|
#include <fcntl.h>
|
2015-08-30 21:37:34 +02:00
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef STDOUT_FILENO
|
|
|
|
#define STDOUT_FILENO 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef VERSION
|
2019-03-17 19:36:55 +01:00
|
|
|
#include "version.h"
|
2017-01-15 23:09:37 +01:00
|
|
|
#endif
|
|
|
|
#ifndef VERSION
|
|
|
|
#define VERSION "(unknown version)"
|
2015-08-30 21:37:34 +02:00
|
|
|
#endif
|
2008-01-31 07:15:03 +01:00
|
|
|
|
2019-10-14 00:32:07 +02:00
|
|
|
/* low values are ok as there is very little performance difference, but higher
|
|
|
|
* may improve write I/O in some systems as this*channels doubles as output buffer */
|
|
|
|
#define SAMPLE_BUFFER_SIZE 32768
|
2008-01-31 07:15:03 +01:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
/* getopt globals (the horror...) */
|
2020-07-21 19:46:55 +02:00
|
|
|
extern char* optarg;
|
2008-02-04 13:11:40 +01:00
|
|
|
extern int optind, opterr, optopt;
|
|
|
|
|
2017-01-14 00:10:45 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static size_t make_wav_header(uint8_t* buf, size_t buf_size, int32_t sample_count, int32_t sample_rate, int channels, int smpl_chunk, int32_t loop_start, int32_t loop_end);
|
2017-01-14 00:10:45 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static void usage(const char* name, int is_full) {
|
2018-01-19 00:39:04 +01:00
|
|
|
fprintf(stderr,"vgmstream CLI decoder " VERSION " " __DATE__ "\n"
|
2018-09-07 00:21:17 +02:00
|
|
|
"Usage: %s [-o outfile.wav] [options] infile\n"
|
|
|
|
"Options:\n"
|
2020-07-26 11:20:49 +02:00
|
|
|
" -o <outfile.wav>: name of output .wav file, default <infile>.wav\n"
|
2018-09-07 00:21:17 +02:00
|
|
|
" -l loop count: loop count, default 2.0\n"
|
|
|
|
" -f fade time: fade time in seconds after N loops, default 10.0\n"
|
|
|
|
" -d fade delay: fade delay in seconds, default 0.0\n"
|
|
|
|
" -F: don't fade after N loops and play the rest of the stream\n"
|
|
|
|
" -i: ignore looping information and play the whole stream once\n"
|
|
|
|
" -e: force end-to-end looping\n"
|
|
|
|
" -E: force end-to-end looping even if file has real loop points\n"
|
|
|
|
" -s N: select subsong N, if the format supports multiple subsongs\n"
|
|
|
|
" -m: print metadata only, don't decode\n"
|
|
|
|
" -L: append a smpl chunk and create a looping wav\n"
|
|
|
|
" -2 N: only output the Nth (first is 0) set of stereo channels\n"
|
|
|
|
" -p: output to stdout (for piping into another program)\n"
|
|
|
|
" -P: output to stdout even if stdout is a terminal\n"
|
|
|
|
" -c: loop forever (continuously) to stdout\n"
|
|
|
|
" -x: decode and print adxencd command line to encode as ADX\n"
|
|
|
|
" -g: decode and print oggenc command line to encode as OGG\n"
|
|
|
|
" -b: decode and print batch variable commands\n"
|
2019-10-05 15:02:45 +02:00
|
|
|
" -h: print extra commands\n"
|
2018-09-07 00:21:17 +02:00
|
|
|
, name);
|
2019-10-05 15:02:45 +02:00
|
|
|
if (is_full) {
|
|
|
|
fprintf(stderr,
|
|
|
|
" -r: output a second file after resetting (for reset testing)\n"
|
|
|
|
" -k N: seeks to N samples before decoding (for seek testing)\n"
|
|
|
|
" -t file: print tags found in file (for tag testing)\n"
|
|
|
|
" -O: decode but don't write to file (for performance testing)\n"
|
|
|
|
);
|
|
|
|
}
|
2008-02-04 13:11:40 +01:00
|
|
|
}
|
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
typedef struct {
|
2020-07-21 19:46:55 +02:00
|
|
|
char* infilename;
|
|
|
|
char* outfilename;
|
|
|
|
char* tag_filename;
|
2019-10-05 15:02:45 +02:00
|
|
|
int decode_only;
|
2020-06-21 00:33:21 +02:00
|
|
|
int play_forever;
|
2018-09-06 23:32:22 +02:00
|
|
|
int play_sdtout;
|
|
|
|
int play_wreckless;
|
|
|
|
int print_metaonly;
|
|
|
|
int print_adxencd;
|
|
|
|
int print_oggenc;
|
|
|
|
int print_batchvar;
|
2018-09-07 19:48:03 +02:00
|
|
|
int test_reset;
|
2018-09-06 23:32:22 +02:00
|
|
|
int write_lwav;
|
|
|
|
int only_stereo;
|
|
|
|
int stream_index;
|
2020-06-21 00:33:21 +02:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
double loop_count;
|
2018-09-07 19:35:04 +02:00
|
|
|
double fade_time;
|
|
|
|
double fade_delay;
|
2018-09-06 23:32:22 +02:00
|
|
|
int ignore_fade;
|
2020-06-21 00:33:21 +02:00
|
|
|
int ignore_loop;
|
|
|
|
int force_loop;
|
|
|
|
int really_force_loop;
|
|
|
|
|
2019-03-22 00:37:59 +01:00
|
|
|
int seek_samples;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
/* not quite config but eh */
|
|
|
|
int lwav_loop_start;
|
|
|
|
int lwav_loop_end;
|
|
|
|
} cli_config;
|
|
|
|
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static int parse_config(cli_config* cfg, int argc, char** argv) {
|
2008-02-04 13:11:40 +01:00
|
|
|
int opt;
|
2008-12-24 10:11:58 +01:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
/* non-zero defaults */
|
|
|
|
cfg->only_stereo = -1;
|
|
|
|
cfg->loop_count = 2.0;
|
2018-09-07 19:35:04 +02:00
|
|
|
cfg->fade_time = 10.0;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2018-09-07 00:21:17 +02:00
|
|
|
/* don't let getopt print errors to stdout automatically */
|
|
|
|
opterr = 0;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
/* read config */
|
2019-10-05 15:02:45 +02:00
|
|
|
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:hO")) != -1) {
|
2008-02-04 13:11:40 +01:00
|
|
|
switch (opt) {
|
|
|
|
case 'o':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->outfilename = optarg;
|
2008-02-04 13:11:40 +01:00
|
|
|
break;
|
|
|
|
case 'l':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->loop_count = atof(optarg);
|
2008-02-04 13:11:40 +01:00
|
|
|
break;
|
|
|
|
case 'f':
|
2018-09-07 19:35:04 +02:00
|
|
|
cfg->fade_time = atof(optarg);
|
2008-05-16 22:28:36 +02:00
|
|
|
break;
|
|
|
|
case 'd':
|
2018-09-07 19:35:04 +02:00
|
|
|
cfg->fade_delay = atof(optarg);
|
2008-02-04 13:11:40 +01:00
|
|
|
break;
|
|
|
|
case 'i':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->ignore_loop = 1;
|
2008-02-04 13:11:40 +01:00
|
|
|
break;
|
2008-02-06 21:53:10 +01:00
|
|
|
case 'p':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->play_sdtout = 1;
|
2008-02-06 21:53:10 +01:00
|
|
|
break;
|
2008-05-10 06:08:01 +02:00
|
|
|
case 'P':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->play_wreckless = 1;
|
|
|
|
cfg->play_sdtout = 1;
|
2008-05-10 06:08:01 +02:00
|
|
|
break;
|
2008-02-06 21:53:10 +01:00
|
|
|
case 'c':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->play_forever = 1;
|
2008-02-06 21:53:10 +01:00
|
|
|
break;
|
2008-02-14 23:38:23 +01:00
|
|
|
case 'm':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->print_metaonly = 1;
|
2008-02-14 23:38:23 +01:00
|
|
|
break;
|
2008-03-04 08:40:41 +01:00
|
|
|
case 'x':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->print_adxencd = 1;
|
2008-03-04 08:40:41 +01:00
|
|
|
break;
|
2008-09-29 06:08:03 +02:00
|
|
|
case 'g':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->print_oggenc = 1;
|
2008-09-29 06:08:03 +02:00
|
|
|
break;
|
2009-06-24 22:10:07 +02:00
|
|
|
case 'b':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->print_batchvar = 1;
|
2009-06-24 22:10:07 +02:00
|
|
|
break;
|
2008-05-05 00:03:10 +02:00
|
|
|
case 'e':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->force_loop = 1;
|
2008-05-05 00:03:10 +02:00
|
|
|
break;
|
|
|
|
case 'E':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->really_force_loop = 1;
|
2008-05-05 00:03:10 +02:00
|
|
|
break;
|
2015-05-17 00:30:15 +02:00
|
|
|
case 'L':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->write_lwav = 1;
|
2017-05-18 19:55:00 +02:00
|
|
|
break;
|
2008-05-19 05:58:15 +02:00
|
|
|
case 'r':
|
2018-09-07 19:48:03 +02:00
|
|
|
cfg->test_reset = 1;
|
2008-05-19 05:58:15 +02:00
|
|
|
break;
|
2011-05-08 03:51:35 +02:00
|
|
|
case '2':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->only_stereo = atoi(optarg);
|
2011-05-08 03:51:35 +02:00
|
|
|
break;
|
2017-05-18 19:55:00 +02:00
|
|
|
case 'F':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->ignore_fade = 1;
|
2017-05-18 19:55:00 +02:00
|
|
|
break;
|
2017-08-12 18:43:00 +02:00
|
|
|
case 's':
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->stream_index = atoi(optarg);
|
2017-08-12 18:43:00 +02:00
|
|
|
break;
|
2018-11-03 19:29:45 +01:00
|
|
|
case 't':
|
|
|
|
cfg->tag_filename= optarg;
|
|
|
|
break;
|
2019-03-22 00:37:59 +01:00
|
|
|
case 'k':
|
|
|
|
cfg->seek_samples = atoi(optarg);
|
|
|
|
break;
|
2019-10-05 15:02:45 +02:00
|
|
|
case 'O':
|
|
|
|
cfg->decode_only = 1;
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
usage(argv[0], 1);
|
|
|
|
goto fail;
|
2018-09-06 23:32:22 +02:00
|
|
|
case '?':
|
2018-09-07 00:21:17 +02:00
|
|
|
fprintf(stderr, "Unknown option -%c found\n", optopt);
|
|
|
|
goto fail;
|
2008-02-04 13:11:40 +01:00
|
|
|
default:
|
2019-10-05 15:02:45 +02:00
|
|
|
usage(argv[0], 0);
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2008-02-04 13:11:40 +01:00
|
|
|
}
|
|
|
|
}
|
2008-01-31 07:15:03 +01:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
/* filename goes last */
|
|
|
|
if (optind != argc - 1) {
|
2019-10-05 15:02:45 +02:00
|
|
|
usage(argv[0], 0);
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2008-02-04 13:11:40 +01:00
|
|
|
}
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg->infilename = argv[optind];
|
2008-02-06 21:53:10 +01:00
|
|
|
|
2017-12-03 13:46:13 +01:00
|
|
|
|
2018-09-07 00:21:17 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
2018-09-06 23:32:22 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2017-12-03 13:46:13 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static int validate_config(cli_config* cfg) {
|
2018-09-06 23:32:22 +02:00
|
|
|
if (cfg->play_sdtout && (!cfg->play_wreckless && isatty(STDOUT_FILENO))) {
|
2008-05-10 06:08:01 +02:00
|
|
|
fprintf(stderr,"Are you sure you want to output wave data to the terminal?\nIf so use -P instead of -p.\n");
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
if (cfg->play_forever && !cfg->play_sdtout) {
|
|
|
|
fprintf(stderr,"-c must use -p or -P\n");
|
|
|
|
goto fail;
|
2008-05-10 06:08:01 +02:00
|
|
|
}
|
2018-09-06 23:32:22 +02:00
|
|
|
if (cfg->play_sdtout && cfg->outfilename) {
|
2020-07-21 19:46:55 +02:00
|
|
|
fprintf(stderr,"use either -p or -o\n");
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2018-09-06 23:32:22 +02:00
|
|
|
}
|
2008-05-05 00:03:10 +02:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
/* other options have built-in priority defined */
|
|
|
|
|
2018-09-07 00:21:17 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
2018-09-06 23:32:22 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2008-01-31 07:15:03 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static void print_info(VGMSTREAM* vgmstream, cli_config* cfg) {
|
2019-02-17 20:28:27 +01:00
|
|
|
int channels = vgmstream->channels;
|
2018-09-06 23:32:22 +02:00
|
|
|
if (!cfg->play_sdtout) {
|
|
|
|
if (cfg->print_adxencd) {
|
|
|
|
printf("adxencd");
|
|
|
|
if (!cfg->print_metaonly)
|
|
|
|
printf(" \"%s\"",cfg->outfilename);
|
|
|
|
if (vgmstream->loop_flag)
|
|
|
|
printf(" -lps%d -lpe%d",vgmstream->loop_start_sample,vgmstream->loop_end_sample);
|
|
|
|
printf("\n");
|
2017-12-03 13:46:13 +01:00
|
|
|
}
|
2018-09-06 23:32:22 +02:00
|
|
|
else if (cfg->print_oggenc) {
|
|
|
|
printf("oggenc");
|
|
|
|
if (!cfg->print_metaonly)
|
|
|
|
printf(" \"%s\"",cfg->outfilename);
|
|
|
|
if (vgmstream->loop_flag)
|
|
|
|
printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d",vgmstream->loop_start_sample, vgmstream->loop_end_sample-vgmstream->loop_start_sample);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
else if (cfg->print_batchvar) {
|
|
|
|
if (!cfg->print_metaonly)
|
|
|
|
printf("set fname=\"%s\"\n",cfg->outfilename);
|
2019-02-17 20:28:27 +01:00
|
|
|
printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels);
|
2018-09-06 23:32:22 +02:00
|
|
|
if (vgmstream->loop_flag)
|
|
|
|
printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
|
|
|
|
else
|
|
|
|
printf("set loop=0\n");
|
|
|
|
}
|
|
|
|
else if (cfg->print_metaonly) {
|
|
|
|
printf("metadata for %s\n",cfg->infilename);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
printf("decoding %s\n",cfg->infilename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cfg->play_sdtout && !cfg->print_adxencd && !cfg->print_oggenc && !cfg->print_batchvar) {
|
|
|
|
char description[1024];
|
|
|
|
description[0] = '\0';
|
|
|
|
describe_vgmstream(vgmstream,description,1024);
|
2019-03-03 22:24:29 +01:00
|
|
|
printf("%s",description);
|
2008-01-31 07:15:03 +01:00
|
|
|
}
|
2018-09-06 23:32:22 +02:00
|
|
|
}
|
2008-01-31 07:15:03 +01:00
|
|
|
|
2018-09-07 00:21:17 +02:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static void apply_config(VGMSTREAM* vgmstream, cli_config* cfg) {
|
|
|
|
vgmstream_cfg_t vcfg = {0};
|
2018-09-07 19:35:04 +02:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
/* write loops in the wav, but don't actually loop it */
|
|
|
|
if (cfg->write_lwav) {
|
|
|
|
vcfg.disable_config_override = 1;
|
2020-06-21 00:33:21 +02:00
|
|
|
cfg->ignore_loop = 1;
|
2020-07-21 19:46:55 +02:00
|
|
|
cfg->lwav_loop_start = vgmstream->loop_start_sample;
|
|
|
|
cfg->lwav_loop_end = vgmstream->loop_end_sample;
|
2020-06-21 00:33:21 +02:00
|
|
|
}
|
2020-07-21 19:46:55 +02:00
|
|
|
/* only allowed if manually active */
|
2020-06-21 00:33:21 +02:00
|
|
|
if (cfg->play_forever) {
|
2020-07-21 19:46:55 +02:00
|
|
|
vcfg.allow_play_forever = 1;
|
2020-06-21 00:33:21 +02:00
|
|
|
}
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
vcfg.play_forever = cfg->play_forever;
|
|
|
|
vcfg.fade_period = cfg->fade_time;
|
|
|
|
vcfg.loop_times = cfg->loop_count;
|
|
|
|
vcfg.fade_delay = cfg->fade_delay;
|
2018-09-07 19:35:04 +02:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
vcfg.ignore_loop = cfg->ignore_loop;
|
|
|
|
vcfg.force_loop = cfg->force_loop;
|
|
|
|
vcfg.really_force_loop = cfg->really_force_loop;
|
|
|
|
vcfg.ignore_fade = cfg->ignore_fade;
|
2018-09-07 00:21:17 +02:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
vgmstream_apply_config(vgmstream, &vcfg);
|
2018-09-06 23:32:22 +02:00
|
|
|
}
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
|
|
|
|
static void apply_seek(sample_t* buf, VGMSTREAM* vgmstream, int len_samples) {
|
2019-03-22 00:37:59 +01:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
|
|
|
int to_get = SAMPLE_BUFFER_SIZE;
|
|
|
|
if (i + SAMPLE_BUFFER_SIZE > len_samples)
|
|
|
|
to_get = len_samples - i;
|
|
|
|
|
|
|
|
render_vgmstream(buf, to_get, vgmstream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static void print_tags(cli_config* cfg) {
|
|
|
|
VGMSTREAM_TAGS* tags = NULL;
|
|
|
|
STREAMFILE* sf_tags = NULL;
|
|
|
|
const char *tag_key, *tag_val;
|
|
|
|
|
|
|
|
if (!cfg->tag_filename)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sf_tags = open_stdio_streamfile(cfg->tag_filename);
|
|
|
|
if (!sf_tags) {
|
|
|
|
printf("tag file %s not found\n", cfg->tag_filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("tags:\n");
|
|
|
|
|
|
|
|
tags = vgmstream_tags_init(&tag_key, &tag_val);
|
|
|
|
vgmstream_tags_reset(tags, cfg->infilename);
|
|
|
|
while (vgmstream_tags_next_tag(tags, sf_tags)) {
|
|
|
|
printf("- '%s'='%s'\n", tag_key, tag_val);
|
|
|
|
}
|
|
|
|
|
|
|
|
vgmstream_tags_close(tags);
|
|
|
|
close_streamfile(sf_tags);
|
|
|
|
}
|
|
|
|
|
2018-11-03 19:29:45 +01:00
|
|
|
/* ************************************************************ */
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
int main(int argc, char** argv) {
|
|
|
|
VGMSTREAM* vgmstream = NULL;
|
|
|
|
FILE* outfile = NULL;
|
2018-09-06 23:32:22 +02:00
|
|
|
char outfilename_temp[PATH_LIMIT];
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
sample_t* buf = NULL;
|
2019-02-25 00:38:35 +01:00
|
|
|
int channels, input_channels;
|
2018-09-06 23:32:22 +02:00
|
|
|
int32_t len_samples;
|
2018-09-07 00:21:17 +02:00
|
|
|
int i, j;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
cli_config cfg = {0};
|
2018-09-07 00:21:17 +02:00
|
|
|
int res;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2018-04-07 12:11:30 +02:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
/* read args */
|
|
|
|
res = parse_config(&cfg, argc, argv);
|
2018-09-07 00:21:17 +02:00
|
|
|
if (!res) goto fail;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
#ifdef WIN32
|
|
|
|
/* make stdout output work with windows */
|
|
|
|
if (cfg.play_sdtout) {
|
|
|
|
_setmode(fileno(stdout),_O_BINARY);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
res = validate_config(&cfg);
|
2018-09-07 00:21:17 +02:00
|
|
|
if (!res) goto fail;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-09-30 00:56:29 +02:00
|
|
|
#if 0
|
|
|
|
/* CLI has no need to check */
|
|
|
|
{
|
|
|
|
int valid;
|
2019-12-15 23:26:42 +01:00
|
|
|
vgmstream_ctx_valid_cfg vcfg = {0};
|
|
|
|
|
|
|
|
vcfg.skip_standard = 1;
|
|
|
|
vcfg.reject_extensionless = 0;
|
|
|
|
vcfg.accept_unknown = 0;
|
|
|
|
vcfg.accept_common = 0;
|
|
|
|
|
|
|
|
VGM_LOG("CLI: valid %s\n", cfg.infilename);
|
|
|
|
valid = vgmstream_ctx_is_valid(cfg.infilename, &vcfg);
|
|
|
|
if (!valid) {
|
|
|
|
VGM_LOG("CLI: valid ko\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
VGM_LOG("CLI: valid ok\n");
|
2019-09-30 00:56:29 +02:00
|
|
|
}
|
|
|
|
#endif
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
/* open streamfile and pass subsong */
|
|
|
|
{
|
2020-07-21 19:46:55 +02:00
|
|
|
STREAMFILE* sf = open_stdio_streamfile(cfg.infilename);
|
|
|
|
if (!sf) {
|
2018-09-06 23:32:22 +02:00
|
|
|
fprintf(stderr,"file %s not found\n",cfg.infilename);
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2018-09-06 23:32:22 +02:00
|
|
|
}
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
sf->stream_index = cfg.stream_index;
|
|
|
|
vgmstream = init_vgmstream_from_STREAMFILE(sf);
|
|
|
|
close_streamfile(sf);
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
if (!vgmstream) {
|
|
|
|
fprintf(stderr,"failed opening %s\n",cfg.infilename);
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2008-02-06 21:53:10 +01:00
|
|
|
}
|
2018-09-06 23:32:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-18 00:05:44 +01:00
|
|
|
/* modify the VGMSTREAM if needed (before printing file info) */
|
2018-09-06 23:32:22 +02:00
|
|
|
apply_config(vgmstream, &cfg);
|
|
|
|
|
2019-03-18 00:05:44 +01:00
|
|
|
channels = vgmstream->channels;
|
|
|
|
input_channels = vgmstream->channels;
|
|
|
|
|
|
|
|
/* enable after config but before outbuf */
|
|
|
|
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels);
|
|
|
|
|
2020-07-26 11:20:49 +02:00
|
|
|
/* get final play config */
|
|
|
|
len_samples = vgmstream_get_samples(vgmstream);
|
|
|
|
if (len_samples <= 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
if (cfg.play_forever && !vgmstream_get_play_forever(vgmstream)) {
|
|
|
|
fprintf(stderr,"File can't be played forever");
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
|
|
|
|
/* prepare output */
|
|
|
|
if (cfg.play_sdtout) {
|
2008-02-06 21:53:10 +01:00
|
|
|
outfile = stdout;
|
2017-10-28 10:54:46 +02:00
|
|
|
}
|
2019-10-05 15:02:45 +02:00
|
|
|
else if (!cfg.print_metaonly && !cfg.decode_only) {
|
2018-09-06 23:32:22 +02:00
|
|
|
if (!cfg.outfilename) {
|
|
|
|
/* note that outfilename_temp must persist outside this block, hence the external array */
|
|
|
|
strcpy(outfilename_temp, cfg.infilename);
|
|
|
|
strcat(outfilename_temp, ".wav");
|
|
|
|
cfg.outfilename = outfilename_temp;
|
2019-03-11 14:49:29 +01:00
|
|
|
/* maybe should avoid overwriting with this auto-name, for the unlikely
|
|
|
|
* case of file header-body pairs (file.ext+file.ext.wav) */
|
2018-01-05 00:56:15 +01:00
|
|
|
}
|
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
outfile = fopen(cfg.outfilename,"wb");
|
2008-02-04 13:11:40 +01:00
|
|
|
if (!outfile) {
|
2018-09-06 23:32:22 +02:00
|
|
|
fprintf(stderr,"failed to open %s for output\n",cfg.outfilename);
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2008-02-04 13:11:40 +01:00
|
|
|
}
|
2019-09-29 20:09:28 +02:00
|
|
|
|
|
|
|
/* no improvement */
|
|
|
|
//setvbuf(outfile, NULL, _IOFBF, SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels);
|
|
|
|
//setvbuf(outfile, NULL, _IONBF, 0);
|
2008-02-04 13:11:40 +01:00
|
|
|
}
|
|
|
|
|
2017-10-28 10:54:46 +02:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
/* print file info (or batch commands, depending on config) */
|
|
|
|
print_info(vgmstream, &cfg);
|
2018-11-03 19:29:45 +01:00
|
|
|
|
|
|
|
/* print tags info */
|
2020-07-21 19:46:55 +02:00
|
|
|
print_tags(&cfg);
|
2018-11-03 19:29:45 +01:00
|
|
|
|
|
|
|
/* prints done */
|
2018-09-06 23:32:22 +02:00
|
|
|
if (cfg.print_metaonly) {
|
2018-11-30 00:22:30 +01:00
|
|
|
if (!cfg.play_sdtout) {
|
2019-11-03 17:55:47 +01:00
|
|
|
if (outfile != NULL)
|
|
|
|
fclose(outfile);
|
2018-11-30 00:22:30 +01:00
|
|
|
}
|
2017-10-28 10:54:46 +02:00
|
|
|
close_vgmstream(vgmstream);
|
2018-01-07 20:14:50 +01:00
|
|
|
return EXIT_SUCCESS;
|
2008-02-14 23:38:23 +01:00
|
|
|
}
|
2008-01-31 07:15:03 +01:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-03-22 00:37:59 +01:00
|
|
|
if (cfg.seek_samples >= len_samples)
|
|
|
|
cfg.seek_samples = 0;
|
|
|
|
len_samples -= cfg.seek_samples;
|
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
/* last init */
|
2019-03-18 00:05:44 +01:00
|
|
|
buf = malloc(SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels);
|
2018-09-06 23:32:22 +02:00
|
|
|
if (!buf) {
|
|
|
|
fprintf(stderr,"failed allocating output buffer\n");
|
2019-02-17 20:28:27 +01:00
|
|
|
goto fail;
|
2018-09-06 23:32:22 +02:00
|
|
|
}
|
2017-08-18 19:32:51 +02:00
|
|
|
|
2008-02-04 11:34:18 +01:00
|
|
|
/* slap on a .wav header */
|
2019-10-05 15:02:45 +02:00
|
|
|
if (!cfg.decode_only) {
|
2018-03-30 21:33:14 +02:00
|
|
|
uint8_t wav_buf[0x100];
|
2019-02-18 00:53:08 +01:00
|
|
|
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
|
2018-03-30 21:33:14 +02:00
|
|
|
size_t bytes_done;
|
|
|
|
|
|
|
|
bytes_done = make_wav_header(wav_buf,0x100,
|
2019-02-18 00:53:08 +01:00
|
|
|
len_samples, vgmstream->sample_rate, channels_write,
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
|
2018-03-30 21:33:14 +02:00
|
|
|
|
|
|
|
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
2017-05-18 19:55:00 +02:00
|
|
|
}
|
2008-02-04 11:34:18 +01:00
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2008-05-05 00:03:10 +02:00
|
|
|
/* decode forever */
|
2018-09-06 23:32:22 +02:00
|
|
|
while (cfg.play_forever) {
|
2019-03-18 00:05:44 +01:00
|
|
|
int to_get = SAMPLE_BUFFER_SIZE;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-02-17 20:28:27 +01:00
|
|
|
render_vgmstream(buf, to_get, vgmstream);
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-02-17 20:28:27 +01:00
|
|
|
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
2018-09-06 23:32:22 +02:00
|
|
|
if (cfg.only_stereo != -1) {
|
|
|
|
for (j = 0; j < to_get; j++) {
|
2019-02-23 02:54:23 +01:00
|
|
|
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
|
2011-05-08 03:51:35 +02:00
|
|
|
}
|
|
|
|
} else {
|
2019-02-23 02:54:23 +01:00
|
|
|
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
|
2011-05-08 03:51:35 +02:00
|
|
|
}
|
2008-02-06 21:53:10 +01:00
|
|
|
}
|
|
|
|
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-03-22 00:37:59 +01:00
|
|
|
apply_seek(buf, vgmstream, cfg.seek_samples);
|
|
|
|
|
2008-05-05 00:03:10 +02:00
|
|
|
/* decode */
|
2019-03-18 00:05:44 +01:00
|
|
|
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
|
|
|
int to_get = SAMPLE_BUFFER_SIZE;
|
|
|
|
if (i + SAMPLE_BUFFER_SIZE > len_samples)
|
2018-09-07 00:21:17 +02:00
|
|
|
to_get = len_samples - i;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-02-17 20:28:27 +01:00
|
|
|
render_vgmstream(buf, to_get, vgmstream);
|
2017-10-28 10:54:46 +02:00
|
|
|
|
2019-10-05 15:02:45 +02:00
|
|
|
if (!cfg.decode_only) {
|
|
|
|
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
|
|
|
if (cfg.only_stereo != -1) {
|
|
|
|
for (j = 0; j < to_get; j++) {
|
|
|
|
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
|
2011-05-08 03:51:35 +02:00
|
|
|
}
|
|
|
|
}
|
2008-01-31 07:15:03 +01:00
|
|
|
}
|
2015-05-17 00:30:15 +02:00
|
|
|
|
2019-11-03 17:55:47 +01:00
|
|
|
if (outfile != NULL) {
|
|
|
|
fclose(outfile);
|
|
|
|
outfile = NULL;
|
|
|
|
}
|
2008-05-19 05:58:15 +02:00
|
|
|
|
2008-05-20 20:56:44 +02:00
|
|
|
|
2019-10-05 15:02:45 +02:00
|
|
|
/* try again with (for testing reset_vgmstream, simulates a seek to 0 after changing internal state) */
|
2018-09-07 19:48:03 +02:00
|
|
|
if (cfg.test_reset) {
|
2019-02-23 02:54:23 +01:00
|
|
|
char outfilename_reset[PATH_LIMIT];
|
|
|
|
strcpy(outfilename_reset, cfg.outfilename);
|
|
|
|
strcat(outfilename_reset, ".reset.wav");
|
2018-09-07 19:48:03 +02:00
|
|
|
|
2019-02-23 02:54:23 +01:00
|
|
|
outfile = fopen(outfilename_reset,"wb");
|
2008-05-19 05:58:15 +02:00
|
|
|
if (!outfile) {
|
2019-02-23 02:54:23 +01:00
|
|
|
fprintf(stderr,"failed to open %s for output\n",outfilename_reset);
|
2018-09-07 00:21:17 +02:00
|
|
|
goto fail;
|
2008-05-19 05:58:15 +02:00
|
|
|
}
|
|
|
|
|
2017-10-28 10:54:46 +02:00
|
|
|
reset_vgmstream(vgmstream);
|
2008-05-19 19:20:35 +02:00
|
|
|
|
2019-03-22 00:37:59 +01:00
|
|
|
apply_seek(buf, vgmstream, cfg.seek_samples);
|
2018-04-07 12:11:30 +02:00
|
|
|
|
2018-03-30 21:33:14 +02:00
|
|
|
/* slap on a .wav header */
|
2019-10-05 15:02:45 +02:00
|
|
|
if (!cfg.decode_only) {
|
2018-03-30 21:33:14 +02:00
|
|
|
uint8_t wav_buf[0x100];
|
2019-02-18 00:53:08 +01:00
|
|
|
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
|
2018-03-30 21:33:14 +02:00
|
|
|
size_t bytes_done;
|
|
|
|
|
|
|
|
bytes_done = make_wav_header(wav_buf,0x100,
|
2019-02-18 00:53:08 +01:00
|
|
|
len_samples, vgmstream->sample_rate, channels_write,
|
2018-09-06 23:32:22 +02:00
|
|
|
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
|
2018-03-30 21:33:14 +02:00
|
|
|
|
|
|
|
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
|
|
|
}
|
|
|
|
|
2008-05-19 05:58:15 +02:00
|
|
|
/* decode */
|
2019-03-18 00:05:44 +01:00
|
|
|
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
|
|
|
int to_get = SAMPLE_BUFFER_SIZE;
|
|
|
|
if (i + SAMPLE_BUFFER_SIZE > len_samples)
|
2018-09-07 00:21:17 +02:00
|
|
|
to_get = len_samples - i;
|
2018-09-06 23:32:22 +02:00
|
|
|
|
2019-02-17 20:28:27 +01:00
|
|
|
render_vgmstream(buf, to_get, vgmstream);
|
2008-05-19 05:58:15 +02:00
|
|
|
|
2019-10-05 15:02:45 +02:00
|
|
|
if (!cfg.decode_only) {
|
|
|
|
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
|
|
|
if (cfg.only_stereo != -1) {
|
|
|
|
for (j = 0; j < to_get; j++) {
|
|
|
|
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
|
2011-05-08 03:51:35 +02:00
|
|
|
}
|
|
|
|
}
|
2008-05-19 05:58:15 +02:00
|
|
|
}
|
2019-11-03 17:55:47 +01:00
|
|
|
|
|
|
|
if (outfile != NULL) {
|
|
|
|
fclose(outfile);
|
|
|
|
outfile = NULL;
|
|
|
|
}
|
2008-05-19 05:58:15 +02:00
|
|
|
}
|
|
|
|
|
2017-10-28 10:54:46 +02:00
|
|
|
close_vgmstream(vgmstream);
|
2008-12-24 10:11:58 +01:00
|
|
|
free(buf);
|
2008-02-15 17:26:29 +01:00
|
|
|
|
2018-01-07 20:14:50 +01:00
|
|
|
return EXIT_SUCCESS;
|
2018-09-07 00:21:17 +02:00
|
|
|
|
|
|
|
fail:
|
2019-02-17 20:28:27 +01:00
|
|
|
if (!cfg.play_sdtout) {
|
2018-10-10 00:41:43 +02:00
|
|
|
if (outfile != NULL)
|
|
|
|
fclose(outfile);
|
|
|
|
}
|
2018-09-07 00:21:17 +02:00
|
|
|
close_vgmstream(vgmstream);
|
2019-02-17 20:28:27 +01:00
|
|
|
free(buf);
|
2018-09-07 00:21:17 +02:00
|
|
|
return EXIT_FAILURE;
|
2008-01-31 07:15:03 +01:00
|
|
|
}
|
2017-01-14 00:10:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
static void make_smpl_chunk(uint8_t* buf, int32_t loop_start, int32_t loop_end) {
|
2017-12-06 16:55:41 +01:00
|
|
|
int i;
|
2017-01-14 00:10:45 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
memcpy(buf+0, "smpl", 0x04); /* header */
|
|
|
|
put_s32le(buf+0x04, 0x3c); /* size */
|
2017-01-14 00:10:45 +01:00
|
|
|
|
|
|
|
for (i = 0; i < 7; i++)
|
2020-07-21 19:46:55 +02:00
|
|
|
put_s32le(buf+0x08 + i * 0x04, 0);
|
2017-01-14 00:10:45 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
put_s32le(buf+0x24, 1);
|
2017-01-14 00:10:45 +01:00
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
2020-07-21 19:46:55 +02:00
|
|
|
put_s32le(buf+0x28 + i * 0x04, 0);
|
2017-01-14 00:10:45 +01:00
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
put_s32le(buf+0x34, loop_start);
|
|
|
|
put_s32le(buf+0x38, loop_end);
|
|
|
|
put_s32le(buf+0x3C, 0);
|
|
|
|
put_s32le(buf+0x40, 0);
|
2017-01-14 00:10:45 +01:00
|
|
|
}
|
2018-03-30 21:33:14 +02:00
|
|
|
|
|
|
|
/* make a RIFF header for .wav */
|
2020-07-21 19:46:55 +02:00
|
|
|
static size_t make_wav_header(uint8_t* buf, size_t buf_size, int32_t sample_count, int32_t sample_rate, int channels, int smpl_chunk, int32_t loop_start, int32_t loop_end) {
|
2018-03-30 21:33:14 +02:00
|
|
|
size_t data_size, header_size;
|
|
|
|
|
2019-02-23 02:54:23 +01:00
|
|
|
data_size = sample_count * channels * sizeof(sample_t);
|
2018-03-30 21:33:14 +02:00
|
|
|
header_size = 0x2c;
|
2018-04-07 12:11:30 +02:00
|
|
|
if (smpl_chunk && loop_end)
|
2018-03-30 21:33:14 +02:00
|
|
|
header_size += 0x3c+ 0x08;
|
|
|
|
|
|
|
|
if (header_size > buf_size)
|
|
|
|
goto fail;
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
memcpy(buf+0x00, "RIFF", 0x04); /* RIFF header */
|
|
|
|
put_32bitLE(buf+0x04, (int32_t)(header_size - 0x08 + data_size)); /* size of RIFF */
|
2018-03-30 21:33:14 +02:00
|
|
|
|
|
|
|
memcpy(buf+0x08, "WAVE", 4); /* WAVE header */
|
|
|
|
|
2020-07-21 19:46:55 +02:00
|
|
|
memcpy(buf+0x0c, "fmt ", 0x04); /* WAVE fmt chunk */
|
|
|
|
put_s32le(buf+0x10, 0x10); /* size of WAVE fmt chunk */
|
|
|
|
put_s16le(buf+0x14, 0x0001); /* codec PCM */
|
|
|
|
put_s16le(buf+0x16, channels); /* channel count */
|
|
|
|
put_s32le(buf+0x18, sample_rate); /* sample rate */
|
|
|
|
put_s32le(buf+0x1c, sample_rate * channels * sizeof(sample_t)); /* bytes per second */
|
|
|
|
put_s16le(buf+0x20, (int16_t)(channels * sizeof(sample_t))); /* block align */
|
|
|
|
put_s16le(buf+0x22, sizeof(sample_t) * 8); /* significant bits per sample */
|
2018-03-30 21:33:14 +02:00
|
|
|
|
2018-04-07 12:11:30 +02:00
|
|
|
if (smpl_chunk && loop_end) {
|
2018-03-30 21:33:14 +02:00
|
|
|
make_smpl_chunk(buf+0x24, loop_start, loop_end);
|
|
|
|
memcpy(buf+0x24+0x3c+0x08, "data", 0x04); /* WAVE data chunk */
|
2020-07-21 19:46:55 +02:00
|
|
|
put_u32le(buf+0x28+0x3c+0x08, (int32_t)data_size); /* size of WAVE data chunk */
|
2018-03-30 21:33:14 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
memcpy(buf+0x24, "data", 0x04); /* WAVE data chunk */
|
2020-07-21 19:46:55 +02:00
|
|
|
put_s32le(buf+0x28, (int32_t)data_size); /* size of WAVE data chunk */
|
2018-03-30 21:33:14 +02:00
|
|
|
}
|
|
|
|
|
2019-03-11 14:49:29 +01:00
|
|
|
/* could try to add channel_layout, but would need to write WAVEFORMATEXTENSIBLE (maybe only if arg flag?) */
|
|
|
|
|
2018-03-30 21:33:14 +02:00
|
|
|
return header_size;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|