Merge branch 'master' into riff

This commit is contained in:
NicknineTheEagle 2020-07-30 19:51:43 +03:00 committed by GitHub
commit 0ef5b4d40e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 4459 additions and 3297 deletions

View File

@ -73,6 +73,14 @@ REM extracts from subsong 5 to 10 in file.fsb
for /L %A in (5,1,10) do test.exe -s %A -o file_%A.wav file.fsb
```
Output filename in `-o` may use multiple wildcards:
- `:s`: sets current subsong (or 0 if format doesn't have subsongs)
- `:0Ns`: same, but left pads subsong with up to `N` zeroes
- `:n`: internal stream name, or input filename if format doesn't have name
- `:f`: input filename
For example `test.exe -s 2 -o :04s_:n.wav file.fsb` could generate `0002_song1.wav`
### in_vgmstream
*Installation*: drop the ```in_vgmstream.dll``` in your Winamp plugins directory,

View File

@ -26,17 +26,18 @@
#define SAMPLE_BUFFER_SIZE 32768
/* getopt globals (the horror...) */
extern char * optarg;
extern char* optarg;
extern int optind, opterr, optopt;
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);
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);
static void usage(const char * name, int is_full) {
static void usage(const char* name, int is_full) {
fprintf(stderr,"vgmstream CLI decoder " VERSION " " __DATE__ "\n"
"Usage: %s [-o outfile.wav] [options] infile\n"
"Usage: %s [-o <outfile.wav>] [options] <infile>\n"
"Options:\n"
" -o outfile.wav: name of output .wav file, default infile.wav\n"
" -o <outfile.wav>: name of output .wav file, default <infile>.wav\n"
" <outfile> wildcards can be :s=subsong, :n=stream name, :f=infile\n"
" -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"
@ -58,8 +59,10 @@ static void usage(const char * name, int is_full) {
, name);
if (is_full) {
fprintf(stderr,
" -v: validate extensions (for extension testing)\n"
" -r: output a second file after resetting (for reset testing)\n"
" -k N: seeks to N samples before decoding (for seek testing)\n"
" -K N: seeks to N samples before decoding again (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"
);
@ -68,10 +71,9 @@ static void usage(const char * name, int is_full) {
typedef struct {
char * infilename;
char * outfilename;
char * tag_filename;
int decode_only;
char* infilename;
char* outfilename;
char* tag_filename;
int play_forever;
int play_sdtout;
int play_wreckless;
@ -79,7 +81,6 @@ typedef struct {
int print_adxencd;
int print_oggenc;
int print_batchvar;
int test_reset;
int write_lwav;
int only_stereo;
int stream_index;
@ -92,7 +93,11 @@ typedef struct {
int force_loop;
int really_force_loop;
int seek_samples;
int validate_extensions;
int test_reset;
int seek_samples1;
int seek_samples2;
int decode_only;
/* not quite config but eh */
int lwav_loop_start;
@ -100,19 +105,21 @@ typedef struct {
} cli_config;
static int parse_config(cli_config *cfg, int argc, char ** argv) {
static int parse_config(cli_config* cfg, int argc, char** argv) {
int opt;
/* non-zero defaults */
cfg->only_stereo = -1;
cfg->loop_count = 2.0;
cfg->fade_time = 10.0;
cfg->seek_samples1 = -1;
cfg->seek_samples2 = -1;
/* don't let getopt print errors to stdout automatically */
opterr = 0;
/* read config */
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:hO")) != -1) {
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:K:hOv")) != -1) {
switch (opt) {
case 'o':
cfg->outfilename = optarg;
@ -176,11 +183,17 @@ static int parse_config(cli_config *cfg, int argc, char ** argv) {
cfg->tag_filename= optarg;
break;
case 'k':
cfg->seek_samples = atoi(optarg);
cfg->seek_samples1 = atoi(optarg);
break;
case 'K':
cfg->seek_samples2 = atoi(optarg);
break;
case 'O':
cfg->decode_only = 1;
break;
case 'v':
cfg->validate_extensions = 1;
break;
case 'h':
usage(argv[0], 1);
goto fail;
@ -206,7 +219,7 @@ fail:
return 0;
}
static int validate_config(cli_config *cfg) {
static int validate_config(cli_config* cfg) {
if (cfg->play_sdtout && (!cfg->play_wreckless && isatty(STDOUT_FILENO))) {
fprintf(stderr,"Are you sure you want to output wave data to the terminal?\nIf so use -P instead of -p.\n");
goto fail;
@ -215,29 +228,19 @@ static int validate_config(cli_config *cfg) {
fprintf(stderr,"-c must use -p or -P\n");
goto fail;
}
if (cfg->ignore_loop && cfg->force_loop) {
fprintf(stderr,"-e and -i are incompatible\n");
goto fail;
}
if (cfg->ignore_loop && cfg->really_force_loop) {
fprintf(stderr,"-E and -i are incompatible\n");
goto fail;
}
if (cfg->force_loop && cfg->really_force_loop) {
fprintf(stderr,"-E and -e are incompatible\n");
goto fail;
}
if (cfg->play_sdtout && cfg->outfilename) {
fprintf(stderr,"either -p or -o, make up your mind\n");
fprintf(stderr,"use either -p or -o\n");
goto fail;
}
/* other options have built-in priority defined */
return 1;
fail:
return 0;
}
static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) {
static void print_info(VGMSTREAM* vgmstream, cli_config* cfg) {
int channels = vgmstream->channels;
if (!cfg->play_sdtout) {
if (cfg->print_adxencd) {
@ -281,120 +284,164 @@ static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) {
}
}
static void apply_config(VGMSTREAM* vgmstream, cli_config* cfg) {
/* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
//if (vgmstream->config.play_forever) { /* not really suited for CLI */
// cfg->play_forever = 1;
// cfg->ignore_loop = 0;
//}
if (vgmstream->config.loop_count_set) {
cfg->loop_count = vgmstream->config.loop_count;
cfg->play_forever = 0;
cfg->ignore_loop = 0;
}
if (vgmstream->config.fade_delay_set) {
cfg->fade_delay = vgmstream->config.fade_delay;
}
if (vgmstream->config.fade_time_set) {
cfg->fade_time = vgmstream->config.fade_time;
}
if (vgmstream->config.ignore_fade) {
cfg->ignore_fade = 1;
}
if (vgmstream->config.force_loop) {
cfg->ignore_loop = 0;
cfg->force_loop = 1;
cfg->really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->ignore_loop = 0;
cfg->force_loop = 0;
cfg->really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->ignore_loop = 1;
cfg->force_loop = 0;
cfg->really_force_loop = 0;
}
/* apply config */
if (cfg->force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0);
}
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->play_forever = 0;
}
if (cfg->play_forever) {
cfg->ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */
if (cfg->loop_count > 0 && cfg->ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)cfg->loop_count);
cfg->fade_time = 0;
}
vgmstream_cfg_t vcfg = {0};
/* write loops in the wav, but don't actually loop it */
if (cfg->write_lwav) {
cfg->lwav_loop_start = vgmstream->loop_start_sample;
cfg->lwav_loop_end = vgmstream->loop_end_sample;
vgmstream_force_loop(vgmstream, 0, 0,0);
}
}
vcfg.disable_config_override = 1;
cfg->ignore_loop = 1;
void apply_fade(sample_t * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples, int channels) {
int is_fade_on = vgmstream->loop_flag;
if (is_fade_on && fade_samples > 0) {
int samples_into_fade = i - (len_samples - fade_samples);
if (samples_into_fade + to_get > 0) {
int j, k;
for (j = 0; j < to_get; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness = (double)(fade_samples - samples_into_fade) / fade_samples;
for (k = 0; k < channels; k++) {
buf[j*channels + k] = (sample_t)buf[j*channels + k] * fadedness;
}
}
}
if (vgmstream->loop_start_sample < vgmstream->loop_end_sample) {
cfg->lwav_loop_start = vgmstream->loop_start_sample;
cfg->lwav_loop_end = vgmstream->loop_end_sample;
cfg->lwav_loop_end--; /* from spec, +1 is added when reading "smpl" */
}
}
/* only allowed if manually active */
if (cfg->play_forever) {
vcfg.allow_play_forever = 1;
}
vcfg.play_forever = cfg->play_forever;
vcfg.fade_period = cfg->fade_time;
vcfg.loop_times = 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;
vcfg.ignore_fade = cfg->ignore_fade;
vgmstream_apply_config(vgmstream, &vcfg);
}
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);
}
static void clean_filename(char* dst, int clean_paths) {
int i;
for (i = 0; i < strlen(dst); i++) {
char c = dst[i];
int is_badchar = (clean_paths && (c == '\\' || c == '/'))
|| c == '*' || c == '?' || c == ':' /*|| c == '|'*/ || c == '<' || c == '>';
if (is_badchar)
dst[i] = '_';
}
}
void apply_seek(sample_t * buf, VGMSTREAM * vgmstream, int len_samples) {
/* replaces a filename with ":n" (stream name), ":f" (infilename) or ":s" (subsong) wildcards
* (":" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */
void replace_filename(char* dst, size_t dstsize, const char* outfilename, const char* infilename, VGMSTREAM* vgmstream) {
int subsong;
char stream_name[PATH_LIMIT];
char buf[PATH_LIMIT];
char tmp[PATH_LIMIT];
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);
/* file has a "%" > temp replace for sprintf */
strcpy(buf, outfilename);
for (i = 0; i < strlen(buf); i++) {
if (buf[i] == '%')
buf[i] = '|'; /* non-valid filename, not used in format */
}
/* init config */
subsong = vgmstream->stream_index;
if (subsong > vgmstream->num_streams) {
subsong = 0; /* for games without subsongs */
}
if (vgmstream->stream_name && vgmstream->stream_name[0] != '\0') {
snprintf(stream_name, sizeof(stream_name), "%s", vgmstream->stream_name);
clean_filename(stream_name, 1); /* clean subsong name's subdirs */
}
else {
snprintf(stream_name, sizeof(stream_name), "%s", infilename);
clean_filename(stream_name, 0); /* don't clean user's subdirs */
}
/* do controlled replaces of each wildcard (in theory could appear N times) */
do {
char* pos = strchr(buf, ':');
if (!pos)
break;
/* use buf as format and copy formatted result to tmp (assuming sprintf's format must not overlap with dst) */
pos[0] = '%';
if (pos[1] == 'n') {
pos[1] = 's'; /* use %s */
snprintf(tmp, sizeof(tmp), buf, stream_name);
}
else if (pos[1] == 'f') {
pos[1] = 's'; /* use %s */
snprintf(tmp, sizeof(tmp), buf, infilename);
}
else if (pos[1] == 's') {
pos[1] = 'i'; /* use %i */
snprintf(tmp, sizeof(tmp), buf, subsong);
}
else if ((pos[1] == '0' && pos[2] >= '1' && pos[2] <= '9' && pos[3] == 's')) {
pos[3] = 'i'; /* use %0Ni */
snprintf(tmp, sizeof(tmp), buf, subsong);
}
else {
continue;
}
/* copy result to buf again, so it can be used as format in next replace
* (can be optimized with some pointer swapping but who cares about a few extra nanoseconds) */
strcpy(buf, tmp);
}
while (1);
/* replace % back */
for (i = 0; i < strlen(buf); i++) {
if (buf[i] == '|')
buf[i] = '%';
}
snprintf(dst, dstsize, "%s", buf);
}
/* ************************************************************ */
int main(int argc, char ** argv) {
VGMSTREAM * vgmstream = NULL;
FILE * outfile = NULL;
int main(int argc, char** argv) {
VGMSTREAM* vgmstream = NULL;
FILE* outfile = NULL;
char outfilename_temp[PATH_LIMIT];
sample_t * buf = NULL;
sample_t* buf = NULL;
int channels, input_channels;
int32_t len_samples;
int32_t fade_samples;
int i, j;
cli_config cfg = {0};
@ -415,39 +462,32 @@ int main(int argc, char ** argv) {
res = validate_config(&cfg);
if (!res) goto fail;
#if 0
/* CLI has no need to check */
{
/* for plugin testing */
if (cfg.validate_extensions) {
int valid;
vgmstream_ctx_valid_cfg vcfg = {0};
vcfg.skip_standard = 1;
vcfg.skip_standard = 0;
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");
if (!valid) goto fail;
}
#endif
/* open streamfile and pass subsong */
{
//s = init_vgmstream(infilename);
STREAMFILE *streamFile = open_stdio_streamfile(cfg.infilename);
if (!streamFile) {
STREAMFILE* sf = open_stdio_streamfile(cfg.infilename);
if (!sf) {
fprintf(stderr,"file %s not found\n",cfg.infilename);
goto fail;
}
streamFile->stream_index = cfg.stream_index;
vgmstream = init_vgmstream_from_STREAMFILE(streamFile);
close_streamfile(streamFile);
sf->stream_index = cfg.stream_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
if (!vgmstream) {
fprintf(stderr,"failed opening %s\n",cfg.infilename);
@ -465,8 +505,13 @@ int main(int argc, char ** argv) {
/* enable after config but before outbuf */
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels);
if (cfg.play_forever && (!vgmstream->loop_flag || vgmstream->loop_target > 0)) {
fprintf(stderr,"I could play a nonlooped track forever, but it wouldn't end well.");
/* get final play config */
len_samples = vgmstream_get_samples(vgmstream);
if (len_samples <= 0)
goto fail;
if (cfg.play_forever && !vgmstream_get_play_forever(vgmstream)) {
fprintf(stderr,"File can't be played forever");
goto fail;
}
@ -484,10 +529,21 @@ int main(int argc, char ** argv) {
/* maybe should avoid overwriting with this auto-name, for the unlikely
* case of file header-body pairs (file.ext+file.ext.wav) */
}
else if (strchr(cfg.outfilename, ':') != NULL) {
/* special substitution */
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg.outfilename, cfg.infilename, vgmstream);
cfg.outfilename = outfilename_temp;
}
/* don't overwrite itself! */
if (strcmp(cfg.outfilename, cfg.infilename) == 0) {
fprintf(stderr,"same infile and outfile name: %s\n", cfg.outfilename);
goto fail;
}
outfile = fopen(cfg.outfilename,"wb");
if (!outfile) {
fprintf(stderr,"failed to open %s for output\n",cfg.outfilename);
fprintf(stderr,"failed to open %s for output\n", cfg.outfilename);
goto fail;
}
@ -501,26 +557,7 @@ int main(int argc, char ** argv) {
print_info(vgmstream, &cfg);
/* print tags info */
if (cfg.tag_filename) {
VGMSTREAM_TAGS *tags;
const char *tag_key, *tag_val;
STREAMFILE *tagFile = open_stdio_streamfile(cfg.tag_filename);
if (!tagFile) {
fprintf(stderr,"tag file %s not found\n",cfg.tag_filename);
goto fail;
}
printf("tags:\n");
tags = vgmstream_tags_init(&tag_key, &tag_val);
vgmstream_tags_reset(tags, cfg.infilename);
while ( vgmstream_tags_next_tag(tags, tagFile)) {
printf("- '%s'='%s'\n", tag_key, tag_val);
}
vgmstream_tags_close(tags);
close_streamfile(tagFile);
}
print_tags(&cfg);
/* prints done */
if (cfg.print_metaonly) {
@ -533,23 +570,15 @@ int main(int argc, char ** argv) {
}
/* get final play config */
len_samples = get_vgmstream_play_samples(cfg.loop_count,cfg.fade_time,cfg.fade_delay,vgmstream);
fade_samples = (int32_t)(cfg.fade_time < 0 ? 0 : cfg.fade_time * vgmstream->sample_rate);
if (cfg.seek_samples >= len_samples)
cfg.seek_samples = 0;
len_samples -= cfg.seek_samples;
if (!cfg.play_sdtout && !cfg.print_adxencd && !cfg.print_oggenc && !cfg.print_batchvar) {
double time_mm, time_ss, seconds;
seconds = (double)len_samples / vgmstream->sample_rate;
time_mm = (int)(seconds / 60.0);
time_ss = seconds - time_mm * 60.0f;
printf("samples to play: %d (%1.0f:%06.3f seconds)\n", len_samples, time_mm, time_ss);
}
if (cfg.seek_samples1 >= len_samples)
cfg.seek_samples1 = -1;
if (cfg.seek_samples2 >= len_samples)
cfg.seek_samples2 = -1;
if (cfg.seek_samples2 >= 0)
len_samples -= cfg.seek_samples2;
else if (cfg.seek_samples1 >= 0)
len_samples -= cfg.seek_samples1;
/* last init */
@ -559,20 +588,6 @@ int main(int argc, char ** argv) {
goto fail;
}
/* slap on a .wav header */
if (!cfg.decode_only) {
uint8_t wav_buf[0x100];
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
size_t bytes_done;
bytes_done = make_wav_header(wav_buf,0x100,
len_samples, vgmstream->sample_rate, channels_write,
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
}
/* decode forever */
while (cfg.play_forever) {
int to_get = SAMPLE_BUFFER_SIZE;
@ -590,7 +605,24 @@ int main(int argc, char ** argv) {
}
apply_seek(buf, vgmstream, cfg.seek_samples);
/* slap on a .wav header */
if (!cfg.decode_only) {
uint8_t wav_buf[0x100];
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
size_t bytes_done;
bytes_done = make_wav_header(wav_buf,0x100,
len_samples, vgmstream->sample_rate, channels_write,
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
}
if (cfg.seek_samples1 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples1);
if (cfg.seek_samples2 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples2);
/* decode */
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
@ -600,8 +632,6 @@ int main(int argc, char ** argv) {
render_vgmstream(buf, to_get, vgmstream);
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels);
if (!cfg.decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
@ -632,13 +662,6 @@ int main(int argc, char ** argv) {
goto fail;
}
reset_vgmstream(vgmstream);
/* vgmstream manipulations are undone by reset */
apply_config(vgmstream, &cfg);
apply_seek(buf, vgmstream, cfg.seek_samples);
/* slap on a .wav header */
if (!cfg.decode_only) {
uint8_t wav_buf[0x100];
@ -652,6 +675,14 @@ int main(int argc, char ** argv) {
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
}
reset_vgmstream(vgmstream);
if (cfg.seek_samples1 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples1);
if (cfg.seek_samples2 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples2);
/* decode */
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
int to_get = SAMPLE_BUFFER_SIZE;
@ -660,8 +691,6 @@ int main(int argc, char ** argv) {
render_vgmstream(buf, to_get, vgmstream);
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels);
if (!cfg.decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
@ -697,28 +726,28 @@ fail:
static void make_smpl_chunk(uint8_t * buf, int32_t loop_start, int32_t loop_end) {
static void make_smpl_chunk(uint8_t* buf, int32_t loop_start, int32_t loop_end) {
int i;
memcpy(buf+0, "smpl", 4);/* header */
put_32bitLE(buf+4, 0x3c);/* size */
memcpy(buf+0, "smpl", 0x04); /* header */
put_s32le(buf+0x04, 0x3c); /* size */
for (i = 0; i < 7; i++)
put_32bitLE(buf+8 + i * 4, 0);
put_s32le(buf+0x08 + i * 0x04, 0);
put_32bitLE(buf+36, 1);
put_s32le(buf+0x24, 1);
for (i = 0; i < 3; i++)
put_32bitLE(buf+40 + i * 4, 0);
put_s32le(buf+0x28 + i * 0x04, 0);
put_32bitLE(buf+52, loop_start);
put_32bitLE(buf+56, loop_end);
put_32bitLE(buf+60, 0);
put_32bitLE(buf+64, 0);
put_s32le(buf+0x34, loop_start);
put_s32le(buf+0x38, loop_end);
put_s32le(buf+0x3C, 0);
put_s32le(buf+0x40, 0);
}
/* make a RIFF header for .wav */
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) {
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) {
size_t data_size, header_size;
data_size = sample_count * channels * sizeof(sample_t);
@ -729,28 +758,28 @@ static size_t make_wav_header(uint8_t * buf, size_t buf_size, int32_t sample_cou
if (header_size > buf_size)
goto fail;
memcpy(buf+0x00, "RIFF", 4); /* RIFF header */
put_32bitLE(buf+4, (int32_t)(header_size - 0x08 + data_size)); /* size of RIFF */
memcpy(buf+0x00, "RIFF", 0x04); /* RIFF header */
put_32bitLE(buf+0x04, (int32_t)(header_size - 0x08 + data_size)); /* size of RIFF */
memcpy(buf+0x08, "WAVE", 4); /* WAVE header */
memcpy(buf+0x0c, "fmt ", 4); /* WAVE fmt chunk */
put_32bitLE(buf+0x10, 0x10); /* size of WAVE fmt chunk */
put_16bitLE(buf+0x14, 1); /* compression code 1=PCM */
put_16bitLE(buf+0x16, channels); /* channel count */
put_32bitLE(buf+0x18, sample_rate); /* sample rate */
put_32bitLE(buf+0x1c, sample_rate*channels*sizeof(sample_t)); /* bytes per second */
put_16bitLE(buf+0x20, (int16_t)(channels*sizeof(sample_t))); /* block align */
put_16bitLE(buf+0x22, sizeof(sample_t)*8); /* significant bits per sample */
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 */
if (smpl_chunk && loop_end) {
make_smpl_chunk(buf+0x24, loop_start, loop_end);
memcpy(buf+0x24+0x3c+0x08, "data", 0x04); /* WAVE data chunk */
put_32bitLE(buf+0x28+0x3c+0x08, (int32_t)data_size); /* size of WAVE data chunk */
put_u32le(buf+0x28+0x3c+0x08, (int32_t)data_size); /* size of WAVE data chunk */
}
else {
memcpy(buf+0x24, "data", 0x04); /* WAVE data chunk */
put_32bitLE(buf+0x28, (int32_t)data_size); /* size of WAVE data chunk */
put_s32le(buf+0x28, (int32_t)data_size); /* size of WAVE data chunk */
}
/* could try to add channel_layout, but would need to write WAVEFORMATEXTENSIBLE (maybe only if arg flag?) */

View File

@ -60,14 +60,20 @@ set C_W=0e
set C_E=0c
set C_O=0f
REM # remove command options and possibly "
for /f "tokens=1-1 delims= " %%A in ("%OP_CMD_OLD%") do set CHECK_OLD=%%A
for /f "tokens=1-1 delims= " %%A in ("%OP_CMD_NEW%") do set CHECK_NEW=%%A
set CHECK_OLD=%CHECK_OLD:"=%
set CHECK_NEW=%CHECK_NEW:"=%
REM # check exe
set CMD_CHECK=where "%OP_CMD_OLD%" "%OP_CMD_NEW%"
set CMD_CHECK=where "%CHECK_OLD%" "%CHECK_NEW%"
%CMD_CHECK% > nul
if %ERRORLEVEL% NEQ 0 (
echo Old/new exe not found
goto error
)
if %OP_SEARCH%=="" (
echo Search wildcard not specified
goto error

View File

@ -1,192 +1,189 @@
# vgmstream development help
## Code
vgmstream uses C (C89 when possible), and C++ for the foobar2000 and Audacious plugins.
C should be restricted to features VS2010 understands. This mainly means declaring variables at the start of a { .. } block (declare+initialize is fine, as long as it doesn't reference variables declared in the same block) and avoiding C99 features like variable-length arrays (but certain others like // comments are fine).
There are no hard coding rules but for consistency one could follow the style used in most files:
- general C conventions
- 4 spaces instead of tabs
- underscore_and_lowercase_names instead of CamelCase
- /* C89 comments */ for general comments, //C99 comments for special comments (like disabling code but leaving it there for visibility)
- brackets starting in the same line
ex `if (..) { CRLF ... }`
- line length ~100, more is ok for 'noise code' (uninteresting calcs or function defs)
- offsets/sizes in hex, counts/numbers in decimal
- test functions may return 1=ok, 0=ko for simplicity.
- free(ptr) no need to NULL-check per standard, close_stuff(ptr) should follow when possible
- lowercase_helper_structs, UPPERCASE_MAIN_STRUCTS
- spaces in calcs/ifs/etc may be added as desired for clarity
ex. `if (simple_check)` or `if ( complex_and_important_stuff(weird + weird) )`
- goto are used to abort and reach "fail" sections (typical C cleanup style)
But other styles may be found, this isn't very important as most files are isolated. When modifying a file or section of the code just try to follow the style set there so code doesn't clash too much.
### Code quality
There is quite a bit of code that could be improved overall, but given how niche the project is priority is given to adding and improving formats. Parts may segfault or even cause infinite loops on bad data, but it's fixed as encountered rather than worrying too much about improbable cases. There isn't an automated test suite at the moment, so tests are manually done as needed.
For regression testing there is a simple script that compares output of a previous version of vgmstream_cli with current. Some bugs may drastically change output when fixed (for example adjusting loops or decoding) so it could be hard to automate and maintain.
Code is checked for leaks from time to time using detection tools, but most of vgmstream formats are quite simple and don't need to manage memory. It's mainly useful for files using external decoders or complex segmented/layered layout combos.
```
# recommended to compile with debug info, for example:
make vgmstream_cli EXTRA_CFLAGS="-g" STRIP=echo
# find leaks
drmemory -- vgmstream_cli -o file.ext
```
Some of the code can be inefficient or duplicated at places, but it isn't that much of a problem if gives clarity. vgmstream's performance is fast enough (as it mainly deals with playing songs in real time) so that favors clarity over optimization. Performance bottlenecks are mainly:
- I/O: since I/O is buffered it's possible to needlessly trash the buffers when reading previous/next offsets back and forth. It's better to read linearly using big enough data chunks and cache values.
- for loops: since your average audio file contains millions of samples, this means lots of loops. Care should be taken to avoid unnecessary function calls or recalculations per single sample when multiple samples could be processed at once.
## Source structure
```
./ scripts
./audacious/ Audacious plugin
./cli/ CLI tools
./doc/ docs
./ext_includes/ external includes for compiling
./ext_libs/ external libs/DLLs for linking
./fb2k/ foobar2000 plugin
./src/ main vgmstream code and helpers
./src/coding/ format data decoders
./src/layout/ format data demuxers
./src/meta/ format header parsers
./winamp/ Winamp plugin
./xmplay/ XMPlay plugin
```
## Terminology
Quick list of some audio terms used through vgmstream, applied to code. Mainly meant for the neophyte, hopefully helps new people willing to contribute. vgmstream isn't too complex and with some perseverance one can add a new format (*meta*) easily enough.
- stream: an audio file, or a section inside it, or data 'lane' within, as the name implies. Just a generic term for a data chunk.
- Streams normally have a header that tells how to play the file, and encoded ('compressed') audio data.
- encoder: program or code that transforms audio samples to encoded data.
- decoder: program or code that transforms encoded data to audio samples.
- encoded data: bunch of bytes (sometimes bits) that decode into one or many samples (for one or many channels) with a decoder.
- audio sample: digital audio unit (single value) to define playable sound. A sound is a wave, and an array of many samples (digital) together make a wave (analog).
- Each output channel has its own set of samples.
- Normally `1 sample` actually means `1 sample for every channel` (common standard that makes code logic simpler).
- If an stereo file has `1000000` samples it actually means `2*1000000` total samples.
- sample rate: number of samples per second (in *hz*). Also called frequency.
- If a file has a sample rate *44100hz* and lasts *30 seconds* this means `44100 * 30 = 1323000` samples.
- Since many samples together make a wave, the higher the sample rate the more samples we have, and the better-sounding wave we get.
- frame: smallest part of data that a decoder can transform into samples.
- A frame can contain samples for one or many channels, depending on the encoder.
- interleave: size of encoded data for one channel. Some encoders only take a single (mono) channel at a time, so to make stereo or more we interlace frames.
- For example 1 frame L, 1 frame R, 1 frame L, 1 frame R, etc. Or 10 frames L, 10 frames R, etc.
- block: a generic section of data, made of one or many frames for all channels.
## Overview
vgmstream works by parsing a music stream header (*meta/*), preparing/controlling data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
Very simplified it goes like this:
- player (test.exe, plugin, etc) opens a file stream (STREAMFILE) *[plugin's main/decode]*
- init tries all parsers (metas) until one works *[init_vgmstream]*
- parser reads header (channels, sample rate, loop points) and set ups the VGMSTREAM struct, if the format is correct *[init_vgmstream_(format-name)]*
- player finds total_samples to play, based on the number of loops and other settings *[get_vgmstream_play_samples]*
- player asks to fill a small sample buffer *[render_vgmstream]*
- layout prepares samples and offsets to read from the stream *[render_vgmstream_(layout)]*
- decoder reads and decodes bytes into PCM samples *[decode_vgmstream_(coding)]*
- player plays those samples, asks to fill sample buffer again, repeats (until total_samples)
- layout moves offsets back to loop_start when loop_end is reached *[vgmstream_do_loop]*
- player closes the VGMSTREAM once the stream is finished
vgsmtream's main code (located in src) may be considered "libvgmstream", and plugins interface it through vgmstream.h, mainly the part commented as "vgmstream public API". There isn't a clean external API at the moment, this may be improved later.
## Components
### STREAMFILEs
Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
Players should open a base STREAMFILE and pass it to init_vgmstream. Once it's done this STREAMFILE must be closed, as internally vgmstream opens its own copy (using the base one's callbacks).
For optimization purposes vgmstream may open a copy of the FILE per channel, as each has its own I/O buffer, and channel data can be too separate to fit a single buffer.
Custom STREAMFILEs wrapping base STREAMFILEs may be used for complex I/O cases:
- file is a container of another format (`fakename/clamp_streamfile`)
- data needs decryption (`io_streamfile`)
- data must be expanded/reduced on the fly for codecs that are not easy to feed chunked data (`io_streamfile`)
- data is divided in multiple physical files, but must be read as a single (`multifile_streamfile`)
Certain metas combine those streamfiles together with special layouts to support very complex cases, that would require massive changes in vgmstream to support in a cleaner (possible undesirable) way.
### VGMSTREAM
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc), and is used to interact with the API.
### metas
Metadata (header) parsers that identify and handle formats.
To add a new one:
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
- *src/meta/meta.h*: define parser's init
- *src/vgmstream.h*: define meta type in the meta_t list
- *src/vgmstream.c*: add parser init to the init list
- *src/formats.c*: add new extension to the format list, add meta type description
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS
- if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif*
Ultimately the meta must alloc the VGMSTREAM, set config and initial state. vgmstream needs the total of number samples to work, so at times must convert from data sizes to samples (doing calculations or using helpers).
It also needs to open and assign to the VGMSTREAM one or several STREAMFILEs (usually reopening the base one, but could be any other file) to do I/O during decode, as well as setting the starting offsets of each channel and other values; this gives metas full flexibility at the cost of some repetition. The STREAMFILE passed to the meta will be discarded and its pointer must not be reused.
The .c file is usually named after the format's main extension or header id, optionally with affixes. Each file should parse one format and/or its variations (regardless of accepted extensions or decoders used) for consistency, but deviations may be found in the codebase. Sometimes a format is already parsed but not accepted due to bugs though.
Different formats may use the same extension but this isn't a problem as long as the header id or some other validation tells them apart, and should be implemented in separate .c files. If the format is headerless and the extension isn't unique enough it probably needs a generic GENH/TXTH header instead of direct support.
If the format supports subsongs it should read the stream index (subsong number) in the passed STREAMFILE, and use it to parse a section of the file. Then it must report the number of subsongs in the VGMSTREAM, to signal this feature is enabled. The index is 1-based (first subsong is 1, 0 is default/first). This makes possible to directly use bank-like formats like .FSB, and while vgmstream could technically support any container (like generic bigfiles or even .zip) it should be restricted to files that actually are audio banks.
### layouts
Layouts control most of the main logic:
- receive external buffer to fill with PCM samples
- detect when looping must be done
- find max number of samples to do next decoder call (usually one frame, less if loop starts/ends)
- call decoder
- do post-process if necessary (move offsets, check stuff, etc)
- repeat until buffer is filled
Available layouts, depending on how codec data is laid out:
- flat: straight data. Decoder should handle channel offsets and other details normally.
- interleave: one data block per channel, mixed in configurable sizes. Once one channel block is fully decoded this layout skips the other channels, so the decoder only handles one at a time.
- blocked: data is divided into blocks, often with a header. Layout detects when a block is done and asks a helper function to fix offsets (skipping the header and pointing to data per channel), depending on the block format.
- segmented: file is divided into consecutive but separate segments, each one is setup as a fully separate VGMSTREAM.
- layered: file is divided into multichannel layers that play at the same time, each one is setup as a fully separate VGMSTREAM.
- others: uncommon cases may need its own custom layout, but may be dealt with using custom IO STREAMFILEs instead.
The layout used mainly depends on the decoder. MP3 data (that may have 1 or 2 channels per frame) uses flat layout, while DSP ADPCM (that only decodes one channel at a time) is interleaved. In case of mono files either could be used as there won't be any actual difference.
Layouts expect the VGMSTREAM to be properly initialized during the meta processing (channel offsets must point to each channel start offset).
### decoders
Decoders take a sample buffer, convert data to PCM samples and fill one or multiple channels at a time, depending on the decoder itself. Usually its data is divided into frames with a number of samples, and should only need to do one frame at a time (when size is fixed/informed; vgmstream gives flexibility to the decoder), but must take into account that the sample buffer may be smaller than the frame samples, and that may start some samples into the frame.
Every call the decoder will need to find out the current frame offset (usually per channel). This is usually done with a base channel offset (from the VGMSTREAM) plus deriving the frame number (thus sub-offset, but only if frames are fixed) through the current sample, or manually updating the channel offsets every frame. This second method is not suitable to use with the interleave layout as it advances the offsets assuming they didn't change (this is a limitation/bug at the moment). Similarly, the blocked layout cannot contain interleaved data, and must use alt decoders with internal interleave (also a current limitation). Thus, some decoders and layouts don't mix.
If the decoder needs to keep state between calls it may use the VGMSTREAM for common values (like ADPCM history), or alloc a custom data struct. In that case the decoder should provide init/free functions so the meta or vgmstream may use. This is the case with decoders implemented using external libraries (*ext_libs*), as seen in *#ifdef VGM_USE_X ... #endif* sections.
Adding a new decoder involves:
- *src/coding/(decoder-name).c*: create `decode_x` function that decodes stream data into the passed sample buffer. If the codec requires custom internals it may need `init/reset/seek/free_x`, or other helper functions.
- *src/coding/coding.h*: define decoder's functions.
- *src/vgmstream.h*: define new coding type in the list. If the codec requires custom internals, define new `x_codec_data` struct.
- *src/vgmstream.c: reset_vgmstream*: call `reset_x` if needed
- *src/vgmstream.c: close_vgmstream*: call `free_x` if needed
- *src/vgmstream.c: get_vgmstream_samples_per_frame*: define so vgmstream only asks for N samples per decode_x call. May return 0 if variable/unknown/etc (decoder must handle setting arbitrary number of samples)
- *src/vgmstream.c: get_vgmstream_frame_size*: define so vgmstream can do certain internal calculations. May return 0 if variable/unknown/etc, but blocked/interleave layouts will need to be used in a certain way.
- *src/vgmstream.c: decode_vgmstream*: call `decode_x`, possibly once per channel if the decoder works with a channel at a time.
- *src/vgmstream.c: vgmstream_do_loop*: call `seek_x` if needed
- *src/vgmstream.c: reset_vgmstream*: call `reset_x` if needed
- *src/formats.c*: add coding type description
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (decoder-name).c parser in VS
- *src/vgmstream.c*: add parser init to the init list
- if the codec depends on a external library don't forget to mark parts with: *#ifdef VGM_USE_X ... #endif*
### core
The vgmstream core simply consists of functions gluing the above together and some helpers (ex.- extension list, loop adjust, etc).
The *Overview* section should give an idea about how it's used.
# vgmstream development help
## Code
vgmstream uses C (C89 when possible), and C++ for the foobar2000 and Audacious plugins.
C should be restricted to features VS2010 understands. This mainly means declaring variables at the start of a { .. } block (declare+initialize is fine, as long as it doesn't reference variables declared in the same block) and avoiding C99 features like variable-length arrays (but certain others like // comments are fine).
There are no hard coding rules but for consistency one could follow the style used in most files:
- general C conventions
- 4 spaces instead of tabs
- underscore_and_lowercase_names instead of CamelCase
- /* C89 comments */ for general comments, //C99 comments for special comments (like disabling code but leaving it there for visibility)
- brackets starting in the same line
- ex. `if (..) { CRLF ... }`
- line length ~100, more is ok for 'noise code' (uninteresting calcs or function defs)
- offsets/sizes in hex, counts/numbers in decimal
- test functions may return 1=ok, 0=ko for simplicity.
- free(ptr) no need to NULL-check per standard, close_stuff(ptr) should follow when possible
- lowercase_helper_structs, UPPERCASE_MAIN_STRUCTS
- spaces in calcs/ifs/etc may be added as desired for clarity
- ex. `if (simple_check)` or `if ( complex_and_important_stuff(weird + weird) )`
- goto are used to abort and reach "fail" sections (typical C cleanup style)
- pointer definitions should keep the `*` together for consistency
- ex. `VGMSTREAM* init_x() { ... }` `STREAMFILE* sf = ...`
But other styles may be found, this isn't very important as most files are isolated. When modifying a file or section of the code just try to follow the style set there so code doesn't clash too much.
### Code quality
There is quite a bit of code that could be improved overall, but given how niche the project is priority is given to adding and improving formats. Parts may segfault or even cause infinite loops on bad data, but it's fixed as encountered rather than worrying too much about improbable cases. There isn't an automated test suite at the moment, so tests are manually done as needed.
For regression testing there is a simple script that compares output of a previous version of vgmstream_cli with current. Some bugs may drastically change output when fixed (for example adjusting loops or decoding) so it could be hard to automate and maintain.
Code is checked for leaks from time to time using detection tools, but most of vgmstream formats are quite simple and don't need to manage memory. It's mainly useful for files using external decoders or complex segmented/layered layout combos.
```
# recommended to compile with debug info, for example:
make vgmstream_cli EXTRA_CFLAGS="-g" STRIP=echo
# find leaks
drmemory -- vgmstream_cli -o file.ext
```
Some of the code can be inefficient or duplicated at places, but it isn't that much of a problem if gives clarity. vgmstream's performance is fast enough (as it mainly deals with playing songs in real time) so that favors clarity over optimization. Performance bottlenecks are mainly:
- I/O: since I/O is buffered it's possible to needlessly trash the buffers when reading previous/next offsets back and forth. It's better to read linearly using big enough data chunks and cache values.
- for loops: since your average audio file contains millions of samples, this means lots of loops. Care should be taken to avoid unnecessary function calls or recalculations per single sample when multiple samples could be processed at once.
## Source structure
```
./ scripts
./audacious/ Audacious plugin
./cli/ CLI tools
./doc/ docs
./ext_includes/ external includes for compiling
./ext_libs/ external libs/DLLs for linking
./fb2k/ foobar2000 plugin
./src/ main vgmstream code and helpers
./src/coding/ format data decoders
./src/layout/ format data demuxers
./src/meta/ format header parsers
./winamp/ Winamp plugin
./xmplay/ XMPlay plugin
```
## Terminology
Quick list of some audio terms used through vgmstream, applied to code. Mainly meant for the neophyte, hopefully helps new people willing to contribute. vgmstream isn't too complex and with some perseverance one can add a new format (*meta*) easily enough.
- stream: an audio file, or a section inside it, or data 'lane' within, as the name implies. Just a generic term for a data chunk.
- Streams normally have a header that tells how to play the file, and encoded ('compressed') audio data.
- encoder: program or code that transforms audio samples to encoded data.
- decoder: program or code that transforms encoded data to audio samples.
- encoded data: bunch of bytes (sometimes bits) that decode into one or many samples (for one or many channels) with a decoder.
- audio sample: digital audio unit (single value) to define playable sound. A sound is a wave, and an array of many samples (digital) together make a wave (analog).
- Each output channel has its own set of samples.
- Normally `1 sample` actually means `1 sample for every channel` (common standard that makes code logic simpler).
- If an stereo file has `1000000` samples it actually means `2*1000000` total samples.
- sample rate: number of samples per second (in *hz*). Also called frequency.
- If a file has a sample rate *44100hz* and lasts *30 seconds* this means `44100 * 30 = 1323000` samples.
- Since many samples together make a wave, the higher the sample rate the more samples we have, and the better-sounding wave we get.
- frame: smallest part of data that a decoder can transform into samples.
- A frame can contain samples for one or many channels, depending on the encoder.
- interleave: size of encoded data for one channel. Some encoders only take a single (mono) channel at a time, so to make stereo or more we interlace frames.
- For example 1 frame L, 1 frame R, 1 frame L, 1 frame R, etc. Or 10 frames L, 10 frames R, etc.
- block: a generic section of data, made of one or many frames for all channels.
## Overview
vgmstream works by parsing a music stream header (*meta/*), preparing/controlling data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
Very simplified it goes like this:
- player (test.exe, plugin, etc) opens a file stream (STREAMFILE) *[plugin's main/decode]*
- init tries all parsers (metas) until one works *[init_vgmstream]*
- parser reads header (channels, sample rate, loop points) and set ups the VGMSTREAM struct, if the format is correct *[init_vgmstream_(format-name)]*
- player finds total_samples to play, based on the number of loops and other settings *[get_vgmstream_play_samples]*
- player asks to fill a small sample buffer *[render_vgmstream]*
- layout prepares samples and offsets to read from the stream *[render_vgmstream_(layout)]*
- decoder reads and decodes bytes into PCM samples *[decode_vgmstream_(coding)]*
- player plays those samples, asks to fill sample buffer again, repeats (until total_samples)
- layout moves offsets back to loop_start when loop_end is reached *[vgmstream_do_loop]*
- player closes the VGMSTREAM once the stream is finished
vgsmtream's main code (located in src) may be considered "libvgmstream", and plugins interface it through vgmstream.h, mainly the part commented as "vgmstream public API". There isn't a clean external API at the moment, this may be improved later.
## Components
### STREAMFILEs
Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
Players should open a base STREAMFILE and pass it to init_vgmstream. Once it's done this STREAMFILE must be closed, as internally vgmstream opens its own copy (using the base one's callbacks).
For optimization purposes vgmstream may open a copy of the FILE per channel, as each has its own I/O buffer, and channel data can be too separate to fit a single buffer.
Custom STREAMFILEs wrapping base STREAMFILEs may be used for complex I/O cases:
- file is a container of another format (`fakename/clamp_streamfile`)
- data needs decryption (`io_streamfile`)
- data must be expanded/reduced on the fly for codecs that are not easy to feed chunked data (`io_streamfile`)
- data is divided in multiple physical files, but must be read as a single (`multifile_streamfile`)
Certain metas combine those streamfiles together with special layouts to support very complex cases, that would require massive changes in vgmstream to support in a cleaner (possible undesirable) way.
### VGMSTREAM
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc), and is used to interact with the API.
### metas
Metadata (header) parsers that identify and handle formats.
To add a new one:
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
- *src/meta/meta.h*: define parser's init
- *src/vgmstream.h*: define meta type in the meta_t list
- *src/vgmstream.c*: add parser init to the init list
- *src/formats.c*: add new extension to the format list, add meta type description
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS
- if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif*
Ultimately the meta must alloc the VGMSTREAM, set config and initial state. vgmstream needs the total of number samples to work, so at times must convert from data sizes to samples (doing calculations or using helpers).
It also needs to open and assign to the VGMSTREAM one or several STREAMFILEs (usually reopening the base one, but could be any other file) to do I/O during decode, as well as setting the starting offsets of each channel and other values; this gives metas full flexibility at the cost of some repetition. The STREAMFILE passed to the meta will be discarded and its pointer must not be reused.
The .c file is usually named after the format's main extension or header id, optionally with affixes. Each file should parse one format and/or its variations (regardless of accepted extensions or decoders used) for consistency, but deviations may be found in the codebase. Sometimes a format is already parsed but not accepted due to bugs though.
Different formats may use the same extension but this isn't a problem as long as the header id or some other validation tells them apart, and should be implemented in separate .c files. If the format is headerless and the extension isn't unique enough it probably needs a generic GENH/TXTH header instead of direct support.
If the format supports subsongs it should read the stream index (subsong number) in the passed STREAMFILE, and use it to parse a section of the file. Then it must report the number of subsongs in the VGMSTREAM, to signal this feature is enabled. The index is 1-based (first subsong is 1, 0 is default/first). This makes possible to directly use bank-like formats like .FSB, and while vgmstream could technically support any container (like generic bigfiles or even .zip) it should be restricted to files that actually are audio banks.
### layouts
Layouts control most of the main logic:
- receive external buffer to fill with PCM samples
- detect when looping must be done
- find max number of samples to do next decoder call (usually one frame, less if loop starts/ends)
- call decoder
- do post-process if necessary (move offsets, check stuff, etc)
- repeat until buffer is filled
Available layouts, depending on how codec data is laid out:
- flat: straight data. Decoder should handle channel offsets and other details normally.
- interleave: one data block per channel, mixed in configurable sizes. Once one channel block is fully decoded this layout skips the other channels, so the decoder only handles one at a time.
- blocked: data is divided into blocks, often with a header. Layout detects when a block is done and asks a helper function to fix offsets (skipping the header and pointing to data per channel), depending on the block format.
- segmented: file is divided into consecutive but separate segments, each one is setup as a fully separate VGMSTREAM.
- layered: file is divided into multichannel layers that play at the same time, each one is setup as a fully separate VGMSTREAM.
- others: uncommon cases may need its own custom layout, but may be dealt with using custom IO STREAMFILEs instead.
The layout used mainly depends on the decoder. MP3 data (that may have 1 or 2 channels per frame) uses flat layout, while DSP ADPCM (that only decodes one channel at a time) is interleaved. In case of mono files either could be used as there won't be any actual difference.
Layouts expect the VGMSTREAM to be properly initialized during the meta processing (channel offsets must point to each channel start offset).
### decoders
Decoders take a sample buffer, convert data to PCM samples and fill one or multiple channels at a time, depending on the decoder itself. Usually its data is divided into frames with a number of samples, and should only need to do one frame at a time (when size is fixed/informed; vgmstream gives flexibility to the decoder), but must take into account that the sample buffer may be smaller than the frame samples, and that may start some samples into the frame (this is also done to handle looping in some cases, where decoder state must stop in the middle).
Every call the decoder will need to find out the current frame offset (usually per channel). This is usually done with a base channel offset (from the VGMSTREAM) plus deriving the frame number (thus sub-offset, but only if frames are fixed) through the current sample, or manually updating the channel offsets every frame. This second method is not suitable to use with the interleave layout as it advances the offsets assuming they didn't change (this is a limitation/bug at the moment). Similarly, the blocked layout cannot contain interleaved data, and must use alt decoders with internal interleave (also a current limitation). Thus, some decoders and layouts don't mix.
If the decoder needs to keep state between calls it may use the VGMSTREAM for common values (like ADPCM history), or alloc a custom data struct. In that case the decoder should provide init/free functions so the meta or vgmstream may use. This is seen with decoders implemented using external libraries (*ext_libs*), as seen in *#ifdef VGM_USE_X ... #endif* sections.
Adding a new decoder involves:
- *src/coding/(decoder-name).c*: create `decode_x` function that decodes stream data into the passed sample buffer. If the codec requires custom internals it may need `init/reset/seek/free_x`, or other helper functions.
- *src/coding/coding.h*: define decoder's functions and type
- *src/decode.c: get_vgmstream_samples_per_frame*: define so vgmstream only asks for N samples per decode_x call. May return 0 if variable/unknown/etc (decoder then must handle arbitrary number of samples)
- *src/decode.c: get_vgmstream_frame_size*: define so vgmstream can do certain internal calculations. May return 0 if variable/unknown/etc, but blocked/interleave layouts will need to be used in a certain way.
- *src/decode.c: decode_vgmstream*: call `decode_x`, possibly once per channel if the decoder works with a channel at a time.
- *src/decode.c: add handling in `reset/seek/free_codec` if needed
- *src/formats.c*: add coding type description
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (decoder-name).c parser in VS
- if the codec depends on a external library don't forget to mark parts with: *#ifdef VGM_USE_X ... #endif*
### core
The vgmstream core simply consists of functions gluing the above together and some helpers (ex.- extension list, loop adjust, etc).
The *Overview* section should give an idea about how it's used.

View File

@ -1,9 +1,9 @@
# GENH FORMAT
GENH is a generic binary header with fixed values, to make unsupported files playable. This binary header is appended to the beginning of a file and file is renamed to .genh, so vgmstream will read the GENH header and play the file with the new info.
GENH as been mostly superseded by TXTH, as it can do anything that GENH does and more, plus it's cleaner and much simpler to create, so TXTH is the recommended way to make vgmstream play unsupported files.
If you still want to create files with GENH headers, the easiest way is to use VGMToolBox's GENH creator, that provides a simple Windows interface.
For programmers looking for a formal definition the place to check would be vgmstream's parser, located in `genh.c` (particularly `parse_genh`), as new features or fixes may be added anytime.
# GENH FORMAT
GENH is a generic binary header with fixed values, to make unsupported files playable. This binary header is appended to the beginning of a file and file is renamed to .genh, so vgmstream will read the GENH header and play the file with the new info.
GENH as been mostly superseded by TXTH, as it can do anything that GENH does and more, plus it's cleaner (no need to modify the original data) and much simpler to create, so TXTH is the recommended way to make vgmstream play unsupported files. There is no program to help creating TXTH headers at the moment, but they are just text files with a few lines that should be easy to make by hand (this is explained in *TXTH.md*).
If you still want to create files with GENH headers, the easiest way is to use VGMToolBox's GENH creator, that provides a simple Windows interface.
For programmers looking for a formal definition the place to check would be vgmstream's parser, located in `genh.c` (particularly `parse_genh`), as new features or fixes may be added anytime.

View File

@ -22,7 +22,7 @@ file#12 # set "subsong" command for single file
#mode is ignored here as there is only one file
```
*files* may be anything accepted by the file system (including spaces and symbols), and you can use subdirs. Separators can be `\` or `/`, but stick to `/` for consistency. Commands may be chained and use spaces as needed (also see "TXTP parsing" section).
*files* may be anything accepted by the file system (including spaces and symbols), and you can use subdirs. Separators can be `\` or `/`, but stick to `/` for consistency. Commands may be chained and use spaces as needed (also see "*TXTP parsing*" section).
```
sounds/bgm.fsb #s2 #i #for file inside subdir: play subsong 2 + disable looping
```
@ -71,7 +71,7 @@ loop_start_segment = 2
loop_end_segment = 3
loop_mode = keep # loops in 2nd file's loop_start to 3rd file's loop_end
```
Mixing sample rates is ok (uses first) but channel number must be equal for all files. You can use mixing (explained later) to join segments of different channels though.
Mixing sample rates is ok (uses max) but channel number must be equal for all files. You can use mixing (explained later) to join segments of different channels though.
### Layers mode
@ -95,10 +95,11 @@ BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3
mode = layers
```
Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file (you can downmix to stereo using mixing commands, described later). If all layers share loop points they are automatically kept.
Note that the number of channels is the sum of all layers so three 2ch layers play as a 6ch file (you can manually downmix using mixing commands, described later, since vgmstream can't guess if the result should be stereo or 5.1 audio). If all layers share loop points they are automatically kept.
### Mixed groups
You can set "groups" to 'fold' various files into one, as layers or segments, to allow complex cases:
You can set "groups" to 'fold' various files into one, as layers or segments, to allow complex cases. This is an advanced settings for complex cases, so read this after understanding other features first.
```
# commands to make two 6ch segments with layered intro + layered loop:
@ -123,7 +124,8 @@ loop_start_segment = 2
# optional, to avoid "segments" default (for debugging)
mode = mixed
```
From TXTP's perspective, it starts with N separate files and every command joins some files that are treated as a single new file, so positions are reassigned. End result will be a single "file" that may contain groups within groups. It's pretty flexible so you can express similar things in various ways:
From TXTP's perspective, it starts with N separate files and every command joins some files to make a single new "file", so positions are reassigned. End result after all grouping will be a single, final "file" that may contain groups within groups. It's pretty flexible so you can express similar things in various ways:
```
# commands to make a 6ch with segmented intro + loop:
introA_2ch.at3
@ -159,7 +161,16 @@ Examples:
- `1L1`: layer of one file (same)
- `9999L`: absurd values are ignored
Segments and layer settings and rules still apply, so you can't make segments of files with different total channels. To do it you can use commands to "downmix" the group, as well as giving it some config (explained later):
Internally, `mode = segment/layers` are treated basically the same as a (default, at the end) group. You can apply commands to the resulting group (rather than the individual files) too. `commands` would be applied to this final group.
```
mainA_2ch.at3
mainB_2ch.at3
group = L #h44100
commands = #h48000 #overwrites
```
Segments and layer settings and rules still apply when making groups, so you can't group segments of files with different total channels. To do it you could use commands to "downmix" the group first:
```
# this doesn't need to be grouped
intro_2ch.at3
@ -214,7 +225,6 @@ song#2#h22050
song#3#h22050
```
### Channel removing/masking for channel subsongs/layers
**`C(number)`** (single) or **`#C(number)~(number)`** (range), **`#c(number)`**: set number of channels to play. You can add multiple comma-separated numbers, or use ` ` space or `-` as separator and combine multiple ranges with single channels too.
@ -235,60 +245,118 @@ music_Home.ps3.scd#c3 4
music_Home.ps3.scd#C1~3
```
### Play config
*modifiers*
- **`#L`**: play forever (if loops are set set and player supports it)
- **`#i`**: ignore and disable loop points, simply stopping after song's sample count (if file loops)
- **`#e`**: set full looping (end-to-end) but only if file doesn't have loop points (mainly for autogenerated .txtp)
- **`#E`**: force full looping (end-to-end), overriding original loop points
- **`#F`**: don't fade out after N loops but continue playing the song's original end (if file loops)
### Play settings
**`#L`**: play forever (if loops are set and player supports it)
**`#l(loops)`**: set target number of loops (if file loops)
**`#f(fade time)`**: set (in seconds) how long the fade out lasts (if file loops)
**`#d(fade delay)`**: set (in seconds) the delay before fade kicks in after last loop (if file loops)
**`#F`**: don't fade out after N loops but continue playing the song's original end (if file loops)
**`#e`**: set full looping (end-to-end) but only if file doesn't have loop points (mainly for autogenerated .txtp)
**`#E`**: force full looping (end-to-end), overriding original loop points
**`#i`**: ignore and disable loop points, simply stopping after song's sample count (if file loops)
*processing*
- **`#l(loops)`**: set target number of loops, (if file loops) used to calculate the body part (modified by other values)
- **`#b(time)`**: set target time (even without loops) for the body part (modified by other values)
- **`#f(fade period)`**: set (in seconds) how long the fade out lasts after target number of loops (if file loops)
- **`#d(fade delay)`**: set (in seconds) delay before fade out kicks in (if file loops)
- **`#p(time-begin)`**: pad song beginning (not between loops)
- **`#P(time-end)`**: pad song song end (not between loops)
- **`#r(time-begin)`**: remove/trim song beginning (not between loops)
- **`#R(time-end)`**: remove/trim song end (not between loops)
They are equivalent to some `test.exe/vgmstream_cli` options. Settings should mix with or override player's defaults. If player has "play forever" setting loops disables it (while other options don't quite apply), while forcing full loops would allow to play forever, or setting ignore looping would disable it.
*(time)* can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of 10.0 (ten seconds) vs 10 (ten samples). Fade config always assumes seconds for compatibility.
Based on these values and the player config, vgmstream decides a final song time and tweaks audio output as needed. On simple cases this is pretty straightforward, but when using multiple settings it can get a bit complex. You can even make layered/segments with individual config for extra-twisted cases. How all it all interacts is explained later.
Usage details:
- Settings should mix with player's defaults, but player must support that
- TXTP settings have priority over player's
- `#L` loops forever even if player is set to play 2 loops
- `#l` loops N times even if player is set to loop forever
- setting loop forever ignores some options (loops/fades/etc), but are still used to get final time shown in player
- setting target body time overrides loops (`#b` > `#l`)
- setting ignore fade overrides fades (`#F` > `#f`, `#d`)
- setting ignore loops overrides loops (`#i` > `#e` > `#E`)
- files without loop points or disabled loops (`#i`) ignore loops/fade settings (`#L`, `#l`, `#f`, `#d`)
- loops may be forced first (`#e`, `#E`, `#I n n`) to allow fades/etc
- "body part" depends on number of loops (including non-looped part), or may be a fixed value:
- a looped file from 0..30s and `#l 2.0` has a body of 30+30s, or could set `#b 60.0` instead
- a looped file from 0..10..30s (loop start at 10s), and `#l 2.0` has a body of 10+20+20s, or could set `#b 50.0` instead
- a non-looped file of 30s ignores `#l 2.0`, but can set a body of `#b 40.0` (plays silence after end at 30s)
- loops can actually be half values: `#l 2.5` means a body of 30+30+15s
- setting loops to 0 may only play part before loop
- due to technical limitations a body may only play for so many hours (shouldn't apply to *play forever*)
- using `#F` will truncate loops to nearest integer, so `#F #l 2.7` becomes `#F #l 2.0`
- *modifier*" settings pre-configure how file is played
- *processing* settings are applied when playing
Processing goes like this:
- order: `pad-begin > trim-begin > body > (trim-end) > (fade-delay) > fade-period > pad-end`
- `pad-begin` adds silence before anything else
- `trim-begin` gets audio from `body`, but immediately removes it (substracts time from body)
- `body` is the main audio decode, possibly including N loops or silence
- `fade-delay` waits after body (decode actually continues so it's an extension of `body`)
- `fade-period` fades-out last decoded part
- `trim-begin` removes audio from `body` (mainly useful when using `#l`
- `pad-end` adds silence after everything else
- final time is: `pad-begin + body - trim-begin - trim-end + fade-delay + fade-period + pad-end`
- `#R 10.0 #b 60.0` plays for 50s, but it's the same as `#b 50.0`
- `#d 10.0 #b 60.0` plays for 70s, but it's the same as `#b 70.0`
- big trims may be slow
Those steps are defined separate from "base" decoding (file's actual loops/samples, used to generate the "body") to simplify some parts. For example, if pad/trim were part of the base decode, loop handling becomes less clear.
**God Hand (PS2)**: *boss2_3ningumi_ver6.txtp*
```
boss2_3ningumi_ver6.adx#l3 #default is usually 2.0
boss2_3ningumi_ver6.adx #l 3 #default is usually 2.0
```
```
boss2_3ningumi_ver6.adx#f11.5 #default is usually 10.0
boss2_3ningumi_ver6.adx #f11.5 #default is usually 10.0
```
```
boss2_3ningumi_ver6.adx#d0.5 #default is usually 0.0
boss2_3ningumi_ver6.adx #d0.5 #default is usually 0.0
```
```
boss2_3ningumi_ver6.adx#i #this song has a nice stop
boss2_3ningumi_ver6.adx #i #this song has a nice stop
```
```
boss2_3ningumi_ver6.adx#F #loop some then hear that stop
boss2_3ningumi_ver6.adx #F #loop some then hear that stop
```
```
boss2_3ningumi_ver6.adx#e #song has loops, so ignored here
boss2_3ningumi_ver6.adx #e #song has loops, so ignored here
```
```
boss2_3ningumi_ver6.adx#E #force full loops
boss2_3ningumi_ver6.adx #E #force full loops
```
```
boss2_3ningumi_ver6.adx#L #keep on loopin'
boss2_3ningumi_ver6.adx #L #keep on loopin'
```
```
boss2_3ningumi_ver6.adx #l 3 #F #combined: 3 loops + ending
boss2_3ningumi_ver6.adx #l 3 #F #combined: 3 loops + ending
```
```
boss2_3ningumi_ver6.adx #l1.5#d1#f5 #combined: partial loops + some delay + smaller fade
boss2_3ningumi_ver6.adx #l 1.5 #d1.0 #f5.0 #combined: partial loops + some delay + smaller fade
```
```
# boss2_3ningumi_ver6.adx #l1.0 #F #combined: equivalent to #i
#boss2_3ningumi_ver6.adx #l 1.0 #F #combined: equivalent to #i
```
```
boss2_3ningumi_ver6.adx #p 1.0 2.0 #f 10.0 #adds 1s to start, fade 10s, add 2s to end
```
```
boss2_3ningumi_ver6.adx #r 1.0 #removes 1s from start
```
```
boss2_3ningumi_ver6.adx #b 100.0s #f 10.0 #plays for 100s + 10s seconds
```
### Time modifications
**`#t(time)`**: trims the file so base duration (before applying loops/fades/etc) is `(time)`. If value is negative substracts `(time)` to duration. Loop end is adjusted when necessary, and ignored if value is bigger than possible (use `#l(loops)` config to extend time instead).
### Trim file
**`#t(time)`**: trims the file so base sample duration (before applying loops/fades/etc) is `(time)`. If value is negative substracts `(time)` to duration.
Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of the subtle difference between 10.0 (ten seconds) and 10 (ten samples).
*(time)* can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of 10.0 (ten seconds) vs 10 (ten samples).
This changes the internal samples count, and loop end is also adjusted as needed. If value is bigger than max samples it's ignored (use `#l(loops)` and similar play config to alter final play time instead).
Some segments have padding/silence at the end for some reason, that don't allow smooth transitions. You can fix it like this:
```
@ -298,31 +366,18 @@ main.fsb
Similarly other games don't use loop points, but rather repeat/loops the song internally many times:
```
bgm01.vag #t3:20 #i #l1.0 # trim + combine with forced loops for easy fades
bgm01.vag #t3:20
```
Note that if you need to remove very few samples (like 1) to get smooth transitions it may be a bug in vgmstream, consider reporting.
### Force sample rate
**`#h(sample rate)`**: changes sample rate to selected value (within some limits).
Needed for a few games set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it).
**Super Paper Mario (Wii)**
```
btl_koopa1_44k_lp.brstm#h22050 #in hz
```
**Patapon (PSP)**
```
ptp_btl_bgm_voice.sgd#s1#h11050
```
If you need to remove very few samples (like 1) to get smooth transitions it may be a bug in vgmstream, consider reporting.
### Install loops
**`#I(loop start time) [loop end time]`**: force/override looping values, same as .pos but nicer. Loop end is optional and defaults to total samples.
**`#I(loop start time) [loop end time]`**: force/override looping values (same as .pos but nicer). Loop end is optional and defaults to total samples.
Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds, with dot), `0xN` (samples in hex format) or `N` (samples). Beware of the subtle difference between 10.0 (ten seconds) and 10 (ten samples). Wrong loop values (for example loop end being much larger than file's samples) will be ignored, but there is some leeway when using seconds for the loop end.
*(time)* can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of 10.0 (ten seconds) vs 10 (ten samples).
Wrong loop values (for example loop end being much larger than file's samples) will be ignored, but there is some leeway when using seconds for the loop end.
**Jewels Ocean (PC)**
```
@ -340,6 +395,23 @@ Use this feature responsibly, though. If you find a format that should loop usin
Note that a few codecs may not work with arbitrary loop values since they weren't tested with loops. Misaligned loops will cause audible "clicks" at loop point too.
### Force sample rate
**`#h(sample rate)`**: changes sample rate to selected value (within some limits).
Needed for a few games set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it), resulting in wrong play speed if not changed.
**Super Paper Mario (Wii)**
```
btl_koopa1_44k_lp.brstm#h22050 #in hz
```
**Patapon (PSP)**
```
ptp_btl_bgm_voice.sgd#s1#h11050
```
Note that this doesn't resample (change sample rate while keeping play speed).
### Channel mixing
**`#m(op),(...),(op)`**: mix channels in various ways by specifying multiple comma-separated sub-commands:
@ -352,7 +424,7 @@ Possible operations:
- `Nu`: upmix (insert) N ('pushing' all following channels forward)
- `Nd`: downmix (remove) N ('pulling' all following channels backward)
- `ND`: downmix (remove) N and all following channels
- `N(type)(position)(time-start)+(time-length)`: defines a fade
- `N(type)(position)(time-start)+(time-length)`: defines a curve envelope (post-processing fade)
* `type` can be `{` = fade-in, `}` = fade-out, `(` = crossfade-in, `)` = crossfade-out
* crossfades are better tuned to use when changing between tracks
* `(position)` pre-adjusts `(time-start)` to start after certain time (optional)
@ -361,7 +433,7 @@ Possible operations:
* `}` then `{` or `{` then `}` makes sense, but `}` then `}` will make funny volume bumps
* example: `1{0:10+0:5, 1}0:30+0:5` fades-in at 10 seconds, then fades-out at 30 seconds
- `N^(volume-start)~(volume-end)=(shape)@(time-pre)~(time-start)+(time-length)~(time-last)`: defines full fade envelope
* full definition of a fade envelope to allow precise volume changes over time
* full definition of a curve envelope to allow precise volume changes over time
* not necessarily fades, as you could set length 0 for volume "bumps" like `1.0~0.5`
* `(shape)` can be `{` = fade, `(` = crossfade, other values are reserved for internal testing and may change anytime
* `(time-start)`+`(time-length)` make `(time-end)`
@ -427,36 +499,52 @@ Examples:
```
# plays 2ch layer1 (base melody)
okami-ryoshima_coast.aix#@track 1,2
```
```
# plays 2ch layer1+2 (base melody+percussion)
okami-ryoshima_coast.aix#@layer-b 2 1~4 #1~4 may be skipped
```
```
# uses 2ch layer1 (base melody) in the first loop, adds 2ch layer2 (percussion) to layer1 in the second
okami-ryoshima_coast.aix#@crosslayer-b 2
```
```
# uses 2ch track1 (exploration) in the first loop, changes to 2ch track2 (combat) in the second
ffxiii2-eclipse.scd#@crosstrack 2
```
```
# plays 2ch from 4ch track1 (sneaking)
mgs4-bgm_ee_alert_01.mta2#@layer-e 2 1~4
```
```
# downmix bgm + vocals to stereo
nier_automata-BGM_0_012_04.wem
nier_automata-BGM_0_012_07.wem
mode = layers
commands = #@layer-v 2
```
```
# can be combined with normal mixes too for creative results
# (add channel clone of ch1, then 50% of range)
song#m4u,4+1#@volume 0.5 2~4
```
```
# equivalent to #@layer-e 2 1~4
mgs4-bgm_ee_alert_01.mta2#@track 1~4#@layer-b 2
```
```
# equivalent to #@track 1,2
okami-ryoshima_coast.aix#@layer-b 2 1,2
```
```
# works well enough for pseudo-dynamic tracks
RGG-Ishin-battle01a.hca
RGG-Ishin-battle01b.hca
RGG-Ishin-battle01c.hca
RGG-Ishin-battle01d.hca
mode = layers
commands = #@crosstrack 2
```
## OTHER FEATURES
@ -488,6 +576,7 @@ bgm.sxd2
commands = #s12
```
### Force plugin extensions
vgmstream supports a few common extensions that confuse plugins, like .wav/ogg/aac/opus/etc, so for them those extensions are disabled and are expected to be renamed to .lwav/logg/laac/lopus/etc. TXTP can make plugins play those disabled extensions, since it calls files directly by filename.
@ -498,16 +587,17 @@ Combined with TXTH, this can also be used for extensions that aren't normally ac
TXTP may even reference other TXTP, or files that require TXTH, for extra complex cases. Each file defined in TXTP is internally parsed like it was a completely separate file, so there is a bunch of valid ways to mix them.
### TXTP parsing
Here is a rough look of how TXTP parses files, so you get a better idea of what's going on if some command fails.
## TXTP PARSING
Here is a rough look of how TXTP parses files, so you get a better idea of what's going on if some .txtp fails.
*Filenames* should accept be anything accepted by the file system, and multiple *commands* can be chained:
```
subdir name/bgm bank.fsb#s2#C1,2
subdir name/bgm bank.fsb #s2 #C1,2 #comment
```
All defined files must exist and be parseable by vgmstream, and general config like `mode` must make sense (not `mde = layers` or `mode = laye`).
Commands may add spaces as needed, but try to keep it simple and don't go overboard). They *must* start with `#(command)`, as `#(space)(anything)` is a comment. Commands without corresponding file are ignored too (seen as comments), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
Commands may add spaces as needed, but try to keep it simple. They *must* start with `#(command)`, as `#(space)(anything)` is a comment. Commands without corresponding file are ignored too (seen as comments), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
```
# those are all equivalent
song#s2#C1,2
@ -565,6 +655,14 @@ commands=#s2 # commands here are allowed
commands= #C1,2
```
You can also add spaces before files/commands, mainly to improve readability when using more complex features like groups (don't go overboard though):
```
#segment x2
song1
song2
group = 1S2 #E
```
Repeated commands overwrite previous setting, except comma-separated commands that are additive:
```
# overwrites, equivalent to #s2
@ -578,14 +676,14 @@ commands = #m5-6
commands = #l 3.0
```
The parser is fairly simplistic and lax, and may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files.
The parser is fairly simplistic and lax, and may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or # inside, certain name patterns could fool it too. Keep all this in mind this while making .txtp files.
## MINI-TXTP
To simplify TXTP creation, if the .txtp doesn't set a name inside then its filename is used directly, including config. Note that extension must be included (since vgmstream needs a full filename). You can set `commands` inside the .txtp too:
- *bgm.sxd2#12.txtp*: plays subsong 12
- *bgm.sxd2#12.txtp*, , inside has `commands = #@volume 0.5`: plays subsong 12 at half volume
- *bgm.sxd2.txtp*, , inside has `commands = #12 #@volume 0.5`: plays subsong 12 at half volume
- *bgm.sxd2#12.txtp*, inside has `commands = #@volume 0.5`: plays subsong 12 at half volume
- *bgm.sxd2.txtp*, inside has `commands = #12 #@volume 0.5`: plays subsong 12 at half volume
- *Ryoshima Coast 1 & 2.aix#C1,2.txtp*: channel downmix
- *boss2_3ningumi_ver6.adx#l2#F.txtp*: loop twice then play song end file normally
- etc
@ -602,15 +700,15 @@ A song file is just data that can contain a (sometimes unlimited) number of chan
- `5.1: FL, FR, FC, LF, SL, SR`
- ... (channels in order, where FL=front left, FC=front center, etc)
If you only have stereo speakers, when playing a 5.1 file your player may silently transform to stereo, as otherwise you would miss some channels. But a game song's channels can be various things: standard mapped, per-format map, per-game, multilayers combined ("downmixed") to a final stereo file, music then all language tracks, etc. So you need to decide which channels drop or combine and their individual volumes via mixing.
If you only have stereo speakers, when playing a 5.1 file your player may silently transform to stereo, as otherwise you would miss some channels. But a game song's channels can be various things: standard mapped, per-format map, per-game, multilayers combined ("downmixed") to a final stereo file, music + all language tracks, etc. So you may need to decide which channels drop or combine and their individual volumes via mixing.
Say you want to mix 4ch to 2ch (ch3 to ch1, ch4 to ch2). Due to how audio signals work, mixing just combines (adds) sounds. So if channels 1/2 are LOUD, and channels 3/4 are LOUD, you get a LOUDER channel 1/2. To fix this we set mixing volume, for example: `mix channel 3/4 * 0.707 (-3db/30% lower volume) to channel 1/2`: the resulting stereo file is now more listenable. Those volumes are just for standard audio and may work ok for every game though.
Say you want to mix 4ch to 2ch (ch3 to ch1, ch4 to ch2). Due to how audio signals work, mixing just combines (adds) sounds. So if channels 1/2 are LOUD, and channels 3/4 are LOUD, you get a LOUDER (clipping) channel 1/2. To fix this we set mixing volume, for example: `mix channel 3/4 * 0.707 (-3db/30% lower volume) to channel 1/2`: the resulting stereo file is now more listenable. Those volumes are just for standard audio and may work ok for every game though.
All this means there is no simple, standard way to mix, so you must experiment a bit.
### MIXING EXAMPLES
For most common usages you can stick with macros but actual mixing is quite flexible:
### Mixing examples
TXTP has a few macros that help you handle most simpler cases (like `#C 1 2`, `#@layer-v 2`), that you should use when possible, but below is a full explanation of manual mixing (macros just automate these options using some standard formulas).
```
# boost volume of all channels by 30%
song#m0*1.3
@ -736,3 +834,171 @@ song#m1+2*0.5,1u
# for a 2ch file 2nd command is ignored, since ch2 is removed after 1st command
song#m1d,2+1*0.5
```
Performance isn't super-well tuned at the moment, and lots of mixes do add up (shouldn't matter in most simple cases though). Mixing order does affect performance, if you need to optimize:
```
# apply volume then downmix = slower (more channels need volume changes)
song#m0*0.5,1d
# downmix then apply volume = faster (less channels need volume changes)
song#m1d,0*0.5
```
## UNDERSTANDING PLAY CONFIG AND FINAL TIME
When handling a new file, vgmstream reads its loop points and total samples. Based on that and player/TXTP's config it decides actual "final time" that is used to play it. "internal file's samples" and "external play duration" are treated separatedly, so a non-looping 100s file could be forced to play for 200s (100s of audio then 100s of silence), or a looping 100s file could be set to play 310s (so 3 loops + 10s fade).
For example, with a 100s file that loops from 5..90s, `file.adx #p 5.0 #r 10.0 #l 2.0 #f 10.0 #P 2.0` means:
- pad with 5s of silence first
- decode first loop 0..90s, but trim first 10s seconds, ending up like 10..90s
- decode second loop 5..90 (trim doesn't affect other loops)
- during 3rd loop fade-out audio for 10 seconds
- pad end by 2 seconds
- song ends and player moves to next track
- final time would be: `5s + (90 - 10)s + 85s + 10s + 2s = 182s`
When you use commands to alter samples/looping it actually changes the "internal decoding", so final times are calculated differently So adding `#t -5.0 #I 5.0 85.0` to the above would result in a final time of `5s + (85 - 10)s + 80s + 10s + 2s = 172s`.
### Complex cases
Normally the above is mostly transparent, and vgmstream should "just work" with the player's settings. To understand how some extra-complex cases are handled, here is what it happens when you start having things over things over things.
Internally, a regular file just plays once or keeps looping (if loops are set) for a duration that depends on config (pads/fades/etc, with no settings default duration would be file's samples). Anything past pre-calculated final time would be silence (player should stop at that point, unless "loop forever" is set).
Behavior for layers and segments is a bit more complex. First, a layered/segmented vgmstream is made of multiple "internal vgmstreams", and one "external vgmstream" that manages them. The external takes its values from the internals:
- layered samples: highest base sample count among all internals
- layered loops: inherited if all internals share loop points, not set otherwise
- segmented samples: sum of all internals
- segmented loops: not set (uses `loop segment` settings instead)
Play config (loops/fades/etc) is normally applied to the "base" external vgmstream, and same applies to most TXTP commands (changing some values like sample rate only makes sense in the external part). This allows looping the resulting vgmstream in sync.
However each internal vgmstream can have its own config. This means first layer may be padded and internally looped before going to next one. In that case external vgmstream doesn't inherit loop points, but internals *can* be looped, and sample count is the calculated final duration (so segment of 30s that loops 2 times > external time of 60s). More or less, when using play config on an internal vgmstream it becomes a solid "clip". So when dealing with complex layouts make sure you only put play config in the internal vgmstream if you really need this, otherwise set the external's config.
Since layout/segments can also contain other layouts/segments this works in cascade, and each part can be configured via `groups`, as explained before). Certain combos involving looping (like installing loops in the internal and external vgmstreams) may work unexpectedly.
### Examples
Example with a file of 100s, loop points 10s..95s, no settings.
```
#l 2.0 #f 10.0 / time=190s (default settings for most players)
[0..10..95][10..95][10..20)
#l 2.0 #f 10.0 #d 10.0 / time=200s (same with delay)
[0..10..95][10..95][10..20..30)
#l 2.0 / time=180s (same without fade/delay)
[0..10..95][10..95]
#l 3.0 / time=265s (three loops)
[0..10..95][10..95][10..95]
#i #l 2.0 #f 10.0 / time=100s (loop times and fades don't apply with loop disabled)
[0..100]
#E #l 2.0 #f 10.0 / time=210s (ignores original loops and force 0..100)
[0..100][0..100][0..10)
#l 2.5 / time=185s (ends in half loop)
[0..10..95][10..95][10..95][10..53]
#l 2.0 #F / time=185s (play rest of file after all loops)
[0..10..95][10..95..100s]
#l 2.3 #F #f 10.0 / time=185s (2.3 becomes 2.0 to make sense of it, and fade is removed)
[0..10..95][10..95..100s]
#p 10.0 #l 2.0 / time=190 (pad 10 seconds, then play normally)
(0..10)[0..10..95][10..95]
#r 5.0 #l 2.0 / time=175s (removes first seconds of the output, then loops normally)
[5..10..95][10..95]
#r 5.0 #b 180 / time=175s (same as the above, note that 180 is base body including non-looped part)
[5..10..95][10..95]
#r 25.0 #l 2.0 / time=155s (removes part of loop start, but next loop will play it)
[25..95][10..95]
#p 10.0 #r 25.0 #l 2.0 / time=165s (pad goes before trim, and trim is applied over file's output)
(0..10)[25..95][10..95]
#b 120.0 #i / time = 120s (force time, no sound after 100s)
[0...100](0..20)
#b 120s #l 2.0 / time = 120s (force time, stops in the middle of looping)
[0..10..95][10..35]
```
Using *loop forever* is a bit special. Play time is calculated based on config to be shown by the player, but it's ignored internally (vgmstream won't stop looping/playing after that time).
```
#L #l 1.0 / time=95s (loops forever, but time info is shown using defaults)
[0..10..95][10..95] ... [10..95] ...
#L #p 10.0 #l 1.0 / time=105s (does padding first)
(0..10)[0..10..95][10..95] ... [10..95] ...
#L #r 5.0 #l 1.0 / time=90s (does trims first)
[5..95][10..95] ... [10..95] ...
#L #l 1.0 #f 8.0 / time=103s (song never ends, so fade isn't applied, but is part of shown time)
[0..10..95][10..95] ... [10..95] ...
```
With segments/layers note the difference between this:
```
# play config is set in the external vgmstream
bgm1a.adx
bgm1b.adx
layout = layered
commands = #l 2.0
```
and this:
```
# play config is set in the internal vgmstream, and shouldn't set the external's
# (may change other settings like sample rate, but not others like loops)
bgm1a.adx #l 2.0
bgm1b.adx #l 2.0
layout = layered
#commands = ## doesn't inherit looping now
```
It's not possible to do "inside" padding or trims (modifying in the middle of a song), but you can achieve those results by combining trimmed/padded segments (used to simulate unusual Wwise loops). You can do internal loops per part too.
```
#???
[0..10..50][10..95]
#???
[10..95](0..10)[10..95]
#segmented: play 2 clips with padding between
[10..95] #r 10.0 #b 95.0
(0..10)[10..95] #r 10.0 #b 95.0 #p 10.0
layout = segmented
loop_mode = auto
#segmented: play 2 clips sequentially and loops them
[0..10..50] #b 50.0
[10..95] #r 10.0 #b 95.0
layout = segmented
loop_mode = auto
#layered: plays a single track with "sequential" clips
[0..10..50] #b 50.0
(0......50)[10..95] #p 50.0 #r 10.0 #b 95.0
layout = layered
#segmented: loop partially a segment before going next
[0..10] #b 10.0
[10..100][0..50] #E #l 1.5 #r 10.0
layout = segmented
loop_start_segment = 2 #note this plays 10s then loops the *whole* 140s part (new 2nd segment)
```
Since padding has the effect of delaying the start of a part, it can be used for unaligned transitions.
```
# layered: plays a single track with *overlapped* clips (first 10s)
[0..10..50] #b 50.0
(0...40)[10..95] #p 40.0 #r 10.0 #b 95.0
layout = layered
```

View File

@ -38,6 +38,9 @@ extern "C" {
"https://github.com/kode54/vgmstream/\n" \
"https://sourceforge.net/projects/vgmstream/ (original)"
#define PLUGIN_FILENAME "foo_input_vgmstream.dll"
// called every time a file is added to the playlist (to get info) or when playing
input_vgmstream::input_vgmstream() {
vgmstream = NULL;
@ -49,13 +52,11 @@ input_vgmstream::input_vgmstream() {
paused = 0;
decode_pos_ms = 0;
decode_pos_samples = 0;
stream_length_samples = 0;
fade_samples = 0;
seek_pos_samples = 0;
length_samples = 0;
fade_seconds = 10.0f;
fade_delay_seconds = 0.0f;
loop_count = 2.0f;
fade_seconds = 10.0;
fade_delay_seconds = 0.0;
loop_count = 2.0;
loop_forever = false;
ignore_loop = 0;
disable_subsongs = false;
@ -74,7 +75,7 @@ input_vgmstream::~input_vgmstream() {
}
// called first when a new file is accepted, before playing it
void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort) {
if (!p_path) { // shouldn't be possible
throw exception_io_data();
@ -180,14 +181,14 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
strcpy(tagfile_path,tagfile_name);
}
STREAMFILE *tagFile = open_foo_streamfile(tagfile_path, &p_abort, NULL);
if (tagFile != NULL) {
VGMSTREAM_TAGS *tags;
STREAMFILE* sf_tags = open_foo_streamfile(tagfile_path, &p_abort, NULL);
if (sf_tags != NULL) {
VGMSTREAM_TAGS* tags;
const char *tag_key, *tag_val;
tags = vgmstream_tags_init(&tag_key, &tag_val);
vgmstream_tags_reset(tags, filename);
while (vgmstream_tags_next_tag(tags, tagFile)) {
while (vgmstream_tags_next_tag(tags, sf_tags)) {
if (replaygain_info::g_is_meta_replaygain(tag_key)) {
p_info.info_set_replaygain(tag_key, tag_val);
/* there is info_set_replaygain_auto too but no doc */
@ -200,17 +201,17 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
}
}
vgmstream_tags_close(tags);
close_streamfile(tagFile);
close_streamfile(sf_tags);
}
}
/* set technical info (details tab in file properties) */
p_info.info_set("vgmstream_version",PLUGIN_VERSION);
p_info.info_set("vgmstream_version", PLUGIN_VERSION);
p_info.info_set_int("samplerate", samplerate);
p_info.info_set_int("channels", channels);
p_info.info_set_int("bitspersample",16);
p_info.info_set_int("bitspersample", 16);
/* not quite accurate but some people are confused by "lossless"
* (could set lossless if PCM, but then again PCMFloat or PCM8 are converted/"lossy" in vgmstream) */
p_info.info_set("encoding","lossy/lossless");
@ -259,7 +260,7 @@ void input_vgmstream::decode_initialize(t_uint32 p_subsong, unsigned p_flags, ab
};
// called when audio buffer needs to be filled
bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
bool input_vgmstream::decode_run(audio_chunk & p_chunk, abort_callback & p_abort) {
if (!decoding) return false;
if (!vgmstream) return false;
@ -268,111 +269,48 @@ bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort)
t_size bytes;
{
bool loop_okay = config.song_play_forever && vgmstream->loop_flag && !config.song_ignore_loop && !force_ignore_loop;
if (decode_pos_samples + max_buffer_samples > stream_length_samples && !loop_okay)
samples_to_do = stream_length_samples - decode_pos_samples;
bool play_forever = vgmstream_get_play_forever(vgmstream);
if (decode_pos_samples + max_buffer_samples > length_samples && !play_forever)
samples_to_do = length_samples - decode_pos_samples;
else
samples_to_do = max_buffer_samples;
if (samples_to_do /*< DECODE_SIZE*/ == 0) {
if (samples_to_do == 0) { /*< DECODE_SIZE*/
decoding = false;
return false; /* EOF, didn't decode samples in this call */
}
render_vgmstream(sample_buffer,samples_to_do,vgmstream);
/* fade! */
if (vgmstream->loop_flag && fade_samples > 0 && !loop_okay) {
int fade_channels = output_channels;
int samples_into_fade = decode_pos_samples - (stream_length_samples - fade_samples);
if (samples_into_fade + samples_to_do > 0) {
int j,k;
for (j=0;j<samples_to_do;j++,samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples;
for (k = 0; k < fade_channels; k++) {
sample_buffer[j*fade_channels+k] =
(short)(sample_buffer[j*fade_channels+k]*fadedness);
}
}
}
}
}
render_vgmstream(sample_buffer, samples_to_do, vgmstream);
unsigned channel_config = vgmstream->channel_layout;
if (!channel_config)
channel_config = audio_chunk::g_guess_channel_config(output_channels);
bytes = (samples_to_do*output_channels * sizeof(sample_buffer[0]));
bytes = (samples_to_do * output_channels * sizeof(sample_buffer[0]));
p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, output_channels, 16, channel_config);
decode_pos_samples+=samples_to_do;
decode_pos_ms=decode_pos_samples*1000LL/vgmstream->sample_rate;
decode_pos_samples += samples_to_do;
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
return true; /* decoded in this call (sample_buffer or less) */
}
}
// called when seeking
void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
seek_pos_samples = (int) audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
int max_buffer_samples = SAMPLE_BUFFER_SIZE;
bool loop_okay = config.song_play_forever && vgmstream->loop_flag && !config.song_ignore_loop && !force_ignore_loop;
void input_vgmstream::decode_seek(double p_seconds, abort_callback & p_abort) {
int32_t seek_sample = (int)audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
bool play_forever = vgmstream_get_play_forever(vgmstream);
// possible when disabling looping without refreshing foobar's cached song length
// (with infinite looping on p_seconds can't go over seek bar though)
if (seek_pos_samples > stream_length_samples)
seek_pos_samples = stream_length_samples;
// (p_seconds can't go over seek bar with infinite looping on, though)
if (seek_sample > length_samples)
seek_sample = length_samples;
int corrected_pos_samples = seek_pos_samples;
int loop_length = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
int loop_count = 0;
// optimize seeks withing loops
if (vgmstream->loop_flag && loop_length > 0 && seek_pos_samples >= vgmstream->loop_end_sample) {
corrected_pos_samples -= vgmstream->loop_start_sample;
loop_count = corrected_pos_samples / loop_length;
corrected_pos_samples %= loop_length;
corrected_pos_samples += vgmstream->loop_start_sample;
}
// Allow for delta seeks forward, by up to the total length of the stream, if the delta is less than the corrected offset
if (decode_pos_samples > corrected_pos_samples && decode_pos_samples <= seek_pos_samples &&
(seek_pos_samples - decode_pos_samples) < stream_length_samples) {
if (corrected_pos_samples > (seek_pos_samples - decode_pos_samples))
corrected_pos_samples = seek_pos_samples;
}
// Reset of backwards seek
else if(corrected_pos_samples < decode_pos_samples) {
reset_vgmstream(vgmstream);
apply_config(vgmstream, &config); /* config is undone by reset */
decode_pos_samples = 0;
}
// seeking overrun = bad
if (corrected_pos_samples > stream_length_samples)
corrected_pos_samples = stream_length_samples;
while(decode_pos_samples < corrected_pos_samples) {
int seek_samples = max_buffer_samples;
if ((decode_pos_samples + max_buffer_samples >= stream_length_samples) && !loop_okay)
seek_samples = stream_length_samples - seek_pos_samples;
if (decode_pos_samples + max_buffer_samples > seek_pos_samples)
seek_samples = seek_pos_samples - decode_pos_samples;
decode_pos_samples += seek_samples;
render_vgmstream(sample_buffer, seek_samples, vgmstream);
}
// seek may have been clamped to skip unneeded loops, adjust as some internals need this value
vgmstream->loop_count = loop_count; //todo make seek_vgmstream, not ok if seeking in fade section with ignore_fade
// remove seek loop correction from counter so file ends correctly
decode_pos_samples = seek_pos_samples;
seek_vgmstream(vgmstream, seek_sample);
decode_pos_samples = seek_sample;
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
decoding = loop_okay || decode_pos_samples < stream_length_samples;
decoding = play_forever || decode_pos_samples < length_samples;
}
bool input_vgmstream::decode_can_seek() {return true;}
@ -383,7 +321,7 @@ void input_vgmstream::decode_on_idle(abort_callback & p_abort) {/*m_file->on_idl
void input_vgmstream::retag_set_info(t_uint32 p_subsong, const file_info & p_info, abort_callback & p_abort) { /*throw exception_io_data();*/ }
void input_vgmstream::retag_commit(abort_callback & p_abort) { /*throw exception_io_data();*/ }
bool input_vgmstream::g_is_our_content_type(const char * p_content_type) {return false;}
bool input_vgmstream::g_is_our_content_type(const char * p_content_type) { return false; }
// called to check if file can be processed by the plugin
bool input_vgmstream::g_is_our_path(const char * p_path, const char * p_extension) {
@ -395,14 +333,14 @@ bool input_vgmstream::g_is_our_path(const char * p_path, const char * p_extensio
}
// internal util to create a VGMSTREAM
VGMSTREAM * input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
VGMSTREAM *vgmstream = NULL;
VGMSTREAM* input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *streamFile = open_foo_streamfile(filename, &p_abort, &stats);
if (streamFile) {
streamFile->stream_index = p_subsong;
vgmstream = init_vgmstream_from_STREAMFILE(streamFile);
close_streamfile(streamFile);
STREAMFILE* sf = open_foo_streamfile(filename, &p_abort, &stats);
if (sf) {
sf->stream_index = p_subsong;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
}
return vgmstream;
}
@ -431,8 +369,7 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
if (subsong == 0)
subsong = 1;
set_config_defaults(&config);
apply_config(vgmstream, &config);
apply_config(vgmstream);
/* enable after all config but before outbuf (though ATM outbuf is not dynamic so no need to read input_channels) */
vgmstream_mixing_autodownmix(vgmstream, downmix_channels);
@ -441,15 +378,13 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
decode_pos_ms = 0;
decode_pos_samples = 0;
paused = 0;
stream_length_samples = get_vgmstream_play_samples(config.song_loop_count,config.song_fade_time,config.song_fade_delay,vgmstream);
fade_samples = (int)(config.song_fade_time * vgmstream->sample_rate);
length_samples = vgmstream_get_samples(vgmstream);
}
// internal util to get info
void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) {
VGMSTREAM * infostream = NULL;
VGMSTREAM* infostream = NULL;
bool is_infostream = false;
foobar_song_config infoconfig;
char temp[1024];
int info_channels;
@ -461,16 +396,16 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
if (!infostream) {
throw exception_io_data();
}
set_config_defaults(&infoconfig);
apply_config(infostream,&infoconfig);
is_infostream = true;
apply_config(infostream);
vgmstream_mixing_autodownmix(infostream, downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL /*&input_channels*/, &info_channels);
} else {
// vgmstream ready as get_info is valid after open() with any reason
infostream = vgmstream;
infoconfig = config;
info_channels = output_channels;
}
@ -486,7 +421,7 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
*loop_start = infostream->loop_start_sample;
*loop_end = infostream->loop_end_sample;
int num_samples = get_vgmstream_play_samples(infoconfig.song_loop_count,infoconfig.song_fade_time,infoconfig.song_fade_delay,infostream);
int num_samples = vgmstream_get_samples(infostream);
*length_in_ms = num_samples*1000LL / infostream->sample_rate;
char temp[1024];
@ -546,80 +481,17 @@ bool input_vgmstream::get_description_tag(pfc::string_base & temp, pfc::string_b
return false;
}
void input_vgmstream::set_config_defaults(foobar_song_config *current) {
current->song_play_forever = loop_forever;
current->song_loop_count = loop_count;
current->song_fade_time = fade_seconds;
current->song_fade_delay = fade_delay_seconds;
current->song_ignore_fade = 0;
current->song_force_loop = 0;
current->song_really_force_loop = 0;
current->song_ignore_loop = ignore_loop;
}
void input_vgmstream::apply_config(VGMSTREAM* vgmstream) {
vgmstream_cfg_t vcfg = {0};
void input_vgmstream::apply_config(VGMSTREAM* vgmstream, foobar_song_config* cfg) {
vcfg.allow_play_forever = 1;
vcfg.play_forever = loop_forever;
vcfg.loop_times = loop_count;
vcfg.fade_period = fade_seconds;
vcfg.fade_delay = fade_delay_seconds;
vcfg.ignore_loop = ignore_loop;
/* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
if (vgmstream->config.play_forever) {
cfg->song_play_forever = 1;
cfg->song_ignore_loop = 0;
}
if (vgmstream->config.loop_count_set) {
cfg->song_loop_count = vgmstream->config.loop_count;
cfg->song_play_forever = 0;
cfg->song_ignore_loop = 0;
}
if (vgmstream->config.fade_delay_set) {
cfg->song_fade_delay = vgmstream->config.fade_delay;
}
if (vgmstream->config.fade_time_set) {
cfg->song_fade_time = vgmstream->config.fade_time;
}
if (vgmstream->config.ignore_fade) {
cfg->song_ignore_fade = 1;
}
if (vgmstream->config.force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 1;
cfg->song_really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->song_ignore_loop = 1;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 0;
}
/* apply config */
if (cfg->song_force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0);
}
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->song_play_forever = 0;
}
if (cfg->song_play_forever) {
cfg->song_ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */
if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count);
cfg->song_fade_time = 0;
}
vgmstream_apply_config(vgmstream, &vcfg);
}
GUID input_vgmstream::g_get_guid() {
@ -632,7 +504,7 @@ const char * input_vgmstream::g_get_name() {
}
GUID input_vgmstream::g_get_preferences_guid() {
static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };;
static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };
return guid;
}
@ -644,5 +516,5 @@ bool input_vgmstream::g_is_low_merit() {
// foobar plugin defs
static input_factory_t<input_vgmstream> g_input_vgmstream_factory;
DECLARE_COMPONENT_VERSION(APP_NAME,PLUGIN_VERSION,PLUGIN_DESCRIPTION);
VALIDATE_COMPONENT_FILENAME("foo_input_vgmstream.dll");
DECLARE_COMPONENT_VERSION(APP_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION);
VALIDATE_COMPONENT_FILENAME(PLUGIN_FILENAME);

View File

@ -3,44 +3,32 @@
#define SAMPLE_BUFFER_SIZE 1024
/* current song settings */
typedef struct {
int song_play_forever;
double song_loop_count;
double song_fade_time;
double song_fade_delay;
int song_ignore_loop;
int song_force_loop;
int song_really_force_loop;
int song_ignore_fade;
} foobar_song_config;
class input_vgmstream : public input_stubs {
public:
input_vgmstream();
~input_vgmstream();
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
void open(service_ptr_t<file> p_filehint, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort);
unsigned get_subsong_count();
t_uint32 get_subsong(unsigned p_index);
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort);
void get_info(t_uint32 p_subsong, file_info & p_info, abort_callback & p_abort);
t_filestats get_file_stats(abort_callback & p_abort);
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort);
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort);
void decode_seek(double p_seconds,abort_callback & p_abort);
void decode_initialize(t_uint32 p_subsong, unsigned p_flags, abort_callback & p_abort);
bool decode_run(audio_chunk & p_chunk, abort_callback & p_abort);
void decode_seek(double p_seconds, abort_callback & p_abort);
bool decode_can_seek();
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta);
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta);
void decode_on_idle(abort_callback & p_abort);
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort);
void retag_set_info(t_uint32 p_subsong, const file_info & p_info, abort_callback & p_abort);
void retag_commit(abort_callback & p_abort);
static bool g_is_our_content_type(const char * p_content_type);
static bool g_is_our_path(const char * p_path,const char * p_extension);
static bool g_is_our_path(const char * p_path, const char * p_extension);
static GUID g_get_guid();
static const char * g_get_name();
@ -53,7 +41,7 @@ class input_vgmstream : public input_stubs {
t_filestats stats;
/* state */
VGMSTREAM * vgmstream;
VGMSTREAM* vgmstream;
t_uint32 subsong;
bool direct_subsong;
int output_channels;
@ -62,9 +50,7 @@ class input_vgmstream : public input_stubs {
int paused;
int decode_pos_ms;
int decode_pos_samples;
int stream_length_samples;
int fade_samples;
int seek_pos_samples;
int length_samples;
short sample_buffer[SAMPLE_BUFFER_SIZE * VGMSTREAM_MAX_CHANNELS];
/* settings */
@ -75,6 +61,7 @@ class input_vgmstream : public input_stubs {
bool force_ignore_loop;
int ignore_loop;
bool disable_subsongs;
int downmix_channels;
bool tagfile_disable;
pfc::string8 tagfile_name;
@ -82,17 +69,13 @@ class input_vgmstream : public input_stubs {
//bool exts_common_on;
//bool exts_unknown_on;
/* song config */
foobar_song_config config;
/* helpers */
VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort);
void setup_vgmstream(abort_callback & p_abort);
void load_settings();
void get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort);
bool get_description_tag(pfc::string_base & temp, pfc::string_base const& description, const char *tag, char delimiter = '\n');
void set_config_defaults(foobar_song_config *current);
void apply_config(VGMSTREAM * vgmstream, foobar_song_config *current);
void apply_config(VGMSTREAM * vgmstream);
static void g_load_cfg(int *accept_unknown, int *accept_common);
};

View File

@ -258,7 +258,6 @@ int test_hca_key(hca_codec_data* data, unsigned long long keycode) {
}
void hca_set_encryption_key(hca_codec_data* data, uint64_t keycode) {
VGM_LOG("k=%x%x", (uint32_t)((keycode >> 32) & 0xFFFFFFFF), (uint32_t)(keycode & 0xFFFFFFFF));
clHCA_SetKey(data->handle, (unsigned long long)keycode);
}

View File

@ -31,8 +31,8 @@ void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t fir
streamfile = ch1->streamfile;
/* external interleave (variable size), stereo */
bytes_per_frame = get_vgmstream_frame_size(vgmstream);
samples_per_frame = get_vgmstream_samples_per_frame(vgmstream);
bytes_per_frame = vgmstream->frame_size;
samples_per_frame = (vgmstream->frame_size - 0x07*vgmstream->channels)*2 / vgmstream->channels + 2;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
@ -104,8 +104,8 @@ void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample_t * outbuf, int channelsp
off_t frame_offset;
/* external interleave (variable size), mono */
bytes_per_frame = get_vgmstream_frame_size(vgmstream);
samples_per_frame = get_vgmstream_samples_per_frame(vgmstream);
bytes_per_frame = vgmstream->frame_size;
samples_per_frame = (vgmstream->frame_size - 0x07)*2 + 2;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
@ -167,8 +167,8 @@ void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspac
off_t frame_offset;
/* external interleave (variable size), mono */
bytes_per_frame = get_vgmstream_frame_size(vgmstream);
samples_per_frame = get_vgmstream_samples_per_frame(vgmstream);
bytes_per_frame = vgmstream->frame_size;
samples_per_frame = (vgmstream->frame_size - 0x07)*2 + 2;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;

1459
src/decode.c Normal file

File diff suppressed because it is too large Load Diff

32
src/decode.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef _DECODE_H
#define _DECODE_H
#include "vgmstream.h"
void free_codec(VGMSTREAM* vgmstream);
void seek_codec(VGMSTREAM* vgmstream);
void reset_codec(VGMSTREAM* vgmstream);
/* Decode samples into the buffer. Assume that we have written samples_written into the
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer);
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns 1 if loop was done. */
int vgmstream_do_loop(VGMSTREAM* vgmstream);
/* Calculate number of consecutive samples to do (taking into account stopping for loop start and end) */
int get_vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMSTREAM* vgmstream);
/* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */
int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream);
/* Get the number of bytes of a single frame (smallest self-contained byte group, 1/N channels) */
int get_vgmstream_frame_size(VGMSTREAM* vgmstream);
/* In NDS IMA the frame size is the block size, but last one is shorter */
int get_vgmstream_samples_per_shortframe(VGMSTREAM* vgmstream);
int get_vgmstream_shortframe_size(VGMSTREAM* vgmstream);
#endif

View File

@ -242,7 +242,6 @@ static const char* extension_list[] = {
"kat",
"kces",
"kcey", //fake extension/header id for .pcm (renamed, to be removed)
"khv", //fake extension/header id for .vas (renamed, to be removed)
"km9",
"kovs", //fake extension/header id for .kvs
"kns",
@ -560,6 +559,7 @@ static const char* extension_list[] = {
"wem",
"wii",
"wip", //txth/reserved [Colin McRae DiRT (PC)]
"wlv", //txth/reserved [ToeJam & Earl III: Mission to Earth (DC)]
"wma", //common
"wmus",
"wp2",

View File

@ -1,5 +1,6 @@
#include "layout.h"
#include "../vgmstream.h"
#include "../decode.h"
/* Decodes samples for blocked streams.

View File

@ -1,5 +1,6 @@
#include "layout.h"
#include "../vgmstream.h"
#include "../decode.h"
/* Decodes samples for flat streams.

View File

@ -1,5 +1,6 @@
#include "layout.h"
#include "../vgmstream.h"
#include "../decode.h"
/* Decodes samples for interleaved streams.

View File

@ -1,9 +1,9 @@
#include "layout.h"
#include "../vgmstream.h"
#include "../decode.h"
#include "../mixing.h"
#include "../plugins.h"
/* NOTE: if loop settings change the layered vgmstreams must be notified (preferably using vgmstream_force_loop) */
#define VGMSTREAM_MAX_LAYERS 255
#define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192
@ -13,22 +13,53 @@
* with custom codecs and different number of channels, creating a single super-vgmstream.
* Usually combined with custom streamfiles to handle data interleaved in weird ways. */
void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
int samples_written = 0;
int samples_written = 0, loop_samples_skip = 0;
layered_layout_data* data = vgmstream->layout_data;
while (samples_written < sample_count) {
int samples_to_do = VGMSTREAM_LAYER_SAMPLE_BUFFER;
int samples_to_do;
int samples_this_block = VGMSTREAM_LAYER_SAMPLE_BUFFER;
int layer, ch = 0;
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
if (data->external_looping) {
/* normally each layer handles its own looping internally, except when using config
* were each layer is treated as a solid part, so loop is applied externally */
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
for (layer = 0; layer < data->layer_count; layer++) {
reset_vgmstream(data->layers[layer]);
//todo per-layer seeking instead of layout looping
}
loop_samples_skip = vgmstream->loop_start_sample;
vgmstream->current_sample = 0;
vgmstream->samples_into_block = 0;
continue;
}
samples_to_do = get_vgmstream_samples_to_do(vgmstream->num_samples, samples_this_block, vgmstream);
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
/* looping: discard until actual start */
if (loop_samples_skip > 0) {
if (samples_to_do > loop_samples_skip)
samples_to_do = loop_samples_skip;
}
}
else {
samples_to_do = samples_this_block;
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
}
/* decode all layers */
for (layer = 0; layer < data->layer_count; layer++) {
int s, layer_ch, layer_channels;
/* each layer will handle its own looping/mixing internally */
/* layers may have its own number of channels */
mixing_info(data->layers[layer], NULL, &layer_channels);
@ -37,6 +68,10 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
samples_to_do,
data->layers[layer]);
if (loop_samples_skip > 0) {
continue;
}
/* mix layer samples to main samples */
for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) {
for (s = 0; s < samples_to_do; s++) {
@ -49,11 +84,24 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
}
}
samples_written += samples_to_do;
/* needed for info (ex. for mixing) */
vgmstream->current_sample = data->layers[0]->current_sample;
vgmstream->loop_count = data->layers[0]->loop_count;
//vgmstream->samples_into_block = 0; /* handled in each layer */
if (loop_samples_skip > 0) {
loop_samples_skip -= samples_to_do;
vgmstream->samples_into_block += samples_to_do;
continue;
}
if (data->external_looping) {
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
}
else {
samples_written += samples_to_do;
vgmstream->current_sample = data->layers[0]->current_sample;
vgmstream->samples_into_block = 0; /* handled in each layer */
vgmstream->loop_count = data->layers[0]->loop_count;
}
}
}
@ -88,12 +136,12 @@ int setup_layout_layered(layered_layout_data* data) {
int layer_input_channels, layer_output_channels;
if (data->layers[i] == NULL) {
VGM_LOG("layered: no vgmstream in %i\n", i);
VGM_LOG("LAYERED: no vgmstream in %i\n", i);
goto fail;
}
if (data->layers[i]->num_samples <= 0) {
VGM_LOG("layered: no samples in %i\n", i);
VGM_LOG("LAYERED: no samples in %i\n", i);
goto fail;
}
@ -107,21 +155,25 @@ int setup_layout_layered(layered_layout_data* data) {
if (i > 0) {
/* a bit weird, but no matter */
if (data->layers[i]->sample_rate != data->layers[i-1]->sample_rate) {
VGM_LOG("layered: layer %i has different sample rate\n", i);
VGM_LOG("LAYERED: layer %i has different sample rate\n", i);
}
/* also weird */
if (data->layers[i]->coding_type != data->layers[i-1]->coding_type) {
VGM_LOG("layered: layer %i has different coding type\n", i);
VGM_LOG("LAYERED: layer %i has different coding type\n", i);
}
}
/* loops and other values could be mismatched but hopefully not */
/* loops and other values could be mismatched, but should be handled on allocate */
/* init mixing */
mixing_setup(data->layers[i], VGMSTREAM_LAYER_SAMPLE_BUFFER);
setup_vgmstream(data->layers[i]); /* final setup in case the VGMSTREAM was created manually */
/* allow config if set for fine-tuned parts (usually TXTP only) */
data->layers[i]->config_enabled = data->layers[i]->config.config_set;
mixing_setup(data->layers[i], VGMSTREAM_LAYER_SAMPLE_BUFFER); /* init mixing */
/* final setup in case the VGMSTREAM was created manually */
setup_vgmstream(data->layers[i]);
}
if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS)
@ -168,33 +220,66 @@ void reset_layout_layered(layered_layout_data *data) {
}
/* helper for easier creation of layers */
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data) {
VGMSTREAM *vgmstream = NULL;
int i, channels, loop_flag, num_samples;
VGMSTREAM* allocate_layered_vgmstream(layered_layout_data* data) {
VGMSTREAM* vgmstream = NULL;
int i, channels, loop_flag, sample_rate, external_looping;
int32_t num_samples, loop_start, loop_end;
int delta = 1024;
/* get data */
channels = data->output_channels;
loop_flag = 1;
num_samples = 0;
loop_flag = 1;
loop_start = data->layers[0]->loop_start_sample;
loop_end = data->layers[0]->loop_end_sample;
external_looping = 0;
sample_rate = 0;
for (i = 0; i < data->layer_count; i++) {
if (loop_flag && !data->layers[i]->loop_flag)
int32_t layer_samples = vgmstream_get_samples(data->layers[i]);
int layer_loop = data->layers[i]->loop_flag;
int32_t layer_loop_start = data->layers[i]->loop_start_sample;
int32_t layer_loop_end = data->layers[i]->loop_end_sample;
int layer_rate = data->layers[i]->sample_rate;
/* internal has own config (and maybe looping), looping now must be done on layout level
* (instead of on each layer, that is faster) */
if (data->layers[i]->config_enabled) {
loop_flag = 0;
if (num_samples < data->layers[i]->num_samples)
num_samples = data->layers[i]->num_samples;
layer_loop = 0;
external_looping = 1;
}
/* all layers should share loop pointsto consider looping enabled,
* but allow some leeway (ex. Dragalia Lost bgm+vocals ~12 samples) */
if (!layer_loop
|| !(loop_start >= layer_loop_start - delta && loop_start <= layer_loop_start + delta)
|| !(loop_end >= layer_loop_end - delta && loop_start <= layer_loop_end + delta)) {
loop_flag = 0;
loop_start = 0;
loop_end = 0;
}
if (num_samples < layer_samples) /* max */
num_samples = layer_samples;
if (sample_rate < layer_rate)
sample_rate = layer_rate;
}
data->external_looping = external_looping;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = data->layers[0]->meta_type;
vgmstream->sample_rate = data->layers[0]->sample_rate;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = data->layers[0]->loop_start_sample;
vgmstream->loop_end_sample = data->layers[0]->loop_end_sample;
vgmstream->coding_type = data->layers[0]->coding_type;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
vgmstream->meta_type = data->layers[0]->meta_type; /* info */
vgmstream->coding_type = data->layers[0]->coding_type; /* info */
vgmstream->layout_type = layout_layered;
vgmstream->layout_data = data;

View File

@ -1,6 +1,8 @@
#include "layout.h"
#include "../vgmstream.h"
#include "../decode.h"
#include "../mixing.h"
#include "../plugins.h"
#define VGMSTREAM_MAX_SEGMENTS 1024
#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192
@ -15,15 +17,19 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
int use_internal_buffer = 0;
/* normally uses outbuf directly (faster) but could need internal buffer if downmixing */
/* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */
if (vgmstream->channels != data->input_channels) {
use_internal_buffer = 1;
}
if (data->current_segment >= data->segment_count) {
VGM_LOG("SEGMENT: wrong current segment\n");
return;
}
while (samples_written < sample_count) {
int samples_to_do;
int samples_this_segment = data->segments[data->current_segment]->num_samples;
int samples_this_segment = vgmstream_get_samples(data->segments[data->current_segment]);
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
int segment, loop_segment, total_samples;
@ -32,7 +38,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
loop_segment = 0;
total_samples = 0;
while (total_samples < vgmstream->num_samples) {
int32_t segment_samples = data->segments[loop_segment]->num_samples;
int32_t segment_samples = vgmstream_get_samples(data->segments[loop_segment]);
if (vgmstream->loop_current_sample >= total_samples &&
vgmstream->loop_current_sample < total_samples + segment_samples) {
@ -44,7 +50,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
}
if (loop_segment == data->segment_count) {
VGM_LOG("segmented_layout: can't find loop segment\n");
VGM_LOG("SEGMENTED: can't find loop segment\n");
loop_segment = 0;
}
@ -65,7 +71,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
/* segment looping: discard until actual start */
/* looping: discard until actual start */
if (loop_samples_skip > 0) {
if (samples_to_do > loop_samples_skip)
samples_to_do = loop_samples_skip;
@ -74,6 +80,10 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
/* detect segment change and restart */
if (samples_to_do == 0) {
data->current_segment++;
/* could happen on last segment trying to decode more samples */
if (data->current_segment >= data->segment_count) {
break;
}
reset_vgmstream(data->segments[data->current_segment]);
vgmstream->samples_into_block = 0;
continue;
@ -136,22 +146,27 @@ int setup_layout_segmented(segmented_layout_data* data) {
for (i = 0; i < data->segment_count; i++) {
int segment_input_channels, segment_output_channels;
/* allow config if set for fine-tuned parts (usually TXTP only) */
data->segments[i]->config_enabled = data->segments[i]->config.config_set;
if (data->segments[i] == NULL) {
VGM_LOG("segmented: no vgmstream in segment %i\n", i);
VGM_LOG("SEGMENTED: no vgmstream in segment %i\n", i);
goto fail;
}
if (data->segments[i]->num_samples <= 0) {
VGM_LOG("segmented: no samples in segment %i\n", i);
VGM_LOG("SEGMENTED: no samples in segment %i\n", i);
goto fail;
}
/* disable so that looping is controlled by render_vgmstream_segmented */
if (data->segments[i]->loop_flag != 0) {
VGM_LOG("segmented: segment %i is looped\n", i);
data->segments[i]->loop_flag = 0;
VGM_LOG("SEGMENTED: segment %i is looped\n", i);
/* config allows internal loops */
if (!data->segments[i]->config_enabled) {
data->segments[i]->loop_flag = 0;
}
}
/* different segments may have different input channels, though output should be
@ -167,13 +182,13 @@ int setup_layout_segmented(segmented_layout_data* data) {
mixing_info(data->segments[i-1], NULL, &prev_output_channels);
if (segment_output_channels != prev_output_channels) {
VGM_LOG("segmented: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
goto fail;
}
/* a bit weird, but no matter */
if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) {
VGM_LOG("segmented: segment %i has different sample rate\n", i);
VGM_LOG("SEGMENTED: segment %i has different sample rate\n", i);
}
/* perfectly acceptable */
@ -181,10 +196,11 @@ int setup_layout_segmented(segmented_layout_data* data) {
// goto fail;
}
/* init mixing */
mixing_setup(data->segments[i], VGMSTREAM_SEGMENT_SAMPLE_BUFFER);
setup_vgmstream(data->segments[i]); /* final setup in case the VGMSTREAM was created manually */
mixing_setup(data->segments[i], VGMSTREAM_SEGMENT_SAMPLE_BUFFER); /* init mixing */
/* final setup in case the VGMSTREAM was created manually */
setup_vgmstream(data->segments[i]);
}
if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS)
@ -242,21 +258,27 @@ void reset_layout_segmented(segmented_layout_data* data) {
}
/* helper for easier creation of segments */
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment) {
VGMSTREAM *vgmstream = NULL;
VGMSTREAM* allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment) {
VGMSTREAM* vgmstream = NULL;
int channel_layout;
int i, num_samples, loop_start, loop_end;
int i, sample_rate;
int32_t num_samples, loop_start, loop_end;
/* save data */
channel_layout = data->segments[0]->channel_layout;
num_samples = 0;
loop_start = 0;
loop_end = 0;
sample_rate = 0;
for (i = 0; i < data->segment_count; i++) {
/* needs get_samples since element may use play settings */
int32_t segment_samples = vgmstream_get_samples(data->segments[i]);
int segment_rate = data->segments[i]->sample_rate;
if (loop_flag && i == loop_start_segment)
loop_start = num_samples;
num_samples += data->segments[i]->num_samples;
num_samples += segment_samples;
if (loop_flag && i == loop_end_segment)
loop_end = num_samples;
@ -264,6 +286,9 @@ VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_fl
/* inherit first segment's layout but only if all segments' layout match */
if (channel_layout != 0 && channel_layout != data->segments[i]->channel_layout)
channel_layout = 0;
if (sample_rate < segment_rate)
sample_rate = segment_rate;
}
/* respect loop_flag even when no loop_end found as it's possible file loops are set outside */

View File

@ -147,6 +147,14 @@
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath=".\decode.h"
>
</File>
<File
RelativePath=".\render.h"
>
</File>
<File
RelativePath=".\mixing.h"
>
@ -181,6 +189,14 @@
RelativePath=".\formats.c"
>
</File>
<File
RelativePath=".\decode.c"
>
</File>
<File
RelativePath=".\render.c"
>
</File>
<File
RelativePath=".\mixing.c"
>

View File

@ -92,6 +92,8 @@
<ClInclude Include="meta\xwb_xsb.h" />
<ClInclude Include="meta\xwma_konami_streamfile.h" />
<ClInclude Include="meta\zsnd_streamfile.h" />
<ClInclude Include="decode.h" />
<ClInclude Include="render.h" />
<ClInclude Include="mixing.h" />
<ClInclude Include="plugins.h" />
<ClInclude Include="streamfile.h" />
@ -246,6 +248,8 @@
<ClCompile Include="meta\x360_tra.c" />
<ClCompile Include="formats.c" />
<ClCompile Include="meta\xmv_valve.c" />
<ClCompile Include="decode.c" />
<ClCompile Include="render.c" />
<ClCompile Include="mixing.c" />
<ClCompile Include="plugins.c" />
<ClCompile Include="meta\ps2_va3.c" />

View File

@ -47,6 +47,12 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="decode.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="render.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="mixing.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -268,6 +274,12 @@
<ClCompile Include="formats.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="decode.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="render.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mixing.c">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -106,26 +106,39 @@ VGMSTREAM * init_vgmstream_brstm(STREAMFILE *streamFile) {
if (vgmstream->coding_type == coding_NGC_DSP) {
off_t coef_offset;
off_t coef_offset1;
off_t coef_offset2;
off_t head_part3_offset;
off_t adpcm_header_offset;
int i,j;
int coef_spacing = 0x38;
int coef_spacing;
if (atlus_shrunken_head)
{
coef_offset = 0x50;
coef_spacing = 0x30;
for (j = 0; j < vgmstream->channels; j++) {
for (i = 0; i < 16; i++) {
vgmstream->ch[j].adpcm_coef[i] = read_16bitBE(head_offset + coef_offset + j * coef_spacing + i * 2,streamFile);
}
}
}
else
{
coef_offset1=read_32bitBE(head_offset+0x1c,streamFile);
coef_offset2=read_32bitBE(head_offset+0x10+coef_offset1,streamFile);
coef_offset=coef_offset2+0x10;
}
head_part3_offset = read_32bitBE(head_offset + 0x1c, streamFile);
for (j=0;j<vgmstream->channels;j++) {
for (i=0;i<16;i++) {
vgmstream->ch[j].adpcm_coef[i]=read_16bitBE(head_offset+coef_offset+j*coef_spacing+i*2,streamFile);
for (j = 0; j < vgmstream->channels; j++) {
adpcm_header_offset = head_offset + 0x08
+ head_part3_offset + 0x04 /* skip over HEAD part 3 */
+ j * 0x08 /* skip to channel's ADPCM offset table */
+ 0x04; /* ADPCM header offset field */
coef_offset = head_offset + 0x08
+ read_32bitBE(adpcm_header_offset, streamFile)
+ 0x08; /* coeffs field */
for (i = 0; i < 16; i++) {
vgmstream->ch[j].adpcm_coef[i] = read_16bitBE(coef_offset + i * 2, streamFile);
}
}
}
}

View File

@ -219,13 +219,19 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
extraflag_offset += 0x04 + extraflag_size;
stream_header_size += 0x04 + extraflag_size;
} while (extraflag_end != 0x00);
}
while (extraflag_end != 0x00);
}
/* target found */
if (i + 1 == target_subsong) {
fsb5.stream_offset = fsb5.base_header_size + fsb5.sample_header_size + fsb5.name_table_size + data_offset;
/* catch bad rips (like incorrectly split +1.5GB .fsb with wrong header+data) */
if (fsb5.stream_offset > get_streamfile_size(sf))
goto fail;
/* get stream size from next stream offset or full size if there is only one */
if (i + 1 == fsb5.total_subsongs) {
fsb5.stream_size = fsb5.sample_data_size - data_offset;

View File

@ -124,29 +124,6 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
vgmstream->layout_type = layout_layered;
vgmstream->coding_type = coding_ATRAC9;
break;
#if 0
atrac9_config cfg = {0};
if (ktsr.channels > 1) {
VGM_LOG("1\n");
goto fail;
}
/* 0x00: samples per frame */
/* 0x02: frame size */
cfg.config_data = read_u32be(ktsr.extra_offset + 0x04, sf_b);
if ((cfg.config_data & 0xFF) == 0xFE) /* later versions(?) in LE */
cfg.config_data = read_u32le(ktsr.extra_offset + 0x04, sf_b);
cfg.channels = vgmstream->channels;
cfg.encoder_delay = 256; /* observed default (ex. Attack on Titan PC vs Vita) */
vgmstream->codec_data = init_atrac9(&cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_ATRAC9;
vgmstream->layout_type = layout_none;
break;
#endif
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -20,9 +20,8 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
* .str: Ben10 Galactic Racing
* .vig: MX vs. ATV Untamed (PS2)
* .l/r: Crash Nitro Kart (PS2), Gradius V (PS2)
* .vas: Kingdom Hearts II (PS2)
* .khv: fake for .vas */
if ( !check_extensions(streamFile,"vag,swag,str,vig,l,r,vas,khv") )
* .vas: Kingdom Hearts II (PS2) */
if ( !check_extensions(streamFile,"vag,swag,str,vig,l,r,vas") )
goto fail;
/* check VAG Header */

View File

@ -119,6 +119,10 @@ static int is_active(mixing_data *data, int32_t current_start, int32_t current_e
static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
int32_t current_pos;
if (vgmstream->config_enabled) {
return vgmstream->pstate.play_position;
}
if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
@ -807,8 +811,11 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
/* set loops to hear all track changes */
track_num = output_channels / max;
if (vgmstream->config.loop_count < track_num)
if (vgmstream->config.loop_count < track_num) {
vgmstream->config.loop_count = track_num;
vgmstream->config.loop_count_set = 1;
vgmstream->config.config_set = 1;
}
ch = 0;
for (track = 0; track < track_num; track++) {
@ -868,8 +875,11 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
/* set loops to hear all track changes */
layer_num = output_channels / max;
if (vgmstream->config.loop_count < layer_num)
if (vgmstream->config.loop_count < layer_num) {
vgmstream->config.loop_count = layer_num;
vgmstream->config.loop_count_set = 1;
vgmstream->config.config_set = 1;
}
/* mode 'v': constant volume
* mode 'e': sets fades to successively lower/equalize volume per loop for each layer
@ -1062,12 +1072,6 @@ void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) {
if (!data) goto fail;
/* a bit wonky but eh... */
if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) {
vgmstream->channel_layout = 0;
((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = 0;
}
/* special value to not actually enable anything (used to query values) */
if (max_sample_count <= 0)
goto fail;
@ -1079,6 +1083,12 @@ void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) {
data->mixbuf = mixbuf_re;
data->mixing_on = 1;
/* a bit wonky but eh... */
if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) {
vgmstream->channel_layout = 0;
((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = 0;
}
/* since data exists on its own memory and pointer is already set
* there is no need to propagate to start_vgmstream */
@ -1089,7 +1099,7 @@ fail:
return;
}
void mixing_info(VGMSTREAM * vgmstream, int *out_input_channels, int *out_output_channels) {
void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_channels) {
mixing_data *data = vgmstream->mixing_data;
int input_channels, output_channels;
@ -1101,11 +1111,13 @@ void mixing_info(VGMSTREAM * vgmstream, int *out_input_channels, int *out_output
else
input_channels = vgmstream->channels;
if (out_input_channels) *out_input_channels = input_channels;
if (out_output_channels) *out_output_channels = output_channels;
if (p_input_channels) *p_input_channels = input_channels;
if (p_output_channels) *p_output_channels = output_channels;
//;VGM_LOG("MIX: channels %i, in=%i, out=%i, mix=%i\n", vgmstream->channels, input_channels, output_channels, data->mixing_channels);
return;
fail:
if (p_input_channels) *p_input_channels = vgmstream->channels;
if (p_output_channels) *p_output_channels = vgmstream->channels;
return;
}

View File

@ -63,6 +63,102 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
return 0;
}
static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) {
if (!*src_flag)
return;
*dst_flag = 1;
*dst_time = *src_time;
*dst_time_s = *src_time_s;
}
//todo reuse in txtp?
static void load_default_config(play_config_t* def, play_config_t* tcfg) {
/* loop limit: txtp #L > txtp #l > player #L > player #l */
if (tcfg->play_forever) {
def->play_forever = 1;
def->ignore_loop = 0;
}
if (tcfg->loop_count_set) {
def->ignore_loop = 0;
def->loop_count = tcfg->loop_count;
def->loop_count_set = 1;
if (!tcfg->play_forever)
def->play_forever = 0;
}
/* fade priority: #F > #f, #d */
if (tcfg->ignore_fade) {
def->ignore_fade = 1;
}
if (tcfg->fade_delay_set) {
def->fade_delay = tcfg->fade_delay;
}
if (tcfg->fade_time_set) {
def->fade_time = tcfg->fade_time;
}
/* loop priority: #i > #e > #E */
if (tcfg->really_force_loop) {
def->ignore_loop = 0;
def->force_loop = 0;
def->really_force_loop = 1;
}
if (tcfg->force_loop) {
def->ignore_loop = 0;
def->force_loop = 1;
def->really_force_loop = 0;
}
if (tcfg->ignore_loop) {
def->ignore_loop = 1;
def->force_loop = 0;
def->really_force_loop = 0;
}
copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s);
copy_time(&def->pad_end_set, &def->pad_end, &def->pad_end_s, &tcfg->pad_end_set, &tcfg->pad_end, &tcfg->pad_end_s);
copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s);
copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s);
copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s);
}
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
def->play_forever = vcfg->play_forever;
def->ignore_loop = vcfg->ignore_loop;
def->force_loop = vcfg->force_loop;
def->really_force_loop = vcfg->really_force_loop;
def->ignore_fade = vcfg->ignore_fade;
def->loop_count = vcfg->loop_times; //todo loop times
def->loop_count_set = 1;
def->fade_delay = vcfg->fade_delay;
def->fade_delay_set = 1;
def->fade_time = vcfg->fade_period; //todo loop period
def->fade_time_set = 1;
}
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) {
play_config_t defs = {0};
play_config_t* def = &defs; /* for convenience... */
play_config_t* tcfg = &vgmstream->config;
load_player_config(def, vcfg);
def->config_set = 1;
if (!vcfg->disable_config_override)
load_default_config(def, tcfg);
if (!vcfg->allow_play_forever)
def->play_forever = 0;
/* copy final config back */
*tcfg = *def;
vgmstream->config_enabled = def->config_set;
setup_state_vgmstream(vgmstream);
}
/* ****************************************** */
/* TAGS: loads key=val tags from a file */
/* ****************************************** */
@ -329,6 +425,10 @@ void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
mixing_setup(vgmstream, max_sample_count);
mixing_info(vgmstream, input_channels, output_channels);
/* update internals */
mixing_info(vgmstream, &vgmstream->pstate.input_channels, &vgmstream->pstate.output_channels);
setup_vgmstream(vgmstream);
}
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {

View File

@ -5,6 +5,25 @@
#define _PLUGINS_H_
#include "streamfile.h"
//todo rename to api.h once public enough
#if 0
/* define standard C param call and name mangling (to avoid __stdcall / .defs) */
//#define VGMSTREAM_CALL __cdecl //needed?
/* define external function types (during compilation) */
#if defined(VGMSTREAM_EXPORT)
#define VGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */
#elif defined(VGMSTREAM_IMPORT)
#define VGMSTREAM_API __declspec(dllimport) /* when importing/linking vgmstream DLL */
#else
#define VGMSTREAM_API /* nothing, internal/default */
#endif
//VGMSTREAM_API void VGMSTREAM_CALL vgmstream_function(void);
#endif
/* ****************************************** */
/* CONTEXT: simplifies plugin code */
@ -21,42 +40,129 @@ typedef struct {
/* returns if vgmstream can parse file by extension */
int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg);
#if 0
/* opaque player state */
typedef struct VGMSTREAM_CTX VGMSTREAM_CTX;
typedef struct {
//...
} VGMSTREAM_CTX_INFO;
int allow_play_forever;
int disable_config_override;
VGMSTREAM_CTX* vgmstream_ctx_init(...);
/* song mofidiers */
int play_forever; /* keeps looping forever (needs loop points) */
int ignore_loop; /* ignores loops points */
int force_loop; /* enables full loops (0..samples) if file doesn't have loop points */
int really_force_loop; /* forces full loops even if file has loop points */
int ignore_fade; /* don't fade after N loops */
VGMSTREAM_CTX* vgmstream_ctx_format_check(...);
VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...);
VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...);
/* song processing */
double loop_times; /* target loops */
double fade_delay; /* fade delay after target loops */
double fade_period; /* fade time after target loops */
VGMSTREAM_CTX* vgmstream_ctx_set_file(...);
//int downmix; /* max number of channels allowed (0=disable downmix) */
VGMSTREAM_CTX* vgmstream_ctx_get_config(...);
} vgmstream_cfg_t;
VGMSTREAM_CTX* vgmstream_ctx_set_config(...);
// WARNING: these are not stable and may change anytime without notice
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* pcfg);
int32_t vgmstream_get_samples(VGMSTREAM* vgmstream);
int vgmstream_get_play_forever(VGMSTREAM* vgmstream);
VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...);
VGMSTREAM_CTX* vgmstream_ctx_get_info(...);
VGMSTREAM_CTX* vgmstream_ctx_describe(...);
#if 0
//possible future public/opaque API
VGMSTREAM_CTX* vgmstream_ctx_get_title(...);
/* opaque player state */
//#define VGMSTREAM_CTX_VERSION 1
typedef struct VGMSTREAM_CTX VGMSTREAM_CTX;
VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...);
VGMSTREAM_CTX* vgmstream_ctx_play(...);
/* Setups base vgmstream player context. */
VGMSTREAM_CTX* vgmstream_init_ctx(void);
VGMSTREAM_CTX* vgmstream_ctx_seek(...);
VGMSTREAM_CTX* vgmstream_ctx_close(...);
/* Sets default config, that will be applied to song on open (some formats like TXTP may override
* these settings).
* May only be called without song loaded (before _open or after _close), otherwise ignored. */
void vgmstream_set_config(VGMSTREAM_CTX* vctx, VGMSTREAM_CFG* vcfg);
void vgmstream_set_buffer(VGMSTREAM_CTX* vctx, int samples, int max_samples);
/* Opens a new STREAMFILE to play. Returns < 0 on error when the file isn't recogniced.
* If file has subsongs, first open usually loads first subsong. get_info then can be used to check
* whether file has more subsongs (total_subsongs > 1), and call others.
* */
int vgmstream_open(STREAMFILE* sf);
int vgmstream_open_subsong(STREAMFILE* sf, int subsong);
typedef struct {
const int channels;
const int sample_rate;
const int sample_count; /* file's samples (not final duration) */
const int loop_start_sample;
const int loop_end_sample;
const int loop_flag;
const int current_subsong; /* 0=not set, N=loaded subsong N */
const int total_subsongs; /* 0=format has no subsongs, N=has N subsongs */
const int file_bitrate; /* file's average bitrate */
//const int codec_bitrate; /* codec's average bitrate */
/* descriptions */
//const char* codec;
//const char* layout;
//const char* metadata;
//int type; /* 0=pcm16, 1=float32, always interleaved: [0]=ch0, [1]=ch1 ... */
} VGMSTREAM_INFO;
/* Get info from current song. */
void vgmstream_ctx_get_info(VGMSTREAM_CTX* vctx, VGMSTREAM_INFO* vinfo);
/* Gets final time based on config and current song. If config is set to "play forever"
* this still returns final time based on config as a reference. Returns > 0 on success. */
int32_t vgmstream_get_total_time(VGMSTREAM_CTX* vctx);
double vgmstream_get_total_samples(VGMSTREAM_CTX* vctx);
/* Gets current position within song. When "play forever" is set, it'll clamp results to total_time. */
int32_t vgmstream_get_current_time(VGMSTREAM_CTX* vctx);
double vgmstream_get_current_samples(VGMSTREAM_CTX* vctx);
/* Seeks to position */
VGMSTREAM_CTX* vgmstream_seek_absolute_sample(VGMSTREAM_CTX* vctx, int32_t sample);
VGMSTREAM_CTX* vgmstream_seek_absolute_time(VGMSTREAM_CTX* vctx, double time);
VGMSTREAM_CTX* vgmstream_seek_current_sample(VGMSTREAM_CTX* vctx, int32_t sample);
VGMSTREAM_CTX* vgmstream_seek_current_time(VGMSTREAM_CTX* vctx, double time);
/* Closes current song. */
void vgmstream_close(VGMSTREAM_CTX* vctx);
/* Frees vgmstream context. */
void vgmstream_free_ctx(VGMSTREAM_CTX* vctx);
/* Converts samples. returns number of rendered samples, or <=0 if no more
* samples left (will fill buffer with silence) */
int vgmstream_play(VGMSTREAM_CTX* vctx);
#if 0
void vgmstream_get_buffer(...);
void vgmstream_format_check(...);
void vgmstream_set_format_whilelist(...);
void vgmstream_set_format_blacklist(...);
const char* vgmstream_describe(...);
const char* vgmstream_get_title(...);
VGMSTREAM_TAGS* vgmstream_get_tagfile(...);
#endif
#endif

683
src/render.c Normal file
View File

@ -0,0 +1,683 @@
#include "vgmstream.h"
#include "layout/layout.h"
#include "render.h"
#include "decode.h"
#include "mixing.h"
#include "plugins.h"
/* VGMSTREAM RENDERING
* Caller asks for N samples in buf. vgmstream then calls layouts, that call decoders, and some optional pre/post-processing.
* Processing must be enabled externally: padding/trimming (config), mixing (output changes), resampling, etc.
*
* - MIXING
* After decoding sometimes we need to change number of channels, volume, etc. This is applied in order as
* a mixing chain, and modifies the final buffer (see mixing.c).
*
* - CONFIG
* A VGMSTREAM can work in 2 modes, defaults to simple mode:
* - simple mode (lib-like): decodes/loops forever and results are controlled externally (fades/max time/etc).
* - config mode (player-like): everything is internally controlled (pads/trims/time/fades/etc may be applied).
*
* It's done this way mainly for compatibility and to enable complex TXTP for layers/segments in selected cases
* (Wwise emulation). Could apply always some config like begin trim/padding + modify get_vgmstream_samples, but
* external caller may read loops/samples manually or apply its own config/fade, and changed output would mess it up.
*
* To enable config mode it needs 2 steps:
* - add some internal config settings (via TXTP, or passed by plugin).
* - enable flag with function (to signal "really delegate all decoding to vgmstream").
* Once done, plugin should simply decode until max samples (calculated by vgmstream).
*
* For complex layouts, behavior of "internal" (single segment/layer) and "external" (main) VGMSTREAMs is
* a bit complex. Internals' enable flag if play config exists (via TXTP), and this allows each part to be
* padded/trimmed/set time/loop/etc individually.
*
* Config mode in the external VGMSTREAM is mostly straighforward with segments:
* - each internal is always decoded separatedly (in simple or config mode) and results in N samples
* - segments may even loop "internally" before moving to next segment (by default they don't)
* - external's samples is the sum of all segments' N samples
* - looping, fades, etc then can be applied in the external part normally.
*
* With layers it's a bit more complex:
* - external's samples is the max of all layers
* - in simple mode external uses internal's looping to loop (for performance)
* - if layers' config mode is set, external can't rely on internal looping, so it uses it's own
*
* Layouts can contain layouts in cascade, so behavior can be a bit hard to understand at times.
* This mainly applies to TXTP, segments/layers in metas usually don't need to trigger config mode.
*/
int vgmstream_get_play_forever(VGMSTREAM* vgmstream) {
return vgmstream->config.play_forever;
}
int32_t vgmstream_get_samples(VGMSTREAM* vgmstream) {
if (!vgmstream->config_enabled || !vgmstream->config.config_set)
return vgmstream->num_samples;
return vgmstream->pstate.play_duration;
}
/* calculate samples based on player's config */
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream) {
if (vgmstream->loop_flag) {
if (vgmstream->loop_target == (int)looptimes) { /* set externally, as this function is info-only */
/* Continue playing the file normally after looping, instead of fading.
* Most files cut abruply after the loop, but some do have proper endings.
* With looptimes = 1 this option should give the same output vs loop disabled */
int loop_count = (int)looptimes; /* no half loops allowed */
return vgmstream->loop_start_sample
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count
+ (vgmstream->num_samples - vgmstream->loop_end_sample);
}
else {
return vgmstream->loop_start_sample
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes
+ (fadedelayseconds + fadeseconds) * vgmstream->sample_rate;
}
}
else {
return vgmstream->num_samples;
}
}
/*****************************************************************************/
static void setup_state_modifiers(VGMSTREAM* vgmstream) {
play_config_t* pc = &vgmstream->config;
/* apply final config */
if (pc->really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (pc->force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (pc->ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0);
}
if (!vgmstream->loop_flag) {
pc->play_forever = 0;
}
if (pc->play_forever) {
pc->ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */
if (pc->ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)pc->loop_count);
pc->fade_time = 0;
pc->fade_delay = 0;
}
}
static void setup_state_processing(VGMSTREAM* vgmstream) {
play_state_t* ps = &vgmstream->pstate;
play_config_t* pc = &vgmstream->config;
double sample_rate = vgmstream->sample_rate;
/* time to samples */
if (pc->pad_begin_s)
pc->pad_begin = pc->pad_begin_s * sample_rate;
if (pc->pad_end_s)
pc->pad_end = pc->pad_end_s * sample_rate;
if (pc->trim_begin_s)
pc->trim_begin = pc->trim_begin_s * sample_rate;
if (pc->trim_end_s)
pc->trim_end = pc->trim_end_s * sample_rate;
if (pc->body_time_s)
pc->body_time = pc->body_time_s * sample_rate;
//todo fade time also set to samples
/* samples before all decode */
ps->pad_begin_duration = pc->pad_begin;
/* removed samples from first decode */
ps->trim_begin_duration = pc->trim_begin;
/* main samples part */
ps->body_duration = 0;
if (pc->body_time) {
ps->body_duration += pc->body_time; /* whether it loops or not */
}
else if (vgmstream->loop_flag) {
double loop_count = 1.0;
if (pc->loop_count_set) /* may set 0.0 on purpose I guess */
loop_count = pc->loop_count;
ps->body_duration += vgmstream->loop_start_sample;
if (pc->ignore_fade) {
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * (int)loop_count;
ps->body_duration += (vgmstream->num_samples - vgmstream->loop_end_sample);
}
else {
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count;
}
}
else {
ps->body_duration += vgmstream->num_samples;
}
/* samples from some modify body */
if (pc->trim_begin)
ps->body_duration -= pc->trim_begin;
if (pc->trim_end)
ps->body_duration -= pc->trim_end;
if (pc->fade_delay && vgmstream->loop_flag)
ps->body_duration += pc->fade_delay * vgmstream->sample_rate;
/* samples from fade part */
if (pc->fade_time && vgmstream->loop_flag)
ps->fade_duration = pc->fade_time * vgmstream->sample_rate;
/* samples from last part (anything beyond this is empty, unless play forever is set) */
ps->pad_end_duration = pc->pad_end;
/* final count */
ps->play_duration = ps->pad_begin_duration + ps->body_duration + ps->fade_duration + ps->pad_end_duration;
ps->play_position = 0;
/* values too big can overflow, just ignore */
if (ps->pad_begin_duration < 0)
ps->pad_begin_duration = 0;
if (ps->body_duration < 0)
ps->body_duration = 0;
if (ps->fade_duration < 0)
ps->fade_duration = 0;
if (ps->pad_end_duration < 0)
ps->pad_end_duration = 0;
if (ps->play_duration < 0)
ps->play_duration = 0;
ps->pad_begin_left = ps->pad_begin_duration;
ps->trim_begin_left = ps->trim_begin_duration;
ps->fade_left = ps->fade_duration;
ps->fade_start = ps->pad_begin_duration + ps->body_duration;
//ps->pad_end_left = ps->pad_end_duration;
ps->pad_end_start = ps->fade_start + ps->fade_duration;
/* other info (updated once mixing is enabled) */
ps->input_channels = vgmstream->channels;
ps->output_channels = vgmstream->channels;
}
void setup_state_vgmstream(VGMSTREAM* vgmstream) {
if (!vgmstream->config.config_set)
return;
setup_state_modifiers(vgmstream);
setup_state_processing(vgmstream);
setup_vgmstream(vgmstream); /* save current config for reset */
}
/*****************************************************************************/
void free_layout(VGMSTREAM* vgmstream) {
if (vgmstream->layout_type == layout_segmented) {
free_layout_segmented(vgmstream->layout_data);
}
if (vgmstream->layout_type == layout_layered) {
free_layout_layered(vgmstream->layout_data);
}
}
void reset_layout(VGMSTREAM* vgmstream) {
if (vgmstream->layout_type == layout_segmented) {
reset_layout_segmented(vgmstream->layout_data);
}
if (vgmstream->layout_type == layout_layered) {
reset_layout_layered(vgmstream->layout_data);
}
}
static int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
/* current_sample goes between loop points (if looped) or up to max samples,
* must detect beyond that decoders would encounter garbage data */
if (vgmstream->current_sample > vgmstream->num_samples) {
int channels = vgmstream->channels;
int to_do = sample_count;
int done = 0;
memset(buf + done * channels, 0, to_do * sizeof(sample_t) * channels);
return sample_count;
}
switch (vgmstream->layout_type) {
case layout_interleave:
render_vgmstream_interleave(buf, sample_count, vgmstream);
break;
case layout_none:
render_vgmstream_flat(buf, sample_count, vgmstream);
break;
case layout_blocked_mxch:
case layout_blocked_ast:
case layout_blocked_halpst:
case layout_blocked_xa:
case layout_blocked_ea_schl:
case layout_blocked_ea_1snh:
case layout_blocked_caf:
case layout_blocked_wsi:
case layout_blocked_str_snds:
case layout_blocked_ws_aud:
case layout_blocked_matx:
case layout_blocked_dec:
case layout_blocked_vs:
case layout_blocked_mul:
case layout_blocked_gsb:
case layout_blocked_xvas:
case layout_blocked_thp:
case layout_blocked_filp:
case layout_blocked_ivaud:
case layout_blocked_ea_swvr:
case layout_blocked_adm:
case layout_blocked_bdsp:
case layout_blocked_tra:
case layout_blocked_ps2_iab:
case layout_blocked_vs_str:
case layout_blocked_rws:
case layout_blocked_hwas:
case layout_blocked_ea_sns:
case layout_blocked_awc:
case layout_blocked_vgs:
case layout_blocked_vawx:
case layout_blocked_xvag_subsong:
case layout_blocked_ea_wve_au00:
case layout_blocked_ea_wve_ad10:
case layout_blocked_sthd:
case layout_blocked_h4m:
case layout_blocked_xa_aiff:
case layout_blocked_vs_square:
case layout_blocked_vid1:
case layout_blocked_ubi_sce:
render_vgmstream_blocked(buf, sample_count, vgmstream);
break;
case layout_segmented:
render_vgmstream_segmented(buf, sample_count,vgmstream);
break;
case layout_layered:
render_vgmstream_layered(buf, sample_count, vgmstream);
break;
default:
break;
}
if (vgmstream->current_sample > vgmstream->num_samples) {
int channels = vgmstream->channels;
int to_do = (vgmstream->current_sample - vgmstream->num_samples);
int done = sample_count - to_do;
memset(buf + done * channels, 0, to_do * sizeof(sample_t) * channels);
return sample_count;
}
return sample_count;
}
static void render_trim(VGMSTREAM* vgmstream) {
/* big-ish buffer since the average trim would be a few seconds for >=2ch at 48000, and less calls = better */
sample_t tmpbuf[0x40000];
int max_samples = 0x40000 / vgmstream->pstate.input_channels;
while (vgmstream->pstate.trim_begin_left) {
int to_do = vgmstream->pstate.trim_begin_left;
if (to_do > max_samples)
to_do = max_samples;
render_layout(tmpbuf, to_do, vgmstream);
/* just consume samples so no need to apply mixing */
vgmstream->pstate.trim_begin_left -= to_do;
}
}
static int render_pad_begin(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_do) {
int channels = vgmstream->pstate.output_channels;
int to_do = vgmstream->pstate.pad_begin_left;
if (to_do > samples_to_do)
to_do = samples_to_do;
memset(buf, 0, to_do * sizeof(sample_t) * channels);
vgmstream->pstate.pad_begin_left -= to_do;
return to_do;
}
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
play_state_t* ps = &vgmstream->pstate;
//play_config_t* pc = &vgmstream->config;
//if (!ps->fade_left || pc->play_forever)
// return;
//if (ps->play_position + samples_done < ps->fade_start)
// return;
{
int s, ch, start, fade_pos;
int channels = ps->output_channels;
int32_t to_do = ps->fade_left;
if (ps->play_position < ps->fade_start) {
start = samples_done - (ps->play_position + samples_done - ps->fade_start);
fade_pos = 0;
}
else {
start = 0;
fade_pos = ps->play_position - ps->fade_start;
}
if (to_do > samples_done - start)
to_do = samples_done - start;
//TODO: use delta fadedness to improve performance?
for (s = start; s < start + to_do; s++, fade_pos++) {
double fadedness = (double)(ps->fade_duration - fade_pos) / ps->fade_duration;
for (ch = 0; ch < channels; ch++) {
buf[s*channels + ch] = (sample_t)buf[s*channels + ch] * fadedness;
}
}
ps->fade_left -= to_do;
/* next samples after fade end would be pad end/silence, so we can just memset */
memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels);
return samples_done;
}
}
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_do) {
int channels = vgmstream->pstate.output_channels;
/* since anything beyond pad end is silence no need to check ranges */
memset(buf, 0, samples_to_do * sizeof(sample_t) * channels);
return samples_to_do;
}
/* Decode data into sample buffer. Controls the "external" part of the decoding,
* while layout/decode control the "internal" part. */
int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
play_state_t* ps = &vgmstream->pstate;
int samples_to_do = sample_count;
int samples_done = 0;
int done;
sample_t* tmpbuf = buf;
/* simple mode with no settings (just skip everything below) */
if (!vgmstream->config_enabled) {
render_layout(buf, samples_to_do, vgmstream);
mix_vgmstream(buf, samples_to_do, vgmstream);
return samples_to_do;
}
/* trim may go first since it doesn't need output nor changes totals */
if (ps->trim_begin_left) {
render_trim(vgmstream);
}
/* adds empty samples to buf */
if (ps->pad_begin_left) {
done = render_pad_begin(vgmstream, tmpbuf, samples_to_do);
samples_done += done;
samples_to_do -= done;
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
}
/* end padding (done before to avoid decoding if possible) */
if (!vgmstream->config.play_forever /* && ps->pad_end_left */
&& ps->play_position + samples_done >= ps->pad_end_start) {
done = render_pad_end(vgmstream, tmpbuf, samples_to_do);
samples_done += done;
samples_to_do -= done;
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
}
/* main decode */
{ //if (samples_to_do) /* 0 ok, less likely */
done = render_layout(tmpbuf, samples_to_do, vgmstream);
mix_vgmstream(tmpbuf, done, vgmstream);
samples_done += done;
/* simple fadeout */
if (!vgmstream->config.play_forever && ps->fade_left
&& ps->play_position + done >= ps->fade_start) {
render_fade(vgmstream, tmpbuf, done);
}
tmpbuf += done * vgmstream->pstate.output_channels;
}
vgmstream->pstate.play_position += samples_done;
/* signal end */
if (!vgmstream->config.play_forever
&& ps->play_position > ps->play_duration) {
int excess = ps->play_position - ps->play_duration;
if (excess > sample_count)
excess = sample_count;
samples_done = (sample_count - excess);
ps->play_position = ps->play_duration;
}
return samples_done;
}
/*****************************************************************************/
static void seek_force_loop(VGMSTREAM* vgmstream) {
/* only called after hit loop */
if (!vgmstream->hit_loop)
return;
/* pretend decoder reached loop end so state is set to loop start */
vgmstream->loop_count = 0;
vgmstream->current_sample = vgmstream->loop_end_sample;
vgmstream_do_loop(vgmstream);
}
static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
sample_t tmpbuf[0x40000]; /* big-ish buffer as less calls = better */
int32_t buf_samples = 0x40000 / vgmstream->channels; /* base channels, no need to apply mixing */
int i;
for (i = 0; i < samples; i += buf_samples) {
int to_get = buf_samples;
if (i + buf_samples > samples)
to_get = samples - i;
render_layout(tmpbuf, to_get, vgmstream);
/* no mixing */
}
}
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
play_state_t* ps = &vgmstream->pstate;
int play_forever = vgmstream->config.play_forever;
int32_t decode_samples = 0;
int loop_count = -1;
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
if (!vgmstream->config_enabled) {
//todo same but ignore play duration or play_position
return;
}
//todo optimize layout looping with seek_vgmstream
//todo could improve performance bit if hit_loop wasn't lost when calling reset
//todo wrong seek with ignore fade
/* seeking to requested sample normally means decoding and discarding up to that point (from
* the beginning, or current position), but can be optimized a bit to decode less with some tricks:
* - seek may fall in part of the song that isn't actually decoding (due to config, like padding)
* - if file loops there is no need to decode N full loops, seek can be set relative to loop region
* - can decode to seek sample from current position or loop start depending on lowest
*
* some of the cases below could be simplified but the logic to get this going is kinda mind-bending
*
* (ex. with file = 100, pad=5s, trim=3s, loop=20s..90s)
* | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond)
* 0 5s (-3s) 25s 95s 165s 235s 245s Ns
*/
if (seek_sample < 0)
seek_sample = 0;
if (seek_sample > ps->play_duration && !play_forever) /* play forever can seek to any loop */
seek_sample = ps->play_duration;
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
/* start/pad-begin: consume pad samples */
if (seek_sample < ps->pad_begin_duration) {
/* seek=3: pad=5-3=2 */
decode_samples = 0;
reset_vgmstream(vgmstream);
ps->pad_begin_left = ps->pad_begin_duration - seek_sample;
//;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples);
}
/* body: find position relative to decoder's current sample */
else if (play_forever || seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_relative, vgmstream->current_sample);
/* seek can be in some part of the body, depending on looped/decoder's current/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* seekr=50s, curr=95 > restart + decode=50s */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* seekr=95s, curr=50 > decode=95-50=45s */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* seekr=120s (outside decode, can happen when body is set manually) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* seekr=6s > 6-5+3 > seek=4s inside decoder < 20s: decode 4s from start, or 1s if current was at 3s */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* seek can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
int32_t loop_body, loop_seek, loop_curr;
/* current must have reached loop start at some point */
if (!vgmstream->hit_loop) {
int32_t skip_samples;
if (vgmstream->current_sample >= vgmstream->loop_start_sample) {
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: must loop / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (shouldn't happen?) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: current outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop(vgmstream);
}
loop_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
loop_seek = seek_relative - vgmstream->loop_start_sample;
loop_count = loop_seek / loop_body;
loop_seek = loop_seek % loop_body;
loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
//;VGM_LOG("SEEK: in loop / seekl=%i, loops=%i, cur=%i, dec=%i\n", loop_seek, loop_count, loop_curr, decode_samples);
if (loop_seek < loop_curr) {
decode_samples += loop_seek;
seek_force_loop(vgmstream);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples += (loop_seek - loop_curr);
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (!play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
/* done at the end in case of reset */
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
/* pad end and beyond: ignored */
else {
decode_samples = 0;
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
if (!is_looped)
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: end silence / dec=%i\n", decode_samples);
/* looping decoder state isn't changed (seek backwards could use current sample) */
}
seek_force_decode(vgmstream, decode_samples);
/* adjust positions */
if (vgmstream->loop_count >= 0)
vgmstream->loop_count = loop_count;
vgmstream->pstate.play_position = seek_sample;
}

10
src/render.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _RENDER_H
#define _RENDER_H
#include "vgmstream.h"
void free_layout(VGMSTREAM* vgmstream);
void reset_layout(VGMSTREAM* vgmstream);
#endif

View File

@ -67,6 +67,11 @@ void put_32bitBE(uint8_t* buf, int32_t i);
#define put_u32le put_32bitLE
#define put_u16be put_16bitBE
#define put_u32be put_32bitBE
#define put_s8 put_8bit
#define put_s16le put_16bitLE
#define put_s32le put_32bitLE
#define put_s16be put_16bitBE
#define put_s32be put_32bitBE
/* signed nibbles come up a lot */

File diff suppressed because it is too large Load Diff

View File

@ -780,20 +780,68 @@ typedef enum {
} mapping_t;
typedef struct {
int config_set; /* some of the mods below are set */
/* modifiers */
int play_forever;
int loop_count_set;
double loop_count;
int fade_time_set;
double fade_time;
int fade_delay_set;
double fade_delay;
int ignore_fade;
int ignore_loop;
int force_loop;
int really_force_loop;
int ignore_loop;
int ignore_fade;
/* processing */
double loop_count;
int32_t pad_begin;
int32_t trim_begin;
int32_t body_time;
int32_t trim_end;
double fade_delay; /* not in samples for backwards compatibility */
double fade_time;
int32_t pad_end;
double pad_begin_s;
double trim_begin_s;
double body_time_s;
double trim_end_s;
//double fade_delay_s;
//double fade_time_s;
double pad_end_s;
/* internal flags */
int pad_begin_set;
int trim_begin_set;
int body_time_set;
int loop_count_set;
int trim_end_set;
int fade_delay_set;
int fade_time_set;
int pad_end_set;
} play_config_t;
typedef struct {
int input_channels;
int output_channels;
int32_t pad_begin_duration;
int32_t pad_begin_left;
int32_t trim_begin_duration;
int32_t trim_begin_left;
int32_t body_duration;
int32_t fade_duration;
int32_t fade_left;
int32_t fade_start;
int32_t pad_end_duration;
//int32_t pad_end_left;
int32_t pad_end_start;
int32_t play_duration; /* total samples that the stream lasts (after applying all config) */
int32_t play_position; /* absolute sample where stream is */
} play_state_t;
/* info for a single vgmstream channel */
typedef struct {
STREAMFILE* streamfile; /* file used by this channel */
@ -879,16 +927,6 @@ typedef struct {
int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
/* config requests, players must read and honor these values
* (ideally internally would work as a player, but for now player must do it manually) */
play_config_t config;
/* play state */
int loop_count; /* counter of complete loops (1=looped once) */
int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */
/* layout/block state */
size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */
int32_t current_sample; /* sample point within the file (for loop detection) */
@ -930,6 +968,14 @@ typedef struct {
/* Same, for special layouts. layout_data + codec_data may exist at the same time. */
void* layout_data;
/* play config/state */
int config_enabled; /* config can be used */
play_config_t config; /* player config (applied over decoding) */
play_state_t pstate; /* player state (applied over decoding) */
int loop_count; /* counter of complete loops (1=looped once) */
int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */
} VGMSTREAM;
@ -950,6 +996,7 @@ typedef struct {
sample_t* buffer;
int input_channels; /* internal buffer channels */
int output_channels; /* resulting channels (after mixing, if applied) */
int external_looping; /* don't loop using per-layer loops, but layout's own looping */
} layered_layout_data;
@ -1061,8 +1108,11 @@ void close_vgmstream(VGMSTREAM* vgmstream);
/* calculate the number of samples to be played based on looping parameters */
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream);
/* Decode data into sample buffer */
void render_vgmstream(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
/* Decode data into sample buffer. Returns < sample_count on stream end */
int render_vgmstream(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
/* Seek to sample position (next render starts from that point). Use only after config is set (vgmstream_apply_config) */
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample);
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
* Will always be null-terminated if length > 0 */
@ -1093,29 +1143,11 @@ int vgmstream_is_virtual_filename(const char* filename);
/* -------------------------------------------------------------------------*/
/* Allocate initial memory for the VGMSTREAM */
VGMSTREAM * allocate_vgmstream(int channel_count, int looped);
VGMSTREAM* allocate_vgmstream(int channel_count, int looped);
/* Prepare the VGMSTREAM's initial state once parsed and ready, but before playing. */
void setup_vgmstream(VGMSTREAM* vgmstream);
/* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */
int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream);
/* Get the number of bytes of a single frame (smallest self-contained byte group, 1/N channels) */
int get_vgmstream_frame_size(VGMSTREAM* vgmstream);
/* In NDS IMA the frame size is the block size, so the last one is short */
int get_vgmstream_samples_per_shortframe(VGMSTREAM* vgmstream);
int get_vgmstream_shortframe_size(VGMSTREAM* vgmstream);
/* Decode samples into the buffer. Assume that we have written samples_written into the
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer);
/* Calculate number of consecutive samples to do (taking into account stopping for loop start and end) */
int get_vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMSTREAM* vgmstream);
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns 1 if loop was done. */
int vgmstream_do_loop(VGMSTREAM* vgmstream);
/* Open the stream for reading at offset (taking into account layouts, channels and so on).
* Returns 0 on failure */
int vgmstream_open_stream(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t start_offset);
@ -1126,4 +1158,5 @@ void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t ou
void get_vgmstream_layout_description(VGMSTREAM* vgmstream, char* out, size_t out_size);
void get_vgmstream_meta_description(VGMSTREAM* vgmstream, char* out, size_t out_size);
void setup_state_vgmstream(VGMSTREAM* vgmstream);
#endif

View File

@ -81,27 +81,14 @@ typedef struct {
int is_xmplay;
} winamp_settings_t;
/* current song config */
typedef struct {
int song_play_forever;
double song_loop_count;
double song_fade_time;
double song_fade_delay;
int song_ignore_fade;
int song_ignore_loop;
int song_force_loop;
int song_really_force_loop;
} winamp_song_config;
/* current play state */
typedef struct {
int paused;
int decode_abort;
int seek_needed_samples;
int seek_sample;
int decode_pos_ms;
int decode_pos_samples;
int stream_length_samples;
int fade_samples;
int length_samples;
int output_channels;
double volume;
} winamp_state_t;
@ -114,12 +101,11 @@ const char* tagfile_name = "!tags.m3u";
/* plugin state */
HANDLE decode_thread_handle = INVALID_HANDLE_VALUE;
VGMSTREAM * vgmstream = NULL;
VGMSTREAM* vgmstream = NULL;
in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
winamp_settings_t defaults;
winamp_settings_t settings;
winamp_song_config config;
winamp_state_t state;
short sample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS]; //todo maybe should be dynamic
@ -228,23 +214,23 @@ typedef struct {
static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * path);
static STREAMFILE *open_winamp_streamfile_by_ipath(const in_char *wpath);
static size_t wasf_read(WINAMP_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) {
return streamfile->stdiosf->read(streamfile->stdiosf,dest,offset,length);
static size_t wasf_read(WINAMP_STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length) {
return sf->stdiosf->read(sf->stdiosf, dest, offset, length);
}
static off_t wasf_get_size(WINAMP_STREAMFILE *streamfile) {
return streamfile->stdiosf->get_size(streamfile->stdiosf);
static off_t wasf_get_size(WINAMP_STREAMFILE* sf) {
return sf->stdiosf->get_size(sf->stdiosf);
}
static off_t wasf_get_offset(WINAMP_STREAMFILE *streamfile) {
return streamfile->stdiosf->get_offset(streamfile->stdiosf);
static off_t wasf_get_offset(WINAMP_STREAMFILE* sf) {
return sf->stdiosf->get_offset(sf->stdiosf);
}
static void wasf_get_name(WINAMP_STREAMFILE *streamfile, char *buffer, size_t length) {
streamfile->stdiosf->get_name(streamfile->stdiosf, buffer, length);
static void wasf_get_name(WINAMP_STREAMFILE* sf, char* buffer, size_t length) {
sf->stdiosf->get_name(sf->stdiosf, buffer, length);
}
static STREAMFILE *wasf_open(WINAMP_STREAMFILE *streamFile, const char *const filename, size_t buffersize) {
static STREAMFILE *wasf_open(WINAMP_STREAMFILE* sf, const char* const filename, size_t buffersize) {
in_char wpath[PATH_LIMIT];
char name[PATH_LIMIT];
@ -256,13 +242,13 @@ static STREAMFILE *wasf_open(WINAMP_STREAMFILE *streamFile, const char *const fi
* IO buffers when using dup(), noticeable by re-opening the same streamfile with small buffer sizes
* (reads garbage). This reportedly causes issues in Android too */
streamFile->stdiosf->get_name(streamFile->stdiosf, name, PATH_LIMIT);
sf->stdiosf->get_name(sf->stdiosf, name, PATH_LIMIT);
/* if same name, duplicate the file descriptor we already have open */ //unsure if all this is needed
if (streamFile->infile_ref && !strcmp(name,filename)) {
if (sf->infile_ref && !strcmp(name,filename)) {
int new_fd;
FILE *new_file;
if (((new_fd = dup(fileno(streamFile->infile_ref))) >= 0) && (new_file = wa_fdopen(new_fd))) {
if (((new_fd = dup(fileno(sf->infile_ref))) >= 0) && (new_file = wa_fdopen(new_fd))) {
STREAMFILE *new_sf = open_winamp_streamfile_by_file(new_file, filename);
if (new_sf)
return new_sf;
@ -276,24 +262,24 @@ static STREAMFILE *wasf_open(WINAMP_STREAMFILE *streamFile, const char *const fi
#endif
/* STREAMFILEs carry char/UTF8 names, convert to wchar for Winamp */
wa_char_to_ichar(wpath,PATH_LIMIT, filename);
wa_char_to_ichar(wpath, PATH_LIMIT, filename);
return open_winamp_streamfile_by_ipath(wpath);
}
static void wasf_close(WINAMP_STREAMFILE *streamfile) {
static void wasf_close(WINAMP_STREAMFILE* sf) {
/* closes infile_ref + frees in the internal STDIOSTREAMFILE (fclose for wchar is not needed) */
streamfile->stdiosf->close(streamfile->stdiosf);
free(streamfile); /* and the current struct */
sf->stdiosf->close(sf->stdiosf);
free(sf); /* and the current struct */
}
static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * path) {
WINAMP_STREAMFILE *this_sf = NULL;
STREAMFILE *stdiosf = NULL;
static STREAMFILE *open_winamp_streamfile_by_file(FILE* file, const char* path) {
WINAMP_STREAMFILE* this_sf = NULL;
STREAMFILE* stdiosf = NULL;
this_sf = calloc(1,sizeof(WINAMP_STREAMFILE));
if (!this_sf) goto fail;
stdiosf = open_stdio_streamfile_by_file(infile, path);
stdiosf = open_stdio_streamfile_by_file(file, path);
if (!stdiosf) goto fail;
this_sf->sf.read = (void*)wasf_read;
@ -304,7 +290,7 @@ static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * pat
this_sf->sf.close = (void*)wasf_close;
this_sf->stdiosf = stdiosf;
this_sf->infile_ref = infile;
this_sf->infile_ref = file;
return &this_sf->sf; /* pointer to STREAMFILE start = rest of the custom data follows */
@ -315,9 +301,9 @@ fail:
}
static STREAMFILE *open_winamp_streamfile_by_ipath(const in_char *wpath) {
FILE *infile = NULL;
STREAMFILE *streamFile;
static STREAMFILE* open_winamp_streamfile_by_ipath(const in_char* wpath) {
FILE* infile = NULL;
STREAMFILE* sf;
char path[PATH_LIMIT];
@ -332,26 +318,26 @@ static STREAMFILE *open_winamp_streamfile_by_ipath(const in_char *wpath) {
return NULL;
}
streamFile = open_winamp_streamfile_by_file(infile,path);
if (!streamFile) {
sf = open_winamp_streamfile_by_file(infile,path);
if (!sf) {
if (infile) fclose(infile);
}
return streamFile;
return sf;
}
/* opens vgmstream for winamp */
static VGMSTREAM* init_vgmstream_winamp(const in_char *fn, int stream_index) {
VGMSTREAM * vgmstream = NULL;
static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) {
VGMSTREAM* vgmstream = NULL;
//return init_vgmstream(fn);
/* manually init streamfile to pass the stream index */
STREAMFILE *streamFile = open_winamp_streamfile_by_ipath(fn); //open_stdio_streamfile(fn);
if (streamFile) {
streamFile->stream_index = stream_index;
vgmstream = init_vgmstream_from_STREAMFILE(streamFile);
close_streamfile(streamFile);
STREAMFILE* sf = open_winamp_streamfile_by_ipath(fn); //open_stdio_streamfile(fn);
if (sf) {
sf->stream_index = stream_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
}
return vgmstream;
@ -506,7 +492,7 @@ static void ini_set_b(const char *inifile, const char *entry, int val) {
ini_set_i(inifile, entry, val);
}
static void load_defaults(winamp_settings_t *defaults) {
static void load_defaults(winamp_settings_t* defaults) {
defaults->thread_priority = 3;
defaults->fade_time = 10.0;
defaults->fade_delay = 0.0;
@ -523,7 +509,7 @@ static void load_defaults(winamp_settings_t *defaults) {
defaults->clip_type = 2;
}
static void load_config(winamp_settings_t *settings, winamp_settings_t *defaults) {
static void load_config(winamp_settings_t* settings, winamp_settings_t* defaults) {
TCHAR inifile[PATH_LIMIT];
ini_get_filename(inifile);
@ -551,7 +537,7 @@ static void load_config(winamp_settings_t *settings, winamp_settings_t *defaults
settings->ignore_loop = 0;
}
static void save_config(winamp_settings_t *settings) {
static void save_config(winamp_settings_t* settings) {
TCHAR inifile[PATH_LIMIT];
ini_get_filename(inifile);
@ -638,7 +624,7 @@ static void dlg_combo_get(HWND hDlg, int idc, int *p_val) {
*p_val = SendMessage(GetDlgItem(hDlg, idc), CB_GETCURSEL, 0, 0);
}
static int dlg_load_form(HWND hDlg, winamp_settings_t *settings) {
static int dlg_load_form(HWND hDlg, winamp_settings_t* settings) {
int err = 0;
dlg_input_get_d(hDlg, IDC_FADE_TIME, &settings->fade_time, TEXT("Fade Length must be a positive number"), &err);
dlg_input_get_d(hDlg, IDC_FADE_DELAY, &settings->fade_delay, TEXT("Fade Delay must be a positive number"), &err);
@ -660,7 +646,7 @@ static int dlg_load_form(HWND hDlg, winamp_settings_t *settings) {
return err ? 0 : 1;
}
static void dlg_save_form(HWND hDlg, winamp_settings_t *settings, int reset) {
static void dlg_save_form(HWND hDlg, winamp_settings_t* settings, int reset) {
cfg_slider_set(hDlg, IDC_THREAD_PRIORITY_SLIDER, IDC_THREAD_PRIORITY_TEXT, settings->thread_priority, 1, 7, dlg_priority_strings);
dlg_input_set_d(hDlg, IDC_FADE_TIME, settings->fade_time);
@ -904,8 +890,8 @@ static void build_extension_list(char *winamp_list, int winamp_list_size) {
}
/* unicode utils */
static void get_title(in_char * dst, int dst_size, const in_char * fn, VGMSTREAM * infostream) {
in_char *basename;
static void get_title(in_char* dst, int dst_size, const in_char* fn, VGMSTREAM* infostream) {
in_char* basename;
in_char buffer[PATH_LIMIT];
in_char filename[PATH_LIMIT];
//int stream_index = 0;
@ -945,80 +931,17 @@ static void get_title(in_char * dst, int dst_size, const in_char * fn, VGMSTREAM
}
}
static void set_config_defaults(winamp_song_config *current) {
current->song_play_forever = settings.loop_forever;
current->song_loop_count = settings.loop_count;
current->song_fade_time = settings.fade_time;
current->song_fade_delay = settings.fade_delay;
current->song_ignore_fade = 0;
current->song_force_loop = 0;
current->song_really_force_loop = 0;
current->song_ignore_loop = settings.ignore_loop;
}
static void apply_config(VGMSTREAM* vgmstream, winamp_settings_t* settings) {
vgmstream_cfg_t vcfg = {0};
static void apply_config(VGMSTREAM* vgmstream, winamp_song_config* cfg) {
vcfg.allow_play_forever = 1;
vcfg.play_forever = settings->loop_forever;
vcfg.loop_times = settings->loop_count;
vcfg.fade_period = settings->fade_time;
vcfg.fade_delay = settings->fade_delay;
vcfg.ignore_loop = settings->ignore_loop;
/* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
if (vgmstream->config.play_forever) {
cfg->song_play_forever = 1;
cfg->song_ignore_loop = 0;
}
if (vgmstream->config.loop_count_set) {
cfg->song_loop_count = vgmstream->config.loop_count;
cfg->song_play_forever = 0;
cfg->song_ignore_loop = 0;
}
if (vgmstream->config.fade_delay_set) {
cfg->song_fade_delay = vgmstream->config.fade_delay;
}
if (vgmstream->config.fade_time_set) {
cfg->song_fade_time = vgmstream->config.fade_time;
}
if (vgmstream->config.ignore_fade) {
cfg->song_ignore_fade = 1;
}
if (vgmstream->config.force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 1;
cfg->song_really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->song_ignore_loop = 1;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 0;
}
/* apply config */
if (cfg->song_force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0);
}
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->song_play_forever = 0;
}
if (cfg->song_play_forever) {
cfg->song_ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */
if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count);
cfg->song_fade_time = 0;
}
vgmstream_apply_config(vgmstream, &vcfg);
}
static int winampGetExtendedFileInfo_common(in_char* filename, char *metadata, char* ret, int retlen);
@ -1161,8 +1084,7 @@ int winamp_Play(const in_char *fn) {
}
/* config */
set_config_defaults(&config);
apply_config(vgmstream, &config);
apply_config(vgmstream, &settings);
/* enable after all config but before outbuf (though ATM outbuf is not dynamic so no need to read input_channels) */
vgmstream_mixing_autodownmix(vgmstream, settings.downmix_channels);
@ -1171,11 +1093,10 @@ int winamp_Play(const in_char *fn) {
/* reset internals */
state.paused = 0;
state.decode_abort = 0;
state.seek_needed_samples = -1;
state.seek_sample = -1;
state.decode_pos_ms = 0;
state.decode_pos_samples = 0;
state.stream_length_samples = get_vgmstream_play_samples(config.song_loop_count,config.song_fade_time,config.song_fade_delay,vgmstream);
state.fade_samples = (int)(config.song_fade_time * vgmstream->sample_rate);
state.length_samples = vgmstream_get_samples(vgmstream);
state.volume = get_album_gain_volume(fn);
@ -1191,7 +1112,7 @@ int winamp_Play(const in_char *fn) {
}
/* set info display */
input_module.SetInfo(get_vgmstream_average_bitrate(vgmstream)/1000, vgmstream->sample_rate/1000, state.output_channels, 1);
input_module.SetInfo(get_vgmstream_average_bitrate(vgmstream) / 1000, vgmstream->sample_rate / 1000, state.output_channels, 1);
/* setup visualization */
input_module.SAVSAInit(max_latency,vgmstream->sample_rate);
@ -1230,6 +1151,7 @@ int winamp_IsPaused() {
/* stop (unload) stream */
void winamp_Stop() {
if (decode_thread_handle != INVALID_HANDLE_VALUE) {
state.decode_abort = 1;
@ -1252,20 +1174,25 @@ void winamp_Stop() {
/* get length in ms */
int winamp_GetLength() {
return state.stream_length_samples * 1000LL / vgmstream->sample_rate;
return state.length_samples * 1000LL / vgmstream->sample_rate;
}
/* current output time in ms */
int winamp_GetOutputTime() {
return state.decode_pos_ms + (input_module.outMod->GetOutputTime() - input_module.outMod->GetWrittenTime());
int32_t pos_ms = state.decode_pos_ms;
/* pretend we have reached destination if called while seeking is on */
if (state.seek_sample >= 0)
pos_ms = state.seek_sample * 1000LL / vgmstream->sample_rate;
return pos_ms + (input_module.outMod->GetOutputTime() - input_module.outMod->GetWrittenTime());
}
/* seeks to point in stream (in ms) */
void winamp_SetOutputTime(int time_in_ms) {
if (!vgmstream)
return;
state.seek_needed_samples = (long long)time_in_ms * vgmstream->sample_rate / 1000LL;
state.seek_sample = (long long)time_in_ms * vgmstream->sample_rate / 1000LL;
}
/* pass these commands through */
@ -1293,8 +1220,7 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
}
else {
/* some other file in playlist given by filename */
VGMSTREAM * infostream = NULL;
winamp_song_config infoconfig = {0};
VGMSTREAM* infostream = NULL;
in_char filename[PATH_LIMIT];
int stream_index = 0;
@ -1305,8 +1231,7 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
infostream = init_vgmstream_winamp(filename, stream_index);
if (!infostream) return 0;
set_config_defaults(&infoconfig);
apply_config(infostream, &infoconfig);
apply_config(infostream, &settings);
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL, NULL);
@ -1351,8 +1276,7 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
}
else {
/* some other file in playlist given by filename */
VGMSTREAM * infostream = NULL;
winamp_song_config infoconfig = {0};
VGMSTREAM* infostream = NULL;
in_char filename[PATH_LIMIT];
int stream_index = 0;
@ -1363,8 +1287,7 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
infostream = init_vgmstream_winamp(filename, stream_index);
if (!infostream) return;
set_config_defaults(&infoconfig);
apply_config(infostream, &infoconfig);
apply_config(infostream, &settings);
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL, NULL);
@ -1376,8 +1299,7 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
if (length_in_ms) {
*length_in_ms = -1000;
if (infostream) {
const int num_samples = get_vgmstream_play_samples(
infoconfig.song_loop_count,infoconfig.song_fade_time,infoconfig.song_fade_delay,infostream);
const int num_samples = vgmstream_get_samples(infostream);
*length_in_ms = num_samples * 1000LL /infostream->sample_rate;
}
}
@ -1391,57 +1313,80 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
void winamp_EQSet(int on, char data[10], int preamp) {
}
/*****************************************************************************/
/* MAIN DECODE (some used in extended part too, so avoid globals) */
static void setup_seek(winamp_state_t* state, int32_t max_samples, int play_forever) {
/* adjust seeking past file, can happen using the right (->) key
* (should be done here and not in SetOutputTime due to threads/race conditions) */
if (state->seek_sample > max_samples && !play_forever) {
state->seek_sample = max_samples;
}
#if 0
/* reset if we need to seek backwards (causes funny cursor jumps though) */
if (state->seek_sample < state->decode_pos_samples) {
state->decode_pos_samples = 0;
state->decode_pos_ms = 0;
}
/* seek done */
else if (state->decode_pos_samples >= state->seek_sample) {
state->seek_sample = -1;
}
#endif
}
static void do_seek(winamp_state_t* state, VGMSTREAM* vgmstream) {
/* could divide in N seeks (from pos) for slower files so cursor moves, but doesn't seem too necessary */
seek_vgmstream(vgmstream, state->seek_sample);
/* discard decoded samples and keep seeking */
state->decode_pos_samples = state->seek_sample;
state->decode_pos_ms = state->decode_pos_samples * 1000LL / vgmstream->sample_rate;
state->seek_sample = -1;
}
static void apply_gain(winamp_state_t* state, int samples_to_do) {
/* apply ReplayGain, if needed */
if (state->volume != 1.0) {
int j, k;
int channels = state->output_channels;
for (j = 0; j < samples_to_do; j++) {
for (k = 0; k < channels; k++) {
sample_buffer[j*channels + k] = (short)(sample_buffer[j*channels + k] * state->volume);
}
}
}
}
/* the decode thread */
DWORD WINAPI __stdcall decode(void *arg) {
const int max_buffer_samples = SAMPLE_BUFFER_SIZE;
const int max_samples = state.stream_length_samples;
const int max_samples = state.length_samples;
int play_forever = vgmstream_get_play_forever(vgmstream);
while (!state.decode_abort) {
int samples_to_do;
int output_bytes;
if (state.decode_pos_samples + max_buffer_samples > state.stream_length_samples
&& (!config.song_play_forever || !vgmstream->loop_flag))
samples_to_do = state.stream_length_samples - state.decode_pos_samples;
if (state.decode_pos_samples + max_buffer_samples > state.length_samples && !play_forever)
samples_to_do = state.length_samples - state.decode_pos_samples;
else
samples_to_do = max_buffer_samples;
/* seek setup (max samples to skip if still seeking, mark done) */
if (state.seek_needed_samples >= 0) {
/* reset if we need to seek backwards */
if (state.seek_needed_samples < state.decode_pos_samples) {
reset_vgmstream(vgmstream);
apply_config(vgmstream, &config); /* config is undone by reset */
state.decode_pos_samples = 0;
state.decode_pos_ms = 0;
}
/* adjust seeking past file, can happen using the right (->) key
* (should be done here and not in SetOutputTime due to threads/race conditions) */
if (state.seek_needed_samples > max_samples && !config.song_play_forever) {
state.seek_needed_samples = max_samples;
}
/* adjust max samples to seek */
if (state.decode_pos_samples < state.seek_needed_samples) {
samples_to_do = state.seek_needed_samples - state.decode_pos_samples;
if (samples_to_do > max_buffer_samples)
samples_to_do = max_buffer_samples;
}
else {
state.seek_needed_samples = -1;
}
/* flush Winamp buffers */
input_module.outMod->Flush((int)state.decode_pos_ms);
/* seek setup */
if (state.seek_sample >= 0) {
setup_seek(&state, max_samples, play_forever);
}
output_bytes = (samples_to_do * state.output_channels * sizeof(short));
if (input_module.dsp_isactive())
output_bytes = output_bytes * 2; /* Winamp's DSP may need double samples */
if (samples_to_do == 0) { /* track finished */
if (samples_to_do == 0 && state.seek_sample < 0) { /* track finished and not seeking */
input_module.outMod->CanWrite(); /* ? */
if (!input_module.outMod->IsPlaying()) {
PostMessage(input_module.hMainWindow, WM_WA_MPEG_EOF, 0,0); /* end */
@ -1449,51 +1394,23 @@ DWORD WINAPI __stdcall decode(void *arg) {
}
Sleep(10);
}
else if (state.seek_needed_samples != -1) { /* seek */
render_vgmstream(sample_buffer,samples_to_do,vgmstream);
else if (state.seek_sample >= 0) { /* seek */
do_seek(&state, vgmstream);
/* discard decoded samples and keep seeking */
state.decode_pos_samples += samples_to_do;
state.decode_pos_ms = state.decode_pos_samples * 1000LL / vgmstream->sample_rate;
/* flush Winamp buffers *after* fully seeking (allows to play buffered samples while we seek, feels a bit snappier) */
input_module.outMod->Flush(state.decode_pos_ms);
}
else if (input_module.outMod->CanWrite() >= output_bytes) { /* decode */
render_vgmstream(sample_buffer,samples_to_do,vgmstream);
render_vgmstream(sample_buffer, samples_to_do, vgmstream);
/* apply ReplayGain, if needed */
if (state.volume != 1.0) {
int j, k;
for (j = 0; j < samples_to_do; j++) {
for (k = 0; k < vgmstream->channels; k++) {
sample_buffer[j*vgmstream->channels + k] =
(short)(sample_buffer[j*vgmstream->channels + k] * state.volume);
}
}
}
/* fade near the end */
if (vgmstream->loop_flag && state.fade_samples > 0 && !config.song_play_forever) {
int fade_channels = state.output_channels;
int samples_into_fade = state.decode_pos_samples - (state.stream_length_samples - state.fade_samples);
if (samples_into_fade + samples_to_do > 0) {
int j, k;
for (j = 0; j < samples_to_do; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
const double fadedness = (double)(state.fade_samples - samples_into_fade) / state.fade_samples;
for (k = 0; k < fade_channels; k++) {
sample_buffer[j*fade_channels+k] =
(short)(sample_buffer[j*fade_channels+k]*fadedness);
}
}
}
}
}
apply_gain(&state, samples_to_do); /* apply ReplayGain, if needed */
/* output samples */
input_module.SAAddPCMData((char*)sample_buffer,state.output_channels,16,state.decode_pos_ms);
input_module.VSAAddPCMData((char*)sample_buffer,state.output_channels,16,state.decode_pos_ms);
input_module.SAAddPCMData((char*)sample_buffer, state.output_channels, 16, state.decode_pos_ms);
input_module.VSAAddPCMData((char*)sample_buffer, state.output_channels, 16, state.decode_pos_ms);
if (input_module.dsp_isactive()) { /* find out DSP's needs */
int dsp_output_samples = input_module.dsp_dosamples(sample_buffer,samples_to_do,16,state.output_channels,vgmstream->sample_rate);
int dsp_output_samples = input_module.dsp_dosamples(sample_buffer, samples_to_do, 16, state.output_channels, vgmstream->sample_rate);
output_bytes = dsp_output_samples * state.output_channels * sizeof(short);
}
@ -1622,7 +1539,7 @@ static void load_tagfile_info(in_char* filename) {
/* load all tags from tagfile */
tagFile = open_winamp_streamfile_by_ipath(tagfile_path_i);
if (tagFile != NULL) {
VGMSTREAM_TAGS *tags;
VGMSTREAM_TAGS* tags;
const char *tag_key, *tag_val;
int i;
@ -1776,7 +1693,6 @@ __declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg
* library or CD burner, if implemented. In usual Winamp fashion they are messy, barely
* documented, slightly different repeats of the above. */
winamp_song_config xconfig;
winamp_state_t xstate;
short xsample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS];
@ -1798,8 +1714,7 @@ static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps,
}
/* config */
set_config_defaults(&xconfig);
apply_config(xvgmstream, &xconfig);
apply_config(xvgmstream, &settings);
/* enable after all config but before outbuf (though ATM outbuf is not dynamic so no need to read input_channels) */
vgmstream_mixing_autodownmix(xvgmstream, settings.downmix_channels);
@ -1808,15 +1723,14 @@ static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps,
/* reset internals */
xstate.paused = 0; /* unused */
xstate.decode_abort = 0; /* unused */
xstate.seek_needed_samples = -1;
xstate.seek_sample = -1;
xstate.decode_pos_ms = 0; /* unused */
xstate.decode_pos_samples = 0;
xstate.stream_length_samples = get_vgmstream_play_samples(xconfig.song_loop_count, xconfig.song_fade_time, xconfig.song_fade_delay, xvgmstream);
xstate.fade_samples = (int)(xconfig.song_fade_time * xvgmstream->sample_rate);
xstate.length_samples = vgmstream_get_samples(xvgmstream);
xstate.volume = 1.0; /* unused */
if (size) /* bytes to decode (-1 if unknown) */
*size = xstate.stream_length_samples * xstate.output_channels * sizeof(short);
*size = xstate.length_samples * xstate.output_channels * sizeof(short);
if (bps)
*bps = 16;
if (nch)
@ -1846,81 +1760,38 @@ __declspec(dllexport) void *winampGetExtendedRead_openW(const wchar_t *fn, int *
/* decode len to dest buffer, called multiple times until file done or decoding is aborted */
__declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *dest, size_t len, int *killswitch) {
const int max_buffer_samples = SAMPLE_BUFFER_SIZE;
const int max_samples = xstate.stream_length_samples;
const int max_samples = xstate.length_samples;
unsigned copied = 0;
int done = 0;
VGMSTREAM *xvgmstream = handle;
if (!xvgmstream) {
VGMSTREAM* xvgmstream = handle;
int play_forever;
if (!xvgmstream)
return 0;
}
play_forever = vgmstream_get_play_forever(xvgmstream);
while (copied + (max_buffer_samples * xvgmstream->channels * sizeof(short)) < len && !done) {
int samples_to_do;
if (xstate.decode_pos_samples + max_buffer_samples > xstate.stream_length_samples
&& (!config.song_play_forever || !xvgmstream->loop_flag))
samples_to_do = xstate.stream_length_samples - xstate.decode_pos_samples;
if (xstate.decode_pos_samples + max_buffer_samples > xstate.length_samples && !play_forever)
samples_to_do = xstate.length_samples - xstate.decode_pos_samples;
else
samples_to_do = max_buffer_samples;
/* seek setup (max samples to skip if still seeking, mark done) */
if (xstate.seek_needed_samples != -1) {
/* reset if we need to seek backwards */
if (xstate.seek_needed_samples < xstate.decode_pos_samples) {
reset_vgmstream(xvgmstream);
apply_config(xvgmstream, &xconfig); /* config is undone by reset */
xstate.decode_pos_samples = 0;
}
/* adjust seeking past file, can happen using the right (->) key
* (should be done here and not in SetOutputTime due to threads/race conditions) */
if (xstate.seek_needed_samples > max_samples && !config.song_play_forever) {
xstate.seek_needed_samples = max_samples;
}
/* adjust max samples to seek */
if (xstate.decode_pos_samples < xstate.seek_needed_samples) {
samples_to_do = xstate.seek_needed_samples - xstate.decode_pos_samples;
if (samples_to_do > max_buffer_samples)
samples_to_do = max_buffer_samples;
}
else {
xstate.seek_needed_samples = -1;
}
if (xstate.seek_sample != -1) {
setup_seek(&xstate, max_samples, play_forever);
}
if (!samples_to_do) { /* track finished */
break;
}
else if (xstate.seek_needed_samples != -1) { /* seek */
render_vgmstream(xsample_buffer, samples_to_do, xvgmstream);
/* discard decoded samples and keep seeking */
xstate.decode_pos_samples += samples_to_do;
else if (xstate.seek_sample != -1) { /* seek */
do_seek(&xstate, xvgmstream);
}
else { /* decode */
render_vgmstream(xsample_buffer, samples_to_do, xvgmstream);
/* fade near the end */
if (xvgmstream->loop_flag && xstate.fade_samples > 0 && !config.song_play_forever) {
int fade_channels = xstate.output_channels;
int samples_into_fade = xstate.decode_pos_samples - (xstate.stream_length_samples - xstate.fade_samples);
if (samples_into_fade + xstate.decode_pos_samples > 0) {
int j, k;
for (j = 0; j < samples_to_do; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
const double fadedness = (double)(xstate.fade_samples - samples_into_fade) / xstate.fade_samples;
for (k = 0; k < fade_channels; k++) {
xsample_buffer[j*fade_channels+k] =
(short)(xsample_buffer[j*fade_channels+k]*fadedness);
}
}
}
}
}
/* output samples */
memcpy(&dest[copied], xsample_buffer, samples_to_do * xstate.output_channels * sizeof(short));
copied += samples_to_do * xstate.output_channels * sizeof(short);
@ -1941,7 +1812,7 @@ __declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *d
__declspec(dllexport) int winampGetExtendedRead_setTime(void *handle, int time_in_ms) {
VGMSTREAM *xvgmstream = handle;
if (xvgmstream) {
xstate.seek_needed_samples = (long long)time_in_ms * xvgmstream->sample_rate / 1000LL;
xstate.seek_sample = (long long)time_in_ms * xvgmstream->sample_rate / 1000LL;
return 1;
}
return 0;

View File

@ -11,8 +11,9 @@
#include <string.h>
#include <ctype.h>
#include "../src/vgmstream.h"
#include "xmpin.h"
#include "../src/vgmstream.h"
#include "../src/plugins.h"
#ifndef VERSION
@ -24,6 +25,8 @@
/* ************************************* */
#define SAMPLE_BUFFER_SIZE 1024
/* XMPlay extension list, only needed to associate extensions in Windows */
/* todo: as of v3.8.2.17, any more than ~1000 will crash XMplay's file list screen (but not using the non-native Winamp plugin...) */
#define EXTENSION_LIST_SIZE 1000 /* (0x2000 * 2) */
@ -39,18 +42,20 @@ char filepath[MAX_PATH];
/* plugin config */
double fade_seconds = 10.0;
double fade_delay_seconds = 0.0;
double fade_delay = 0.0;
double loop_count = 2.0;
int ignore_loop = 0;
int disable_subsongs = 1;
BOOL xmplay_doneloop = 0;
/* plugin state */
VGMSTREAM * vgmstream = NULL;
int framesDone, framesLength;
int stream_length_samples = 0;
int fade_samples = 0;
VGMSTREAM* vgmstream = NULL;
int decode_pos_samples;
int length_samples = 0;
int output_channels;
int current_subsong = 0;
INT16 sample_buffer[SAMPLE_BUFFER_SIZE * VGMSTREAM_MAX_CHANNELS];
//XMPFILE current_file = NULL;
//char current_fn[XMPLAY_MAX_PATH] = {0};
@ -67,39 +72,39 @@ typedef struct _XMPLAY_STREAMFILE {
int internal_xmpfile; /* infile was not supplied externally and can be closed */
} XMPLAY_STREAMFILE;
static STREAMFILE *open_xmplay_streamfile_by_xmpfile(XMPFILE file, const char *path, int internal);
static STREAMFILE* open_xmplay_streamfile_by_xmpfile(XMPFILE file, const char* path, int internal);
static size_t xmpsf_read(XMPLAY_STREAMFILE *this, uint8_t *dest, off_t offset, size_t length) {
static size_t xmpsf_read(XMPLAY_STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length) {
size_t read;
if (this->offset != offset) {
if (xmpffile->Seek(this->infile, offset))
this->offset = offset;
if (sf->offset != offset) {
if (xmpffile->Seek(sf->infile, offset))
sf->offset = offset;
else
this->offset = xmpffile->Tell(this->infile);
sf->offset = xmpffile->Tell(sf->infile);
}
read = xmpffile->Read(this->infile, dest, length);
read = xmpffile->Read(sf->infile, dest, length);
if (read > 0)
this->offset += read;
sf->offset += read;
return read;
}
static off_t xmpsf_get_size(XMPLAY_STREAMFILE *this) {
return xmpffile->GetSize(this->infile);
static off_t xmpsf_get_size(XMPLAY_STREAMFILE* sf) {
return xmpffile->GetSize(sf->infile);
}
static off_t xmpsf_get_offset(XMPLAY_STREAMFILE *this) {
return xmpffile->Tell(this->infile);
static off_t xmpsf_get_offset(XMPLAY_STREAMFILE* sf) {
return xmpffile->Tell(sf->infile);
}
static void xmpsf_get_name(XMPLAY_STREAMFILE *this, char *buffer, size_t length) {
strncpy(buffer, this->name, length);
static void xmpsf_get_name(XMPLAY_STREAMFILE* sf, char* buffer, size_t length) {
strncpy(buffer, sf->name, length);
buffer[length-1] = '\0';
}
static STREAMFILE *xmpsf_open(XMPLAY_STREAMFILE *this, const char *const filename, size_t buffersize) {
static STREAMFILE *xmpsf_open(XMPLAY_STREAMFILE* sf, const char* const filename, size_t buffersize) {
XMPFILE newfile;
if (!filename)
@ -114,55 +119,69 @@ static STREAMFILE *xmpsf_open(XMPLAY_STREAMFILE *this, const char *const filenam
return open_xmplay_streamfile_by_xmpfile(newfile, filename, 1); /* internal XMPFILE */
}
static void xmpsf_close(XMPLAY_STREAMFILE *this) {
static void xmpsf_close(XMPLAY_STREAMFILE* sf) {
/* Close XMPFILE, but only if we opened it (ex. for subfiles inside metas).
* Otherwise must be left open as other parts of XMPlay need it and would crash. */
if (this->internal_xmpfile) {
xmpffile->Close(this->infile);
if (sf->internal_xmpfile) {
xmpffile->Close(sf->infile);
}
free(this);
free(sf);
}
static STREAMFILE *open_xmplay_streamfile_by_xmpfile(XMPFILE infile, const char *path, int internal) {
XMPLAY_STREAMFILE *streamfile = calloc(1,sizeof(XMPLAY_STREAMFILE));
if (!streamfile) return NULL;
static STREAMFILE *open_xmplay_streamfile_by_xmpfile(XMPFILE infile, const char* path, int internal) {
XMPLAY_STREAMFILE* sf = calloc(1, sizeof(XMPLAY_STREAMFILE));
if (!sf) return NULL;
streamfile->sf.read = (void*)xmpsf_read;
streamfile->sf.get_size = (void*)xmpsf_get_size;
streamfile->sf.get_offset = (void*)xmpsf_get_offset;
streamfile->sf.get_name = (void*)xmpsf_get_name;
streamfile->sf.open = (void*)xmpsf_open;
streamfile->sf.close = (void*)xmpsf_close;
streamfile->infile = infile;
streamfile->offset = 0;
strncpy(streamfile->name, path, sizeof(streamfile->name));
sf->sf.read = (void*)xmpsf_read;
sf->sf.get_size = (void*)xmpsf_get_size;
sf->sf.get_offset = (void*)xmpsf_get_offset;
sf->sf.get_name = (void*)xmpsf_get_name;
sf->sf.open = (void*)xmpsf_open;
sf->sf.close = (void*)xmpsf_close;
sf->infile = infile;
sf->offset = 0;
strncpy(sf->name, path, sizeof(sf->name));
streamfile->internal_xmpfile = internal;
sf->internal_xmpfile = internal;
return &streamfile->sf; /* pointer to STREAMFILE start = rest of the custom data follows */
return &sf->sf; /* pointer to STREAMFILE start = rest of the custom data follows */
}
VGMSTREAM *init_vgmstream_xmplay(XMPFILE file, const char *path, int subsong) {
STREAMFILE *streamfile = NULL;
VGMSTREAM *vgmstream = NULL;
VGMSTREAM* init_vgmstream_xmplay(XMPFILE file, const char *path, int subsong) {
STREAMFILE* sf = NULL;
VGMSTREAM* vgmstream = NULL;
streamfile = open_xmplay_streamfile_by_xmpfile(file, path, 0); /* external XMPFILE */
if (!streamfile) return NULL;
sf = open_xmplay_streamfile_by_xmpfile(file, path, 0); /* external XMPFILE */
if (!sf) return NULL;
streamfile->stream_index = subsong;
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
sf->stream_index = subsong;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
if (!vgmstream) goto fail;
return vgmstream;
fail:
xmpsf_close((XMPLAY_STREAMFILE *)streamfile);
close_streamfile(sf);
return NULL;
}
/* ************************************* */
static void apply_config(VGMSTREAM* vgmstream) {
vgmstream_cfg_t vcfg = {0};
vcfg.allow_play_forever = 0;
//vcfg.play_forever = loop_forever;
vcfg.loop_times = loop_count;
vcfg.fade_period = fade_seconds;
vcfg.fade_delay = fade_delay;
vcfg.ignore_loop = ignore_loop;
vgmstream_apply_config(vgmstream, &vcfg);
}
/* get the tags as an array of "key\0value\0", NULL-terminated */
static char *get_tags(VGMSTREAM * infostream) {
char* tags;
@ -461,10 +480,15 @@ DWORD WINAPI xmplay_GetFileInfo(const char *filename, XMPFILE file, float **leng
if (!infostream)
return 0;
apply_config(infostream);
//vgmstream_mixing_autodownmix(infostream, downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL, NULL);
if (length && infostream->sample_rate) {
int stream_length_samples = get_vgmstream_play_samples(loop_count, fade_seconds, fade_delay_seconds, infostream);
int length_samples = vgmstream_get_samples(infostream);
float *lens = (float*)xmpfmisc->Alloc(sizeof(float));
lens[0] = (float)stream_length_samples / (float)infostream->sample_rate;
lens[0] = (float)length_samples / (float)infostream->sample_rate;
*length = lens;
}
@ -487,18 +511,21 @@ DWORD WINAPI xmplay_Open(const char *filename, XMPFILE file) {
if (!vgmstream)
return 0;
framesDone = 0;
stream_length_samples = get_vgmstream_play_samples(loop_count, fade_seconds, fade_delay_seconds, vgmstream);
fade_samples = (int)(fade_seconds * vgmstream->sample_rate);
framesLength = stream_length_samples - fade_samples;
apply_config(vgmstream);
//vgmstream_mixing_autodownmix(vgmstream, downmix_channels);
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, NULL /*&input_channels*/, &output_channels);
decode_pos_samples = 0;
length_samples = vgmstream_get_samples(vgmstream);
//strncpy(current_fn,filename,XMPLAY_MAX_PATH);
//current_file = file;
//current_subsong = 0;
if (stream_length_samples) {
float length = (float)stream_length_samples / (float)vgmstream->sample_rate;
if (length_samples) {
float length = (float)length_samples / (float)vgmstream->sample_rate;
xmpfin->SetLength(length, TRUE);
}
@ -514,7 +541,7 @@ void WINAPI xmplay_Close() {
/* set the sample format */
void WINAPI xmplay_SetFormat(XMPFORMAT *form) {
form->res = 16 / 8; /* PCM 16 */
form->chan = vgmstream->channels;
form->chan = output_channels;
form->rate = vgmstream->sample_rate;
}
@ -603,8 +630,8 @@ double WINAPI xmplay_GetGranularity() {
/* seek to a position (in granularity units), return new position or -1 = failed */
double WINAPI xmplay_SetPosition(DWORD pos) {
double cpos = (double)framesDone / (double)vgmstream->sample_rate;
double time = pos * xmplay_GetGranularity();
double cpos;
int seek_sample = pos * xmplay_GetGranularity() * vgmstream->sample_rate;
if (pos == XMPIN_POS_AUTOLOOP || pos == XMPIN_POS_LOOP)
xmplay_doneloop = 1;
@ -632,89 +659,48 @@ double WINAPI xmplay_SetPosition(DWORD pos) {
}
#endif
if (time < cpos) {
reset_vgmstream(vgmstream);
cpos = 0.0;
}
seek_vgmstream(vgmstream, seek_sample);
while (cpos < time) {
INT16 buffer[1024];
long max_sample_count = 1024 / vgmstream->channels;
long samples_to_skip = (long)((time - cpos) * vgmstream->sample_rate);
if (samples_to_skip > max_sample_count)
samples_to_skip = max_sample_count;
if (!samples_to_skip)
break;
render_vgmstream(buffer, (int)samples_to_skip, vgmstream);
cpos += (double)samples_to_skip / (double)vgmstream->sample_rate;
}
framesDone = (int32_t)(cpos * vgmstream->sample_rate);
decode_pos_samples = seek_sample;
cpos = (double)decode_pos_samples / vgmstream->sample_rate;
return cpos;
}
/* decode some sample data */
DWORD WINAPI xmplay_Process(float* buf, DWORD bufsize) {
INT16 sample_buffer[1024];
UINT32 i, j, todo, done;
BOOL doLoop = xmpfin->GetLooping();
int32_t i, done, samples_to_do;
BOOL do_loop = xmpfin->GetLooping();
float *sbuf = buf;
UINT32 samplesTodo;
bufsize /= vgmstream->channels;
samplesTodo = doLoop ? bufsize : stream_length_samples - framesDone;
if (samplesTodo > bufsize)
samplesTodo = bufsize;
bufsize /= output_channels;
samples_to_do = do_loop ? bufsize : length_samples - decode_pos_samples;
if (samples_to_do > bufsize)
samples_to_do = bufsize;
/* decode */
done = 0;
while (done < samplesTodo) {
todo = 1024 / vgmstream->channels;
if (todo > samplesTodo - done)
todo = samplesTodo - done;
while (done < samples_to_do) {
int to_do = SAMPLE_BUFFER_SIZE;
if (to_do > samples_to_do - done)
to_do = samples_to_do - done;
render_vgmstream(sample_buffer, todo, vgmstream);
render_vgmstream(sample_buffer, to_do, vgmstream);
for (i = 0, j = todo * vgmstream->channels; i < j; ++i) {
for (i = 0; i < to_do * output_channels; i++) {
*sbuf++ = sample_buffer[i] * 1.0f / 32768.0f;
}
done += todo;
done += to_do;
}
sbuf = buf;
/* fade */
if ((!doLoop || xmplay_doneloop) && vgmstream->loop_flag && framesDone + done > framesLength) {
long fadeStart = (framesLength > framesDone) ? framesLength : framesDone;
long fadeEnd = (framesDone + done) > stream_length_samples ? stream_length_samples : (framesDone + done);
long fadePos;
decode_pos_samples += done;
float fadeScale = (float)(stream_length_samples - fadeStart) / fade_samples;
float fadeStep = 1.0f / fade_samples;
sbuf += (fadeStart - framesDone) * vgmstream->channels;
j = vgmstream->channels;
for (fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
for (i = 0; i < j; ++i) {
sbuf[i] = sbuf[i] * fadeScale;
}
sbuf += j;
fadeScale -= fadeStep;
if (fadeScale <= 0.0f)
break;
}
done = (int)(fadePos - framesDone);
}
framesDone += done;
return done * vgmstream->channels;
return done * output_channels;
}
static DWORD WINAPI xmplay_GetSubSongs(float *length) {
@ -740,8 +726,8 @@ static DWORD WINAPI xmplay_GetSubSongs(float *length) {
//}
/* simply use the current length */ //todo just use 0?
stream_length_samples = get_vgmstream_play_samples(loop_count, fade_seconds, fade_delay_seconds, vgmstream);
*length = (float)stream_length_samples / (float)vgmstream->sample_rate;
length_samples = vgmstream_get_samples(vgmstream);
*length = (float)length_samples / (float)vgmstream->sample_rate;
}
return subsong_count;