cleanup: simplify mixing

This commit is contained in:
bnnm 2024-07-23 22:31:30 +02:00
parent f1464ac2cc
commit 693b4685bb
6 changed files with 373 additions and 320 deletions

View File

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

View File

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

View File

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

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

View File

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

View File

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