diff --git a/doc/TXTP.md b/doc/TXTP.md index e240cbd1..6e2f3666 100644 --- a/doc/TXTP.md +++ b/doc/TXTP.md @@ -14,7 +14,11 @@ fileN mode = (mode) # "segments" is the default if not set ``` -You can set commands to alter how files play (described later). Having a single file is ok too. +You can set commands to alter how files play (described later). Having a single file is ok too, so are subdirs: +``` +# set "subsong" command for single file inside subdir +sounds/file#12 +``` ### Segments mode @@ -83,9 +87,9 @@ You can set file commands by adding multiple `#(command)` after the name. `# (an **Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)**: *bgm_12.txtp* ``` # select subsong 12 -bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP +bgm.sxd2#12 -#bigfiles/bgm.sxd2#s12 # "sN" is alt for subsong +#bgm.sxd2#s12 # "sN" is alt for subsong # single files loop normally by default # if loop segment is defined it forces a full loop (0..num_samples) diff --git a/src/meta/txtp.c b/src/meta/txtp.c index f3c07714..272d3e84 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -53,7 +53,7 @@ typedef struct { /* macros */ int max; uint32_t mask; - int overlap; + char mode; } txtp_mix_data; #endif @@ -352,9 +352,9 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { /* macro mixes */ case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix.vol, mix.mask); break; case MACRO_TRACK: mixing_macro_track(vgmstream, mix.mask); break; - case MACRO_LAYER: mixing_macro_layer(vgmstream, mix.max, mix.mask, mix.overlap); break; + case MACRO_LAYER: mixing_macro_layer(vgmstream, mix.max, mix.mask, mix.mode); break; case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix.max); break; - case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix.max); break; + case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix.max, mix.mode); break; default: break; @@ -712,15 +712,19 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { { char *config; - /* find config start (filenames and config can contain multiple dots and #, - * so this may be fooled by certain patterns of . and #) */ - config = strchr(filename, '.'); /* first dot (may be a false positive) */ - if (!config) /* extensionless */ - config = filename; - config = strchr(config, '#'); /* next should be config */ - if (!config) /* no config */ - config = filename; //todo if no config just exit? - + if (is_default) { + config = filename; /* multiple commands without filename */ + } + else { + /* find config start (filenames and config can contain multiple dots and #, + * so this may be fooled by certain patterns of . and #) */ + config = strchr(filename, '.'); /* first dot (may be a false positive) */ + if (!config) /* extensionless */ + config = filename; + config = strchr(config, '#'); /* next should be config */ + if (!config) /* no config */ + config = filename; //todo if no config just exit? + } range_start = 0; range_end = 1; @@ -936,6 +940,7 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { nm = get_double(config, &mix.vol); config += nm; + if (nm == 0) continue; nm = get_mask(config, &mix.mask); @@ -952,7 +957,9 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { add_mixing(&cfg, &mix, MACRO_TRACK); } - else if (strcmp(command,"@layer") == 0 || strcmp(command,"@overlap") == 0) { + else if (strcmp(command,"@layer-v") == 0 || + strcmp(command,"@layer-b") == 0 || + strcmp(command,"@layer-e") == 0) { txtp_mix_data mix = {0}; nm = get_int(config, &mix.max); @@ -962,17 +969,22 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { nm = get_mask(config, &mix.mask); config += nm; - mix.overlap = (strcmp(command,"@overlap") == 0); - + mix.mode = command[7]; /* pass letter */ add_mixing(&cfg, &mix, MACRO_LAYER); } - else if (strcmp(command,"@crosslayer") == 0 || strcmp(command,"@crosstrack") == 0) { + else if (strcmp(command,"@crosslayer-v") == 0 || + strcmp(command,"@crosslayer-b") == 0 || + strcmp(command,"@crosslayer-e") == 0 || + strcmp(command,"@crosstrack") == 0) { txtp_mix_data mix = {0}; txtp_mix_t type; - if (strcmp(command,"@crosstrack") == 0) + if (strcmp(command,"@crosstrack") == 0) { type = MACRO_CROSSTRACK; - else + } + else { type = MACRO_CROSSLAYER; + mix.mode = command[12]; /* pass letter */ + } nm = get_int(config, &mix.max); config += nm; diff --git a/src/mixing.c b/src/mixing.c index 579d714b..5ff4fee1 100644 --- a/src/mixing.c +++ b/src/mixing.c @@ -694,9 +694,9 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) { } } -void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overlap) { +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { mixing_data *data = vgmstream->mixing_data; - int current, ch, selected_channels; + int current, ch, output_channels, selected_channels; if (!data) return; @@ -708,9 +708,12 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overla mask = ~mask; } + /* save before adding fake channels */ + output_channels = data->output_channels; + /* count possibly set channels */ selected_channels = 0; - for (ch = 0; ch < data->output_channels; ch++) { + for (ch = 0; ch < output_channels; ch++) { selected_channels += (mask >> ch) & 1; } @@ -721,14 +724,27 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overla /* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */ current = 0; - for (ch = 0; ch < data->output_channels; ch++) { + for (ch = 0; ch < output_channels; ch++) { double volume = 1.0; if (!((mask >> ch) & 1)) continue; - /* adjust volume (layer = lower, for layered bgm, overlap = same, for layered vocals) */ - if (!overlap) { + /* mode 'v': same volume for all layers (for layered vocals) */ + /* mode 'b': volume adjusted depending on layers (for layered bgm) */ + /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */ + if (mode == 'b' && ch < max) { + /* reduce a bit main channels (see below) */ + int channel_mixes = selected_channels / max; + if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ + channel_mixes += 1; + channel_mixes -= 1; /* better formula? */ + if (channel_mixes <= 0) /* ??? */ + channel_mixes = 1; + + volume = 1 / sqrt(channel_mixes); + } + if ((mode == 'b' && ch >= max) || (mode == 'e')) { /* find how many will be mixed in current channel (earlier channels receive more * mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */ int channel_mixes = selected_channels / max; @@ -737,8 +753,9 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overla if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ channel_mixes += 1; - volume = 1 / sqrt(channel_mixes); /* "power" add, good results most of the time */ + volume = 1 / sqrt(channel_mixes); /* "power" add */ } + //;VGM_LOG("MIX: layer ch=%i, cur=%i, v=%f\n", ch, current, volume); mixing_push_add(vgmstream, current, max + ch, volume); /* ch adjusted considering upmixed channels */ current++; @@ -811,7 +828,7 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { mixing_push_killmix(vgmstream, max); } -void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max) { +void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { mixing_data *data = vgmstream->mixing_data; int current, ch, layer, layer_ch, layer_num, loop, output_channels; int32_t change_pos, change_time; @@ -835,25 +852,44 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max) { if (vgmstream->config_loop_count < layer_num) vgmstream->config_loop_count = layer_num; - /* must set fades to successively lower volume per loop for each layer + /* mode 'v': constant volume + * mode 'e': sets fades to successively lower/equalize volume per loop for each layer * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max: * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]-- + * mode 'b': similar but 1st layer (main) has higher/delayed volume: + * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]-- */ for (loop = 1; loop < layer_num; loop++) { - double volume1 = 1 / sqrt(loop + 0); - double volume2 = 1 / sqrt(loop + 1); + double volume1 = 1.0; + double volume2 = 1.0; int loop_pre = vgmstream->loop_start_sample; int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; change_pos = loop_pre + loop_samples * loop; change_time = 10.0 * vgmstream->sample_rate; /* in secs */ + if (mode == 'e') { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + ch = 0; for (layer = 0; layer < layer_num; layer++) { char type; + if (mode == 'b') { + if (layer == 0) { + volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1); + volume2 = 1 / sqrt(loop + 0); + } + else { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + } + if (layer > loop) { /* not playing yet (volume is implicitly 0.0 from first fade in) */ continue; } else if (layer == loop) { /* fades in for the first time */ @@ -863,6 +899,8 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max) { type = ')'; } + //;VGM_LOG("MIX: loop=%i, layer %i, vol1=%f, vol2=%f\n", loop, layer, volume1, volume2); + for (layer_ch = 0; layer_ch < max; layer_ch++) { mixing_push_fade(vgmstream, ch + layer_ch, volume1, volume2, type, -1, change_pos, change_pos + change_time, -1); } @@ -893,6 +931,16 @@ 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; + /* create or alter internal buffer */ mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float)); if (!mixbuf_re) goto fail; @@ -900,12 +948,6 @@ 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 */ diff --git a/src/mixing.h b/src/mixing.h index d5f9f1a7..98e0b2e1 100644 --- a/src/mixing.h +++ b/src/mixing.h @@ -33,9 +33,9 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask); void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask); -void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overlap); +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode); void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max); -void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max); +void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode); #endif /* _MIXING_H_ */ diff --git a/src/plugins.c b/src/plugins.c index b0ead332..b17df2e4 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -1,6 +1,8 @@ #include "vgmstream.h" #include "plugins.h" +#ifdef VGMSTREAM_MIXING #include "mixing.h" +#endif #define VGMSTREAM_TAGS_LINE_MAX 2048 @@ -225,7 +227,7 @@ void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) { } } - +#ifdef VGMSTREAM_MIXING 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); @@ -240,7 +242,8 @@ void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) { // https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables#tbl_mono // https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations - mixing_macro_layer(vgmstream, max_channels, 0, 0); + mixing_macro_layer(vgmstream, max_channels, 0, 'e'); return; } +#endif diff --git a/src/plugins.h b/src/plugins.h index a6c2d08e..d2fa1d3a 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -83,6 +83,7 @@ void vgmstream_tags_close(VGMSTREAM_TAGS* tags); /* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin * must use returned input_channels to create outbuf and output_channels to output audio. + * max_sample_count may be 0 if you only need to query values and not actually enable it. * Needs to be enabled last after adding effects. */ void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); diff --git a/src/vgmstream.c b/src/vgmstream.c index e8940932..2158fdf6 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -2323,6 +2323,8 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { mixing_info(vgmstream, NULL, &output_channels); if (output_channels != vgmstream->channels) { + snprintf(temp,TEMPSIZE, "input channels: %d\n", vgmstream->channels); /* repeated but mainly for plugins */ + concatn(length,desc,temp); snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels); concatn(length,desc,temp); } diff --git a/winamp/in_vgmstream.c b/winamp/in_vgmstream.c index fbc397b0..5f146fa6 100644 --- a/winamp/in_vgmstream.c +++ b/winamp/in_vgmstream.c @@ -1202,7 +1202,7 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) { apply_config(infostream, &infoconfig); #ifdef VGMSTREAM_MIXING vgmstream_mixing_autodownmix(infostream, settings.downmix_channels); - //vgmstream_mixing_enable(infostream, SAMPLE_BUFFER_SIZE, NULL, NULL); + vgmstream_mixing_enable(infostream, 0, NULL, NULL); #endif describe_vgmstream(infostream,description,description_size);