mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-27 16:10:48 +01:00
cleanup: simplify mixing
This commit is contained in:
parent
f1464ac2cc
commit
693b4685bb
@ -2,227 +2,132 @@
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "mixing.h"
|
||||
#include "mixing_priv.h"
|
||||
#include "mixing_fades.h"
|
||||
#include "plugins.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
//TODO simplify
|
||||
/**
|
||||
* 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.
|
||||
* Mixer modifies decoded sample buffer before final output. This is implemented
|
||||
* mostly with simplicity in mind rather than performance. Process:
|
||||
* - detect if mixing applies at current moment or exit (mini performance optimization)
|
||||
* - copy/upgrade buf to float mixbuf if needed
|
||||
* - do mixing ops
|
||||
* - copy/downgrade mixbuf to original buf if needed
|
||||
*
|
||||
* Mixing may add or remove channels. input_channels is the buf's original channels,
|
||||
* and output_channels the resulting buf's channels. buf and mixbuf must be
|
||||
* as big as whichever of input/output channels is bigger.
|
||||
*
|
||||
* Mixing ops are added by a meta (ex. TXTP) or plugin through the API. Non-sensical
|
||||
* mixes are ignored (to avoid rechecking every time).
|
||||
*
|
||||
* Currently, mixing must be manually enabled before starting to decode, because plugins
|
||||
* need to setup bigger bufs when upmixing. (to be changed)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
static void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
|
||||
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
|
||||
static void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, 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
|
||||
*/
|
||||
buf_s16[s] = clamp16( (int32_t)buf_f32[s] );
|
||||
buf_f32[s] = buf_s16[s]; // / 32767.0f
|
||||
}
|
||||
}
|
||||
|
||||
static void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
|
||||
/* 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
|
||||
*/
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_subpos = 0;
|
||||
float temp_f, temp_min, temp_max, cur_vol = 0.0f;
|
||||
float *temp_mixbuf;
|
||||
sample_t *temp_outbuf;
|
||||
|
||||
const float limiter_max = 32767.0f;
|
||||
const float limiter_min = -32768.0f;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
|
||||
/* no support or not need to apply */
|
||||
if (!data || !data->mixing_on || data->mixing_count == 0)
|
||||
return;
|
||||
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades */
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades
|
||||
* (could be done in mix op but avoids upgrading bufs in some cases) */
|
||||
data->current_subpos = 0;
|
||||
if (data->has_fade) {
|
||||
int32_t current_pos = get_current_pos(vgmstream, sample_count);
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, is_fade_active(data, current_pos, current_pos + sample_count));
|
||||
if (!data->has_non_fade && !is_fade_active(data, current_pos, current_pos + sample_count))
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, mixer_op_fade_is_active(data, current_pos, current_pos + sample_count));
|
||||
if (!data->has_non_fade && !mixer_op_fade_is_active(data, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
|
||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
||||
current_subpos = current_pos;
|
||||
data->current_subpos = current_pos;
|
||||
}
|
||||
|
||||
|
||||
/* use advancing buffer pointers to simplify logic */
|
||||
temp_mixbuf = data->mixbuf; /* you'd think using a int32 temp buf would be faster but somehow it's slower? */
|
||||
temp_outbuf = outbuf;
|
||||
// upgrade buf for mixing (somehow using float buf rather than int32 is faster?)
|
||||
sbuf_copy_s16_to_f32(data->mixbuf, outbuf, sample_count, vgmstream->channels);
|
||||
|
||||
/* 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
|
||||
/* apply mixing ops in order. Some ops change total channels they may change around:
|
||||
* - 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
|
||||
* - 2ch w/ "1u" = downmix to 1ch (current_channels decreases once)
|
||||
*/
|
||||
data->current_channels = vgmstream->channels;
|
||||
for (int m = 0; m < data->mixing_count; m++) {
|
||||
mix_command_data* mix = &data->mixing_chain[m];
|
||||
|
||||
/* 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' */
|
||||
//TODO: set callback
|
||||
switch(mix->command) {
|
||||
case MIX_SWAP: mixer_op_swap(data, sample_count, mix); break;
|
||||
case MIX_ADD: mixer_op_add(data, sample_count, mix); break;
|
||||
case MIX_VOLUME: mixer_op_volume(data, sample_count, mix); break;
|
||||
case MIX_LIMIT: mixer_op_limit(data, sample_count, mix); break;
|
||||
case MIX_UPMIX: mixer_op_upmix(data, sample_count, mix); break;
|
||||
case MIX_DOWNMIX: mixer_op_downmix(data, sample_count, mix); break;
|
||||
case MIX_KILLMIX: mixer_op_killmix(data, sample_count, mix); break;
|
||||
case MIX_FADE: mixer_op_fade(data, sample_count, mix);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (m = 0; m < data->mixing_count; m++) {
|
||||
mix_command_data *mix = &data->mixing_chain[m];
|
||||
|
||||
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_ADD_COPY:
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src];
|
||||
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:
|
||||
ok = get_fade_gain(mix, &cur_vol, current_subpos);
|
||||
if (!ok) {
|
||||
break; /* fade doesn't apply right now */
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
current_subpos++;
|
||||
|
||||
temp_mixbuf += step_channels;
|
||||
temp_outbuf += vgmstream->channels;
|
||||
}
|
||||
|
||||
/* copy resulting temp mix to output */
|
||||
/* downgrade mix to original output */
|
||||
sbuf_copy_f32_to_s16(outbuf, data->mixbuf, sample_count, data->output_channels);
|
||||
}
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
void mixing_init(VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = calloc(1, sizeof(mixing_data));
|
||||
mixer_data_t* data = calloc(1, sizeof(mixer_data_t));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */
|
||||
@ -238,10 +143,10 @@ fail:
|
||||
}
|
||||
|
||||
void mixing_close(VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = NULL;
|
||||
if (!vgmstream) return;
|
||||
if (!vgmstream)
|
||||
return;
|
||||
|
||||
data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
if (!data) return;
|
||||
|
||||
free(data->mixbuf);
|
||||
@ -249,7 +154,7 @@ void mixing_close(VGMSTREAM* vgmstream) {
|
||||
}
|
||||
|
||||
void mixing_update_channel(VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
if (!data) return;
|
||||
|
||||
/* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */
|
||||
@ -262,7 +167,7 @@ void mixing_update_channel(VGMSTREAM* vgmstream) {
|
||||
|
||||
static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
|
||||
int i;
|
||||
mixing_data* data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
layered_layout_data* layout_data;
|
||||
uint32_t prev_cl;
|
||||
|
||||
@ -294,7 +199,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
|
||||
|
||||
/* channel layout + down/upmixing = ?, salvage what we can */
|
||||
static void fix_channel_layout(VGMSTREAM* vgmstream) {
|
||||
mixing_data* data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
|
||||
if (fix_layered_channel_layout(vgmstream))
|
||||
goto done;
|
||||
@ -310,22 +215,23 @@ done:
|
||||
((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = vgmstream->channel_layout;
|
||||
}
|
||||
|
||||
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
float *mixbuf_re = NULL;
|
||||
|
||||
if (!data) goto fail;
|
||||
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count) {
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
/* special value to not actually enable anything (used to query values) */
|
||||
if (max_sample_count <= 0)
|
||||
goto fail;
|
||||
return;
|
||||
|
||||
/* create or alter internal buffer */
|
||||
mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float));
|
||||
float *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;
|
||||
data->mixing_on = true;
|
||||
|
||||
fix_channel_layout(vgmstream);
|
||||
|
||||
@ -340,10 +246,11 @@ fail:
|
||||
}
|
||||
|
||||
void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_channels) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
int input_channels, output_channels;
|
||||
|
||||
if (!data) goto fail;
|
||||
if (!data)
|
||||
goto fail;
|
||||
|
||||
output_channels = data->output_channels;
|
||||
if (data->output_channels > vgmstream->channels)
|
||||
|
@ -1,24 +1,24 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "mixing.h"
|
||||
#include "mixing_priv.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
if (!data) return 0;
|
||||
static bool add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
|
||||
if (data->mixing_on) {
|
||||
VGM_LOG("MIX: ignoring new mixes when mixing active\n");
|
||||
return 0; /* to avoid down/upmixing after activation */
|
||||
return false; /* to avoid down/upmixing after activation */
|
||||
}
|
||||
|
||||
if (data->mixing_count + 1 > data->mixing_size) {
|
||||
VGM_LOG("MIX: too many mixes\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->mixing_chain[data->mixing_count] = *mix; /* memcpy */
|
||||
@ -26,19 +26,19 @@ static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
|
||||
|
||||
|
||||
if (mix->command == MIX_FADE) {
|
||||
data->has_fade = 1;
|
||||
data->has_fade = true;
|
||||
}
|
||||
else {
|
||||
data->has_non_fade = 1;
|
||||
data->has_non_fade = true;
|
||||
}
|
||||
|
||||
//;VGM_LOG("MIX: total %i\n", data->mixing_count);
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
|
||||
if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return;
|
||||
@ -51,7 +51,7 @@ 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) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
if (!data) return;
|
||||
|
||||
@ -60,7 +60,7 @@ void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume
|
||||
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.command = MIX_ADD;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.ch_src = ch_src;
|
||||
mix.vol = volume;
|
||||
@ -70,7 +70,7 @@ 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) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
|
||||
//if (ch_dst < 0) return; /* means all channels */
|
||||
@ -87,7 +87,7 @@ void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) {
|
||||
}
|
||||
|
||||
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
|
||||
//if (ch_dst < 0) return; /* means all channels */
|
||||
@ -104,7 +104,7 @@ void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) {
|
||||
}
|
||||
|
||||
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
int ok;
|
||||
|
||||
@ -124,7 +124,7 @@ void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
}
|
||||
|
||||
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
int ok;
|
||||
|
||||
@ -141,7 +141,7 @@ void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
}
|
||||
|
||||
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
int ok;
|
||||
|
||||
@ -159,7 +159,7 @@ void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
}
|
||||
|
||||
|
||||
static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
|
||||
static mix_command_data* get_last_fade(mixer_data_t* data, int target_channel) {
|
||||
int i;
|
||||
for (i = data->mixing_count; i > 0; i--) {
|
||||
mix_command_data *mix = &data->mixing_chain[i-1];
|
||||
@ -175,7 +175,7 @@ static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
|
||||
|
||||
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;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mix_command_data *mix_prev;
|
||||
|
||||
|
@ -11,9 +11,7 @@
|
||||
#define MIX_MACRO_BGM 'b'
|
||||
|
||||
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch;
|
||||
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
@ -22,7 +20,7 @@ void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ch = 0; ch < data->output_channels; ch++) {
|
||||
for (int ch = 0; ch < data->output_channels; ch++) {
|
||||
if (!((mask >> ch) & 1))
|
||||
continue;
|
||||
mixing_push_volume(vgmstream, ch, volume);
|
||||
@ -30,9 +28,7 @@ void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
}
|
||||
|
||||
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch;
|
||||
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
@ -41,7 +37,7 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
|
||||
}
|
||||
|
||||
/* reverse remove all channels (easier this way as when removing channels numbers change) */
|
||||
for (ch = data->output_channels - 1; ch >= 0; ch--) {
|
||||
for (int ch = data->output_channels - 1; ch >= 0; ch--) {
|
||||
if ((mask >> ch) & 1)
|
||||
continue;
|
||||
mixing_push_downmix(vgmstream, ch);
|
||||
@ -51,16 +47,13 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
|
||||
|
||||
/* 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;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
max = 0;
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
int max = 0;
|
||||
for (int i = 0; i < data->layer_count; i++) {
|
||||
int output_channels = 0;
|
||||
|
||||
mixing_info(data->layers[i], NULL, &output_channels);
|
||||
@ -73,11 +66,6 @@ static int get_layered_max_channels(VGMSTREAM* vgmstream) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -86,15 +74,16 @@ static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
return 0;
|
||||
|
||||
/* no channel down/upmixing (cannot guess output) */
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
for (int 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++) {
|
||||
layered_layout_data* l_data = vgmstream->layout_data;
|
||||
for (int i = 0; i < l_data->layer_count; i++) {
|
||||
int output_channels = 0;
|
||||
|
||||
mixing_info(l_data->layers[i], NULL, &output_channels);
|
||||
@ -110,9 +99,8 @@ static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
/* 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;
|
||||
int target_layer = 0, target_chs = 0, target_ch = 0, target_silence = 0;
|
||||
int ch_num, ch_max;
|
||||
|
||||
/* 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 */
|
||||
@ -120,7 +108,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
/* 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++) {
|
||||
for (int i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
@ -148,7 +136,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
/* 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++) {
|
||||
for (int i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
if (target_layer == i) {
|
||||
@ -165,7 +153,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
|
||||
if (layer_chs == target_chs) {
|
||||
/* 1:1 mapping */
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
for (int ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
}
|
||||
@ -184,7 +172,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
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++) {
|
||||
for (int ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
break;
|
||||
@ -196,13 +184,13 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
|
||||
/* drop non-target channels */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
for (int 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++) {
|
||||
for (int ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_downmix(vgmstream, ch_num); //+ ch
|
||||
}
|
||||
|
||||
@ -220,7 +208,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
|
||||
|
||||
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
int current, ch, output_channels, selected_channels;
|
||||
|
||||
if (!data)
|
||||
@ -304,7 +292,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
}
|
||||
|
||||
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
int current, ch, track, track_ch, track_num, output_channels;
|
||||
int32_t change_pos, change_next, change_time;
|
||||
|
||||
@ -368,7 +356,7 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
|
||||
}
|
||||
|
||||
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* data = vgmstream->mixing_data;
|
||||
int current, ch, layer, layer_ch, layer_num, loop, output_channels;
|
||||
int32_t change_pos, change_time;
|
||||
|
||||
@ -481,7 +469,7 @@ typedef enum {
|
||||
} mixing_position_t;
|
||||
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_data_t* 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;
|
||||
|
142
src/base/mixing_ops_common.c
Normal file
142
src/base/mixing_ops_common.c
Normal file
@ -0,0 +1,142 @@
|
||||
#include "mixing_priv.h"
|
||||
|
||||
|
||||
// TO-DO: some ops can be done with original PCM sbuf to avoid copying to the float sbuf
|
||||
// when there are no actual float ops (ex. 'swap', if no ' volume' )
|
||||
// Performance gain is probably fairly small, though.
|
||||
|
||||
void mixer_op_swap(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
float* sbuf = data->mixbuf;
|
||||
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
float temp_f = sbuf[mix->ch_dst];
|
||||
sbuf[mix->ch_dst] = sbuf[mix->ch_src];
|
||||
sbuf[mix->ch_src] = temp_f;
|
||||
|
||||
sbuf += data->current_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_add(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
float* sbuf = data->mixbuf;
|
||||
|
||||
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
sbuf[mix->ch_dst] = sbuf[mix->ch_dst] + sbuf[mix->ch_src] * mix->vol;
|
||||
|
||||
sbuf += data->current_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_volume(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
float* sbuf = data->mixbuf;
|
||||
|
||||
if (mix->ch_dst < 0) {
|
||||
/* "all channels", most common case */
|
||||
for (int s = 0; s < sample_count * data->current_channels; s++) {
|
||||
sbuf[s] = sbuf[s] * mix->vol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
sbuf[mix->ch_dst] = sbuf[mix->ch_dst] * mix->vol;
|
||||
|
||||
sbuf += data->current_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_limit(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
float* sbuf = data->mixbuf;
|
||||
|
||||
const float limiter_max = 32767.0f;
|
||||
const float limiter_min = -32768.0f;
|
||||
|
||||
const float temp_max = limiter_max * mix->vol;
|
||||
const float temp_min = limiter_min * mix->vol;
|
||||
|
||||
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
|
||||
if (mix->ch_dst < 0) {
|
||||
for (int ch = 0; ch < data->current_channels; ch++) {
|
||||
if (sbuf[ch] > temp_max)
|
||||
sbuf[ch] = temp_max;
|
||||
else if (sbuf[ch] < temp_min)
|
||||
sbuf[ch] = temp_min;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (sbuf[mix->ch_dst] > temp_max)
|
||||
sbuf[mix->ch_dst] = temp_max;
|
||||
else if (sbuf[mix->ch_dst] < temp_min)
|
||||
sbuf[mix->ch_dst] = temp_min;
|
||||
}
|
||||
|
||||
sbuf += data->current_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_upmix(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
int max_channels = data->current_channels;
|
||||
data->current_channels += 1;
|
||||
|
||||
float* sbuf_tmp = data->mixbuf + sample_count * data->current_channels;
|
||||
float* sbuf = data->mixbuf + sample_count * max_channels;
|
||||
|
||||
/* copy 'backwards' as otherwise would overwrite samples before moving them forward */
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
sbuf_tmp -= data->current_channels;
|
||||
sbuf -= max_channels;
|
||||
|
||||
int sbuf_ch = max_channels - 1;
|
||||
for (int ch = data->current_channels - 1; ch >= 0; ch--) {
|
||||
if (ch == mix->ch_dst) {
|
||||
sbuf_tmp[ch] = 0; /* inserted as silent */
|
||||
}
|
||||
else {
|
||||
sbuf_tmp[ch] = sbuf[sbuf_ch]; /* 'pull' channels backward */
|
||||
sbuf_ch--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_downmix(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
int max_channels = data->current_channels;
|
||||
data->current_channels -= 1;
|
||||
|
||||
float* sbuf = data->mixbuf;
|
||||
float* sbuf_tmp = sbuf;
|
||||
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
|
||||
for (int ch = 0; ch < mix->ch_dst; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch]; /* copy untouched channels */
|
||||
}
|
||||
|
||||
for (int ch = mix->ch_dst; ch < max_channels; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch + 1]; /* 'pull' dropped channels back */
|
||||
}
|
||||
|
||||
sbuf_tmp += data->current_channels;
|
||||
sbuf += max_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_killmix(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
int max_channels = data->current_channels;
|
||||
data->current_channels = mix->ch_dst; /* clamp channels */
|
||||
|
||||
float* sbuf = data->mixbuf;
|
||||
float* sbuf_tmp = sbuf;
|
||||
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
for (int ch = 0; ch < data->current_channels; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch];
|
||||
}
|
||||
|
||||
sbuf_tmp += data->current_channels;
|
||||
sbuf += max_channels;
|
||||
}
|
||||
}
|
@ -1,65 +1,9 @@
|
||||
#ifndef _MIXING_FADE_H_
|
||||
#define _MIXING_FADE_H_
|
||||
|
||||
#include "mixing_priv.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#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;
|
||||
|
||||
@ -68,7 +12,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
return index;
|
||||
}
|
||||
|
||||
//todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
|
||||
//TODO optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
|
||||
|
||||
/* (curve math mostly from SoX/FFmpeg) */
|
||||
switch(shape) {
|
||||
@ -79,6 +23,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
//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));
|
||||
@ -95,6 +40,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
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;
|
||||
@ -108,7 +54,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
return gain;
|
||||
}
|
||||
|
||||
static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
|
||||
static bool 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) {
|
||||
@ -159,13 +105,69 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
|
||||
}
|
||||
else {
|
||||
/* fade is outside reach */
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
*out_cur_vol = cur_vol;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
void mixer_op_fade(mixer_data_t* data, int32_t sample_count, mix_command_data* mix) {
|
||||
float* sbuf = data->mixbuf;
|
||||
float new_gain = 0.0f;
|
||||
|
||||
int channels = data->current_channels;
|
||||
int32_t current_subpos = data->current_subpos;
|
||||
|
||||
//TODO optimize for case 0?
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
bool fade_applies = get_fade_gain(mix, &new_gain, current_subpos);
|
||||
if (!fade_applies) //TODO optimize?
|
||||
continue;
|
||||
|
||||
if (mix->ch_dst < 0) {
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
sbuf[ch] = sbuf[ch] * new_gain;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sbuf[mix->ch_dst] = sbuf[mix->ch_dst] * new_gain;
|
||||
}
|
||||
|
||||
sbuf += channels;
|
||||
current_subpos++;
|
||||
}
|
||||
|
||||
data->current_subpos = current_subpos;
|
||||
}
|
||||
|
||||
|
||||
bool mixer_op_fade_is_active(mixer_data_t* data, int32_t current_start, int32_t current_end) {
|
||||
|
||||
for (int 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 true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
#ifndef _MIXING_PRIV_H_
|
||||
#define _MIXING_PRIV_H_
|
||||
#include "../streamtypes.h"
|
||||
|
||||
#include "../vgmstream.h"
|
||||
#define VGMSTREAM_MAX_MIXING 512
|
||||
|
||||
//TODO rename
|
||||
|
||||
/* mixing info */
|
||||
typedef enum {
|
||||
MIX_SWAP,
|
||||
MIX_ADD,
|
||||
MIX_ADD_COPY,
|
||||
MIX_VOLUME,
|
||||
MIX_LIMIT,
|
||||
MIX_UPMIX,
|
||||
@ -37,16 +38,29 @@ typedef struct {
|
||||
typedef struct {
|
||||
int mixing_channels; /* max channels needed to mix */
|
||||
int output_channels; /* resulting channels after mixing */
|
||||
int mixing_on; /* mixing allowed */
|
||||
|
||||
bool 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 */
|
||||
int current_channels; /* state: channels may increase/decrease during ops */
|
||||
int32_t current_subpos; /* state: current sample pos in the stream */
|
||||
|
||||
/* fades only apply at some points, other mixes are active */
|
||||
int has_non_fade;
|
||||
int has_fade;
|
||||
} mixing_data;
|
||||
|
||||
bool has_non_fade;
|
||||
bool has_fade;
|
||||
} mixer_data_t;
|
||||
|
||||
void mixer_op_swap(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_add(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_volume(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_limit(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_upmix(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_downmix(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_killmix(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
void mixer_op_fade(mixer_data_t* data, int32_t sample_count, mix_command_data* mix);
|
||||
bool mixer_op_fade_is_active(mixer_data_t* data, int32_t current_start, int32_t current_end);
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user