Add TXTP @downmix macro and improve plugin stereo downmixing

This commit is contained in:
bnnm 2019-08-15 15:14:32 +02:00
parent 706b71d7fd
commit dbfa909a9f
5 changed files with 136 additions and 6 deletions

View File

@ -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).

View File

@ -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 */

View File

@ -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<<mp_in)))
continue;
ch_out = 0;
for (mp_out = 0; mp_out < 16; mp_out++) {
/* read output mapping (ex. 2.0) and find channel */
if (!(output_mapping & (1<<mp_out)))
continue;
VGM_LOG("i=%i,j=%i, ch_out=%i, ch_in=%i + %i, %f\n", mp_in,mp_out,ch_out, max, ch_in, matrix[mp_out][mp_in]);
mixing_push_add(vgmstream, ch_out, max + ch_in, matrix[mp_in][mp_out]);
ch_out++;
if (ch_out > 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) {

View File

@ -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_ */

View File

@ -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;
}