mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-31 12:23:44 +01:00
commit
722808f732
@ -37,6 +37,7 @@
|
||||
#endif
|
||||
|
||||
#include "../src/vgmstream.h"
|
||||
#include "../src/plugins.h"
|
||||
|
||||
#ifndef VERSION
|
||||
# include "version.h"
|
||||
@ -70,17 +71,21 @@
|
||||
#undef MIN
|
||||
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||
|
||||
/* Stream playback parameters
|
||||
*/
|
||||
struct params {
|
||||
int loop_count;
|
||||
|
||||
#define DEFAULT_PARAMS { 0, -1, 2.0, 10.0, 0.0, 0, 0, 0, 0 }
|
||||
typedef struct {
|
||||
int stream_index;
|
||||
|
||||
double min_time;
|
||||
double loop_count;
|
||||
double fade_time;
|
||||
double fade_delay;
|
||||
int stream_index;
|
||||
};
|
||||
|
||||
#define DEFAULT_PARAMS { 2, -1, 10.0, 0.0, 0 }
|
||||
int ignore_loop;
|
||||
int force_loop;
|
||||
int really_force_loop;
|
||||
int play_forever;
|
||||
} song_settings_t;
|
||||
|
||||
static const char *out_filename = NULL;
|
||||
static int driver_id;
|
||||
@ -99,7 +104,7 @@ static int verbose = 0;
|
||||
static volatile int interrupted = 0;
|
||||
static double interrupt_time = 0.0;
|
||||
|
||||
static int play_file(const char *filename, struct params *par);
|
||||
static int play_file(const char *filename, song_settings_t *par);
|
||||
|
||||
static void interrupt_handler(int signum) {
|
||||
interrupted = 1;
|
||||
@ -125,7 +130,7 @@ static int record_interrupt(void) {
|
||||
}
|
||||
|
||||
static void usage(const char *progname) {
|
||||
struct params default_par = DEFAULT_PARAMS;
|
||||
song_settings_t default_par = DEFAULT_PARAMS;
|
||||
const char *default_driver = "???";
|
||||
|
||||
{
|
||||
@ -163,21 +168,24 @@ static void usage(const char *progname) {
|
||||
" -b N Use an audio buffer of N kilobytes [%d]\n"
|
||||
" -@ LSTFILE Read playlist from LSTFILE\n"
|
||||
" -h Print this help\n"
|
||||
" -r Repeat playback indefinitely\n"
|
||||
" -r Repeat playback again (with fade, use -p for infinite loops)\n"
|
||||
" -v Display stream metadata and playback progress\n"
|
||||
" -S N Play substream with index N [%d]\n"
|
||||
" -S N Play substream with index N\n"
|
||||
"\n"
|
||||
"Options for looped streams:\n"
|
||||
" -L N Play loop N times [%d]\n"
|
||||
"Looping options:\n"
|
||||
" -M MINTIME Loop for a playback time of at least MINTIME seconds\n"
|
||||
" -L N Loop N times [%.1f]\n"
|
||||
" -F FTIME End playback with a fade-out of FTIME seconds [%.1f]\n"
|
||||
" -D FDELAY Delay fade-out for an additional FDELAY seconds [%.1f]\n"
|
||||
" -i Ignore loop\n"
|
||||
" -e Force loop (loop only if file doesn't have loop points)\n"
|
||||
" -E Really force loop (repeat file)\n"
|
||||
" -p Play forever (loops file until stopped)\n"
|
||||
"\n"
|
||||
"INFILE can be any stream file type supported by vgmstream, or an .m3u/.m3u8\n"
|
||||
"playlist referring to same. This program supports the \"EXT-X-VGMSTREAM\" tag\n"
|
||||
"in playlists, and files compressed with gzip/bzip2/xz.\n",
|
||||
buffer_size_kb,
|
||||
default_par.stream_index,
|
||||
default_par.loop_count,
|
||||
default_par.fade_time,
|
||||
default_par.fade_delay
|
||||
@ -186,13 +194,14 @@ static void usage(const char *progname) {
|
||||
|
||||
/* Opens the audio device with the appropriate parameters
|
||||
*/
|
||||
static int set_sample_format(VGMSTREAM *vgms) {
|
||||
static int set_sample_format(int channels, int sample_rate) {
|
||||
ao_sample_format format;
|
||||
|
||||
|
||||
memset(&format, 0, sizeof(format));
|
||||
format.bits = 8 * sizeof(sample);
|
||||
format.channels = vgms->channels;
|
||||
format.rate = vgms->sample_rate;
|
||||
format.bits = 8 * sizeof(sample_t);
|
||||
format.channels = channels;
|
||||
format.rate = sample_rate;
|
||||
format.byte_format =
|
||||
#ifdef LITTLE_ENDIAN_OUTPUT
|
||||
AO_FMT_LITTLE
|
||||
@ -235,20 +244,33 @@ static int set_sample_format(VGMSTREAM *vgms) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int play_vgmstream(const char *filename, struct params *par) {
|
||||
static void apply_config(VGMSTREAM* vgmstream, song_settings_t* cfg) {
|
||||
vgmstream_cfg_t vcfg = {0};
|
||||
|
||||
vcfg.allow_play_forever = 1;
|
||||
|
||||
vcfg.play_forever = cfg->play_forever;
|
||||
vcfg.fade_time = cfg->fade_time;
|
||||
vcfg.loop_count = cfg->loop_count;
|
||||
vcfg.fade_delay = cfg->fade_delay;
|
||||
|
||||
vcfg.ignore_loop = cfg->ignore_loop;
|
||||
vcfg.force_loop = cfg->force_loop;
|
||||
vcfg.really_force_loop = cfg->really_force_loop;
|
||||
|
||||
vgmstream_apply_config(vgmstream, &vcfg);
|
||||
}
|
||||
|
||||
static int play_vgmstream(const char *filename, song_settings_t *cfg) {
|
||||
int ret = 0;
|
||||
STREAMFILE *sf;
|
||||
VGMSTREAM *vgms;
|
||||
STREAMFILE* sf;
|
||||
VGMSTREAM *vgmstream;
|
||||
FILE *save_fps[4];
|
||||
int loop_count;
|
||||
int64_t total_samples;
|
||||
size_t buffer_size;
|
||||
int64_t buffer_samples;
|
||||
int64_t fade_time_samples, fade_start;
|
||||
int time_total_min;
|
||||
double time_total_sec;
|
||||
int64_t s;
|
||||
int32_t max_buffer_samples;
|
||||
int i;
|
||||
int output_channels;
|
||||
|
||||
|
||||
sf = open_stdio_streamfile(filename);
|
||||
if (!sf) {
|
||||
@ -256,11 +278,11 @@ static int play_vgmstream(const char *filename, struct params *par) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sf->stream_index = par->stream_index;
|
||||
vgms = init_vgmstream_from_STREAMFILE(sf);
|
||||
sf->stream_index = cfg->stream_index;
|
||||
vgmstream = init_vgmstream_from_STREAMFILE(sf);
|
||||
close_streamfile(sf);
|
||||
|
||||
if (!vgms) {
|
||||
if (!vgmstream) {
|
||||
fprintf(stderr, "%s: error opening stream\n", filename);
|
||||
return -1;
|
||||
}
|
||||
@ -271,7 +293,7 @@ static int play_vgmstream(const char *filename, struct params *par) {
|
||||
*/
|
||||
if (verbose) {
|
||||
char description[4096] = { '\0' };
|
||||
describe_vgmstream(vgms, description, sizeof(description));
|
||||
describe_vgmstream(vgmstream, description, sizeof(description));
|
||||
puts(description);
|
||||
putchar('\n');
|
||||
}
|
||||
@ -292,37 +314,32 @@ static int play_vgmstream(const char *filename, struct params *par) {
|
||||
for (i = 0; i < 4; i++)
|
||||
save_fps[i] = fopen("/dev/null", "r");
|
||||
|
||||
ret = set_sample_format(vgms);
|
||||
if (ret) goto fail;
|
||||
|
||||
loop_count = par->loop_count;
|
||||
|
||||
if (vgms->loop_flag && loop_count < 0) {
|
||||
/*
|
||||
* Calculate how many loops are needed to achieve a minimum
|
||||
* playback time. Note: This calculation is derived from the
|
||||
* logic in get_vgmstream_play_samples().
|
||||
*/
|
||||
double intro = (double)vgms->loop_start_sample / vgms->sample_rate;
|
||||
double loop = (double)(vgms->loop_end_sample - vgms->loop_start_sample) / vgms->sample_rate;
|
||||
double end = par->fade_time + par->fade_delay;
|
||||
/* Calculate how many loops are needed to achieve a minimum
|
||||
* playback time. Note: This calculation is derived from the
|
||||
* logic in get_vgmstream_play_samples().
|
||||
*/
|
||||
if (vgmstream->loop_flag && cfg->loop_count < 0) {
|
||||
double intro = (double)vgmstream->loop_start_sample / vgmstream->sample_rate;
|
||||
double loop = (double)(vgmstream->loop_end_sample - vgmstream->loop_start_sample) / vgmstream->sample_rate;
|
||||
double end = cfg->fade_time + cfg->fade_delay;
|
||||
if (loop < 1.0) loop = 1.0;
|
||||
loop_count = (int)((par->min_time - intro - end) / loop + 0.99);
|
||||
if (loop_count < 1) loop_count = 1;
|
||||
cfg->loop_count = ((cfg->min_time - intro - end) / loop + 0.99);
|
||||
if (cfg->loop_count < 1.0) cfg->loop_count = 1.0;
|
||||
}
|
||||
|
||||
total_samples = get_vgmstream_play_samples(loop_count, par->fade_time, par->fade_delay, vgms);
|
||||
|
||||
{
|
||||
double total = (double)total_samples / vgms->sample_rate;
|
||||
time_total_min = (int)total / 60;
|
||||
time_total_sec = total - 60 * time_total_min;
|
||||
}
|
||||
/* Config
|
||||
*/
|
||||
apply_config(vgmstream, cfg);
|
||||
|
||||
/* Buffer size in bytes
|
||||
output_channels = vgmstream->channels;
|
||||
vgmstream_mixing_enable(vgmstream, 0, NULL, &output_channels); /* query */
|
||||
|
||||
|
||||
/* Buffer size in bytes (after getting channels)
|
||||
*/
|
||||
buffer_size = 1024 * buffer_size_kb;
|
||||
|
||||
if (!buffer) {
|
||||
if (buffer_size_kb < 1) {
|
||||
fprintf(stderr, "Invalid buffer size '%d'\n", buffer_size_kb);
|
||||
@ -333,85 +350,110 @@ static int play_vgmstream(const char *filename, struct params *par) {
|
||||
if (!buffer) goto fail;
|
||||
}
|
||||
|
||||
buffer_samples = buffer_size / (vgms->channels * sizeof(sample));
|
||||
max_buffer_samples = buffer_size / (output_channels * sizeof(sample));
|
||||
|
||||
fade_time_samples = (int64_t)(par->fade_time * vgms->sample_rate);
|
||||
fade_start = total_samples - fade_time_samples;
|
||||
if (fade_start < 0)
|
||||
fade_start = total_samples;
|
||||
vgmstream_mixing_enable(vgmstream, max_buffer_samples, NULL, NULL); /* enable */
|
||||
|
||||
for (s = 0; s < total_samples && !interrupted; s += buffer_samples) {
|
||||
int64_t buffer_used_samples = MIN(buffer_samples, total_samples - s);
|
||||
char *suffix = "";
|
||||
|
||||
render_vgmstream(buffer, buffer_used_samples, vgms);
|
||||
/* Init
|
||||
*/
|
||||
ret = set_sample_format(output_channels, vgmstream->sample_rate);
|
||||
if (ret) goto fail;
|
||||
|
||||
#ifdef LITTLE_ENDIAN_OUTPUT
|
||||
swap_samples_le(buffer, vgms->channels * buffer_used_samples);
|
||||
#endif
|
||||
/* Decode
|
||||
*/
|
||||
{
|
||||
double total;
|
||||
int time_total_min;
|
||||
double time_total_sec;
|
||||
int play_forever = vgmstream_get_play_forever(vgmstream);
|
||||
|
||||
if (vgms->loop_flag && fade_time_samples > 0 && s >= fade_start) {
|
||||
/* Got to do the fade-out ourselves :p */
|
||||
int64_t b;
|
||||
for (b = 0; b < buffer_used_samples; b++) {
|
||||
double factor = 1.0 - (double)(s + b - fade_start) / fade_time_samples;
|
||||
int c;
|
||||
for (c = 0; c < vgms->channels; c++)
|
||||
buffer[vgms->channels * b + c] *= factor;
|
||||
}
|
||||
suffix = " (fading)";
|
||||
if (out_filename && play_forever) {
|
||||
fprintf(stderr, "%s: cannot play forever and use output filename\n", filename);
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (verbose && !out_filename) {
|
||||
double played = (double)s / vgms->sample_rate;
|
||||
double remain = (double)(total_samples - s) / vgms->sample_rate;
|
||||
int32_t decode_pos_samples = 0;
|
||||
int32_t length_samples = vgmstream_get_samples(vgmstream);
|
||||
if (length_samples <= 0) goto fail;
|
||||
|
||||
int time_played_min = (int)played / 60;
|
||||
double time_played_sec = played - 60 * time_played_min;
|
||||
int time_remain_min = (int)remain / 60;
|
||||
double time_remain_sec = remain - 60 * time_remain_min;
|
||||
total = (double)length_samples / vgmstream->sample_rate;
|
||||
time_total_min = (int)total / 60;
|
||||
time_total_sec = total - 60 * time_total_min;
|
||||
|
||||
/* Time: 01:02.34 [08:57.66] of 10:00.00 */
|
||||
printf("\rTime: %02d:%05.2f [%02d:%05.2f] of %02d:%05.2f%s ",
|
||||
time_played_min, time_played_sec,
|
||||
time_remain_min, time_remain_sec,
|
||||
time_total_min, time_total_sec,
|
||||
suffix);
|
||||
|
||||
while (!interrupted) {
|
||||
int to_do;
|
||||
|
||||
if (decode_pos_samples + max_buffer_samples > length_samples && !play_forever)
|
||||
to_do = length_samples - decode_pos_samples;
|
||||
else
|
||||
to_do = max_buffer_samples;
|
||||
|
||||
if (to_do <= 0) {
|
||||
break; /* EOF */
|
||||
}
|
||||
|
||||
render_vgmstream(buffer, to_do, vgmstream);
|
||||
|
||||
#ifdef LITTLE_ENDIAN_OUTPUT
|
||||
swap_samples_le(buffer, output_channels * to_do);
|
||||
#endif
|
||||
|
||||
if (verbose && !out_filename) {
|
||||
double played = (double)decode_pos_samples / vgmstream->sample_rate;
|
||||
double remain = (double)(length_samples - decode_pos_samples) / vgmstream->sample_rate;
|
||||
|
||||
int time_played_min = (int)played / 60;
|
||||
double time_played_sec = played - 60 * time_played_min;
|
||||
int time_remain_min = (int)remain / 60;
|
||||
double time_remain_sec = remain - 60 * time_remain_min;
|
||||
|
||||
/* Time: 01:02.34 [08:57.66] of 10:00.00 */
|
||||
printf("\rTime: %02d:%05.2f [%02d:%05.2f] of %02d:%05.2f ",
|
||||
time_played_min, time_played_sec,
|
||||
time_remain_min, time_remain_sec,
|
||||
time_total_min, time_total_sec);
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (!ao_play(device, (char *)buffer, to_do * output_channels * sizeof(sample))) {
|
||||
fputs("\nAudio playback error\n", stderr);
|
||||
ao_close(device);
|
||||
device = NULL;
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
decode_pos_samples += to_do;
|
||||
}
|
||||
|
||||
|
||||
if (verbose && !ret) {
|
||||
/* Clear time status line */
|
||||
putchar('\r');
|
||||
for (i = 0; i < 64; i++)
|
||||
putchar(' ');
|
||||
putchar('\r');
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (!ao_play(device, (char *)buffer, buffer_used_samples * vgms->channels * sizeof(sample))) {
|
||||
fputs("\nAudio playback error\n", stderr);
|
||||
ao_close(device);
|
||||
device = NULL;
|
||||
ret = -1;
|
||||
break;
|
||||
if (out_filename && !ret)
|
||||
printf("Wrote %02d:%05.2f of audio to %s\n\n",
|
||||
time_total_min, time_total_sec, out_filename);
|
||||
|
||||
if (interrupted) {
|
||||
fputs("Playback terminated.\n\n", stdout);
|
||||
ret = record_interrupt();
|
||||
if (ret) fputs("Exiting...\n", stdout);
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose && !ret) {
|
||||
/* Clear time status line */
|
||||
putchar('\r');
|
||||
for (i = 0; i < 64; i++)
|
||||
putchar(' ');
|
||||
putchar('\r');
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (out_filename && !ret)
|
||||
printf("Wrote %02d:%05.2f of audio to %s\n\n",
|
||||
time_total_min, time_total_sec, out_filename);
|
||||
|
||||
if (interrupted) {
|
||||
fputs("Playback terminated.\n\n", stdout);
|
||||
ret = record_interrupt();
|
||||
if (ret) fputs("Exiting...\n", stdout);
|
||||
}
|
||||
|
||||
fail:
|
||||
|
||||
close_vgmstream(vgms);
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
fclose(save_fps[i]);
|
||||
@ -419,13 +461,13 @@ static int play_vgmstream(const char *filename, struct params *par) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int play_playlist(const char *filename, struct params *default_par) {
|
||||
static int play_playlist(const char *filename, song_settings_t *default_par) {
|
||||
int ret = 0;
|
||||
FILE *f;
|
||||
char *line = NULL;
|
||||
size_t line_mem = 0;
|
||||
ssize_t line_len = 0;
|
||||
struct params par;
|
||||
song_settings_t par;
|
||||
|
||||
memcpy(&par, default_par, sizeof(par));
|
||||
|
||||
@ -469,7 +511,7 @@ static int play_playlist(const char *filename, struct params *default_par) {
|
||||
else if (PARAM_MATCHES("FADETIME"))
|
||||
par.fade_time = atof(arg);
|
||||
else if (PARAM_MATCHES("LOOPCOUNT"))
|
||||
par.loop_count = atoi(arg);
|
||||
par.loop_count = atof(arg);
|
||||
else if (PARAM_MATCHES("STREAMINDEX"))
|
||||
par.stream_index = atoi(arg);
|
||||
|
||||
@ -495,7 +537,7 @@ static int play_playlist(const char *filename, struct params *default_par) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int play_compressed_file(const char *filename, struct params *par, const char *expand_cmd) {
|
||||
static int play_compressed_file(const char *filename, song_settings_t *par, const char *expand_cmd) {
|
||||
int ret;
|
||||
char temp_dir[128] = "/tmp/vgmXXXXXX";
|
||||
const char *base_name;
|
||||
@ -577,7 +619,7 @@ fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int play_file(const char *filename, struct params *par) {
|
||||
static int play_file(const char *filename, song_settings_t *par) {
|
||||
size_t len = strlen(filename);
|
||||
|
||||
#define ENDS_IN(EXT) !strcasecmp(EXT, filename + len - sizeof(EXT) + 1)
|
||||
@ -614,9 +656,10 @@ static void add_driver_option(const char *key_value) {
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int status = 0;
|
||||
struct params par;
|
||||
int error = 0;
|
||||
int opt;
|
||||
song_settings_t cfg;
|
||||
int extension = 0;
|
||||
|
||||
signal(SIGHUP, interrupt_handler);
|
||||
signal(SIGINT, interrupt_handler);
|
||||
@ -632,43 +675,59 @@ int main(int argc, char **argv) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
again:
|
||||
|
||||
again_opts:
|
||||
{
|
||||
struct params default_par = DEFAULT_PARAMS;
|
||||
memcpy(&par, &default_par, sizeof(par));
|
||||
song_settings_t default_par = DEFAULT_PARAMS;
|
||||
cfg = default_par;
|
||||
}
|
||||
|
||||
while ((opt = getopt(argc, argv, "-D:F:L:M:S:b:d:f:o:@:hrv")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "-D:F:L:M:S:b:d:f:o:@:hrvieEp")) != -1) {
|
||||
switch (opt) {
|
||||
case 1:
|
||||
if (play_file(optarg, &par)) {
|
||||
status = 1;
|
||||
/* glibc getopt extension
|
||||
* (files may appear multiple times in any position, ex. "file.adx -L 1.0 file.adx") */
|
||||
extension = 1;
|
||||
if (play_file(optarg, &cfg)) {
|
||||
error = 1;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case '@':
|
||||
if (play_playlist(optarg, &par)) {
|
||||
status = 1;
|
||||
if (play_playlist(optarg, &cfg)) {
|
||||
error = 1;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
par.fade_delay = atof(optarg);
|
||||
cfg.fade_delay = atof(optarg);
|
||||
break;
|
||||
case 'F':
|
||||
par.fade_time = atof(optarg);
|
||||
cfg.fade_time = atof(optarg);
|
||||
break;
|
||||
case 'L':
|
||||
par.loop_count = atoi(optarg);
|
||||
cfg.loop_count = atof(optarg);
|
||||
break;
|
||||
case 'M':
|
||||
par.min_time = atof(optarg);
|
||||
par.loop_count = -1;
|
||||
cfg.min_time = atof(optarg);
|
||||
cfg.loop_count = -1.0;
|
||||
break;
|
||||
case 'S':
|
||||
par.stream_index = atoi(optarg);
|
||||
cfg.stream_index = atoi(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
cfg.ignore_loop = 1;
|
||||
break;
|
||||
case 'e':
|
||||
cfg.force_loop = 1;
|
||||
break;
|
||||
case 'E':
|
||||
cfg.really_force_loop = 1;
|
||||
break;
|
||||
case 'p':
|
||||
cfg.play_forever = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (!buffer)
|
||||
buffer_size_kb = atoi(optarg);
|
||||
@ -677,7 +736,7 @@ again:
|
||||
driver_id = ao_driver_id(optarg);
|
||||
if (driver_id < 0) {
|
||||
fprintf(stderr, "Invalid output driver \"%s\"\n", optarg);
|
||||
status = 1;
|
||||
error = 1;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
@ -702,24 +761,29 @@ again:
|
||||
}
|
||||
}
|
||||
|
||||
/* try to read infile here in case getopt didn't pass "1" to the above switch I guess */
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
for (opt = 0; opt < argc; ++opt) {
|
||||
if (play_file(argv[opt], &par)) {
|
||||
status = 1;
|
||||
goto done;
|
||||
again_files:
|
||||
if (!extension) {
|
||||
/* standard POSIX getopt
|
||||
* (files are expected to go at the end, optind is at that point) */
|
||||
for (opt = optind; opt < argc; ++opt) {
|
||||
if (play_file(argv[opt], &cfg)) {
|
||||
error = 1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (repeat) {
|
||||
optind = 0;
|
||||
goto again;
|
||||
if (extension) {
|
||||
optind = 0; /* mark reset (BSD may need optreset?) */
|
||||
goto again_opts;
|
||||
}
|
||||
else {
|
||||
goto again_files;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
if (device)
|
||||
ao_close(device);
|
||||
if (buffer)
|
||||
@ -728,7 +792,5 @@ done:
|
||||
ao_free_options(device_options);
|
||||
ao_shutdown();
|
||||
|
||||
return status;
|
||||
return error;
|
||||
}
|
||||
|
||||
/* end vgmstream123.c */
|
||||
|
@ -35,11 +35,11 @@ PKG_CHECK_MODULES(MPG123, [libmpg123], have_libmpg123=yes,
|
||||
)
|
||||
AM_CONDITIONAL(HAVE_LIBMPG123, test "$have_libmpg123" = yes)
|
||||
|
||||
#have_ffmpeg=no
|
||||
have_ffmpeg=no
|
||||
#PKG_CHECK_MODULES(FFMPEG, [libavformat libavcodec libavutil libswresample], have_ffmpeg=yes,
|
||||
# [AC_MSG_WARN([Cannot find ffmpeg - will not enable FFmpeg formats])]
|
||||
#)
|
||||
#AM_CONDITIONAL(HAVE_FFMPEG, test "$have_ffmpeg" = yes)
|
||||
AM_CONDITIONAL(HAVE_FFMPEG, test "$have_ffmpeg" = yes)
|
||||
|
||||
have_audacious=no
|
||||
PKG_CHECK_MODULES(AUDACIOUS, [audacious >= 3.5.0], have_audacious=yes,
|
||||
|
80
doc/TXTH.md
80
doc/TXTH.md
@ -944,3 +944,83 @@ sample_rate = 22050
|
||||
channels = 2
|
||||
num_samples = data_size
|
||||
```
|
||||
|
||||
#### Sega Rally 3 (SAT) ALL_SOUND.txth
|
||||
```
|
||||
codec = PCM16LE
|
||||
|
||||
header_file = ALL_AUDIO.sfx
|
||||
body_file = ALL_AUDIO.sfx
|
||||
|
||||
#header format
|
||||
# 0..0x100: garbage/info?
|
||||
# 0x100 table1 offset (points to audio configs w/ floats, etc)
|
||||
# 0x104 table1 count
|
||||
# 0x108 table2 offset (points to stream offsets for audio configs?)
|
||||
# 0x10c table2 count
|
||||
|
||||
# 0x110 table3 offset (points to headers)
|
||||
# 0x114 table3 count
|
||||
# 0x118 table3 offset (points to stream offsets)
|
||||
# 0x11c table3 count
|
||||
|
||||
|
||||
# read stream header using table3
|
||||
subsong_count = @0x114
|
||||
base_offset = @0x110
|
||||
subsong_offset = 0xc8
|
||||
|
||||
name_offset = 0x00
|
||||
#0xc0: file number
|
||||
base_offset = @0xc4 #absolute jump
|
||||
subsong_offset = 0 #stop offsetting for next vals
|
||||
|
||||
channels = @0xC0
|
||||
sample_rate = @0xC4
|
||||
data_size = @0xC8 #without header
|
||||
num_samples = data_size
|
||||
|
||||
# read stream offset using table4
|
||||
base_offset = 0 #reset current jump
|
||||
base_offset = @0x118
|
||||
subsong_offset = 0xc8
|
||||
|
||||
start_offset = @0xc4 + 0xc0
|
||||
```
|
||||
#### Sega Rally 3 (PC) EnglishStream.txth
|
||||
```
|
||||
codec = PCM16LE
|
||||
|
||||
header_file = EnglishStreamHeader.stm
|
||||
body_file = EnglishStreamData.stm
|
||||
|
||||
#header format
|
||||
# 0..0x100: garbage/info?
|
||||
# 0x100 table1 offset (points to headers)
|
||||
# 0x104 table1 count
|
||||
# 0x108 table2 offset (points to stream offsets)
|
||||
# 0x10c table2 count
|
||||
|
||||
|
||||
# read stream header using table1
|
||||
subsong_count = @0x104
|
||||
base_offset = @0x100
|
||||
subsong_offset = 0xc8
|
||||
|
||||
name_offset = 0x00
|
||||
#0xc0: file number
|
||||
base_offset = @0xc4 #absolute jump
|
||||
subsong_offset = 0 #stop offsetting for next vals
|
||||
|
||||
channels = @0xC0
|
||||
sample_rate = @0xC4
|
||||
data_size = @0xC8 #without header
|
||||
num_samples = data_size
|
||||
|
||||
# read stream offset using table1
|
||||
base_offset = 0 #reset current jump
|
||||
base_offset = @0x108
|
||||
subsong_offset = 0xc8
|
||||
|
||||
start_offset = @0xc4 + 0xc0
|
||||
```
|
||||
|
@ -150,6 +150,7 @@ static const char* extension_list[] = {
|
||||
"diva",
|
||||
"dmsg",
|
||||
"ds2", //txth/reserved [Star Wars Bounty Hunter (GC)]
|
||||
"dsb",
|
||||
"dsf",
|
||||
"dsp",
|
||||
"dspw",
|
||||
@ -610,6 +611,7 @@ static const char* extension_list[] = {
|
||||
"zsm",
|
||||
"zss",
|
||||
"zwdsp",
|
||||
"zwv",
|
||||
|
||||
"vgmstream" /* fake extension, catch-all for FFmpeg/txth/etc */
|
||||
|
||||
|
@ -596,10 +596,14 @@
|
||||
RelativePath=".\meta\brstm.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\btsnd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\btsnd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\bsf.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\capdsp.c"
|
||||
>
|
||||
@ -660,6 +664,10 @@
|
||||
RelativePath=".\meta\dmsg_segh.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\dsb.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\dsf.c"
|
||||
>
|
||||
@ -1946,10 +1954,14 @@
|
||||
RelativePath=".\meta\zsnd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\zwdsp.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\zwdsp.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\zwv.c"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
</Filter>
|
||||
<Filter
|
||||
|
@ -300,6 +300,7 @@
|
||||
<ClCompile Include="meta\bnsf.c" />
|
||||
<ClCompile Include="meta\brstm.c" />
|
||||
<ClCompile Include="meta\btsnd.c" />
|
||||
<ClCompile Include="meta\bsf.c" />
|
||||
<ClCompile Include="meta\capdsp.c" />
|
||||
<ClCompile Include="meta\ck.c" />
|
||||
<ClCompile Include="meta\cri_utf.c" />
|
||||
@ -316,6 +317,7 @@
|
||||
<ClCompile Include="meta\derf.c" />
|
||||
<ClCompile Include="meta\diva.c" />
|
||||
<ClCompile Include="meta\dmsg_segh.c" />
|
||||
<ClCompile Include="meta\dsb.c" />
|
||||
<ClCompile Include="meta\dsf.c" />
|
||||
<ClCompile Include="meta\dsp_adx.c" />
|
||||
<ClCompile Include="meta\dsp_bdsp.c" />
|
||||
@ -575,6 +577,7 @@
|
||||
<ClCompile Include="meta\zsd.c" />
|
||||
<ClCompile Include="meta\zsnd.c" />
|
||||
<ClCompile Include="meta\zwdsp.c" />
|
||||
<ClCompile Include="meta\zwv.c" />
|
||||
<ClCompile Include="coding\acm_decoder.c" />
|
||||
<ClCompile Include="coding\acm_decoder_decode.c" />
|
||||
<ClCompile Include="coding\acm_decoder_util.c" />
|
||||
|
@ -412,6 +412,9 @@
|
||||
<ClCompile Include="meta\btsnd.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\bsf.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\capdsp.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -457,6 +460,9 @@
|
||||
<ClCompile Include="meta\dmsg_segh.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\dsb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\dsf.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1216,6 +1222,9 @@
|
||||
<ClCompile Include="meta\zwdsp.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\zwv.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\acm_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
97
src/meta/bsf.c
Normal file
97
src/meta/bsf.c
Normal file
@ -0,0 +1,97 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* .bsf - from Kuju games [Reign of Fire ((PS2/GC/Xbox)] */
|
||||
VGMSTREAM* init_vgmstream_bsf(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
uint32_t subfile_type;
|
||||
off_t subfile_name;
|
||||
off_t subfile_offset;
|
||||
size_t subfile_size;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"bsf"))
|
||||
goto fail;
|
||||
if (read_u32le(0x00,sf) != 0x42534648) /* "BSFH" (notice chunks are LE even on GC) */
|
||||
goto fail;
|
||||
|
||||
total_subsongs = read_u32le(0x08,sf);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
/* dumb container of other formats */
|
||||
{
|
||||
int i;
|
||||
off_t offset = 0x08 + read_u32le(0x04,sf); /* 0x04: chunk size */
|
||||
|
||||
subfile_type = 0;
|
||||
for (i = 0; i < total_subsongs; i++) {
|
||||
/* subsong header "xxxH" */
|
||||
//uint32_t head_type = read_u32le(offset + 0x00,sf);
|
||||
uint32_t head_size = read_u32le(offset + 0x04,sf);
|
||||
/* 0x08: name
|
||||
* 0x28: audio config? */
|
||||
/* subsong data "xxxD" */
|
||||
uint32_t data_type = read_u32le(offset + 0x08 + head_size + 0x00,sf);
|
||||
uint32_t data_size = read_u32le(offset + 0x08 + head_size + 0x04,sf);
|
||||
|
||||
if (i + 1 == target_subsong) {
|
||||
subfile_name = offset + 0x08;
|
||||
subfile_type = data_type;
|
||||
subfile_size = data_size;
|
||||
subfile_offset = offset + 0x08 + head_size + 0x08;
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 0x08 + head_size + 0x08 + data_size;
|
||||
}
|
||||
|
||||
if (subfile_type == 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
switch(subfile_type) {
|
||||
case 0x44535044: /* "DSPD" */
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "dsp");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_ngc_dsp_std(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case 0x56414744: /* "VAGD" */
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "vag");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_vag(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case 0x57415644: /* "WAVD" */
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "wav");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_riff(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
read_string(vgmstream->stream_name, 0x20, subfile_name, sf);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
54
src/meta/dsb.c
Normal file
54
src/meta/dsb.c
Normal file
@ -0,0 +1,54 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .dsb - from Namco games [Taiko no Tatsujin DS: Dororon! Yokai Daikessen!! (DS)] */
|
||||
VGMSTREAM* init_vgmstream_dsb(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t subfile_offset;
|
||||
size_t subfile_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"dsb"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00,sf) != 0x44535342) /* "DSSB" */
|
||||
goto fail;
|
||||
if (read_u32be(0x40,sf) != 0x44535354) /* "DSST" */
|
||||
goto fail;
|
||||
|
||||
/* - DDSB:
|
||||
* 0x04: chunk size
|
||||
* 0x08: file name
|
||||
* 0x14: sample rate
|
||||
* 0x18: v01?
|
||||
* 0x1c: file size
|
||||
* 0x20: DSST offset
|
||||
*
|
||||
* - DDST:
|
||||
* 0x44: chunk size
|
||||
* 0x48: file name
|
||||
* 0x58: small signed number?
|
||||
* 0x5c: data size (with padding)
|
||||
* 0x60: small signed number?
|
||||
* 0x64: ?
|
||||
* rest: null
|
||||
*/
|
||||
|
||||
subfile_offset = 0x80;
|
||||
subfile_size = read_u32be(0x80 + 0x04, sf) + 0x08; /* files are padded so use BNSF */
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "bnsf");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_bnsf(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -911,4 +911,10 @@ VGMSTREAM* init_vgmstream_ktsc(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_adp_konami(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_zwv(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_dsb(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_bsf(STREAMFILE* sf);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
52
src/meta/zwv.c
Normal file
52
src/meta/zwv.c
Normal file
@ -0,0 +1,52 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .zwv - from Namco games [THE iDOLM@STER Shiny TV (PS3)] */
|
||||
VGMSTREAM* init_vgmstream_zwv(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t subfile_offset;
|
||||
size_t subfile_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"zwv"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00,sf) != 0x77617665) /* "wave" */
|
||||
goto fail;
|
||||
|
||||
/* has a mini header then a proper MSF:
|
||||
* 0x04: null
|
||||
* 0x08: null
|
||||
* 0x0c: version/config? (0x06040000)
|
||||
* 0x10: version/config? (0x00030210)
|
||||
* 0x14: sample rate
|
||||
* 0x18: ? (related to sample rate)
|
||||
* 0x1c: null
|
||||
* 0x20: data offset
|
||||
* 0x24: data size
|
||||
* 0x28: loop flag (0x30+ is removed if no loop)
|
||||
* 0x2c: ? (related to loop, or null)
|
||||
* 0x30: null
|
||||
* 0x30: loop start offset (same as MSF)
|
||||
* 0x30: loop end offset (same as MSF start+length)
|
||||
* 0x3c: null
|
||||
*/
|
||||
|
||||
subfile_offset = read_u32be(0x20, sf) - 0x40;
|
||||
subfile_size = read_u32be(0x24, sf) + 0x40;
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "msf");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_msf(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -1000,7 +1000,8 @@ size_t read_string(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf) {
|
||||
size_t pos;
|
||||
|
||||
for (pos = 0; pos < buf_size; pos++) {
|
||||
char c = read_8bit(offset + pos, sf);
|
||||
uint8_t byte = read_u8(offset + pos, sf);
|
||||
char c = (char)byte;
|
||||
if (buf) buf[pos] = c;
|
||||
if (c == '\0')
|
||||
return pos;
|
||||
@ -1008,7 +1009,8 @@ size_t read_string(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf) {
|
||||
if (buf) buf[pos] = '\0';
|
||||
return buf_size;
|
||||
}
|
||||
if (c < 0x20 || (uint8_t)c > 0xA5)
|
||||
/* UTF-8 only goes to 0x7F, but allow a bunch of Windows-1252 codes that some games use */
|
||||
if (byte < 0x20 || byte > 0xF0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -505,6 +505,9 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_pcm_success,
|
||||
init_vgmstream_ktsc,
|
||||
init_vgmstream_adp_konami,
|
||||
init_vgmstream_zwv,
|
||||
init_vgmstream_dsb,
|
||||
init_vgmstream_bsf,
|
||||
|
||||
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
|
||||
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
|
||||
|
Loading…
x
Reference in New Issue
Block a user