From 4defb555893697a166fb569e444d1561a910c002 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 16 Mar 2019 00:49:52 +0100 Subject: [PATCH] Add layer/segment mixing and main code (disabled) --- src/layout/layered.c | 69 ++- src/layout/segmented.c | 93 ++- src/libvgmstream.vcproj | 8 + src/libvgmstream.vcxproj | 2 + src/libvgmstream.vcxproj.filters | 6 + src/mixing.c | 954 +++++++++++++++++++++++++++++++ src/mixing.h | 41 ++ src/plugins.h | 6 +- src/vgmstream.h | 6 + 9 files changed, 1147 insertions(+), 38 deletions(-) create mode 100644 src/mixing.c create mode 100644 src/mixing.h diff --git a/src/layout/layered.c b/src/layout/layered.c index 59014b78..5bffe07e 100644 --- a/src/layout/layered.c +++ b/src/layout/layered.c @@ -1,45 +1,54 @@ #include "layout.h" #include "../vgmstream.h" +#ifdef VGMSTREAM_MIXING +#include "../mixing.h" +#endif /* NOTE: if loop settings change the layered vgmstreams must be notified (preferably using vgmstream_force_loop) */ -#define LAYER_BUF_SIZE 512 -#define LAYER_MAX_CHANNELS 6 /* at least 2, but let's be generous */ #define VGMSTREAM_MAX_LAYERS 255 +#define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192 /* Decodes samples for layered streams. * Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each * with custom codecs and different number of channels, creating a single super-vgmstream. * Usually combined with custom streamfiles to handle data interleaved in weird ways. */ -void render_vgmstream_layered(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { +void render_vgmstream_layered(sample_t * outbuf, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0; layered_layout_data *data = vgmstream->layout_data; - sample_t interleave_buf[LAYER_BUF_SIZE*LAYER_MAX_CHANNELS]; while (samples_written < sample_count) { - int samples_to_do = LAYER_BUF_SIZE; + int samples_to_do = VGMSTREAM_LAYER_SAMPLE_BUFFER; int layer, ch = 0; if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; for (layer = 0; layer < data->layer_count; layer++) { - int s, layer_ch; - int layer_channels = data->layers[layer]->channels; + int s, layer_ch, layer_channels; - /* each layer will handle its own looping internally */ + /* each layer will handle its own looping/mixing internally */ - render_vgmstream(interleave_buf, samples_to_do, data->layers[layer]); +#ifdef VGMSTREAM_MIXING + /* layers may have its own number of channels */ + mixing_info(data->layers[layer], NULL, &layer_channels); +#else + layer_channels = data->layers[layer]->channels; +#endif + render_vgmstream( + data->buffer, + samples_to_do, + data->layers[layer]); /* mix layer samples to main samples */ for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) { for (s = 0; s < samples_to_do; s++) { size_t layer_sample = s*layer_channels + layer_ch; - size_t buffer_sample = (samples_written+s)*vgmstream->channels + ch; + size_t buffer_sample = (samples_written+s)*data->output_channels + ch; - buffer[buffer_sample] = interleave_buf[layer_sample]; + outbuf[buffer_sample] = data->buffer[layer_sample]; } ch++; } @@ -73,18 +82,29 @@ fail: } int setup_layout_layered(layered_layout_data* data) { - int i; + int i, max_input_channels = 0, max_output_channels = 0; + sample_t *outbuf_re = NULL; + /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (i = 0; i < data->layer_count; i++) { + int layer_input_channels, layer_output_channels; + if (!data->layers[i]) goto fail; if (data->layers[i]->num_samples <= 0) goto fail; - if (data->layers[i]->channels > LAYER_MAX_CHANNELS) - goto fail; +#ifdef VGMSTREAM_MIXING + /* different layers may have different input/output channels */ + mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels); +#else + layer_input_channels = layer_output_channels = data->layers[i]->channels; +#endif + max_output_channels += layer_output_channels; + if (max_input_channels < layer_input_channels) + max_input_channels = layer_input_channels; if (i > 0) { /* a bit weird, but no matter */ @@ -100,9 +120,24 @@ int setup_layout_layered(layered_layout_data* data) { /* loops and other values could be mismatched but hopefully not */ + setup_vgmstream(data->layers[i]); /* final setup in case the VGMSTREAM was created manually */ +#ifdef VGMSTREAM_MIXING + mixing_setup(data->layers[i], VGMSTREAM_LAYER_SAMPLE_BUFFER); /* init mixing */ +#endif } + if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS) + goto fail; + + /* create internal buffer big enough for mixing */ + outbuf_re = realloc(data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t)); + if (!outbuf_re) goto fail; + data->buffer = outbuf_re; + + data->input_channels = max_input_channels; + data->output_channels = max_output_channels; + return 1; fail: return 0; /* caller is expected to free */ @@ -136,15 +171,13 @@ void reset_layout_layered(layered_layout_data *data) { /* helper for easier creation of layers */ VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data) { - VGMSTREAM *vgmstream; + VGMSTREAM *vgmstream = NULL; int i, channels, loop_flag; /* get data */ - channels = 0; + channels = data->output_channels; loop_flag = 1; for (i = 0; i < data->layer_count; i++) { - channels += data->layers[i]->channels; - if (loop_flag && !data->layers[i]->loop_flag) loop_flag = 0; } diff --git a/src/layout/segmented.c b/src/layout/segmented.c index 904ab81a..e602c681 100644 --- a/src/layout/segmented.c +++ b/src/layout/segmented.c @@ -1,22 +1,31 @@ #include "layout.h" #include "../vgmstream.h" - +#ifdef VGMSTREAM_MIXING +#include "../mixing.h" +#endif #define VGMSTREAM_MAX_SEGMENTS 255 +#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192 /* Decodes samples for segmented streams. * Chains together sequential vgmstreams, for data divided into separate sections or files * (like one part for intro and other for loop segments, which may even use different codecs). */ -void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { +void render_vgmstream_segmented(sample_t * outbuf, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0, loop_samples_skip = 0; segmented_layout_data *data = vgmstream->layout_data; + int use_internal_buffer = 0; + + + /* normally uses outbuf directly (faster) but could need internal buffer if downmixing */ + if (vgmstream->channels != data->input_channels) { + use_internal_buffer = 1; + } while (samples_written < sample_count) { int samples_to_do; - int samples_this_block = data->segments[data->current_segment]->num_samples; - + int samples_this_segment = data->segments[data->current_segment]->num_samples; if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { int segment, loop_segment, total_samples; @@ -51,9 +60,11 @@ void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTRE continue; } - samples_to_do = vgmstream_samples_to_do(samples_this_block, sample_count, vgmstream); + samples_to_do = vgmstream_samples_to_do(samples_this_segment, sample_count, vgmstream); if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; + if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */ + samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER; /* segment looping: discard until actual start */ if (loop_samples_skip > 0) { @@ -69,18 +80,29 @@ void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTRE continue; } - render_vgmstream(&buffer[samples_written*data->segments[data->current_segment]->channels], - samples_to_do,data->segments[data->current_segment]); + render_vgmstream( + use_internal_buffer ? + data->buffer : + &outbuf[samples_written * data->output_channels], + samples_to_do, + data->segments[data->current_segment]); if (loop_samples_skip > 0) { loop_samples_skip -= samples_to_do; vgmstream->samples_into_block += samples_to_do; + continue; } - else { - samples_written += samples_to_do; - vgmstream->current_sample += samples_to_do; - vgmstream->samples_into_block += samples_to_do; + + if (use_internal_buffer) { + int s; + for (s = 0; s < samples_to_do * data->output_channels; s++) { + outbuf[samples_written * data->output_channels + s] = data->buffer[s]; + } } + + samples_written += samples_to_do; + vgmstream->current_sample += samples_to_do; + vgmstream->samples_into_block += samples_to_do; } } @@ -107,24 +129,47 @@ fail: } int setup_layout_segmented(segmented_layout_data* data) { - int i; + int i, max_input_channels = 0, max_output_channels = 0; + sample_t *outbuf_re = NULL; + /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (i = 0; i < data->segment_count; i++) { + int segment_input_channels, segment_output_channels; + if (!data->segments[i]) goto fail; if (data->segments[i]->num_samples <= 0) goto fail; - /* shouldn't happen */ + /* disable so that looping is controlled by render_vgmstream_segmented */ if (data->segments[i]->loop_flag != 0) { VGM_LOG("segmented layout: segment %i is looped\n", i); data->segments[i]->loop_flag = 0; } +#ifdef VGMSTREAM_MIXING + /* different segments may have different input channels, though output should be + * the same for all (ex. 2ch + 1ch segments, but 2ch segment is downmixed to 1ch) */ + mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels); +#else + segment_input_channels = segment_output_channels = data->segments[i]->channels; +#endif + if (max_input_channels < segment_input_channels) + max_input_channels = segment_input_channels; + if (max_output_channels < segment_output_channels) + max_output_channels = segment_output_channels; + if (i > 0) { - if (data->segments[i]->channels != data->segments[i-1]->channels) + int prev_output_channels; + +#ifdef VGMSTREAM_MIXING + mixing_info(data->segments[i-1], NULL, &prev_output_channels); +#else + prev_output_channels = data->segments[i-1]->channels; +#endif + if (segment_output_channels != prev_output_channels) goto fail; /* a bit weird, but no matter */ @@ -132,14 +177,28 @@ int setup_layout_segmented(segmented_layout_data* data) { VGM_LOG("segmented layout: segment %i has different sample rate\n", i); } + /* perfectly acceptable */ //if (data->segments[i]->coding_type != data->segments[i-1]->coding_type) - // goto fail; /* perfectly acceptable */ + // goto fail; } setup_vgmstream(data->segments[i]); /* final setup in case the VGMSTREAM was created manually */ +#ifdef VGMSTREAM_MIXING + mixing_setup(data->segments[i], VGMSTREAM_SEGMENT_SAMPLE_BUFFER); /* init mixing */ +#endif } + if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS) + goto fail; + + /* create internal buffer big enough for mixing */ + outbuf_re = realloc(data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t)); + if (!outbuf_re) goto fail; + data->buffer = outbuf_re; + + data->input_channels = max_input_channels; + data->output_channels = max_output_channels; return 1; fail: @@ -175,7 +234,7 @@ void reset_layout_segmented(segmented_layout_data *data) { /* helper for easier creation of segments */ VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment) { - VGMSTREAM *vgmstream; + VGMSTREAM *vgmstream = NULL; int channel_layout; int i, num_samples, loop_start, loop_end; @@ -201,7 +260,7 @@ VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_fl /* respect loop_flag even when no loop_end found as it's possible file loops are set outside */ /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(data->segments[0]->channels, loop_flag); + vgmstream = allocate_vgmstream(data->output_channels, loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = data->segments[0]->meta_type; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 63c71836..dbeae35b 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -147,6 +147,10 @@ Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > + + @@ -177,6 +181,10 @@ RelativePath=".\formats.c" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 2f40fb24..02605a32 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -92,6 +92,7 @@ + @@ -202,6 +203,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 66dd3014..fa4257f5 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -47,6 +47,9 @@ + + Header Files + Header Files @@ -184,6 +187,9 @@ Source Files + + Source Files + Source Files diff --git a/src/mixing.c b/src/mixing.c new file mode 100644 index 00000000..88c77cc9 --- /dev/null +++ b/src/mixing.c @@ -0,0 +1,954 @@ +#include "vgmstream.h" +#include "mixing.h" +#include "plugins.h" +#include + + +#ifdef VGMSTREAM_MIXING + +/** + * Mixing lets vgmstream modify the resulting sample buffer before final output. + * This can be implemented in a number of ways but it's done like it is considering + * overall simplicity in coding, usage and performance (main complexity is allowing + * down/upmixing). Code is mostly independent with some hooks in the main vgmstream + * code. + * + * It works using two buffers: + * - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count + * - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count + * outbuf starts with decoded samples of vgmstream->channel size. This unsures that + * if no mixing is done (most common case) we can skip copying samples between buffers. + * Resulting outbuf after mixing has samples for ->output_channels (plus garbage). + * - output_channels is the resulting total channels (that may be less/more/equal) + * - input_channels is normally ->channels or ->output_channels when it's higher + * + * First, a meta (ex. TXTP) or plugin may add mixing commands through the API, + * validated so non-sensical mixes are ignored (to ensure mixing code doesn't + * have to recheck every time). Then, before starting to decode mixing must be + * manually activated, because plugins need to be ready for possibly different + * input/output channels. API could be improved but this way we can avoid having + * to update all plugins, while allowing internal setup and layer/segment mixing + * (may change in the future for simpler usage). + * + * Then after decoding normally, vgmstream applies mixing internally: + * - detect if mixing is active and needs to be done at this point (some effects + * like fades only apply after certain time) and skip otherwise. + * - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops + * apply float volumes) and slightly improve performance (avoids doing + * int16-to-float casts per mix, as it's not free) + * - apply all mixes on mixbuf + * - copy mixbuf to outbuf + * segmented/layered layouts handle mixing on their own. + * + * Mixing is tuned for most common case (no mix except fade-out at the end) and is + * fast enough but not super-optimized yet, there is some penalty the more effects + * are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0 + * could simply use a clear op), only use mixbuf if necessary (swap can be done without + * mixbuf if it goes first) or add function pointer indexes but isn't too important. + * Operations are applied once per "step" with 1 sample from all channels to simplify code + * (and maybe improve memory cache?), though maybe it should call one function per operation. + */ + +#define VGMSTREAM_MAX_MIXING 128 + + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + 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 */ +} mixing_data; + + +/* ******************************************************************* */ + +static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) { + int i; + int32_t fade_start, fade_end; + + for (i = 0; i < data->mixing_count; i++) { + mix_command_data mix = data->mixing_chain[i]; + + if (mix.command != MIX_FADE) + return 1; /* has non-fades = active */ + + /* check is current range falls within a fade + * (assuming fades were already optimized on add) */ + fade_start = mix.time_pre < 0 ? 0 : mix.time_pre; + fade_end = mix.time_post < 0 ? INT_MAX : mix.time_post; + + if (current_start < fade_end && current_end > fade_start) + return 1; + } + + return 0; +} + +static int32_t get_current_pos(VGMSTREAM* vgmstream) { + int32_t current_pos; + + if (vgmstream->loop_flag && vgmstream->current_sample > vgmstream->loop_start_sample) { + int loop_pre = vgmstream->loop_start_sample; + int loop_into = vgmstream->current_sample - vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + current_pos = loop_pre + loop_into + loop_samples*vgmstream->loop_count; + } + else { + current_pos = vgmstream->current_sample; + } + + return current_pos; +} + +static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) { + //todo optimizations: interleave calcs, maybe use cosf, powf, etc? + 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. + */ + + /* (curve math mostly from SoX/FFmpeg) */ + switch(mix->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 * M_PI )) / 2.0f; + break; + + case 'Q': /* quarter of sine wave (for musical fades) */ + gain = sin(index * M_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; + } + + 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 { + goto fail; + } + + *out_cur_vol = cur_vol; + return 1; +fail: + return 0; +} + +void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { + mixing_data *data = vgmstream->mixing_data; + int ch, s, m, ok; + + int32_t current_pos, current_subpos; + float temp_f, temp_min, temp_max, cur_vol; + float *temp_mixbuf; + sample_t *temp_outbuf; + + const float limiter_max = 32767.0f; + const float limiter_min = -32768.0f; + + + /* no support or not need to apply */ + if (!data || !data->mixing_on || data->mixing_count == 0) + return; + + /* try to skip if no ops apply (for example if fade set but does nothing yet) */ + current_pos = get_current_pos(vgmstream); + if (!is_active(data, current_pos, current_pos + sample_count)) + return; + + + /* use advancing buffer pointers to simplify logic */ + temp_mixbuf = data->mixbuf; + temp_outbuf = outbuf; + + /* apply mixes in order per channel */ + for (s = 0; s < sample_count; s++) { + /* reset after new sample 'step'*/ + float *stpbuf = temp_mixbuf; + int step_channels = vgmstream->channels; + + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */ + } + + 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: + temp_f = stpbuf[mix.ch_dst]; + stpbuf[mix.ch_dst] = stpbuf[mix.ch_src]; + stpbuf[mix.ch_src] = temp_f; + break; + + case MIX_ADD: + stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] + stpbuf[mix.ch_src] * mix.vol; + break; + + case MIX_VOLUME: + if (mix.ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch] * mix.vol; + } + } + else { + stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * mix.vol; + } + break; + + case MIX_LIMIT: + temp_max = limiter_max * mix.vol; + temp_min = limiter_min * mix.vol; + + if (mix.ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + if (stpbuf[ch] > temp_max) + stpbuf[ch] = temp_max; + else if (stpbuf[ch] < temp_min) + stpbuf[ch] = temp_min; + } + } + else { + if (stpbuf[mix.ch_dst] > temp_max) + stpbuf[mix.ch_dst] = temp_max; + else if (stpbuf[mix.ch_dst] < temp_min) + stpbuf[mix.ch_dst] = temp_min; + } + break; + + case MIX_UPMIX: + step_channels += 1; + for (ch = step_channels - 1; ch > mix.ch_dst; ch--) { + stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */ + } + stpbuf[mix.ch_dst] = 0; /* inserted as silent */ + break; + + case MIX_DOWNMIX: + step_channels -= 1; + for (ch = mix.ch_dst; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */ + } + break; + + case MIX_KILLMIX: + step_channels = mix.ch_dst; /* clamp channels */ + break; + + case MIX_FADE: + current_subpos = current_pos + s; + + ok = get_fade_gain(&mix, &cur_vol, current_subpos); + if (!ok) { + break; + } + + if (mix.ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch] * cur_vol; + } + } + else { + stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * cur_vol; + } + break; + + default: + break; + } + } + + temp_mixbuf += step_channels; + temp_outbuf += vgmstream->channels; + } + + /* copy resulting mix to output */ + 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] ); + } +} + +/* ******************************************************************* */ + +void mixing_init(VGMSTREAM* vgmstream) { + mixing_data *data = calloc(1, sizeof(mixing_data)); + if (!data) goto fail; + + data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */ + data->mixing_channels = vgmstream->channels; + data->output_channels = vgmstream->channels; + + vgmstream->mixing_data = data; + return; + +fail: + free(data); + return; +} + +void mixing_close(VGMSTREAM* vgmstream) { + mixing_data *data = NULL; + if (!vgmstream) return; + + data = vgmstream->mixing_data; + if (!data) return; + + free(data->mixbuf); + free(data); +} + +void mixing_update_channel(VGMSTREAM* vgmstream) { + mixing_data *data = vgmstream->mixing_data; + if (!data) return; + + /* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */ + data->mixing_channels++; + 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++; + + //;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 = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */ + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + mix.vol = 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; + + 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 <= 1) return; /* can't kill from 1 */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_KILLMIX; + mix.ch_dst = 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 chaning 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; + 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_prev->time_post < 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); +} + +/* ******************************************************************* */ + +void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch, output_channels; + + + if (mask == 0) { + mixing_push_volume(vgmstream, -1, volume); + return; + } + + output_channels = data ? data->output_channels : vgmstream->channels; + + for (ch = 0; ch < 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, output_channels; + + if (mask == 0) { + return; + } + + output_channels = data ? data->output_channels : vgmstream->channels; + + /* reverse remove all channels (easier this way as when removing channels numbers change) */ + for (ch = output_channels - 1; ch >= 0; ch--) { + if ((mask >> ch) & 1) + continue; + mixing_push_downmix(vgmstream, ch); + } +} + +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overlap) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, output_channels, selected_channels; + + /* set all channels (non-existant channels will be ignored) */ + if (mask == 0) { + mask = ~mask; + } + + output_channels = data ? data->output_channels : vgmstream->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; + + /* adjust volume (layer = lower, for layered bgm, overlap = same, for layered vocals) */ + if (!overlap) { + /* 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 (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 */ + } + + 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 (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data ? data->output_channels : vgmstream->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; + + 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) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, layer, layer_ch, layer_num, loop, output_channels; + int32_t change_pos, change_time; + + + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data ? data->output_channels : vgmstream->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; + + /* must set fades to successively lower 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]-- + */ + for (loop = 1; loop < layer_num; loop++) { + double volume1 = 1 / sqrt(loop + 0); + double volume2 = 1 / sqrt(loop + 1); + + 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 */ + + ch = 0; + for (layer = 0; layer < layer_num; layer++) { + char type; + + 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 = ')'; + } + + 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); +} + +/* ******************************************************************* */ + +void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) { + mixing_data *data = vgmstream->mixing_data; + float *mixbuf_re = NULL; + + if (!data) 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; + + data->mixbuf = mixbuf_re; + data->mixing_on = 1; + + /* a bit wonky but eh... */ + if (vgmstream->channel_layout && vgmstream->channels != vgmstream->output_channels) { + vgmstream->channel_layout = 0; + 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 */ + + /* segments/layers are independant from external buffers and may always mix */ + + return; +fail: + return; +} + +void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels) { + mixing_data *data = vgmstream->mixing_data; + + if (!data) goto fail; + + if (output_channels) { + *output_channels = data->output_channels; + } + + if (input_channels) { + if (data->output_channels > vgmstream->channels) + *input_channels = data->output_channels; + else + *input_channels = vgmstream->channels; + } + + //;VGM_LOG("MIX: channels in=%i, out=%i, mix=%i\n", *input_channels, *output_channels, data->mixing_channels); + return; +fail: + return; +} + +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); +} + +/* ********************************************************* */ + +void vgmstream_mixing_downmix(VGMSTREAM *vgmstream, int max_channels) { + mixing_data *data = vgmstream->mixing_data; + + if (!data) goto fail; + + /* 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, 0); + + return; +fail: + return; +} + +#endif diff --git a/src/mixing.h b/src/mixing.h new file mode 100644 index 00000000..d5f9f1a7 --- /dev/null +++ b/src/mixing.h @@ -0,0 +1,41 @@ +#ifndef _MIXING_H_ +#define _MIXING_H_ + +#include "vgmstream.h" + +/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and + * outbuf must big enough to hold output_channels*samples_to_do */ +void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream); + +/* internal mixing pre-setup for vgmstream (doesn't imply usage). + * If init somehow fails next calls are ignored. */ +void mixing_init(VGMSTREAM* vgmstream); +void mixing_close(VGMSTREAM* vgmstream); +void mixing_update_channel(VGMSTREAM* vgmstream); + +/* Call to let vgmstream apply mixing, which must handle input/output_channels. + * Once mixing is active any new mixes are ignored (to avoid the possibility + * of down/upmixing without querying input/output_channels). */ +void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count); + +/* gets current mixing info */ +void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels); + +/* adds mixes filtering and optimizing if needed */ +void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src); +void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume); +void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume); +void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume); +void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst); +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); + +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_crosstrack(VGMSTREAM* vgmstream, int max); +void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max); + + +#endif /* _MIXING_H_ */ diff --git a/src/plugins.h b/src/plugins.h index bb2015f9..4f9af079 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -86,11 +86,11 @@ void vgmstream_tags_close(VGMSTREAM_TAGS* tags); * 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); +/* sets automatic downmixing if vgmstream's channels are higher than max_channels */ +void vgmstream_mixing_downmix(VGMSTREAM *vgmstream, int max_channels); + /* sets a fadeout */ //void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds); - -/* sets downmixing if needed */ -//void vgmstream_mixing_downmix(VGMSTREAM *vgmstream, int max_channels) #endif #endif /* _PLUGINS_H_ */ diff --git a/src/vgmstream.h b/src/vgmstream.h index 59a993c4..cf9107a4 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -1147,12 +1147,18 @@ typedef struct { int segment_count; VGMSTREAM **segments; int current_segment; + sample_t *buffer; + int input_channels; /* internal buffer channels */ + int output_channels; /* resulting channels (after mixing, if applied) */ } segmented_layout_data; /* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */ typedef struct { int layer_count; VGMSTREAM **layers; + sample_t *buffer; + int input_channels; /* internal buffer channels */ + int output_channels; /* resulting channels (after mixing, if applied) */ } layered_layout_data; /* for compressed NWA */