diff --git a/src/base/mixing.c b/src/base/mixing.c index 17ae4397..867f992d 100644 --- a/src/base/mixing.c +++ b/src/base/mixing.c @@ -1,6 +1,8 @@ #include "../vgmstream.h" #include "../util/channel_mappings.h" #include "mixing.h" +#include "mixing_priv.h" +#include "mixing_fades.h" #include "plugins.h" #include #include @@ -49,215 +51,21 @@ * (and maybe improve memory cache?), though maybe it should call one function per operation. */ -#define VGMSTREAM_MAX_MIXING 512 -#define MIXING_PI 3.14159265358979323846f - - -/* mixing info */ -typedef enum { - MIX_SWAP, - MIX_ADD, - MIX_ADD_COPY, - MIX_VOLUME, - MIX_LIMIT, - MIX_UPMIX, - MIX_DOWNMIX, - MIX_KILLMIX, - MIX_FADE -} mix_command_t; - -typedef struct { - mix_command_t command; - /* common */ - int ch_dst; - int ch_src; - float vol; - - /* fade envelope */ - float vol_start; /* volume from pre to start */ - float vol_end; /* volume from end to post */ - char shape; /* curve type */ - int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */ - int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */ - int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */ - int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */ -} mix_command_data; - -typedef struct { - int mixing_channels; /* max channels needed to mix */ - int output_channels; /* resulting channels after mixing */ - int mixing_on; /* mixing allowed */ - int mixing_count; /* mixing number */ - size_t mixing_size; /* mixing max */ - mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ - float* mixbuf; /* internal mixing buffer */ - - /* fades only apply at some points, other mixes are active */ - int has_non_fade; - int has_fade; -} mixing_data; - - /* ******************************************************************* */ -static int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) { - int i; - - for (i = 0; i < data->mixing_count; i++) { - mix_command_data *mix = &data->mixing_chain[i]; - int32_t fade_start, fade_end; - float vol_start = mix->vol_start; - - if (mix->command != MIX_FADE) - continue; - - /* check is current range falls within a fade - * (assuming fades were already optimized on add) */ - if (mix->time_pre < 0 && vol_start == 1.0) { - fade_start = mix->time_start; /* ignore unused */ - } - else { - fade_start = mix->time_pre < 0 ? 0 : mix->time_pre; - } - fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post; - - //;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end); - if (current_start < fade_end && current_end > fade_start) { - //;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start); - return 1; - } - } - - return 0; -} - -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 */ - int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */ - - current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count; - } - else { - current_pos = (vgmstream->current_sample - sample_count); - } - - return current_pos; -} - -static float get_fade_gain_curve(char shape, float index) { - float gain; - - /* don't bother doing calcs near 0.0/1.0 */ - if (index <= 0.0001f || index >= 0.9999f) { - return index; - } - - //todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines) - - /* (curve math mostly from SoX/FFmpeg) */ - switch(shape) { - /* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast - * (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */ - - case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */ - //gain = pow(0.1f, (1.0f - index) * 2.5f); - gain = exp(-5.75646273248511f * (1.0f - index)); - break; - case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */ - //gain = 1 - pow(0.1f, (index) * 2.5f); - gain = 1 - exp(-5.75646273248511f * (index)); - break; - - case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */ - gain = (1.0f - cos(index * MIXING_PI)) / 2.0f; - break; - - case 'Q': /* quarter of sine wave (for musical fades) */ - gain = sin(index * MIXING_PI / 2.0f); - break; - - case 'p': /* parabola (maybe for crossfades) */ - gain = 1.0f - sqrt(1.0f - index); - break; - case 'P': /* inverted parabola (maybe for fades) */ - gain = (1.0f - (1.0f - index) * (1.0f - index)); - break; - - case 'T': /* triangular/linear (simpler/sharper fades) */ - default: - gain = index; - break; - } - - return gain; -} - -static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) { - float cur_vol = 0.0f; - - if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) { - cur_vol = mix->vol_start; /* before */ - } - else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) { - cur_vol = mix->vol_end; /* after */ - } - else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) { - /* in between */ - float range_vol, range_dur, range_idx, index, gain; - - if (mix->vol_start < mix->vol_end) { /* fade in */ - range_vol = mix->vol_end - mix->vol_start; - range_dur = mix->time_end - mix->time_start; - range_idx = current_subpos - mix->time_start; - index = range_idx / range_dur; - } else { /* fade out */ - range_vol = mix->vol_end - mix->vol_start; - range_dur = mix->time_end - mix->time_start; - range_idx = mix->time_end - current_subpos; - index = range_idx / range_dur; - } - - /* Fading is done like this: - * - find current position within fade duration - * - get linear % (or rather, index from 0.0 .. 1.0) of duration - * - apply shape to % (from linear fade to curved fade) - * - get final volume for that point - * - * Roughly speaking some curve shapes are better for fades (decay rate is more natural - * sounding in that highest to mid/low happens faster but low to lowest takes more time, - * kinda like a gunshot or bell), and others for crossfades (decay of fade-in + fade-out - * is adjusted so that added volume level stays constant-ish). - * - * As curves can fade in two ways ('normal' and curving 'the other way'), they are adjusted - * to get 'normal' shape on both fades (by reversing index and making 1 - gain), thus some - * curves are complementary (exponential fade-in ~= logarithmic fade-out); the following - * are described taking fade-in = normal. +static void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) { + for (int s = 0; s < samples * channels; s++) { + /* when casting float to int, value is simply truncated: + * - (int)1.7 = 1, (int)-1.7 = -1 + * alts for more accurate rounding could be: + * - (int)floor(f) + * - (int)(f < 0 ? f - 0.5f : f + 0.5f) + * - (((int) (f1 + 32768.5)) - 32768) + * - etc + * but since +-1 isn't really audible we'll just cast as it's the fastest */ - gain = get_fade_gain_curve(mix->shape, index); - - if (mix->vol_start < mix->vol_end) { /* fade in */ - cur_vol = mix->vol_start + range_vol * gain; - } else { /* fade out */ - cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain); - } + buf_s16[s] = clamp16( (int32_t)buf_f32[s] ); } - else { - /* fade is outside reach */ - goto fail; - } - - *out_cur_vol = cur_vol; - return 1; -fail: - return 0; } void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { @@ -288,9 +96,19 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) /* use advancing buffer pointers to simplify logic */ - temp_mixbuf = data->mixbuf; + temp_mixbuf = data->mixbuf; /* you'd think using a int32 temp buf would be faster but somehow it's slower? */ temp_outbuf = outbuf; + /* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change + * total channels, channel number meaning varies as ops move them around, ex: + * - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4 + * - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4 + * - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 + * - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2 + * - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1 + * - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2 + */ + /* apply mixes in order per channel */ for (s = 0; s < sample_count; s++) { /* reset after new sample 'step'*/ @@ -304,15 +122,6 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) for (m = 0; m < data->mixing_count; m++) { mix_command_data *mix = &data->mixing_chain[m]; - /* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change - * total channels, channel number meaning varies as ops move them around, ex: - * - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4 - * - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4 - * - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 - * - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2 - * - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1 - * - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2 - */ switch(mix->command) { case MIX_SWAP: @@ -406,20 +215,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) temp_outbuf += vgmstream->channels; } - /* copy resulting mix to output - * (you'd think using a int32 temp buf would be faster but somehow it's slower?) */ - for (s = 0; s < sample_count * data->output_channels; s++) { - /* when casting float to int, value is simply truncated: - * - (int)1.7 = 1, (int)-1.7 = -1 - * alts for more accurate rounding could be: - * - (int)floor(f) - * - (int)(f < 0 ? f - 0.5f : f + 0.5f) - * - (((int) (f1 + 32768.5)) - 32768) - * - etc - * but since +-1 isn't really audible we'll just cast as it's the fastest - */ - outbuf[s] = clamp16( (int32_t)data->mixbuf[s] ); - } + /* copy resulting temp mix to output */ + sbuf_copy_f32_to_s16(outbuf, data->mixbuf, sample_count, data->output_channels); } /* ******************************************************************* */ @@ -460,827 +257,6 @@ void mixing_update_channel(VGMSTREAM* vgmstream) { data->output_channels++; } -/* ******************************************************************* */ - -static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) { - mixing_data *data = vgmstream->mixing_data; - if (!data) return 0; - - - if (data->mixing_on) { - VGM_LOG("MIX: ignoring new mixes when mixing active\n"); - return 0; /* to avoid down/upmixing after activation */ - } - - if (data->mixing_count + 1 > data->mixing_size) { - VGM_LOG("MIX: too many mixes\n"); - return 0; - } - - data->mixing_chain[data->mixing_count] = *mix; /* memcpy */ - data->mixing_count++; - - - if (mix->command == MIX_FADE) { - data->has_fade = 1; - } - else { - data->has_non_fade = 1; - } - - //;VGM_LOG("MIX: total %i\n", data->mixing_count); - return 1; -} - - -void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - - if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return; - if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; - mix.command = MIX_SWAP; - mix.ch_dst = ch_dst; - mix.ch_src = ch_src; - - add_mixing(vgmstream, &mix); -} - -void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - if (!data) return; - - //if (volume < 0.0) return; /* negative volume inverts the waveform */ - if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */ - if (ch_dst < 0 || ch_src < 0) return; - if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; - - mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD; - mix.ch_dst = ch_dst; - mix.ch_src = ch_src; - mix.vol = volume; - - //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume); - add_mixing(vgmstream, &mix); -} - -void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - - //if (ch_dst < 0) return; /* means all channels */ - //if (volume < 0.0) return; /* negative volume inverts the waveform */ - if (volume == 1.0) return; /* no change */ - if (!data || ch_dst >= data->output_channels) return; - - mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */ - mix.ch_dst = ch_dst; - mix.vol = volume; - - //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume); - add_mixing(vgmstream, &mix); -} - -void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - - //if (ch_dst < 0) return; /* means all channels */ - if (volume < 0.0) return; - if (volume == 1.0) return; /* no actual difference */ - if (!data || ch_dst >= data->output_channels) return; - //if (volume == 0.0) return; /* dumb but whatevs */ - - mix.command = MIX_LIMIT; - mix.ch_dst = ch_dst; - mix.vol = volume; - - add_mixing(vgmstream, &mix); -} - -void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - int ok; - - if (ch_dst < 0) return; - if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return; - /* dst can be == output_channels here, since we are inserting */ - - mix.command = MIX_UPMIX; - mix.ch_dst = ch_dst; - - ok = add_mixing(vgmstream, &mix); - if (ok) { - data->output_channels += 1; - if (data->mixing_channels < data->output_channels) - data->mixing_channels = data->output_channels; - } -} - -void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - int ok; - - if (ch_dst < 0) return; - if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return; - - mix.command = MIX_DOWNMIX; - mix.ch_dst = ch_dst; - - ok = add_mixing(vgmstream, &mix); - if (ok) { - data->output_channels -= 1; - } -} - -void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - int ok; - - if (ch_dst <= 0) return; /* can't kill from first channel */ - if (!data || ch_dst >= data->output_channels) return; - - mix.command = MIX_KILLMIX; - mix.ch_dst = ch_dst; - - //;VGM_LOG("MIX: killmix %i\n", ch_dst); - ok = add_mixing(vgmstream, &mix); - if (ok) { - data->output_channels = ch_dst; /* clamp channels */ - } -} - - -static mix_command_data* get_last_fade(mixing_data *data, int target_channel) { - int i; - for (i = data->mixing_count; i > 0; i--) { - mix_command_data *mix = &data->mixing_chain[i-1]; - if (mix->command != MIX_FADE) - continue; - if (mix->ch_dst == target_channel) - return mix; - } - - return NULL; -} - - -void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, - int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - mix_command_data *mix_prev; - - - //if (ch_dst < 0) return; /* means all channels */ - if (!data || ch_dst >= data->output_channels) return; - if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return; - if (time_start < 0 || time_end < 0) return; - //if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */ - //if (vol_start == vol_end) /* weird but let in case of being used to cancel others fades... maybe? */ - - if (shape == '{' || shape == '}') - shape = 'E'; - if (shape == '(' || shape == ')') - shape = 'H'; - - mix.command = MIX_FADE; - mix.ch_dst = ch_dst; - mix.vol_start = vol_start; - mix.vol_end = vol_end; - mix.shape = shape; - mix.time_pre = time_pre; - mix.time_start = time_start; - mix.time_end = time_end; - mix.time_post = time_post; - - - /* cancel fades and optimize a bit when using negative pre/post: - * - fades work like this: - * <----------|----------|----------> - * pre1 start1 end1 post1 - * - when pre and post are set nothing is done (fade is exact and multiple fades may overlap) - * - when previous fade's post or current fade's pre are negative (meaning file end/start) - * they should cancel each other (to allow chaining fade-in + fade-out + fade-in + etc): - * <----------|----------|----------| |----------|----------|----------> - * pre1 start1 end1 post1 pre2 start2 end2 post2 - * - other cases (previous fade is actually after/in-between current fade) are ignored - * as they're uncommon and hard to optimize - * fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels' - */ - mix_prev = get_last_fade(data, mix.ch_dst); - if (mix_prev == NULL) { - if (vol_start == 1.0 && time_pre < 0) - time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */ - if (vol_end == 1.0 && time_post < 0) - time_post = time_end; /* fade-in helds default volume after fade end can be clamped */ - } - else if (mix_prev->time_post < 0 || mix.time_pre < 0) { - int is_prev = 1; - /* test if prev is really cancelled by this */ - if ((mix_prev->time_end > mix.time_start) || - (mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) || - (mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end)) - is_prev = 0; - - if (is_prev) { - /* change negative values to actual points */ - if (mix_prev->time_post < 0 && mix.time_pre < 0) { - mix_prev->time_post = mix_prev->time_end; - mix.time_pre = mix_prev->time_post; - } - - if (mix_prev->time_post >= 0 && mix.time_pre < 0) { - mix.time_pre = mix_prev->time_post; - } - else if (mix_prev->time_post < 0 && mix.time_pre >= 0) { - mix_prev->time_post = mix.time_pre; - } - /* else: both define start/ends, do nothing */ - } - /* should only modify prev if add_mixing but meh */ - } - - //;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post); - add_mixing(vgmstream, &mix); -} - -/* ******************************************************************* */ - -#define MIX_MACRO_VOCALS 'v' -#define MIX_MACRO_EQUAL 'e' -#define MIX_MACRO_BGM 'b' - -void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { - mixing_data *data = vgmstream->mixing_data; - int ch; - - if (!data) - return; - - if (mask == 0) { - mixing_push_volume(vgmstream, -1, volume); - return; - } - - for (ch = 0; ch < data->output_channels; ch++) { - if (!((mask >> ch) & 1)) - continue; - mixing_push_volume(vgmstream, ch, volume); - } -} - -void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) { - mixing_data *data = vgmstream->mixing_data; - int ch; - - if (!data) - return; - - if (mask == 0) { - return; - } - - /* reverse remove all channels (easier this way as when removing channels numbers change) */ - for (ch = data->output_channels - 1; ch >= 0; ch--) { - if ((mask >> ch) & 1) - continue; - mixing_push_downmix(vgmstream, ch); - } -} - - -/* get highest channel count */ -static int get_layered_max_channels(VGMSTREAM* vgmstream) { - int i, max; - layered_layout_data* data; - - if (vgmstream->layout_type != layout_layered) - return 0; - - data = vgmstream->layout_data; - - max = 0; - for (i = 0; i < data->layer_count; i++) { - int output_channels = 0; - - mixing_info(data->layers[i], NULL, &output_channels); - - if (max < output_channels) - max = output_channels; - } - - return max; -} - -static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) { - int i; - mixing_data *data = vgmstream->mixing_data; - layered_layout_data* l_data; - - - if (vgmstream->layout_type != layout_layered) - return 0; - - /* no channels set and only vocals for now */ - if (max > 0 || mode != MIX_MACRO_VOCALS) - return 0; - - /* no channel down/upmixing (cannot guess output) */ - for (i = 0; i < data->mixing_count; i++) { - mix_command_t mix = data->mixing_chain[i].command; - if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */ - return 0; - } - - /* only previsible cases */ - l_data = vgmstream->layout_data; - for (i = 0; i < l_data->layer_count; i++) { - int output_channels = 0; - - mixing_info(l_data->layers[i], NULL, &output_channels); - - if (output_channels > 8) - return 0; - } - - return 1; -} - - -/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */ -static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) { - layered_layout_data* ldata = vgmstream->layout_data; - int i, ch; - int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0; - int ch_num; - - /* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch. - * We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */ - - /* find target "main" channels (will be first most of the time) */ - ch_num = 0; - ch_max = 0; - for (i = 0; i < ldata->layer_count; i++) { - int layer_chs = 0; - - mixing_info(ldata->layers[i], NULL, &layer_chs); - - if (ch_max < layer_chs || (ch_max == layer_chs && target_silence)) { - target_ch = ch_num; - target_chs = layer_chs; - target_layer = i; - ch_max = layer_chs; - /* avoid using silence as main if possible for minor optimization */ - target_silence = (ldata->layers[i]->coding_type == coding_SILENCE); - } - - ch_num += layer_chs; - } - - /* all silences? */ - if (!target_chs) { - target_ch = 0; - target_chs = 0; - target_layer = 0; - mixing_info(ldata->layers[0], NULL, &target_chs); - } - - /* add other channels to target (assumes standard channel mapping to simplify) - * most of the time all layers will have same number of channels though */ - ch_num = 0; - for (i = 0; i < ldata->layer_count; i++) { - int layer_chs = 0; - - if (target_layer == i) { - ch_num += target_chs; - continue; - } - - mixing_info(ldata->layers[i], NULL, &layer_chs); - - if (ldata->layers[i]->coding_type == coding_SILENCE) { - ch_num += layer_chs; - continue; /* unlikely but sometimes in Wwise */ - } - - if (layer_chs == target_chs) { - /* 1:1 mapping */ - for (ch = 0; ch < layer_chs; ch++) { - mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0); - } - } - else { - const double vol_sqrt = 1 / sqrt(2); - - /* extra mixing for better sound in some cases (assumes layer_chs is lower than target_chs) */ - switch(layer_chs) { - case 1: - mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, vol_sqrt); - mixing_push_add(vgmstream, target_ch + 1, ch_num + 0, vol_sqrt); - break; - case 2: - mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, 1.0); - mixing_push_add(vgmstream, target_ch + 1, ch_num + 1, 1.0); - break; - default: /* less common */ - //TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph) - for (ch = 0; ch < layer_chs; ch++) { - mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0); - } - break; - } - } - - ch_num += layer_chs; - } - - /* drop non-target channels */ - ch_num = 0; - for (i = 0; i < ldata->layer_count; i++) { - - if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */ - int layer_chs = 0; - mixing_info(ldata->layers[i], NULL, &layer_chs); - - for (ch = 0; ch < layer_chs; ch++) { - mixing_push_downmix(vgmstream, ch_num); //+ ch - } - - //ch_num += layer_chs; /* dropped channels change this */ - } - else if (i == target_layer) { - ch_num += target_chs; - } - else { /* most common, hopefully (faster) */ - mixing_push_killmix(vgmstream, ch_num); - break; - } - } -} - - -void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { - mixing_data *data = vgmstream->mixing_data; - int current, ch, output_channels, selected_channels; - - if (!data) - return; - - if (is_layered_auto(vgmstream, max, mode)) { - //;VGM_LOG("MIX: auto layer mode\n"); - mixing_macro_layer_auto(vgmstream, max, mode); - return; - } - //;VGM_LOG("MIX: regular layer mode\n"); - - if (max == 0) /* auto calculate */ - max = get_layered_max_channels(vgmstream); - - if (max <= 0 || data->output_channels <= max) - return; - - /* set all channels (non-existant channels will be ignored) */ - if (mask == 0) { - mask = ~mask; - } - - /* save before adding fake channels */ - output_channels = data->output_channels; - - /* count possibly set channels */ - selected_channels = 0; - for (ch = 0; ch < output_channels; ch++) { - selected_channels += (mask >> ch) & 1; - } - - /* make N fake channels at the beginning for easier calcs */ - for (ch = 0; ch < max; ch++) { - mixing_push_upmix(vgmstream, 0); - } - - /* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */ - current = 0; - for (ch = 0; ch < output_channels; ch++) { - double volume = 1.0; - - if (!((mask >> ch) & 1)) - continue; - - /* MIX_MACRO_VOCALS: same volume for all layers (for layered vocals) */ - /* MIX_MACRO_EQUAL: volume adjusted equally for all layers (for generic downmixing) */ - /* MIX_MACRO_BGM: volume adjusted depending on layers (for layered bgm) */ - if (mode == MIX_MACRO_BGM && 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 == MIX_MACRO_BGM && ch >= max) || (mode == MIX_MACRO_EQUAL)) { - /* 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; - if (channel_mixes <= 0) /* ??? */ - channel_mixes = 1; - if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ - channel_mixes += 1; - - 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++; - if (current >= max) - current = 0; - } - - /* remove all mixed channels */ - mixing_push_killmix(vgmstream, max); -} - -void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { - mixing_data *data = vgmstream->mixing_data; - int current, ch, track, track_ch, track_num, output_channels; - int32_t change_pos, change_next, change_time; - - if (!data) - return; - if (max <= 0 || data->output_channels <= max) - return; - if (!vgmstream->loop_flag) /* maybe force loop? */ - return; - - /* this probably only makes sense for even channels so upmix before if needed) */ - output_channels = data->output_channels; - if (output_channels % 2) { - mixing_push_upmix(vgmstream, output_channels); - output_channels += 1; - } - - /* set loops to hear all track changes */ - track_num = output_channels / max; - 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++) { - double volume = 1.0; /* won't play at the same time, no volume change needed */ - - int loop_pre = vgmstream->loop_start_sample; - int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; - change_pos = loop_pre + loop_samples * track; - change_next = loop_pre + loop_samples * (track + 1); - change_time = 15.0 * vgmstream->sample_rate; /* in secs */ - - for (track_ch = 0; track_ch < max; track_ch++) { - if (track > 0) { /* fade-in when prev track fades-out */ - mixing_push_fade(vgmstream, ch + track_ch, 0.0, volume, '(', -1, change_pos, change_pos + change_time, -1); - } - - if (track + 1 < track_num) { /* fade-out when next track fades-in */ - mixing_push_fade(vgmstream, ch + track_ch, volume, 0.0, ')', -1, change_next, change_next + change_time, -1); - } - } - - ch += max; - } - - /* mix all tracks into first */ - current = 0; - for (ch = max; ch < output_channels; ch++) { - mixing_push_add(vgmstream, current, ch, 1.0); /* won't play at the same time, no volume change needed */ - - current++; - if (current >= max) - current = 0; - } - - /* remove unneeded channels */ - mixing_push_killmix(vgmstream, 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; - - if (!data) - return; - if (max <= 0 || data->output_channels <= max) - return; - if (!vgmstream->loop_flag) /* maybe force loop? */ - return; - - /* this probably only makes sense for even channels so upmix before if needed) */ - output_channels = data->output_channels; - if (output_channels % 2) { - mixing_push_upmix(vgmstream, output_channels); - output_channels += 1; - } - - /* set loops to hear all track changes */ - layer_num = output_channels / max; - if (vgmstream->config.loop_count < layer_num) { - vgmstream->config.loop_count = layer_num; - vgmstream->config.loop_count_set = 1; - vgmstream->config.config_set = 1; - } - - /* MIX_MACRO_VOCALS: constant volume - * MIX_MACRO_EQUAL: 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]-- - * MIX_MACRO_BGM: 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.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 == MIX_MACRO_EQUAL) { - volume1 = 1 / sqrt(loop + 0); - volume2 = 1 / sqrt(loop + 1); - } - - ch = 0; - for (layer = 0; layer < layer_num; layer++) { - char type; - - if (mode == MIX_MACRO_BGM) { - 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 */ - volume1 = 0.0; - type = '('; - } else { /* otherwise fades out to match other layers's volume */ - 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); - } - - ch += max; - } - } - - /* mix all tracks into first */ - current = 0; - for (ch = max; ch < output_channels; ch++) { - mixing_push_add(vgmstream, current, ch, 1.0); - - current++; - if (current >= max) - current = 0; - } - - /* remove unneeded channels */ - 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; - channel_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); -} /* ******************************************************************* */ diff --git a/src/base/mixing_commands.c b/src/base/mixing_commands.c new file mode 100644 index 00000000..44c138d9 --- /dev/null +++ b/src/base/mixing_commands.c @@ -0,0 +1,254 @@ +#include "../vgmstream.h" +#include "../util/channel_mappings.h" +#include "mixing.h" +#include "mixing_priv.h" +#include +#include + + +static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) { + mixing_data *data = vgmstream->mixing_data; + if (!data) return 0; + + + if (data->mixing_on) { + VGM_LOG("MIX: ignoring new mixes when mixing active\n"); + return 0; /* to avoid down/upmixing after activation */ + } + + if (data->mixing_count + 1 > data->mixing_size) { + VGM_LOG("MIX: too many mixes\n"); + return 0; + } + + data->mixing_chain[data->mixing_count] = *mix; /* memcpy */ + data->mixing_count++; + + + if (mix->command == MIX_FADE) { + data->has_fade = 1; + } + else { + data->has_non_fade = 1; + } + + //;VGM_LOG("MIX: total %i\n", data->mixing_count); + return 1; +} + + +void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return; + if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; + mix.command = MIX_SWAP; + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + + add_mixing(vgmstream, &mix); +} + +void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + if (!data) return; + + //if (volume < 0.0) return; /* negative volume inverts the waveform */ + if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */ + if (ch_dst < 0 || ch_src < 0) return; + if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; + + mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD; + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + mix.vol = volume; + + //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume); + add_mixing(vgmstream, &mix); +} + +void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + //if (ch_dst < 0) return; /* means all channels */ + //if (volume < 0.0) return; /* negative volume inverts the waveform */ + if (volume == 1.0) return; /* no change */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */ + mix.ch_dst = ch_dst; + mix.vol = volume; + + //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume); + add_mixing(vgmstream, &mix); +} + +void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + //if (ch_dst < 0) return; /* means all channels */ + if (volume < 0.0) return; + if (volume == 1.0) return; /* no actual difference */ + if (!data || ch_dst >= data->output_channels) return; + //if (volume == 0.0) return; /* dumb but whatevs */ + + mix.command = MIX_LIMIT; + mix.ch_dst = ch_dst; + mix.vol = volume; + + add_mixing(vgmstream, &mix); +} + +void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst < 0) return; + if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return; + /* dst can be == output_channels here, since we are inserting */ + + mix.command = MIX_UPMIX; + mix.ch_dst = ch_dst; + + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels += 1; + if (data->mixing_channels < data->output_channels) + data->mixing_channels = data->output_channels; + } +} + +void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst < 0) return; + if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return; + + mix.command = MIX_DOWNMIX; + mix.ch_dst = ch_dst; + + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels -= 1; + } +} + +void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst <= 0) return; /* can't kill from first channel */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_KILLMIX; + mix.ch_dst = ch_dst; + + //;VGM_LOG("MIX: killmix %i\n", ch_dst); + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels = ch_dst; /* clamp channels */ + } +} + + +static mix_command_data* get_last_fade(mixing_data *data, int target_channel) { + int i; + for (i = data->mixing_count; i > 0; i--) { + mix_command_data *mix = &data->mixing_chain[i-1]; + if (mix->command != MIX_FADE) + continue; + if (mix->ch_dst == target_channel) + return mix; + } + + return NULL; +} + + +void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, + int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + mix_command_data *mix_prev; + + + //if (ch_dst < 0) return; /* means all channels */ + if (!data || ch_dst >= data->output_channels) return; + if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return; + if (time_start < 0 || time_end < 0) return; + //if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */ + //if (vol_start == vol_end) /* weird but let in case of being used to cancel others fades... maybe? */ + + if (shape == '{' || shape == '}') + shape = 'E'; + if (shape == '(' || shape == ')') + shape = 'H'; + + mix.command = MIX_FADE; + mix.ch_dst = ch_dst; + mix.vol_start = vol_start; + mix.vol_end = vol_end; + mix.shape = shape; + mix.time_pre = time_pre; + mix.time_start = time_start; + mix.time_end = time_end; + mix.time_post = time_post; + + + /* cancel fades and optimize a bit when using negative pre/post: + * - fades work like this: + * <----------|----------|----------> + * pre1 start1 end1 post1 + * - when pre and post are set nothing is done (fade is exact and multiple fades may overlap) + * - when previous fade's post or current fade's pre are negative (meaning file end/start) + * they should cancel each other (to allow chaining fade-in + fade-out + fade-in + etc): + * <----------|----------|----------| |----------|----------|----------> + * pre1 start1 end1 post1 pre2 start2 end2 post2 + * - other cases (previous fade is actually after/in-between current fade) are ignored + * as they're uncommon and hard to optimize + * fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels' + */ + mix_prev = get_last_fade(data, mix.ch_dst); + if (mix_prev == NULL) { + if (vol_start == 1.0 && time_pre < 0) + time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */ + if (vol_end == 1.0 && time_post < 0) + time_post = time_end; /* fade-in helds default volume after fade end can be clamped */ + } + else if (mix_prev->time_post < 0 || mix.time_pre < 0) { + int is_prev = 1; + /* test if prev is really cancelled by this */ + if ((mix_prev->time_end > mix.time_start) || + (mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) || + (mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end)) + is_prev = 0; + + if (is_prev) { + /* change negative values to actual points */ + if (mix_prev->time_post < 0 && mix.time_pre < 0) { + mix_prev->time_post = mix_prev->time_end; + mix.time_pre = mix_prev->time_post; + } + + if (mix_prev->time_post >= 0 && mix.time_pre < 0) { + mix.time_pre = mix_prev->time_post; + } + else if (mix_prev->time_post < 0 && mix.time_pre >= 0) { + mix_prev->time_post = mix.time_pre; + } + /* else: both define start/ends, do nothing */ + } + /* should only modify prev if add_mixing but meh */ + } + + //;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post); + add_mixing(vgmstream, &mix); +} diff --git a/src/base/mixing_fades.h b/src/base/mixing_fades.h new file mode 100644 index 00000000..dcf66d33 --- /dev/null +++ b/src/base/mixing_fades.h @@ -0,0 +1,171 @@ +#ifndef _MIXING_FADE_H_ +#define _MIXING_FADE_H_ + +#include "mixing_priv.h" +#include +#include + +#define MIXING_PI 3.14159265358979323846f + + +static inline int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) { + int i; + + for (i = 0; i < data->mixing_count; i++) { + mix_command_data *mix = &data->mixing_chain[i]; + int32_t fade_start, fade_end; + float vol_start = mix->vol_start; + + if (mix->command != MIX_FADE) + continue; + + /* check is current range falls within a fade + * (assuming fades were already optimized on add) */ + if (mix->time_pre < 0 && vol_start == 1.0) { + fade_start = mix->time_start; /* ignore unused */ + } + else { + fade_start = mix->time_pre < 0 ? 0 : mix->time_pre; + } + fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post; + + //;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end); + if (current_start < fade_end && current_end > fade_start) { + //;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start); + return 1; + } + } + + return 0; +} + +static inline 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 */ + int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */ + + current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count; + } + else { + current_pos = (vgmstream->current_sample - sample_count); + } + + return current_pos; +} + +static inline float get_fade_gain_curve(char shape, float index) { + float gain; + + /* don't bother doing calcs near 0.0/1.0 */ + if (index <= 0.0001f || index >= 0.9999f) { + return index; + } + + //todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines) + + /* (curve math mostly from SoX/FFmpeg) */ + switch(shape) { + /* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast + * (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */ + + case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */ + //gain = pow(0.1f, (1.0f - index) * 2.5f); + gain = exp(-5.75646273248511f * (1.0f - index)); + break; + case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */ + //gain = 1 - pow(0.1f, (index) * 2.5f); + gain = 1 - exp(-5.75646273248511f * (index)); + break; + + case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */ + gain = (1.0f - cos(index * MIXING_PI)) / 2.0f; + break; + + case 'Q': /* quarter of sine wave (for musical fades) */ + gain = sin(index * MIXING_PI / 2.0f); + break; + + case 'p': /* parabola (maybe for crossfades) */ + gain = 1.0f - sqrt(1.0f - index); + break; + case 'P': /* inverted parabola (maybe for fades) */ + gain = (1.0f - (1.0f - index) * (1.0f - index)); + break; + + case 'T': /* triangular/linear (simpler/sharper fades) */ + default: + gain = index; + break; + } + + return gain; +} + +static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) { + float cur_vol = 0.0f; + + if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) { + cur_vol = mix->vol_start; /* before */ + } + else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) { + cur_vol = mix->vol_end; /* after */ + } + else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) { + /* in between */ + float range_vol, range_dur, range_idx, index, gain; + + if (mix->vol_start < mix->vol_end) { /* fade in */ + range_vol = mix->vol_end - mix->vol_start; + range_dur = mix->time_end - mix->time_start; + range_idx = current_subpos - mix->time_start; + index = range_idx / range_dur; + } else { /* fade out */ + range_vol = mix->vol_end - mix->vol_start; + range_dur = mix->time_end - mix->time_start; + range_idx = mix->time_end - current_subpos; + index = range_idx / range_dur; + } + + /* Fading is done like this: + * - find current position within fade duration + * - get linear % (or rather, index from 0.0 .. 1.0) of duration + * - apply shape to % (from linear fade to curved fade) + * - get final volume for that point + * + * Roughly speaking some curve shapes are better for fades (decay rate is more natural + * sounding in that highest to mid/low happens faster but low to lowest takes more time, + * kinda like a gunshot or bell), and others for crossfades (decay of fade-in + fade-out + * is adjusted so that added volume level stays constant-ish). + * + * As curves can fade in two ways ('normal' and curving 'the other way'), they are adjusted + * to get 'normal' shape on both fades (by reversing index and making 1 - gain), thus some + * curves are complementary (exponential fade-in ~= logarithmic fade-out); the following + * are described taking fade-in = normal. + */ + gain = get_fade_gain_curve(mix->shape, index); + + if (mix->vol_start < mix->vol_end) { /* fade in */ + cur_vol = mix->vol_start + range_vol * gain; + } else { /* fade out */ + cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain); + } + } + else { + /* fade is outside reach */ + goto fail; + } + + *out_cur_vol = cur_vol; + return 1; +fail: + return 0; +} + +#endif diff --git a/src/base/mixing_macros.c b/src/base/mixing_macros.c new file mode 100644 index 00000000..1df002ee --- /dev/null +++ b/src/base/mixing_macros.c @@ -0,0 +1,578 @@ +#include "../vgmstream.h" +#include "../util/channel_mappings.h" +#include "mixing.h" +#include "mixing_priv.h" +#include +#include + + +#define MIX_MACRO_VOCALS 'v' +#define MIX_MACRO_EQUAL 'e' +#define MIX_MACRO_BGM 'b' + +void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch; + + if (!data) + return; + + if (mask == 0) { + mixing_push_volume(vgmstream, -1, volume); + return; + } + + for (ch = 0; ch < data->output_channels; ch++) { + if (!((mask >> ch) & 1)) + continue; + mixing_push_volume(vgmstream, ch, volume); + } +} + +void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch; + + if (!data) + return; + + if (mask == 0) { + return; + } + + /* reverse remove all channels (easier this way as when removing channels numbers change) */ + for (ch = data->output_channels - 1; ch >= 0; ch--) { + if ((mask >> ch) & 1) + continue; + mixing_push_downmix(vgmstream, ch); + } +} + + +/* get highest channel count */ +static int get_layered_max_channels(VGMSTREAM* vgmstream) { + int i, max; + layered_layout_data* data; + + if (vgmstream->layout_type != layout_layered) + return 0; + + data = vgmstream->layout_data; + + max = 0; + for (i = 0; i < data->layer_count; i++) { + int output_channels = 0; + + mixing_info(data->layers[i], NULL, &output_channels); + + if (max < output_channels) + max = output_channels; + } + + return max; +} + +static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) { + int i; + mixing_data *data = vgmstream->mixing_data; + layered_layout_data* l_data; + + + if (vgmstream->layout_type != layout_layered) + return 0; + + /* no channels set and only vocals for now */ + if (max > 0 || mode != MIX_MACRO_VOCALS) + return 0; + + /* no channel down/upmixing (cannot guess output) */ + for (i = 0; i < data->mixing_count; i++) { + mix_command_t mix = data->mixing_chain[i].command; + if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */ + return 0; + } + + /* only previsible cases */ + l_data = vgmstream->layout_data; + for (i = 0; i < l_data->layer_count; i++) { + int output_channels = 0; + + mixing_info(l_data->layers[i], NULL, &output_channels); + + if (output_channels > 8) + return 0; + } + + return 1; +} + + +/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */ +static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) { + layered_layout_data* ldata = vgmstream->layout_data; + int i, ch; + int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0; + int ch_num; + + /* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch. + * We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */ + + /* find target "main" channels (will be first most of the time) */ + ch_num = 0; + ch_max = 0; + for (i = 0; i < ldata->layer_count; i++) { + int layer_chs = 0; + + mixing_info(ldata->layers[i], NULL, &layer_chs); + + if (ch_max < layer_chs || (ch_max == layer_chs && target_silence)) { + target_ch = ch_num; + target_chs = layer_chs; + target_layer = i; + ch_max = layer_chs; + /* avoid using silence as main if possible for minor optimization */ + target_silence = (ldata->layers[i]->coding_type == coding_SILENCE); + } + + ch_num += layer_chs; + } + + /* all silences? */ + if (!target_chs) { + target_ch = 0; + target_chs = 0; + target_layer = 0; + mixing_info(ldata->layers[0], NULL, &target_chs); + } + + /* add other channels to target (assumes standard channel mapping to simplify) + * most of the time all layers will have same number of channels though */ + ch_num = 0; + for (i = 0; i < ldata->layer_count; i++) { + int layer_chs = 0; + + if (target_layer == i) { + ch_num += target_chs; + continue; + } + + mixing_info(ldata->layers[i], NULL, &layer_chs); + + if (ldata->layers[i]->coding_type == coding_SILENCE) { + ch_num += layer_chs; + continue; /* unlikely but sometimes in Wwise */ + } + + if (layer_chs == target_chs) { + /* 1:1 mapping */ + for (ch = 0; ch < layer_chs; ch++) { + mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0); + } + } + else { + const double vol_sqrt = 1 / sqrt(2); + + /* extra mixing for better sound in some cases (assumes layer_chs is lower than target_chs) */ + switch(layer_chs) { + case 1: + mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, vol_sqrt); + mixing_push_add(vgmstream, target_ch + 1, ch_num + 0, vol_sqrt); + break; + case 2: + mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, 1.0); + mixing_push_add(vgmstream, target_ch + 1, ch_num + 1, 1.0); + break; + default: /* less common */ + //TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph) + for (ch = 0; ch < layer_chs; ch++) { + mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0); + } + break; + } + } + + ch_num += layer_chs; + } + + /* drop non-target channels */ + ch_num = 0; + for (i = 0; i < ldata->layer_count; i++) { + + if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */ + int layer_chs = 0; + mixing_info(ldata->layers[i], NULL, &layer_chs); + + for (ch = 0; ch < layer_chs; ch++) { + mixing_push_downmix(vgmstream, ch_num); //+ ch + } + + //ch_num += layer_chs; /* dropped channels change this */ + } + else if (i == target_layer) { + ch_num += target_chs; + } + else { /* most common, hopefully (faster) */ + mixing_push_killmix(vgmstream, ch_num); + break; + } + } +} + + +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, output_channels, selected_channels; + + if (!data) + return; + + if (is_layered_auto(vgmstream, max, mode)) { + //;VGM_LOG("MIX: auto layer mode\n"); + mixing_macro_layer_auto(vgmstream, max, mode); + return; + } + //;VGM_LOG("MIX: regular layer mode\n"); + + if (max == 0) /* auto calculate */ + max = get_layered_max_channels(vgmstream); + + if (max <= 0 || data->output_channels <= max) + return; + + /* set all channels (non-existant channels will be ignored) */ + if (mask == 0) { + mask = ~mask; + } + + /* save before adding fake channels */ + output_channels = data->output_channels; + + /* count possibly set channels */ + selected_channels = 0; + for (ch = 0; ch < output_channels; ch++) { + selected_channels += (mask >> ch) & 1; + } + + /* make N fake channels at the beginning for easier calcs */ + for (ch = 0; ch < max; ch++) { + mixing_push_upmix(vgmstream, 0); + } + + /* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */ + current = 0; + for (ch = 0; ch < output_channels; ch++) { + double volume = 1.0; + + if (!((mask >> ch) & 1)) + continue; + + /* MIX_MACRO_VOCALS: same volume for all layers (for layered vocals) */ + /* MIX_MACRO_EQUAL: volume adjusted equally for all layers (for generic downmixing) */ + /* MIX_MACRO_BGM: volume adjusted depending on layers (for layered bgm) */ + if (mode == MIX_MACRO_BGM && 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 == MIX_MACRO_BGM && ch >= max) || (mode == MIX_MACRO_EQUAL)) { + /* 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; + if (channel_mixes <= 0) /* ??? */ + channel_mixes = 1; + if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ + channel_mixes += 1; + + 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++; + if (current >= max) + current = 0; + } + + /* remove all mixed channels */ + mixing_push_killmix(vgmstream, max); +} + +void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, track, track_ch, track_num, output_channels; + int32_t change_pos, change_next, change_time; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data->output_channels; + if (output_channels % 2) { + mixing_push_upmix(vgmstream, output_channels); + output_channels += 1; + } + + /* set loops to hear all track changes */ + track_num = output_channels / max; + 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++) { + double volume = 1.0; /* won't play at the same time, no volume change needed */ + + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + change_pos = loop_pre + loop_samples * track; + change_next = loop_pre + loop_samples * (track + 1); + change_time = 15.0 * vgmstream->sample_rate; /* in secs */ + + for (track_ch = 0; track_ch < max; track_ch++) { + if (track > 0) { /* fade-in when prev track fades-out */ + mixing_push_fade(vgmstream, ch + track_ch, 0.0, volume, '(', -1, change_pos, change_pos + change_time, -1); + } + + if (track + 1 < track_num) { /* fade-out when next track fades-in */ + mixing_push_fade(vgmstream, ch + track_ch, volume, 0.0, ')', -1, change_next, change_next + change_time, -1); + } + } + + ch += max; + } + + /* mix all tracks into first */ + current = 0; + for (ch = max; ch < output_channels; ch++) { + mixing_push_add(vgmstream, current, ch, 1.0); /* won't play at the same time, no volume change needed */ + + current++; + if (current >= max) + current = 0; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, 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; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data->output_channels; + if (output_channels % 2) { + mixing_push_upmix(vgmstream, output_channels); + output_channels += 1; + } + + /* set loops to hear all track changes */ + layer_num = output_channels / max; + if (vgmstream->config.loop_count < layer_num) { + vgmstream->config.loop_count = layer_num; + vgmstream->config.loop_count_set = 1; + vgmstream->config.config_set = 1; + } + + /* MIX_MACRO_VOCALS: constant volume + * MIX_MACRO_EQUAL: 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]-- + * MIX_MACRO_BGM: 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.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 == MIX_MACRO_EQUAL) { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + + ch = 0; + for (layer = 0; layer < layer_num; layer++) { + char type; + + if (mode == MIX_MACRO_BGM) { + 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 */ + volume1 = 0.0; + type = '('; + } else { /* otherwise fades out to match other layers's volume */ + 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); + } + + ch += max; + } + } + + /* mix all tracks into first */ + current = 0; + for (ch = max; ch < output_channels; ch++) { + mixing_push_add(vgmstream, current, ch, 1.0); + + current++; + if (current >= max) + current = 0; + } + + /* remove unneeded channels */ + 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; + channel_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); +} diff --git a/src/base/mixing_priv.h b/src/base/mixing_priv.h new file mode 100644 index 00000000..a21f83d8 --- /dev/null +++ b/src/base/mixing_priv.h @@ -0,0 +1,52 @@ +#ifndef _MIXING_PRIV_H_ +#define _MIXING_PRIV_H_ + +#include "../vgmstream.h" +#define VGMSTREAM_MAX_MIXING 512 + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_ADD_COPY, + MIX_VOLUME, + MIX_LIMIT, + MIX_UPMIX, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_FADE +} mix_command_t; + +typedef struct { + mix_command_t command; + /* common */ + int ch_dst; + int ch_src; + float vol; + + /* fade envelope */ + float vol_start; /* volume from pre to start */ + float vol_end; /* volume from end to post */ + char shape; /* curve type */ + int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */ + int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */ + int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */ + int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */ +} mix_command_data; + +typedef struct { + int mixing_channels; /* max channels needed to mix */ + int output_channels; /* resulting channels after mixing */ + int mixing_on; /* mixing allowed */ + int mixing_count; /* mixing number */ + size_t mixing_size; /* mixing max */ + mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ + float* mixbuf; /* internal mixing buffer */ + + /* fades only apply at some points, other mixes are active */ + int has_non_fade; + int has_fade; +} mixing_data; + + +#endif diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index af435628..9e8b13da 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -88,6 +88,8 @@ + + @@ -197,6 +199,8 @@ + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index e7cde615..726b131d 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -89,6 +89,12 @@ base\Header Files + + base\Header Files + + + base\Header Files + base\Header Files @@ -412,6 +418,12 @@ base\Source Files + + base\Source Files + + + base\Source Files + base\Source Files