mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-29 19:37:30 +01:00
Fix some TXTP mixing bugs and add loop option in fades
This commit is contained in:
parent
4fac5e7d3e
commit
4d7f5a3eb8
41
doc/TXTP.md
41
doc/TXTP.md
@ -305,17 +305,23 @@ Possible operations:
|
||||
- `Nu`: upmix (insert) N ('pushing' all following channels forward)
|
||||
- `Nd`: downmix (remove) N ('pulling' all following channels backward)
|
||||
- `ND`: downmix (remove) N and all following channels
|
||||
- `N(type)(time-start)+(time-length)`: defines a fade
|
||||
- `N(type)(position)(time-start)+(time-length)`: defines a fade
|
||||
* `type` can be `{` = fade-in, `}` = fade-out, `(` = crossfade-in, `)` = crossfade-out
|
||||
* crossfades are better tuned to use when changing between tracks
|
||||
* `(position)` pre-adjusts `(time-start)` to start after certain time (optional)
|
||||
* using multiple fades in the same channel will cancel previous fades
|
||||
* may only cancel when fade is after previous one
|
||||
* `}` then `{` makes sense, but `}` then `}` will make funny volume bumps
|
||||
* `}` then `{` or `{` then `}` makes sense, but `}` then `}` will make funny volume bumps
|
||||
* example: `1{0:10+0:5, 1}0:30+0:5` fades-in at 10 seconds, then fades-out at 30 seconds
|
||||
- `N^(volume-start)~(volume-end)=(shape)@(time-pre)~(time-start)+(time-length)~(time-last)`: defines full fade envelope
|
||||
* full definition of the above to allow precise volume changes over time
|
||||
* not necessarily fades, as you could set length 0 for volume "bumps", or make volumes 1.0~0.5
|
||||
* pre/post may be -1 to set "file start" and "file end", cancelled by next fade
|
||||
* full definition of a fade envelope to allow precise volume changes over time
|
||||
* not necessarily fades, as you could set length 0 for volume "bumps" like `1.0~0.5`
|
||||
* `(shape)` can be `{` = fade, `(` = crossfade, other values are reserved for internal testing and may change anytime
|
||||
* `(time-start)`+`(time-length)` make `(time-end)`
|
||||
* between `(time-pre)` and `(time-start)` song uses `(volume-start)`
|
||||
* between `(time-start)` and `(time-end)` song gradually changes `(volume-start)` to `(volume-end)` (depending on `(shape)`)
|
||||
* between `(time-end)` and `(time-post)` song uses `(volume-end)`
|
||||
* `time-pre/post` may be -1 to set "file start" and "file end", cancelled by next fade
|
||||
|
||||
Considering:
|
||||
- `N` and `M` are channels (*current* value after previous operators are applied)
|
||||
@ -324,6 +330,8 @@ Considering:
|
||||
- may use `x` instead of `*` and `_` instead of `:` (for mini-TXTP)
|
||||
- `(volume)` is a `N.N` decimal value where 1.0 is 100% base volume
|
||||
- negative volume inverts the waveform (for weird effects)
|
||||
- `(position)` can be `N.NL` or `NL` = N.N loops
|
||||
- if loop start is 1000 and loop end 5000, `0.0L` = 1000 samples, `1.0L` = 5000 samples, `2.0L` = 9000 samples, etc
|
||||
- `(time)` can be `N:NN(.n)` (minutes:seconds), `N.N` (seconds) or `N` (samples)
|
||||
- represents the file's global play time, so it may be set after N loops
|
||||
- beware of `10.0` (ten seconds) vs `10` (ten samples)
|
||||
@ -449,7 +457,7 @@ TXTP may even reference other TXTP, or files that require TXTH, for extra comple
|
||||
bgm bank#s2#c1,2
|
||||
```
|
||||
|
||||
You may add spaces as needed (but try to keep it simple and don't go overboard), though commands *must* start with `#(command)` (`#(space)(anything)` is a comment). Commands without corresponding file are ignored too (seen as comments too), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
|
||||
You may add spaces as needed (but try to keep it simple and don't go overboard), though commands *must* start with `#(command)` (`#(space)(anything)` is a comment). Commands without corresponding file are ignored too (seen as comments), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
|
||||
```
|
||||
# those are all equivalent
|
||||
song#s2#c1,2
|
||||
@ -510,17 +518,20 @@ Repeated commands overwrite previous setting, except comma-separated commands th
|
||||
```
|
||||
# overwrites, equivalent to #s2
|
||||
song#s1#s2
|
||||
|
||||
```
|
||||
```
|
||||
# adds, equivalent to #m1-2,3-4,5-6
|
||||
song#m1-2#m3-4
|
||||
commands = #m5-6
|
||||
# also added to song
|
||||
commands = #l 3.0
|
||||
```
|
||||
|
||||
The parser is fairly simplistic and lax, and may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files.
|
||||
|
||||
|
||||
## MINI-TXTP
|
||||
To simplify TXTP creation, if the .txtp doesn't set a name inside its filename is used directly including config. Note that extension must be included (since vgmstream needs a full filename). You can set `commands` inside the .txtp too:
|
||||
To simplify TXTP creation, if the .txtp doesn't set a name inside then its filename is used directly, including config. Note that extension must be included (since vgmstream needs a full filename). You can set `commands` inside the .txtp too:
|
||||
- *bgm.sxd2#12.txtp*: plays subsong 12
|
||||
- *bgm.sxd2#12.txtp*, , inside has `commands = #@volume 0.5`: plays subsong 12 at half volume
|
||||
- *bgm.sxd2.txtp*, , inside has `commands = #12 #@volume 0.5`: plays subsong 12 at half volume
|
||||
@ -563,7 +574,7 @@ song#m1+3,2+4,3D
|
||||
song#m1+3*0.7,2+4*0.7,3D
|
||||
|
||||
# downmix 4ch layers to stereo with equal adjusted volume (common layer mixdown)
|
||||
song#m0*0.7,m1*0.7,1+3*0.7,2+4*0.7,3D
|
||||
song#m0*0.7,1*0.7,1+3*0.7,2+4*0.7,3D
|
||||
|
||||
# downmix stereo to mono (ignored if file is 1ch)
|
||||
zelda-cdi.xa#m1d
|
||||
@ -591,21 +602,21 @@ song#m1-2,2*0.5
|
||||
# fade-in ch3+4 percussion track layer into main track, downmix to stereo
|
||||
# (may be split in multiple lines, no difference)
|
||||
okami-ryoshima_coast.aix#l2
|
||||
commands = #m3(1:10~0:05 # loop happens after ~1:10
|
||||
commands = #m4(1:10~0:05 # ch3/4 are percussion tracks
|
||||
commands = #m3(1:10+0:05 # loop happens after ~1:10
|
||||
commands = #m4(1:10+0:05 # ch3/4 are percussion tracks
|
||||
commands = #m1+3*0.707,2+4*0.707 # ch3/4 always mixed but silent until 1:10
|
||||
commands = #m3D # remove channels after all mixing
|
||||
|
||||
# same but fade-out percussion after second loop
|
||||
okami-ryoshima_coast.aix#l3
|
||||
commands = #m3(1:10~0:05,3)2:20~0:05
|
||||
commands = #m4(1:10~0:05,4)2:20~0:05
|
||||
commands = #m3(1:10+0:05,3)2:20+0:05
|
||||
commands = #m4(1:10+0:05,4)2:20+0:05
|
||||
commands = #m1+3*0.707,2+4*0.707,3D
|
||||
|
||||
# crossfade exploration and combat sections after loop
|
||||
ffxiii-2~eclipse.aix
|
||||
commands = #m1)1:50~0:10,m2)1:50~0:10
|
||||
commands = #m3(1:50~0:10,m4(1:50~0:10
|
||||
commands = #m1)1:50+0:10,2)1:50+0:10
|
||||
commands = #m3(1:50+0:10,4(1:50+0:10
|
||||
commands = #m1+3,2+4,3D # won't play at the same time, no volume needed
|
||||
|
||||
# ghetto voice removal (invert channel + other channel removes duplicated parts, and vocals are often layered)
|
||||
|
145
src/meta/txtp.c
145
src/meta/txtp.c
@ -5,10 +5,11 @@
|
||||
|
||||
|
||||
#define TXTP_LINE_MAX 1024
|
||||
#define TXTP_MIXING_MAX 128
|
||||
#define TXTP_MIXING_MAX 512
|
||||
#define TXTP_GROUP_MODE_SEGMENTED 'S'
|
||||
#define TXTP_GROUP_MODE_LAYERED 'L'
|
||||
#define TXTP_GROUP_REPEAT 'R'
|
||||
#define TXTP_POSITION_LOOPS 'L'
|
||||
|
||||
/* mixing info */
|
||||
typedef enum {
|
||||
@ -50,6 +51,8 @@ typedef struct {
|
||||
double time_start;
|
||||
double time_end;
|
||||
double time_post;
|
||||
double position;
|
||||
char position_type;
|
||||
|
||||
/* macros */
|
||||
int max;
|
||||
@ -69,8 +72,11 @@ typedef struct {
|
||||
int mixing_count;
|
||||
txtp_mix_data mixing[TXTP_MIXING_MAX];
|
||||
|
||||
int config_loop_count_set;
|
||||
double config_loop_count;
|
||||
int config_fade_time_set;
|
||||
double config_fade_time;
|
||||
int config_fade_delay_set;
|
||||
double config_fade_delay;
|
||||
int config_ignore_loop;
|
||||
int config_force_loop;
|
||||
@ -420,16 +426,21 @@ fail:
|
||||
|
||||
static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
|
||||
vgmstream->config_loop_count = current->config_loop_count;
|
||||
vgmstream->config_fade_time = current->config_fade_time;
|
||||
vgmstream->config_fade_delay = current->config_fade_delay;
|
||||
vgmstream->config_ignore_loop = current->config_ignore_loop;
|
||||
vgmstream->config_force_loop = current->config_force_loop;
|
||||
vgmstream->config_ignore_fade = current->config_ignore_fade;
|
||||
if (current->config_loop_count_set)
|
||||
vgmstream->config_loop_count = current->config_loop_count;
|
||||
if (current->config_fade_time_set)
|
||||
vgmstream->config_fade_time = current->config_fade_time;
|
||||
if (current->config_fade_delay_set)
|
||||
vgmstream->config_fade_delay = current->config_fade_delay;
|
||||
if (current->config_ignore_loop)
|
||||
vgmstream->config_ignore_loop = current->config_ignore_loop;
|
||||
if (current->config_force_loop)
|
||||
vgmstream->config_force_loop = current->config_force_loop;
|
||||
if (current->config_ignore_fade)
|
||||
vgmstream->config_ignore_fade = current->config_ignore_fade;
|
||||
|
||||
if (current->sample_rate > 0) {
|
||||
if (current->sample_rate > 0)
|
||||
vgmstream->sample_rate = current->sample_rate;
|
||||
}
|
||||
|
||||
if (current->loop_install) {
|
||||
if (current->loop_start_second > 0 || current->loop_end_second > 0) {
|
||||
@ -462,7 +473,7 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
|
||||
/* copy mixing list (should be done last as some mixes depend on config) */
|
||||
if (current->mixing_count > 0) {
|
||||
int m;
|
||||
int m, position_samples;
|
||||
|
||||
for (m = 0; m < current->mixing_count; m++) {
|
||||
txtp_mix_data mix = current->mixing[m];
|
||||
@ -489,6 +500,19 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
if (mix.time_pre < 0.0) mix.sample_pre = -1;
|
||||
if (mix.time_post < 0.0) mix.sample_post = -1;
|
||||
|
||||
if (mix.position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) {
|
||||
int loop_pre = vgmstream->loop_start_sample;
|
||||
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
|
||||
VGM_LOG("ls=%i + %i * %f\n", loop_pre, loop_samples, mix.position);
|
||||
position_samples = loop_pre + loop_samples * mix.position;
|
||||
|
||||
if (mix.sample_pre >= 0) mix.sample_pre += position_samples;
|
||||
mix.sample_start += position_samples;
|
||||
mix.sample_end += position_samples;
|
||||
if (mix.sample_post >= 0) mix.sample_post += position_samples;
|
||||
}
|
||||
|
||||
|
||||
mixing_push_fade(vgmstream, mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape,
|
||||
mix.sample_pre, mix.sample_start, mix.sample_end, mix.sample_post);
|
||||
break;
|
||||
@ -545,14 +569,17 @@ static void clean_filename(char * filename) {
|
||||
* - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times)
|
||||
*/
|
||||
|
||||
static int get_double(const char * config, double *value) {
|
||||
static int get_double(const char * config, double *value, int *is_set) {
|
||||
int n, m;
|
||||
double temp;
|
||||
|
||||
if (is_set) *is_set = 0;
|
||||
|
||||
m = sscanf(config, " %lf%n", &temp,&n);
|
||||
if (m != 1 || temp < 0)
|
||||
return 0;
|
||||
|
||||
if (is_set) *is_set = 1;
|
||||
*value = temp;
|
||||
return n;
|
||||
}
|
||||
@ -569,6 +596,26 @@ static int get_int(const char * config, int *value) {
|
||||
return n;
|
||||
}
|
||||
|
||||
static int get_position(const char * config, double *value_f, char *value_type) {
|
||||
int n,m;
|
||||
double temp_f;
|
||||
char temp_c;
|
||||
|
||||
/* test if format is position: N.n(type) */
|
||||
m = sscanf(config, " %lf%c%n", &temp_f,&temp_c,&n);
|
||||
if (m != 2 || temp_f < 0.0)
|
||||
return 0;
|
||||
/* test accepted chars as it will capture anything */
|
||||
if (temp_c != TXTP_POSITION_LOOPS)
|
||||
return 0;
|
||||
|
||||
VGM_LOG("found position: %f, %c\n", temp_f, temp_c);
|
||||
*value_f = temp_f;
|
||||
*value_type = temp_c;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static int get_time(const char * config, double *value_f, int32_t *value_i) {
|
||||
int n,m;
|
||||
int temp_i1, temp_i2;
|
||||
@ -752,6 +799,11 @@ static int get_fade(const char * config, txtp_mix_data *mix, int *out_n) {
|
||||
mix->time_pre = -1.0;
|
||||
mix->sample_pre = -1;
|
||||
|
||||
n = get_position(config, &mix->position, &mix->position_type);
|
||||
//if (n == 0) goto fail; /* optional */
|
||||
config += n;
|
||||
tn += n;
|
||||
|
||||
n = get_time(config, &mix->time_start, &mix->sample_start);
|
||||
if (n == 0) goto fail;
|
||||
config += n;
|
||||
@ -785,8 +837,8 @@ void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* parsers reads ch1 = first, but for mixing code ch0 = first
|
||||
* (if parser reads ch0 here it'll become -1 with special meaning in code) */
|
||||
/* parser reads ch1 = first, but for mixing code ch0 = first
|
||||
* (if parser reads ch0 here it'll become -1 with meaning of "all channels" in mixing code) */
|
||||
mix->ch_dst--;
|
||||
mix->ch_src--;
|
||||
mix->command = command;
|
||||
@ -797,14 +849,19 @@ void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) {
|
||||
|
||||
|
||||
static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) {
|
||||
|
||||
/* don't memcopy to allow list additions and ignore values not set,
|
||||
* as current can be "default" config */
|
||||
//*current = *cfg;
|
||||
|
||||
if (filename)
|
||||
strcpy(current->filename, filename);
|
||||
|
||||
current->subsong = cfg->subsong;
|
||||
if (cfg->subsong)
|
||||
current->subsong = cfg->subsong;
|
||||
|
||||
current->channel_mask = cfg->channel_mask;
|
||||
|
||||
//*current = *cfg; /* don't memcopy to allow list additions */ //todo save list first then memcpy
|
||||
if (cfg->channel_mask)
|
||||
current->channel_mask = cfg->channel_mask;
|
||||
|
||||
if (cfg->mixing_count > 0) {
|
||||
int i;
|
||||
@ -814,20 +871,40 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
|
||||
}
|
||||
}
|
||||
|
||||
current->config_loop_count = cfg->config_loop_count;
|
||||
current->config_fade_time = cfg->config_fade_time;
|
||||
current->config_fade_delay = cfg->config_fade_delay;
|
||||
current->config_ignore_loop = cfg->config_ignore_loop;
|
||||
current->config_force_loop = cfg->config_force_loop;
|
||||
current->config_ignore_fade = cfg->config_ignore_fade;
|
||||
if (cfg->config_loop_count_set) {
|
||||
current->config_loop_count_set = cfg->config_loop_count_set;
|
||||
current->config_loop_count = cfg->config_loop_count;
|
||||
}
|
||||
if (cfg->config_fade_time_set) {
|
||||
current->config_fade_time_set = cfg->config_fade_time_set;
|
||||
current->config_fade_time = cfg->config_fade_time;
|
||||
}
|
||||
if (cfg->config_fade_delay_set) {
|
||||
current->config_fade_delay_set = cfg->config_fade_delay_set;
|
||||
current->config_fade_delay = cfg->config_fade_delay;
|
||||
}
|
||||
if (cfg->config_ignore_loop) {
|
||||
current->config_ignore_loop = cfg->config_ignore_loop;
|
||||
}
|
||||
if (cfg->config_force_loop) {
|
||||
current->config_force_loop = cfg->config_force_loop;
|
||||
}
|
||||
if (cfg->config_ignore_fade) {
|
||||
current->config_ignore_fade = cfg->config_ignore_fade;
|
||||
}
|
||||
|
||||
current->sample_rate = cfg->sample_rate;
|
||||
current->loop_install = cfg->loop_install;
|
||||
current->loop_end_max = cfg->loop_end_max;
|
||||
current->loop_start_sample = cfg->loop_start_sample;
|
||||
current->loop_start_second = cfg->loop_start_second;
|
||||
current->loop_end_sample = cfg->loop_end_sample;
|
||||
current->loop_end_second = cfg->loop_end_second;
|
||||
if (cfg->sample_rate > 0) {
|
||||
current->sample_rate = cfg->sample_rate;
|
||||
}
|
||||
|
||||
if (cfg->loop_install) {
|
||||
current->loop_install = cfg->loop_install;
|
||||
current->loop_end_max = cfg->loop_end_max;
|
||||
current->loop_start_sample = cfg->loop_start_sample;
|
||||
current->loop_start_second = cfg->loop_start_second;
|
||||
current->loop_end_sample = cfg->loop_end_sample;
|
||||
current->loop_end_second = cfg->loop_end_second;
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_config(txtp_entry *cfg, char *config) {
|
||||
@ -982,15 +1059,15 @@ static void parse_config(txtp_entry *cfg, char *config) {
|
||||
//;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade);
|
||||
}
|
||||
else if (strcmp(command,"l") == 0) {
|
||||
config += get_double(config, &cfg->config_loop_count);
|
||||
config += get_double(config, &cfg->config_loop_count, &cfg->config_loop_count_set);
|
||||
//;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count);
|
||||
}
|
||||
else if (strcmp(command,"f") == 0) {
|
||||
config += get_double(config, &cfg->config_fade_time);
|
||||
config += get_double(config, &cfg->config_fade_time, &cfg->config_fade_time_set);
|
||||
//;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time);
|
||||
}
|
||||
else if (strcmp(command,"d") == 0) {
|
||||
config += get_double(config, &cfg->config_fade_delay);
|
||||
config += get_double(config, &cfg->config_fade_delay, &cfg->config_fade_delay_set);
|
||||
//;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay);
|
||||
}
|
||||
else if (strcmp(command,"h") == 0) {
|
||||
@ -1018,7 +1095,7 @@ static void parse_config(txtp_entry *cfg, char *config) {
|
||||
else if (strcmp(command,"@volume") == 0) {
|
||||
txtp_mix_data mix = {0};
|
||||
|
||||
nm = get_double(config, &mix.vol);
|
||||
nm = get_double(config, &mix.vol, NULL);
|
||||
config += nm;
|
||||
|
||||
if (nm == 0) continue;
|
||||
|
163
src/mixing.c
163
src/mixing.c
@ -48,7 +48,8 @@
|
||||
* (and maybe improve memory cache?), though maybe it should call one function per operation.
|
||||
*/
|
||||
|
||||
#define VGMSTREAM_MAX_MIXING 128
|
||||
#define VGMSTREAM_MAX_MIXING 512
|
||||
#define MIXING_PI 3.14159265358979323846f
|
||||
|
||||
|
||||
/* mixing info */
|
||||
@ -115,24 +116,72 @@ static int is_active(mixing_data *data, int32_t current_start, int32_t current_e
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t get_current_pos(VGMSTREAM* vgmstream) {
|
||||
static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
|
||||
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;
|
||||
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;
|
||||
current_pos = (vgmstream->current_sample - sample_count);
|
||||
}
|
||||
|
||||
return current_pos;
|
||||
}
|
||||
|
||||
static float get_fade_gain_curve(char shape, float index) {
|
||||
float gain;
|
||||
|
||||
/* don't bother doing calcs near 0.0/1.0 */
|
||||
if (index <= 0.0001f || index >= 0.9999f) {
|
||||
return index;
|
||||
}
|
||||
|
||||
//todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
|
||||
|
||||
/* (curve math mostly from SoX/FFmpeg) */
|
||||
switch(shape) {
|
||||
/* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast
|
||||
* (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */
|
||||
|
||||
case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */
|
||||
//gain = pow(0.1f, (1.0f - index) * 2.5f);
|
||||
gain = exp(-5.75646273248511f * (1.0f - index));
|
||||
break;
|
||||
case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */
|
||||
//gain = 1 - pow(0.1f, (index) * 2.5f);
|
||||
gain = 1 - exp(-5.75646273248511f * (index));
|
||||
break;
|
||||
|
||||
case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */
|
||||
gain = (1.0f - cos(index * MIXING_PI)) / 2.0f;
|
||||
break;
|
||||
|
||||
case 'Q': /* quarter of sine wave (for musical fades) */
|
||||
gain = sin(index * MIXING_PI / 2.0f);
|
||||
break;
|
||||
|
||||
case 'p': /* parabola (maybe for crossfades) */
|
||||
gain = 1.0f - sqrt(1.0f - index);
|
||||
break;
|
||||
case 'P': /* inverted parabola (maybe for fades) */
|
||||
gain = (1.0f - (1.0f - index) * (1.0f - index));
|
||||
break;
|
||||
|
||||
case 'T': /* triangular/linear (simpler/sharper fades) */
|
||||
default:
|
||||
gain = index;
|
||||
break;
|
||||
}
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
|
||||
//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) {
|
||||
@ -173,41 +222,7 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
|
||||
* 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;
|
||||
}
|
||||
gain = get_fade_gain_curve(mix->shape, index);
|
||||
|
||||
if (mix->vol_start < mix->vol_end) { /* fade in */
|
||||
cur_vol = mix->vol_start + range_vol * gain;
|
||||
@ -216,6 +231,7 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* fade is outside reach */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -230,20 +246,19 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
int ch, s, m, ok;
|
||||
|
||||
int32_t current_pos, current_subpos;
|
||||
float temp_f, temp_min, temp_max, cur_vol;
|
||||
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;
|
||||
|
||||
|
||||
/* 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);
|
||||
current_pos = get_current_pos(vgmstream, sample_count);
|
||||
if (!is_active(data, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
|
||||
@ -252,6 +267,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
temp_mixbuf = data->mixbuf;
|
||||
temp_outbuf = outbuf;
|
||||
|
||||
current_subpos = current_pos;
|
||||
|
||||
/* apply mixes in order per channel */
|
||||
for (s = 0; s < sample_count; s++) {
|
||||
/* reset after new sample 'step'*/
|
||||
@ -263,7 +280,7 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
}
|
||||
|
||||
for (m = 0; m < data->mixing_count; m++) {
|
||||
mix_command_data mix = data->mixing_chain[m];
|
||||
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:
|
||||
@ -274,34 +291,34 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
* - 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) {
|
||||
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;
|
||||
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;
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol;
|
||||
break;
|
||||
|
||||
case MIX_VOLUME:
|
||||
if (mix.ch_dst < 0) {
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
stpbuf[ch] = stpbuf[ch] * mix.vol;
|
||||
stpbuf[ch] = stpbuf[ch] * mix->vol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * mix.vol;
|
||||
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;
|
||||
temp_max = limiter_max * mix->vol;
|
||||
temp_min = limiter_min * mix->vol;
|
||||
|
||||
if (mix.ch_dst < 0) {
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
if (stpbuf[ch] > temp_max)
|
||||
stpbuf[ch] = temp_max;
|
||||
@ -310,47 +327,45 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
}
|
||||
}
|
||||
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;
|
||||
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--) {
|
||||
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 */
|
||||
stpbuf[mix->ch_dst] = 0; /* inserted as silent */
|
||||
break;
|
||||
|
||||
case MIX_DOWNMIX:
|
||||
step_channels -= 1;
|
||||
for (ch = mix.ch_dst; ch < step_channels; ch++) {
|
||||
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 */
|
||||
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);
|
||||
ok = get_fade_gain(mix, &cur_vol, current_subpos);
|
||||
if (!ok) {
|
||||
break;
|
||||
break; /* fade doesn't apply right now */
|
||||
}
|
||||
|
||||
if (mix.ch_dst < 0) {
|
||||
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;
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * cur_vol;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -359,6 +374,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
}
|
||||
}
|
||||
|
||||
current_subpos++;
|
||||
|
||||
temp_mixbuf += step_channels;
|
||||
temp_outbuf += vgmstream->channels;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user