From dbfa909a9fc3edd361a0fd2a70beaa66a1499513 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 15 Aug 2019 15:14:32 +0200 Subject: [PATCH] Add TXTP @downmix macro and improve plugin stereo downmixing --- doc/TXTP.md | 1 + src/meta/txtp.c | 12 +++++ src/mixing.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mixing.h | 1 + src/plugins.c | 14 +++--- 5 files changed, 136 insertions(+), 6 deletions(-) diff --git a/doc/TXTP.md b/doc/TXTP.md index 047bbf8a..1e744e45 100644 --- a/doc/TXTP.md +++ b/doc/TXTP.md @@ -364,6 +364,7 @@ Manually setting values gets old, so TXTP supports a bunch of simple macros. The - `remix N (channels)`: same, but mixes selected channels to N channels properly adjusting volume (for layered bgm) - `crosstrack N`: crossfades between Nch tracks after every loop (loop count is adjusted as needed) - `crosslayer-v/b/e N`: crossfades Nch layers to the main track after every loop (loop count is adjusted as needed) +- `downmix`: downmixes up to 8 channels (7.1, 5.1, etc) to stereo, using standard downmixing formulas. `channels` can be multiple comma-separated channels or N~M ranges and may be ommited were applicable to mean "all channels" (channel order doesn't matter but it's internally fixed). diff --git a/src/meta/txtp.c b/src/meta/txtp.c index fa857616..58423926 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -27,6 +27,7 @@ typedef enum { MACRO_LAYER, MACRO_CROSSTRACK, MACRO_CROSSLAYER, + MACRO_DOWNMIX, } txtp_mix_t; @@ -498,6 +499,7 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { 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, mix.mode); break; + case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix.max); break; default: break; @@ -1071,6 +1073,16 @@ static void parse_config(txtp_entry *cfg, char *config) { add_mixing(cfg, &mix, type); } + else if (strcmp(command,"@downmix") == 0) { + txtp_mix_data mix = {0}; + + mix.max = 2; /* stereo only for now */ + //nm = get_int(config, &mix.max); + //config += nm; + //if (nm == 0) continue; + + add_mixing(cfg, &mix, MACRO_DOWNMIX); + } else if (config[nc] == ' ') { //;VGM_LOG("TXTP: comment\n"); break; /* comment, ignore rest */ diff --git a/src/mixing.c b/src/mixing.c index 81ceffbc..d20f0b82 100644 --- a/src/mixing.c +++ b/src/mixing.c @@ -925,6 +925,120 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { mixing_push_killmix(vgmstream, max); } + +typedef enum { + pos_FL = 0, + pos_FR = 1, + pos_FC = 2, + pos_LFE = 3, + pos_BL = 4, + pos_BR = 5, + pos_FLC = 6, + pos_FRC = 7, + pos_BC = 8, + pos_SL = 9, + pos_SR = 10, +} mixing_position_t; + +void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) { + mixing_data *data = vgmstream->mixing_data; + int ch, output_channels, mp_in, mp_out, ch_in, ch_out; + mapping_t input_mapping, output_mapping; + const double vol_max = 1.0; + const double vol_sqrt = 1 / sqrt(2); + const double vol_half = 1 / 2; + double matrix[16][16] = {{0}}; + + + if (!data) + return; + if (max <= 1 || data->output_channels <= max || max >= 8) + return; + + /* assume WAV defaults if not set */ + input_mapping = vgmstream->channel_layout; + if (input_mapping == 0) { + switch(data->output_channels) { + case 1: input_mapping = mapping_MONO; break; + case 2: input_mapping = mapping_STEREO; break; + case 3: input_mapping = mapping_2POINT1; break; + case 4: input_mapping = mapping_QUAD; break; + case 5: input_mapping = mapping_5POINT0; break; + case 6: input_mapping = mapping_5POINT1; break; + case 7: input_mapping = mapping_7POINT0; break; + case 8: input_mapping = mapping_7POINT1; break; + default: return; + } + } + + /* build mapping matrix[input channel][output channel] = volume, + * using standard WAV/AC3 downmix formulas + * - https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables + * - https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations + */ + switch(max) { + case 1: + output_mapping = mapping_MONO; + matrix[pos_FL][pos_FC] = vol_sqrt; + matrix[pos_FR][pos_FC] = vol_sqrt; + matrix[pos_FC][pos_FC] = vol_max; + matrix[pos_SL][pos_FC] = vol_half; + matrix[pos_SR][pos_FC] = vol_half; + matrix[pos_BL][pos_FC] = vol_half; + matrix[pos_BR][pos_FC] = vol_half; + break; + case 2: + output_mapping = mapping_STEREO; + matrix[pos_FL][pos_FL] = vol_max; + matrix[pos_FR][pos_FR] = vol_max; + matrix[pos_FC][pos_FL] = vol_sqrt; + matrix[pos_FC][pos_FR] = vol_sqrt; + matrix[pos_SL][pos_FL] = vol_sqrt; + matrix[pos_SR][pos_FR] = vol_sqrt; + matrix[pos_BL][pos_FL] = vol_sqrt; + matrix[pos_BR][pos_FR] = vol_sqrt; + break; + default: + /* not sure if +3ch would use FC/LFE, SL/BR and whatnot without passing extra config, so ignore for now */ + return; + } + + /* save and make N fake channels at the beginning for easier calcs */ + output_channels = data->output_channels; + for (ch = 0; ch < max; ch++) { + mixing_push_upmix(vgmstream, 0); + } + + /* downmix */ + ch_in = 0; + for (mp_in = 0; mp_in < 16; mp_in++) { + /* read input mapping (ex. 5.1) and find channel */ + if (!(input_mapping & (1< max) + break; + } + + ch_in++; + if (ch_in >= output_channels) + break; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, max); +} + /* ******************************************************************* */ void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) { diff --git a/src/mixing.h b/src/mixing.h index 98e0b2e1..9bb49b97 100644 --- a/src/mixing.h +++ b/src/mixing.h @@ -36,6 +36,7 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask); 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, char mode); +void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/); #endif /* _MIXING_H_ */ diff --git a/src/plugins.c b/src/plugins.c index 4baf602f..d36cb5b1 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -234,12 +234,14 @@ void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) { if (max_channels <= 0) return; - /* guess mixing the best we can */ - //todo: could use standard downmixing for known max_channels <> vgmstream->channels combos: - // 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, 'e'); + /* guess mixing the best we can, using standard downmixing if possible + * (without mapping we can't be sure if format is using a standard layout) */ + if (vgmstream->channel_layout && max_channels <= 2) { + mixing_macro_downmix(vgmstream, max_channels); + } + else { + mixing_macro_layer(vgmstream, max_channels, 0, 'e'); + } return; }