diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 4b83e0e7..00acc342 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -72,12 +72,9 @@ typedef struct { char * outfilename; char * tag_filename; int decode_only; - int ignore_loop; - int force_loop; - int really_force_loop; + int play_forever; int play_sdtout; int play_wreckless; - int play_forever; int print_metaonly; int print_adxencd; int print_oggenc; @@ -86,10 +83,15 @@ typedef struct { int write_lwav; int only_stereo; int stream_index; + double loop_count; double fade_time; double fade_delay; int ignore_fade; + int ignore_loop; + int force_loop; + int really_force_loop; + int seek_samples; /* 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) - * note that ignore_fade and play_forever should take priority */ - if (vgmstream->config_loop_count > 0.0) { - cfg->loop_count = vgmstream->config_loop_count; + /* honor suggested config (order matters, and config mixes with/overwrites player defaults) */ + //if (vgmstream->config.play_forever) { /* not really suited for CLI */ + // cfg->play_forever = 1; + // 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) { - cfg->fade_delay = vgmstream->config_fade_delay; + if (vgmstream->config.fade_delay_set) { + cfg->fade_delay = vgmstream->config.fade_delay; } - if (vgmstream->config_fade_time > 0.0) { - cfg->fade_time = vgmstream->config_fade_time; + if (vgmstream->config.fade_time_set) { + cfg->fade_time = vgmstream->config.fade_time; } - if (vgmstream->config_force_loop) { - cfg->really_force_loop = 1; - } - if (vgmstream->config_ignore_loop) { - cfg->ignore_loop = 1; - } - if (vgmstream->config_ignore_fade) { + if (vgmstream->config.ignore_fade) { cfg->ignore_fade = 1; } - /* remove non-compatible options */ - if (cfg->play_forever) { - cfg->ignore_fade = 0; + if (vgmstream->config.force_loop) { 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) { 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); } + /* 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 */ if (cfg->loop_count > 0 && cfg->ignore_fade) { vgmstream_set_loop_target(vgmstream, (int)cfg->loop_count); diff --git a/doc/TXTP.md b/doc/TXTP.md index 9a8b8532..dca3a4f1 100644 --- a/doc/TXTP.md +++ b/doc/TXTP.md @@ -12,17 +12,21 @@ file1 ... 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 -#mode = layers/segments/mixed +You can set commands to alter how files play (described later), and having a single file is ok too: +``` +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 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 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_LOOPED.VAG @@ -181,7 +185,7 @@ You can set file commands by adding multiple `#(command)` after the name. `#(spa ### Subsong selection for bank formats **`#(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 bgm.sxd2#12 @@ -192,7 +196,7 @@ bgm.sxd2#12 ``` ### 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* ``` @@ -211,71 +215,73 @@ song#3#h22050 ``` -### Channel mask 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. +### Channel removing/masking for channel subsongs/layers +**`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* ``` #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 #plays 1 to 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 +music_Home.ps3.scd#C1~3 ``` ### 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* ``` -# set number of loops -boss2_3ningumi_ver6.adx#l3 +boss2_3ningumi_ver6.adx#l3 #default is usually 2.0 ``` ``` -# set fade time (in seconds) -boss2_3ningumi_ver6.adx#f10.5 +boss2_3ningumi_ver6.adx#f11.5 #default is usually 10.0 ``` ``` -# set fade delay (in seconds) -boss2_3ningumi_ver6.adx#d0.5 +boss2_3ningumi_ver6.adx#d0.5 #default is usually 0.0 ``` ``` -# ignore and disable loops -boss2_3ningumi_ver6.adx#i +boss2_3ningumi_ver6.adx#i #this song has a nice stop ``` ``` -# don't fade out and instead play the song ending after -boss2_3ningumi_ver6.adx#F # this song has a nice stop +boss2_3ningumi_ver6.adx#F #loop some then hear that stop ``` ``` -# force full loops from end-to-end -boss2_3ningumi_ver6.adx#E +boss2_3ningumi_ver6.adx#e #song has loops, so ignored here ``` ``` -# settings can be combined -boss2_3ningumi_ver6.adx#l2#F # 2 loops + ending +boss2_3ningumi_ver6.adx#E #force full loops ``` ``` -# settings can be combined -boss2_3ningumi_ver6.adx#l1.5#d1#f5 +boss2_3ningumi_ver6.adx#L #keep on loopin' ``` ``` -# 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 -**`#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)** ``` @@ -470,7 +476,7 @@ BGM_0_012_07.wem mode = layers # 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: @@ -493,18 +499,21 @@ TXTP may even reference other TXTP, or files that require TXTH, for extra comple ### 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 -song#s2#c1,2 -song #s2#c1,2 # comment -song #s 2 #c1,2# comment -song #s 2 #c 1 , 2# comment +song#s2#C1,2 +song #s2#C1,2 # comment +song #s 2 #C1,2# comment +song #s 2 #C 1 , 2# comment #s2 #ignores rogue commands/comments @@ -514,17 +523,18 @@ song #E enable song #E 1 song #Enable song #h -48000 +song #l -2.0 # accepted song #E # comment -song #c1, 2, 3 -song #c 1 2 3 +song #C1, 2, 3 +song #C 1 2 3 # ignores first and reads second -song #s TWO#c1,2 +song #s TWO#C1,2 -# seen as #s1#c1,2 -song #s 1,2 #c1,2 +# seen as #s1#C1,2 +song #s 1,2 #C1,2 # all seen as #h48000 song #h48000 @@ -535,7 +545,7 @@ song #h 48000mhz song #h hz48000 # ignored as channels don't go that high (may be modified on request) -song #c32,41 +song #C32,41 # swaps 1 with 2 song #m1-2 @@ -552,7 +562,7 @@ loop_start_segment = 1 #s2# #commands here are ignored song commands=#s2 # commands here are allowed -commands= #c1,2 +commands= #C1,2 ``` Repeated commands overwrite previous setting, except comma-separated commands that are additive: diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index f5999fa2..f9cb93e1 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -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); int max_buffer_samples = SAMPLE_BUFFER_SIZE; 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 // (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; 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 - if (vgmstream->loop_flag && (vgmstream->loop_end_sample - vgmstream->loop_start_sample) && seek_pos_samples >= vgmstream->loop_end_sample) { - int loop_length = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); - + if (vgmstream->loop_flag && loop_length > 0 && seek_pos_samples >= vgmstream->loop_end_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 += 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) { 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; - 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; 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 - 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 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_fade_time = fade_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_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) - * note that ignore_fade and play_forever should take priority */ - if (vgmstream->config_loop_count) { - current->song_loop_count = vgmstream->config_loop_count; + /* honor suggested config (order matters, and config mixes with/overwrites player defaults) */ + if (vgmstream->config.play_forever) { + cfg->song_play_forever = 1; + cfg->song_ignore_loop = 0; } - if (vgmstream->config_fade_delay) { - current->song_fade_delay = vgmstream->config_fade_delay; + if (vgmstream->config.loop_count_set) { + cfg->song_loop_count = vgmstream->config.loop_count; + cfg->song_play_forever = 0; + cfg->song_ignore_loop = 0; } - if (vgmstream->config_fade_time) { - current->song_fade_time = vgmstream->config_fade_time; + if (vgmstream->config.fade_delay_set) { + cfg->song_fade_delay = vgmstream->config.fade_delay; } - if (vgmstream->config_force_loop) { - current->song_really_force_loop = 1; + if (vgmstream->config.fade_time_set) { + cfg->song_fade_time = vgmstream->config.fade_time; } - if (vgmstream->config_ignore_loop) { - current->song_ignore_loop = 1; - } - if (vgmstream->config_ignore_fade) { - current->song_ignore_fade = 1; + if (vgmstream->config.ignore_fade) { + cfg->song_ignore_fade = 1; } - /* remove non-compatible options */ - if (current->song_play_forever) { - current->song_ignore_fade = 0; - current->song_ignore_loop = 0; + if (vgmstream->config.force_loop) { + cfg->song_ignore_loop = 0; + cfg->song_force_loop = 1; + 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); } - 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); } + /* 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 */ - if (current->song_loop_count > 0 && current->song_ignore_fade) { - vgmstream_set_loop_target(vgmstream, (int)current->song_loop_count); - current->song_fade_time = 0; /* force no fade */ + if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) { + vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count); + cfg->song_fade_time = 0; } } diff --git a/fb2k/foo_vgmstream.h b/fb2k/foo_vgmstream.h index 82a2714b..a6e6ab7e 100644 --- a/fb2k/foo_vgmstream.h +++ b/fb2k/foo_vgmstream.h @@ -10,6 +10,7 @@ typedef struct { double song_fade_time; double song_fade_delay; int song_ignore_loop; + int song_force_loop; int song_really_force_loop; int song_ignore_fade; } foobar_song_config; diff --git a/src/meta/txtp.c b/src/meta/txtp.c index e00a09f9..550c33ed 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -72,15 +72,7 @@ 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; - int config_ignore_fade; + play_config_t config; int sample_rate; @@ -437,21 +429,37 @@ fail: static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { - 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->config.play_forever) { + vgmstream->config.play_forever = current->config.play_forever; + } + if (current->config.loop_count_set) { + vgmstream->config.loop_count_set = 1; + vgmstream->config.loop_count = current->config.loop_count; + } + if (current->config.fade_time_set) { + vgmstream->config.fade_time_set = 1; + vgmstream->config.fade_time = current->config.fade_time; + } + 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; + } if (current->loop_install_set) { 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) { - current->config_loop_count_set = cfg->config_loop_count_set; - current->config_loop_count = cfg->config_loop_count; + if (cfg->config.play_forever) { + current->config.play_forever = cfg->config.play_forever; } - 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.loop_count_set) { + current->config.loop_count_set = 1; + current->config.loop_count = cfg->config.loop_count; } - 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.fade_time_set) { + current->config.fade_time_set = 1; + current->config.fade_time = cfg->config.fade_time; } - if (cfg->config_ignore_loop) { - current->config_ignore_loop = cfg->config_ignore_loop; + if (cfg->config.fade_delay_set) { + current->config.fade_delay_set = 1; + current->config.fade_delay = cfg->config.fade_delay; } - 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; } - if (cfg->config_ignore_fade) { - current->config_ignore_fade = cfg->config_ignore_fade; + if (cfg->config.force_loop) { + 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) { @@ -1081,28 +1095,42 @@ static void parse_config(txtp_entry *cfg, char *config) { } } else if (strcmp(command,"i") == 0) { - config += get_bool(config, &cfg->config_ignore_loop); - //;VGM_LOG("TXTP: ignore_loop=%i\n", cfg->config_ignore_loop); + config += get_bool(config, &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) { - config += get_bool(config, &cfg->config_force_loop); - //;VGM_LOG("TXTP: force_loop=%i\n", cfg->config_force_loop); + config += get_bool(config, &cfg->config.really_force_loop); + //;VGM_LOG("TXTP: really_force_loop=%i\n", cfg->config.really_force_loop); } else if (strcmp(command,"F") == 0) { - config += get_bool(config, &cfg->config_ignore_fade); - //;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade); + config += get_bool(config, &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) { - config += get_double(config, &cfg->config_loop_count, &cfg->config_loop_count_set); - //;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count); + config += get_double(config, &cfg->config.loop_count, &cfg->config.loop_count_set); + 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) { - config += get_double(config, &cfg->config_fade_time, &cfg->config_fade_time_set); - //;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time); + config += get_double(config, &cfg->config.fade_time, &cfg->config.fade_time_set); + 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) { - config += get_double(config, &cfg->config_fade_delay, &cfg->config_fade_delay_set); - //;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay); + config += get_double(config, &cfg->config.fade_delay, &cfg->config.fade_delay_set); + 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) { config += get_int(config, &cfg->sample_rate); diff --git a/src/mixing.c b/src/mixing.c index 5dd900b4..e4bd02c7 100644 --- a/src/mixing.c +++ b/src/mixing.c @@ -807,8 +807,8 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { /* set loops to hear all track changes */ track_num = output_channels / max; - if (vgmstream->config_loop_count < track_num) - vgmstream->config_loop_count = track_num; + if (vgmstream->config.loop_count < track_num) + vgmstream->config.loop_count = track_num; ch = 0; 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 */ layer_num = output_channels / max; - if (vgmstream->config_loop_count < layer_num) - vgmstream->config_loop_count = layer_num; + if (vgmstream->config.loop_count < layer_num) + vgmstream->config.loop_count = layer_num; /* mode 'v': constant volume * mode 'e': sets fades to successively lower/equalize volume per loop for each layer diff --git a/src/vgmstream.h b/src/vgmstream.h index db55def8..d69a96f4 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -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_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 */ typedef struct { @@ -861,14 +874,9 @@ typedef struct { /* other config */ 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 */ - /* (ideally internally would work as a player, but for now player must do it manually) */ - double config_loop_count; - double config_fade_time; - double config_fade_delay; - int config_ignore_loop; - int config_force_loop; - int config_ignore_fade; + /* config requests, players must read and honor these values + * (ideally internally would work as a player, but for now player must do it manually) */ + play_config_t config; /* layout/block state */ diff --git a/winamp/in_vgmstream.c b/winamp/in_vgmstream.c index f5ccc4df..9849da3a 100644 --- a/winamp/in_vgmstream.c +++ b/winamp/in_vgmstream.c @@ -87,9 +87,10 @@ typedef struct { double song_loop_count; double song_fade_time; double song_fade_delay; - int song_ignore_loop; - int song_really_force_loop; int song_ignore_fade; + int song_ignore_loop; + int song_force_loop; + int song_really_force_loop; } winamp_song_config; /* 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_fade_time = settings.fade_time; 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_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) - * note that ignore_fade and play_forever should take priority */ - if (vgmstream->config_loop_count) { - current->song_loop_count = vgmstream->config_loop_count; + /* honor suggested config (order matters, and config mixes with/overwrites player defaults) */ + if (vgmstream->config.play_forever) { + cfg->song_play_forever = 1; + cfg->song_ignore_loop = 0; } - if (vgmstream->config_fade_delay) { - current->song_fade_delay = vgmstream->config_fade_delay; + if (vgmstream->config.loop_count_set) { + cfg->song_loop_count = vgmstream->config.loop_count; + cfg->song_play_forever = 0; + cfg->song_ignore_loop = 0; } - if (vgmstream->config_fade_time) { - current->song_fade_time = vgmstream->config_fade_time; + if (vgmstream->config.fade_delay_set) { + cfg->song_fade_delay = vgmstream->config.fade_delay; } - if (vgmstream->config_force_loop) { - current->song_really_force_loop = 1; + if (vgmstream->config.fade_time_set) { + cfg->song_fade_time = vgmstream->config.fade_time; } - if (vgmstream->config_ignore_loop) { - current->song_ignore_loop = 1; - } - if (vgmstream->config_ignore_fade) { - current->song_ignore_fade = 1; + if (vgmstream->config.ignore_fade) { + cfg->song_ignore_fade = 1; } - /* remove non-compatible options */ - if (current->song_play_forever) { - current->song_ignore_fade = 0; - current->song_ignore_loop = 0; + if (vgmstream->config.force_loop) { + cfg->song_ignore_loop = 0; + cfg->song_force_loop = 1; + 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); } - 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); } + /* 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 */ - if (current->song_loop_count > 0 && current->song_ignore_fade) { - vgmstream_set_loop_target(vgmstream, (int)current->song_loop_count); - current->song_fade_time = 0; /* force no fade */ + if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) { + vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count); + cfg->song_fade_time = 0; } } @@ -1378,7 +1401,7 @@ DWORD WINAPI __stdcall decode(void *arg) { int output_bytes; 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; else 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 * (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; } @@ -1448,7 +1471,7 @@ DWORD WINAPI __stdcall decode(void *arg) { } /* 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 samples_into_fade = state.decode_pos_samples - (state.stream_length_samples - state.fade_samples); 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; 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; else 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 * (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; } @@ -1881,7 +1904,7 @@ __declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *d render_vgmstream(xsample_buffer, samples_to_do, xvgmstream); /* 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 samples_into_fade = xstate.decode_pos_samples - (xstate.stream_length_samples - xstate.fade_samples); if (samples_into_fade + xstate.decode_pos_samples > 0) {