Add TXTP e/L options to alter loop config, tweak priority/consistency

This commit is contained in:
bnnm 2020-06-21 00:33:21 +02:00
parent 75f7115e28
commit cb38467380
8 changed files with 327 additions and 216 deletions

View File

@ -72,12 +72,9 @@ typedef struct {
char * outfilename; char * outfilename;
char * tag_filename; char * tag_filename;
int decode_only; int decode_only;
int ignore_loop; int play_forever;
int force_loop;
int really_force_loop;
int play_sdtout; int play_sdtout;
int play_wreckless; int play_wreckless;
int play_forever;
int print_metaonly; int print_metaonly;
int print_adxencd; int print_adxencd;
int print_oggenc; int print_oggenc;
@ -86,10 +83,15 @@ typedef struct {
int write_lwav; int write_lwav;
int only_stereo; int only_stereo;
int stream_index; int stream_index;
double loop_count; double loop_count;
double fade_time; double fade_time;
double fade_delay; double fade_delay;
int ignore_fade; int ignore_fade;
int ignore_loop;
int force_loop;
int really_force_loop;
int seek_samples; int seek_samples;
/* not quite config but eh */ /* not quite config but eh */
@ -279,36 +281,46 @@ static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) {
} }
} }
static void apply_config(VGMSTREAM * vgmstream, cli_config *cfg) { static void apply_config(VGMSTREAM* vgmstream, cli_config* cfg) {
/* honor suggested config, if any (defined order matters) /* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
* note that ignore_fade and play_forever should take priority */ //if (vgmstream->config.play_forever) { /* not really suited for CLI */
if (vgmstream->config_loop_count > 0.0) { // cfg->play_forever = 1;
cfg->loop_count = vgmstream->config_loop_count; // cfg->ignore_loop = 0;
//}
if (vgmstream->config.loop_count_set) {
cfg->loop_count = vgmstream->config.loop_count;
cfg->play_forever = 0;
cfg->ignore_loop = 0;
} }
if (vgmstream->config_fade_delay > 0.0) { if (vgmstream->config.fade_delay_set) {
cfg->fade_delay = vgmstream->config_fade_delay; cfg->fade_delay = vgmstream->config.fade_delay;
} }
if (vgmstream->config_fade_time > 0.0) { if (vgmstream->config.fade_time_set) {
cfg->fade_time = vgmstream->config_fade_time; cfg->fade_time = vgmstream->config.fade_time;
} }
if (vgmstream->config_force_loop) { if (vgmstream->config.ignore_fade) {
cfg->really_force_loop = 1;
}
if (vgmstream->config_ignore_loop) {
cfg->ignore_loop = 1;
}
if (vgmstream->config_ignore_fade) {
cfg->ignore_fade = 1; cfg->ignore_fade = 1;
} }
/* remove non-compatible options */ if (vgmstream->config.force_loop) {
if (cfg->play_forever) {
cfg->ignore_fade = 0;
cfg->ignore_loop = 0; cfg->ignore_loop = 0;
cfg->force_loop = 1;
cfg->really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->ignore_loop = 0;
cfg->force_loop = 0;
cfg->really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->ignore_loop = 1;
cfg->force_loop = 0;
cfg->really_force_loop = 0;
} }
/* change vgmstream's loop stuff (ignore loop goes last) */
/* apply config */
if (cfg->force_loop && !vgmstream->loop_flag) { if (cfg->force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
} }
@ -319,6 +331,14 @@ static void apply_config(VGMSTREAM * vgmstream, cli_config *cfg) {
vgmstream_force_loop(vgmstream, 0, 0,0); vgmstream_force_loop(vgmstream, 0, 0,0);
} }
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->play_forever = 0;
}
if (cfg->play_forever) {
cfg->ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */ /* loop N times, but also play stream end instead of fading out */
if (cfg->loop_count > 0 && cfg->ignore_fade) { if (cfg->loop_count > 0 && cfg->ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)cfg->loop_count); vgmstream_set_loop_target(vgmstream, (int)cfg->loop_count);

View File

@ -12,17 +12,21 @@ file1
... ...
fileN fileN
mode = (mode) # "segments" is the default if not set mode = (segments|layers|mixed) # "segments" is the default if not set
``` ```
You can set commands to alter how files play (described later). Having a single file is ok too, so are subdirs:
```
# set "subsong" command for single file inside subdir
sounds/file#12
# will be ignored as none make sense here and is treated as "single" mode You can set commands to alter how files play (described later), and having a single file is ok too:
#mode = layers/segments/mixed ```
file#12 # set "subsong" command for single file
#mode is ignored here as there is only one file
``` ```
*files* may be anything accepted by the file system (including spaces and symbols), and you can use subdirs. Separators can be `\` or `/`, but stick to `/` for consistency. Commands may be chained and use spaces as needed (also see "TXTP parsing" section).
```
sounds/bgm.fsb #s2 #i #for file inside subdir: play subsong 2 + disable looping
```
### Segments mode ### Segments mode
Some games clumsily loop audio by using multiple full file "segments", so you can play separate intro + loop files together as a single track. Some games clumsily loop audio by using multiple full file "segments", so you can play separate intro + loop files together as a single track.
@ -38,7 +42,7 @@ loop_start_segment = 2 # 2nd file start
loop_end_segment = 2 # optional, default is last loop_end_segment = 2 # optional, default is last
mode = segments # optional, default is segments mode = segments # optional, default is segments
``` ```
You can also set looping to the last segment like this: You can (should) set looping to the last segment like this:
``` ```
BGM01_BEGIN.VAG BGM01_BEGIN.VAG
BGM01_LOOPED.VAG BGM01_LOOPED.VAG
@ -181,7 +185,7 @@ You can set file commands by adding multiple `#(command)` after the name. `#(spa
### Subsong selection for bank formats ### Subsong selection for bank formats
**`#(number)` or `#s(number)`**: set subsong (number) **`#(number)` or `#s(number)`**: set subsong (number)
**Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)**: *bgm_12.txtp* **Super Robot Taisen OG Saga: Masou Kishin III - Pride of Justice (Vita)**: *bgm_12.txtp*
``` ```
# select subsong 12 # select subsong 12
bgm.sxd2#12 bgm.sxd2#12
@ -192,7 +196,7 @@ bgm.sxd2#12
``` ```
### Play segmented subsong ranges as one ### Play segmented subsong ranges as one
**`#(number)~(number)` or `#s(number)~(number)`**: set multiple subsong segments at a time, to avoid so much C&P **`#(number)~(number)` or `#s(number)~(number)`**: set multiple subsong segments at a time, to avoid so much C&P.
**Prince of Persia Sands of Time**: *song_01.txtp* **Prince of Persia Sands of Time**: *song_01.txtp*
``` ```
@ -211,71 +215,73 @@ song#3#h22050
``` ```
### Channel mask for channel subsongs/layers ### Channel removing/masking for channel subsongs/layers
**`#c(number)`** (single) or **`#c(number)~(number)`** (range): set number of channels to play. You can add multiple comma-separated numbers, or use ` ` space or `-` as separator and combine multiple ranges with single channels too. **`C(number)`** (single) or **`#C(number)~(number)`** (range), **`#c(number)`**: set number of channels to play. You can add multiple comma-separated numbers, or use ` ` space or `-` as separator and combine multiple ranges with single channels too.
If you use **`C(number)`** (uppercase) it will remove non-selected channels. This just a shortcut for macro `#@track` (described later):
If you use **`c(number)`** (lowercase) it doesn't change the final number of channels, just mutes non-selected channels (for backwards compatibility).
**Final Fantasy XIII-2**: *music_Home_01.ps3.txtp* **Final Fantasy XIII-2**: *music_Home_01.ps3.txtp*
``` ```
#plays channels 1 and 2 = 1st subsong #plays channels 1 and 2 = 1st subsong
music_Home.ps3.scd#c1,2 music_Home.ps3.scd#C1,2
``` ```
``` ```
#plays channels 3 and 4 = 2nd subsong #plays channels 3 and 4 = 2nd subsong (muting 1 and 2)
music_Home.ps3.scd#c3 4 music_Home.ps3.scd#c3 4
#plays 1 to 3 #plays 1 to 3
music_Home.ps3.scd#c1~3 music_Home.ps3.scd#C1~3
```
Doesn't change the final number of channels though, just mutes non-selected channels.
If you use **`C(number)`** it will remove non-selected channels (not done directly for backwards compatibility). This just a shortcut for macro `#@track` (described later):
```
#plays channels 3 and 4 = 2nd subsong and removes other channels
music_Home.ps3.scd#C3 4
``` ```
### Play settings ### Play settings
**`#l(loops)`**, **`#f(fade)`**, **`#d(fade-delay)`**, **`#i(ignore loop)`**, **`#F(ignore fade)`**, **`#E(end-to-end loop)`** **`#L`**: play forever (if loops are set and player supports it)
**`#l(loops)`**: set target number of loops (if file loops)
**`#f(fade time)`**: set (in seconds) how long the fade out lasts (if file loops)
**`#d(fade delay)`**: set (in seconds) the delay before fade kicks in after last loop (if file loops)
**`#F`**: don't fade out after N loops but continue playing the song's original end (if file loops)
**`#e`**: set full looping (end-to-end) but only if file doesn't have loop points (mainly for autogenerated .txtp)
**`#E`**: force full looping (end-to-end), overriding original loop points
**`#i`**: ignore and disable loop points, simply stopping after song's sample count (if file loops)
They are equivalent to some `test.exe/vgmstream_cli` options. Settings should mix with or override player's defaults. If player has "play forever" setting loops disables it (while other options don't quite apply), while forcing full loops would allow to play forever, or setting ignore looping would disable it.
Those setting should override player's defaults if set. They are equivalent to some test.exe options.
**God Hand (PS2)**: *boss2_3ningumi_ver6.txtp* **God Hand (PS2)**: *boss2_3ningumi_ver6.txtp*
``` ```
# set number of loops boss2_3ningumi_ver6.adx#l3 #default is usually 2.0
boss2_3ningumi_ver6.adx#l3
``` ```
``` ```
# set fade time (in seconds) boss2_3ningumi_ver6.adx#f11.5 #default is usually 10.0
boss2_3ningumi_ver6.adx#f10.5
``` ```
``` ```
# set fade delay (in seconds) boss2_3ningumi_ver6.adx#d0.5 #default is usually 0.0
boss2_3ningumi_ver6.adx#d0.5
``` ```
``` ```
# ignore and disable loops boss2_3ningumi_ver6.adx#i #this song has a nice stop
boss2_3ningumi_ver6.adx#i
``` ```
``` ```
# don't fade out and instead play the song ending after boss2_3ningumi_ver6.adx#F #loop some then hear that stop
boss2_3ningumi_ver6.adx#F # this song has a nice stop
``` ```
``` ```
# force full loops from end-to-end boss2_3ningumi_ver6.adx#e #song has loops, so ignored here
boss2_3ningumi_ver6.adx#E
``` ```
``` ```
# settings can be combined boss2_3ningumi_ver6.adx#E #force full loops
boss2_3ningumi_ver6.adx#l2#F # 2 loops + ending
``` ```
``` ```
# settings can be combined boss2_3ningumi_ver6.adx#L #keep on loopin'
boss2_3ningumi_ver6.adx#l1.5#d1#f5
``` ```
``` ```
# boss2_3ningumi_ver6.adx#l1.0#F # this is equivalent to #i boss2_3ningumi_ver6.adx #l 3 #F #combined: 3 loops + ending
```
```
boss2_3ningumi_ver6.adx #l1.5#d1#f5 #combined: partial loops + some delay + smaller fade
```
```
# boss2_3ningumi_ver6.adx #l1.0 #F #combined: equivalent to #i
``` ```
@ -314,9 +320,9 @@ ptp_btl_bgm_voice.sgd#s1#h11050
### Install loops ### Install loops
**`#I(loop start time) (loop end time)`**: force/override looping values, same as .pos but nicer. Loop end is optional and defaults to total samples. **`#I(loop start time) [loop end time]`**: force/override looping values, same as .pos but nicer. Loop end is optional and defaults to total samples.
Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of the subtle difference between 10.0 (ten seconds) and 10 (ten samples). Wrong loop values (for example loop end being much larger than file's samples) will be ignored, but there is some leeway when using seconds for the loop end. Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds, with dot), `0xN` (samples in hex format) or `N` (samples). Beware of the subtle difference between 10.0 (ten seconds) and 10 (ten samples). Wrong loop values (for example loop end being much larger than file's samples) will be ignored, but there is some leeway when using seconds for the loop end.
**Jewels Ocean (PC)** **Jewels Ocean (PC)**
``` ```
@ -470,7 +476,7 @@ BGM_0_012_07.wem
mode = layers mode = layers
# plays R of BGM_0_012_04 and L of BGM_0_012_07 # plays R of BGM_0_012_04 and L of BGM_0_012_07
commands = #c2,3 commands = #C2,3
``` ```
As it applies at the end, some options with ambiguous or technically hard to handle meanings may be ignored: As it applies at the end, some options with ambiguous or technically hard to handle meanings may be ignored:
@ -493,18 +499,21 @@ TXTP may even reference other TXTP, or files that require TXTH, for extra comple
### TXTP parsing ### TXTP parsing
*Filenames* may be anything accepted by the file system, including spaces and symbols, and multiple *commands* can be chained: Here is a rough look of how TXTP parses files, so you get a better idea of what's going on if some command fails.
*Filenames* should accept be anything accepted by the file system, and multiple *commands* can be chained:
``` ```
bgm bank#s2#c1,2 subdir name/bgm bank.fsb#s2#C1,2
subdir name/bgm bank.fsb #s2 #C1,2 #comment
``` ```
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): Commands may add spaces as needed, but try to keep it simple and don't go overboard). They *must* start with `#(command)`, as `#(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 # those are all equivalent
song#s2#c1,2 song#s2#C1,2
song #s2#c1,2 # comment song #s2#C1,2 # comment
song #s 2 #c1,2# comment song #s 2 #C1,2# comment
song #s 2 #c 1 , 2# comment song #s 2 #C 1 , 2# comment
#s2 #ignores rogue commands/comments #s2 #ignores rogue commands/comments
@ -514,17 +523,18 @@ song #E enable
song #E 1 song #E 1
song #Enable song #Enable
song #h -48000 song #h -48000
song #l -2.0
# accepted # accepted
song #E # comment song #E # comment
song #c1, 2, 3 song #C1, 2, 3
song #c 1 2 3 song #C 1 2 3
# ignores first and reads second # ignores first and reads second
song #s TWO#c1,2 song #s TWO#C1,2
# seen as #s1#c1,2 # seen as #s1#C1,2
song #s 1,2 #c1,2 song #s 1,2 #C1,2
# all seen as #h48000 # all seen as #h48000
song #h48000 song #h48000
@ -535,7 +545,7 @@ song #h 48000mhz
song #h hz48000 song #h hz48000
# ignored as channels don't go that high (may be modified on request) # ignored as channels don't go that high (may be modified on request)
song #c32,41 song #C32,41
# swaps 1 with 2 # swaps 1 with 2
song #m1-2 song #m1-2
@ -552,7 +562,7 @@ loop_start_segment = 1 #s2# #commands here are ignored
song song
commands=#s2 # commands here are allowed commands=#s2 # commands here are allowed
commands= #c1,2 commands= #C1,2
``` ```
Repeated commands overwrite previous setting, except comma-separated commands that are additive: Repeated commands overwrite previous setting, except comma-separated commands that are additive:

View File

@ -318,7 +318,6 @@ void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
seek_pos_samples = (int) audio_math::time_to_samples(p_seconds, vgmstream->sample_rate); seek_pos_samples = (int) audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
int max_buffer_samples = SAMPLE_BUFFER_SIZE; int max_buffer_samples = SAMPLE_BUFFER_SIZE;
bool loop_okay = config.song_play_forever && vgmstream->loop_flag && !config.song_ignore_loop && !force_ignore_loop; bool loop_okay = config.song_play_forever && vgmstream->loop_flag && !config.song_ignore_loop && !force_ignore_loop;
int loop_skips = 0;
// possible when disabling looping without refreshing foobar's cached song length // possible when disabling looping without refreshing foobar's cached song length
// (with infinite looping on p_seconds can't go over seek bar though) // (with infinite looping on p_seconds can't go over seek bar though)
@ -326,13 +325,13 @@ void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
seek_pos_samples = stream_length_samples; seek_pos_samples = stream_length_samples;
int corrected_pos_samples = seek_pos_samples; int corrected_pos_samples = seek_pos_samples;
int loop_length = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
int loop_count = 0;
// optimize seeks withing loops // optimize seeks withing loops
if (vgmstream->loop_flag && (vgmstream->loop_end_sample - vgmstream->loop_start_sample) && seek_pos_samples >= vgmstream->loop_end_sample) { if (vgmstream->loop_flag && loop_length > 0 && seek_pos_samples >= vgmstream->loop_end_sample) {
int loop_length = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
corrected_pos_samples -= vgmstream->loop_start_sample; corrected_pos_samples -= vgmstream->loop_start_sample;
loop_skips = corrected_pos_samples / loop_length; loop_count = corrected_pos_samples / loop_length;
corrected_pos_samples %= loop_length; corrected_pos_samples %= loop_length;
corrected_pos_samples += vgmstream->loop_start_sample; corrected_pos_samples += vgmstream->loop_start_sample;
} }
@ -356,9 +355,9 @@ void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
while(decode_pos_samples < corrected_pos_samples) { while(decode_pos_samples < corrected_pos_samples) {
int seek_samples = max_buffer_samples; int seek_samples = max_buffer_samples;
if((decode_pos_samples + max_buffer_samples >= stream_length_samples) && !loop_okay) if ((decode_pos_samples + max_buffer_samples >= stream_length_samples) && !loop_okay)
seek_samples = stream_length_samples - seek_pos_samples; seek_samples = stream_length_samples - seek_pos_samples;
if(decode_pos_samples + max_buffer_samples > seek_pos_samples) if (decode_pos_samples + max_buffer_samples > seek_pos_samples)
seek_samples = seek_pos_samples - decode_pos_samples; seek_samples = seek_pos_samples - decode_pos_samples;
decode_pos_samples += seek_samples; decode_pos_samples += seek_samples;
@ -366,7 +365,7 @@ void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
} }
// seek may have been clamped to skip unneeded loops, adjust as some internals need this value // seek may have been clamped to skip unneeded loops, adjust as some internals need this value
vgmstream->loop_count += loop_skips; //todo evil, make seek_vgmstream vgmstream->loop_count = loop_count; //todo make seek_vgmstream, not ok if seeking in fade section with ignore_fade
// remove seek loop correction from counter so file ends correctly // remove seek loop correction from counter so file ends correctly
decode_pos_samples = seek_pos_samples; decode_pos_samples = seek_pos_samples;
@ -552,52 +551,74 @@ void input_vgmstream::set_config_defaults(foobar_song_config *current) {
current->song_loop_count = loop_count; current->song_loop_count = loop_count;
current->song_fade_time = fade_seconds; current->song_fade_time = fade_seconds;
current->song_fade_delay = fade_delay_seconds; current->song_fade_delay = fade_delay_seconds;
current->song_ignore_loop = ignore_loop;
current->song_really_force_loop = 0;
current->song_ignore_fade = 0; current->song_ignore_fade = 0;
current->song_force_loop = 0;
current->song_really_force_loop = 0;
current->song_ignore_loop = ignore_loop;
} }
void input_vgmstream::apply_config(VGMSTREAM * vgmstream, foobar_song_config *current) { void input_vgmstream::apply_config(VGMSTREAM* vgmstream, foobar_song_config* cfg) {
/* honor suggested config, if any (defined order matters) /* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
* note that ignore_fade and play_forever should take priority */ if (vgmstream->config.play_forever) {
if (vgmstream->config_loop_count) { cfg->song_play_forever = 1;
current->song_loop_count = vgmstream->config_loop_count; cfg->song_ignore_loop = 0;
} }
if (vgmstream->config_fade_delay) { if (vgmstream->config.loop_count_set) {
current->song_fade_delay = vgmstream->config_fade_delay; cfg->song_loop_count = vgmstream->config.loop_count;
cfg->song_play_forever = 0;
cfg->song_ignore_loop = 0;
} }
if (vgmstream->config_fade_time) { if (vgmstream->config.fade_delay_set) {
current->song_fade_time = vgmstream->config_fade_time; cfg->song_fade_delay = vgmstream->config.fade_delay;
} }
if (vgmstream->config_force_loop) { if (vgmstream->config.fade_time_set) {
current->song_really_force_loop = 1; cfg->song_fade_time = vgmstream->config.fade_time;
} }
if (vgmstream->config_ignore_loop) { if (vgmstream->config.ignore_fade) {
current->song_ignore_loop = 1; cfg->song_ignore_fade = 1;
}
if (vgmstream->config_ignore_fade) {
current->song_ignore_fade = 1;
} }
/* remove non-compatible options */ if (vgmstream->config.force_loop) {
if (current->song_play_forever) { cfg->song_ignore_loop = 0;
current->song_ignore_fade = 0; cfg->song_force_loop = 1;
current->song_ignore_loop = 0; cfg->song_really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->song_ignore_loop = 1;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 0;
} }
/* change loop stuff, in no particular order */
if (current->song_really_force_loop) { /* apply config */
if (cfg->song_force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
} }
if (current->song_ignore_loop) { if (cfg->song_really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0); vgmstream_force_loop(vgmstream, 0, 0,0);
} }
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->song_play_forever = 0;
}
if (cfg->song_play_forever) {
cfg->song_ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */ /* loop N times, but also play stream end instead of fading out */
if (current->song_loop_count > 0 && current->song_ignore_fade) { if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)current->song_loop_count); vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count);
current->song_fade_time = 0; /* force no fade */ cfg->song_fade_time = 0;
} }
} }

View File

@ -10,6 +10,7 @@ typedef struct {
double song_fade_time; double song_fade_time;
double song_fade_delay; double song_fade_delay;
int song_ignore_loop; int song_ignore_loop;
int song_force_loop;
int song_really_force_loop; int song_really_force_loop;
int song_ignore_fade; int song_ignore_fade;
} foobar_song_config; } foobar_song_config;

View File

@ -72,15 +72,7 @@ typedef struct {
int mixing_count; int mixing_count;
txtp_mix_data mixing[TXTP_MIXING_MAX]; txtp_mix_data mixing[TXTP_MIXING_MAX];
int config_loop_count_set; play_config_t config;
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;
int config_ignore_fade;
int sample_rate; int sample_rate;
@ -437,21 +429,37 @@ fail:
static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
if (current->config_loop_count_set) if (current->config.play_forever) {
vgmstream->config_loop_count = current->config_loop_count; vgmstream->config.play_forever = current->config.play_forever;
if (current->config_fade_time_set) }
vgmstream->config_fade_time = current->config_fade_time; if (current->config.loop_count_set) {
if (current->config_fade_delay_set) vgmstream->config.loop_count_set = 1;
vgmstream->config_fade_delay = current->config_fade_delay; vgmstream->config.loop_count = current->config.loop_count;
if (current->config_ignore_loop) }
vgmstream->config_ignore_loop = current->config_ignore_loop; if (current->config.fade_time_set) {
if (current->config_force_loop) vgmstream->config.fade_time_set = 1;
vgmstream->config_force_loop = current->config_force_loop; vgmstream->config.fade_time = current->config.fade_time;
if (current->config_ignore_fade) }
vgmstream->config_ignore_fade = current->config_ignore_fade; if (current->config.fade_delay_set) {
vgmstream->config.fade_delay_set = 1;
vgmstream->config.fade_delay = current->config.fade_delay;
}
if (current->config.ignore_fade) {
vgmstream->config.ignore_fade = current->config.ignore_fade;
}
if (current->config.force_loop) {
vgmstream->config.force_loop = current->config.force_loop;
}
if (current->config.really_force_loop) {
vgmstream->config.really_force_loop = current->config.really_force_loop;
}
if (current->config.ignore_loop) {
vgmstream->config.ignore_loop = current->config.ignore_loop;
}
if (current->sample_rate > 0) if (current->sample_rate > 0) {
vgmstream->sample_rate = current->sample_rate; vgmstream->sample_rate = current->sample_rate;
}
if (current->loop_install_set) { if (current->loop_install_set) {
if (current->loop_start_second > 0 || current->loop_end_second > 0) { if (current->loop_start_second > 0 || current->loop_end_second > 0) {
@ -899,26 +907,32 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
} }
} }
if (cfg->config_loop_count_set) { if (cfg->config.play_forever) {
current->config_loop_count_set = cfg->config_loop_count_set; current->config.play_forever = cfg->config.play_forever;
current->config_loop_count = cfg->config_loop_count;
} }
if (cfg->config_fade_time_set) { if (cfg->config.loop_count_set) {
current->config_fade_time_set = cfg->config_fade_time_set; current->config.loop_count_set = 1;
current->config_fade_time = cfg->config_fade_time; current->config.loop_count = cfg->config.loop_count;
} }
if (cfg->config_fade_delay_set) { if (cfg->config.fade_time_set) {
current->config_fade_delay_set = cfg->config_fade_delay_set; current->config.fade_time_set = 1;
current->config_fade_delay = cfg->config_fade_delay; current->config.fade_time = cfg->config.fade_time;
} }
if (cfg->config_ignore_loop) { if (cfg->config.fade_delay_set) {
current->config_ignore_loop = cfg->config_ignore_loop; current->config.fade_delay_set = 1;
current->config.fade_delay = cfg->config.fade_delay;
} }
if (cfg->config_force_loop) { if (cfg->config.ignore_fade) {
current->config_force_loop = cfg->config_force_loop; current->config.ignore_fade = cfg->config.ignore_fade;
} }
if (cfg->config_ignore_fade) { if (cfg->config.force_loop) {
current->config_ignore_fade = cfg->config_ignore_fade; current->config.force_loop = cfg->config.force_loop;
}
if (cfg->config.really_force_loop) {
current->config.really_force_loop = cfg->config.really_force_loop;
}
if (cfg->config.ignore_loop) {
current->config.ignore_loop = cfg->config.ignore_loop;
} }
if (cfg->sample_rate > 0) { if (cfg->sample_rate > 0) {
@ -1081,28 +1095,42 @@ static void parse_config(txtp_entry *cfg, char *config) {
} }
} }
else if (strcmp(command,"i") == 0) { else if (strcmp(command,"i") == 0) {
config += get_bool(config, &cfg->config_ignore_loop); config += get_bool(config, &cfg->config.ignore_loop);
//;VGM_LOG("TXTP: ignore_loop=%i\n", cfg->config_ignore_loop); //;VGM_LOG("TXTP: ignore_loop=%i\n", cfg->config.ignore_loop);
}
else if (strcmp(command,"e") == 0) {
config += get_bool(config, &cfg->config.force_loop);
//;VGM_LOG("TXTP: force_loop=%i\n", cfg->config.force_loop);
} }
else if (strcmp(command,"E") == 0) { else if (strcmp(command,"E") == 0) {
config += get_bool(config, &cfg->config_force_loop); config += get_bool(config, &cfg->config.really_force_loop);
//;VGM_LOG("TXTP: force_loop=%i\n", cfg->config_force_loop); //;VGM_LOG("TXTP: really_force_loop=%i\n", cfg->config.really_force_loop);
} }
else if (strcmp(command,"F") == 0) { else if (strcmp(command,"F") == 0) {
config += get_bool(config, &cfg->config_ignore_fade); config += get_bool(config, &cfg->config.ignore_fade);
//;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade); //;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config.ignore_fade);
}
else if (strcmp(command,"L") == 0) {
config += get_bool(config, &cfg->config.play_forever);
//;VGM_LOG("TXTP: play_forever=%i\n", cfg->config.play_forever);
} }
else if (strcmp(command,"l") == 0) { else if (strcmp(command,"l") == 0) {
config += get_double(config, &cfg->config_loop_count, &cfg->config_loop_count_set); config += get_double(config, &cfg->config.loop_count, &cfg->config.loop_count_set);
//;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count); if (cfg->config.loop_count < 0)
cfg->config.loop_count_set = 0;
//;VGM_LOG("TXTP: loop_count=%f\n", cfg->config.loop_count);
} }
else if (strcmp(command,"f") == 0) { else if (strcmp(command,"f") == 0) {
config += get_double(config, &cfg->config_fade_time, &cfg->config_fade_time_set); config += get_double(config, &cfg->config.fade_time, &cfg->config.fade_time_set);
//;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time); if (cfg->config.fade_time < 0)
cfg->config.fade_time_set = 0;
//;VGM_LOG("TXTP: fade_time=%f\n", cfg->config.fade_time);
} }
else if (strcmp(command,"d") == 0) { else if (strcmp(command,"d") == 0) {
config += get_double(config, &cfg->config_fade_delay, &cfg->config_fade_delay_set); config += get_double(config, &cfg->config.fade_delay, &cfg->config.fade_delay_set);
//;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay); if (cfg->config.fade_delay < 0)
cfg->config.fade_delay_set = 0;
//;VGM_LOG("TXTP: fade_delay %f\n", cfg->config.fade_delay);
} }
else if (strcmp(command,"h") == 0) { else if (strcmp(command,"h") == 0) {
config += get_int(config, &cfg->sample_rate); config += get_int(config, &cfg->sample_rate);

View File

@ -807,8 +807,8 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
/* set loops to hear all track changes */ /* set loops to hear all track changes */
track_num = output_channels / max; track_num = output_channels / max;
if (vgmstream->config_loop_count < track_num) if (vgmstream->config.loop_count < track_num)
vgmstream->config_loop_count = track_num; vgmstream->config.loop_count = track_num;
ch = 0; ch = 0;
for (track = 0; track < track_num; track++) { for (track = 0; track < track_num; track++) {
@ -868,8 +868,8 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
/* set loops to hear all track changes */ /* set loops to hear all track changes */
layer_num = output_channels / max; layer_num = output_channels / max;
if (vgmstream->config_loop_count < layer_num) if (vgmstream->config.loop_count < layer_num)
vgmstream->config_loop_count = layer_num; vgmstream->config.loop_count = layer_num;
/* mode 'v': constant volume /* mode 'v': constant volume
* mode 'e': sets fades to successively lower/equalize volume per loop for each layer * mode 'e': sets fades to successively lower/equalize volume per loop for each layer

View File

@ -777,6 +777,19 @@ typedef enum {
mapping_7POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_SL | speaker_SR, mapping_7POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_SL | speaker_SR,
} mapping_t; } mapping_t;
typedef struct {
int play_forever;
int loop_count_set;
double loop_count;
int fade_time_set;
double fade_time;
int fade_delay_set;
double fade_delay;
int ignore_fade;
int force_loop;
int really_force_loop;
int ignore_loop;
} play_config_t;
/* info for a single vgmstream channel */ /* info for a single vgmstream channel */
typedef struct { typedef struct {
@ -861,14 +874,9 @@ typedef struct {
/* other config */ /* other config */
int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
/* config requests, players must read and honor these values */ /* config requests, players must read and honor these values
/* (ideally internally would work as a player, but for now player must do it manually) */ * (ideally internally would work as a player, but for now player must do it manually) */
double config_loop_count; play_config_t config;
double config_fade_time;
double config_fade_delay;
int config_ignore_loop;
int config_force_loop;
int config_ignore_fade;
/* layout/block state */ /* layout/block state */

View File

@ -87,9 +87,10 @@ typedef struct {
double song_loop_count; double song_loop_count;
double song_fade_time; double song_fade_time;
double song_fade_delay; double song_fade_delay;
int song_ignore_loop;
int song_really_force_loop;
int song_ignore_fade; int song_ignore_fade;
int song_ignore_loop;
int song_force_loop;
int song_really_force_loop;
} winamp_song_config; } winamp_song_config;
/* current play state */ /* current play state */
@ -949,52 +950,74 @@ static void set_config_defaults(winamp_song_config *current) {
current->song_loop_count = settings.loop_count; current->song_loop_count = settings.loop_count;
current->song_fade_time = settings.fade_time; current->song_fade_time = settings.fade_time;
current->song_fade_delay = settings.fade_delay; current->song_fade_delay = settings.fade_delay;
current->song_ignore_loop = settings.ignore_loop;
current->song_really_force_loop = 0;
current->song_ignore_fade = 0; current->song_ignore_fade = 0;
current->song_force_loop = 0;
current->song_really_force_loop = 0;
current->song_ignore_loop = settings.ignore_loop;
} }
static void apply_config(VGMSTREAM * vgmstream, winamp_song_config *current) { static void apply_config(VGMSTREAM* vgmstream, winamp_song_config* cfg) {
/* honor suggested config, if any (defined order matters) /* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
* note that ignore_fade and play_forever should take priority */ if (vgmstream->config.play_forever) {
if (vgmstream->config_loop_count) { cfg->song_play_forever = 1;
current->song_loop_count = vgmstream->config_loop_count; cfg->song_ignore_loop = 0;
} }
if (vgmstream->config_fade_delay) { if (vgmstream->config.loop_count_set) {
current->song_fade_delay = vgmstream->config_fade_delay; cfg->song_loop_count = vgmstream->config.loop_count;
cfg->song_play_forever = 0;
cfg->song_ignore_loop = 0;
} }
if (vgmstream->config_fade_time) { if (vgmstream->config.fade_delay_set) {
current->song_fade_time = vgmstream->config_fade_time; cfg->song_fade_delay = vgmstream->config.fade_delay;
} }
if (vgmstream->config_force_loop) { if (vgmstream->config.fade_time_set) {
current->song_really_force_loop = 1; cfg->song_fade_time = vgmstream->config.fade_time;
} }
if (vgmstream->config_ignore_loop) { if (vgmstream->config.ignore_fade) {
current->song_ignore_loop = 1; cfg->song_ignore_fade = 1;
}
if (vgmstream->config_ignore_fade) {
current->song_ignore_fade = 1;
} }
/* remove non-compatible options */ if (vgmstream->config.force_loop) {
if (current->song_play_forever) { cfg->song_ignore_loop = 0;
current->song_ignore_fade = 0; cfg->song_force_loop = 1;
current->song_ignore_loop = 0; cfg->song_really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->song_ignore_loop = 1;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 0;
} }
/* change loop stuff, in no particular order */
if (current->song_really_force_loop) { /* apply config */
if (cfg->song_force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
} }
if (current->song_ignore_loop) { if (cfg->song_really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0); vgmstream_force_loop(vgmstream, 0, 0,0);
} }
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->song_play_forever = 0;
}
if (cfg->song_play_forever) {
cfg->song_ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */ /* loop N times, but also play stream end instead of fading out */
if (current->song_loop_count > 0 && current->song_ignore_fade) { if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)current->song_loop_count); vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count);
current->song_fade_time = 0; /* force no fade */ cfg->song_fade_time = 0;
} }
} }
@ -1378,7 +1401,7 @@ DWORD WINAPI __stdcall decode(void *arg) {
int output_bytes; int output_bytes;
if (state.decode_pos_samples + max_buffer_samples > state.stream_length_samples if (state.decode_pos_samples + max_buffer_samples > state.stream_length_samples
&& (!settings.loop_forever || !vgmstream->loop_flag)) && (!config.song_play_forever || !vgmstream->loop_flag))
samples_to_do = state.stream_length_samples - state.decode_pos_samples; samples_to_do = state.stream_length_samples - state.decode_pos_samples;
else else
samples_to_do = max_buffer_samples; samples_to_do = max_buffer_samples;
@ -1396,7 +1419,7 @@ DWORD WINAPI __stdcall decode(void *arg) {
/* adjust seeking past file, can happen using the right (->) key /* adjust seeking past file, can happen using the right (->) key
* (should be done here and not in SetOutputTime due to threads/race conditions) */ * (should be done here and not in SetOutputTime due to threads/race conditions) */
if (state.seek_needed_samples > max_samples && !settings.loop_forever) { if (state.seek_needed_samples > max_samples && !config.song_play_forever) {
state.seek_needed_samples = max_samples; state.seek_needed_samples = max_samples;
} }
@ -1448,7 +1471,7 @@ DWORD WINAPI __stdcall decode(void *arg) {
} }
/* fade near the end */ /* fade near the end */
if (vgmstream->loop_flag && state.fade_samples > 0 && !settings.loop_forever) { if (vgmstream->loop_flag && state.fade_samples > 0 && !config.song_play_forever) {
int fade_channels = state.output_channels; int fade_channels = state.output_channels;
int samples_into_fade = state.decode_pos_samples - (state.stream_length_samples - state.fade_samples); int samples_into_fade = state.decode_pos_samples - (state.stream_length_samples - state.fade_samples);
if (samples_into_fade + samples_to_do > 0) { if (samples_into_fade + samples_to_do > 0) {
@ -1836,7 +1859,7 @@ __declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *d
int samples_to_do; int samples_to_do;
if (xstate.decode_pos_samples + max_buffer_samples > xstate.stream_length_samples if (xstate.decode_pos_samples + max_buffer_samples > xstate.stream_length_samples
&& (!settings.loop_forever || !xvgmstream->loop_flag)) && (!config.song_play_forever || !xvgmstream->loop_flag))
samples_to_do = xstate.stream_length_samples - xstate.decode_pos_samples; samples_to_do = xstate.stream_length_samples - xstate.decode_pos_samples;
else else
samples_to_do = max_buffer_samples; samples_to_do = max_buffer_samples;
@ -1853,7 +1876,7 @@ __declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *d
/* adjust seeking past file, can happen using the right (->) key /* adjust seeking past file, can happen using the right (->) key
* (should be done here and not in SetOutputTime due to threads/race conditions) */ * (should be done here and not in SetOutputTime due to threads/race conditions) */
if (xstate.seek_needed_samples > max_samples && !settings.loop_forever) { if (xstate.seek_needed_samples > max_samples && !config.song_play_forever) {
xstate.seek_needed_samples = max_samples; xstate.seek_needed_samples = max_samples;
} }
@ -1881,7 +1904,7 @@ __declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *d
render_vgmstream(xsample_buffer, samples_to_do, xvgmstream); render_vgmstream(xsample_buffer, samples_to_do, xvgmstream);
/* fade near the end */ /* fade near the end */
if (xvgmstream->loop_flag && xstate.fade_samples > 0 && !settings.loop_forever) { if (xvgmstream->loop_flag && xstate.fade_samples > 0 && !config.song_play_forever) {
int fade_channels = xstate.output_channels; int fade_channels = xstate.output_channels;
int samples_into_fade = xstate.decode_pos_samples - (xstate.stream_length_samples - xstate.fade_samples); int samples_into_fade = xstate.decode_pos_samples - (xstate.stream_length_samples - xstate.fade_samples);
if (samples_into_fade + xstate.decode_pos_samples > 0) { if (samples_into_fade + xstate.decode_pos_samples > 0) {