Merge pull request #389 from bnnm/txtp-groups-aif

txtp groups aif
This commit is contained in:
Christopher Snowhill 2019-04-09 03:43:47 -07:00 committed by GitHub
commit c585f3f502
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 800 additions and 426 deletions

View File

@ -131,7 +131,8 @@ like foobar or Winamp don't react well to that, they may be renamed to make
them playable through vgmstream.
- .aac to .laac (tri-Ace games)
- .ac3 to .lac3 (standard AC3)
- .aif to .aiffl or .aifcl (standard Mac AIF)
- .aif to .laif or .aiffl or .aifcl (standard Mac AIF, Asobo AIF, Ogg)
- .aiff/aifc to .aiffl/aifcl (standard Mac AIF)
- .asf to .lasf (EA games, Argonaut ASF)
- .flac to .lflac (standard FLAC)
- .mp2 to .lmp2 (standard MP2)

View File

@ -79,9 +79,9 @@ def find_files(dir, pattern, recursive):
def make_cmd(cfg, fname_in, fname_out, target_subsong):
if (cfg.test_dupes):
cmd = "{} -s {} -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in)
cmd = "{} -s {} -i -o \"{}\" \"{}\"".format(cfg.cli, target_subsong, fname_out, fname_in)
else:
cmd = "{} -s {} -m -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in)
cmd = "{} -s {} -m -i -o \"{}\" \"{}\"".format(cfg.cli, target_subsong, fname_out, fname_in)
return cmd
class LogHelper(object):

View File

@ -18,6 +18,9 @@ You can set commands to alter how files play (described later). Having a single
```
# 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
```
@ -33,6 +36,7 @@ BGM01_LOOPED.VAG
# segments must define loops
loop_start_segment = 2 # 2nd file start
loop_end_segment = 2 # optional, default is last
mode = segments # optional, default is segments
```
If your loop segment has proper loops you want to keep, you can use:
@ -77,6 +81,87 @@ mode = layers
```
Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file. If all layers share loop points they are automatically kept.
### Mixed groups
You can set "groups" to 'fold' various files into one, as layers or segments, to allow complex cases:
```
# commands to make two 6ch segments with layered intro + layered loop:
# - set introA+B+C as layer (this group becomes position 1, and loopA_2ch position 2)
introA_2ch.at3 #position 1
introB_2ch.at3
introC_2ch.at3
group = 1L3
# - set loopA+B+C as layer (this group becomes position 2)
loopA_2ch.at3 #position 4
loopB_2ch.at3
loopC_2ch.at3
group = 2L3
# - play both as segments (this step is optional if using mode = segments)
group = S2
# - set loop start loopA+B+C (new position 2, not original position 4)
loop_start_segment = 2
# optional, to avoid "segments" default (for debugging)
mode = mixed
```
From TXTP's perspective, it starts with N separate files and every command joins some files that are treated as a single new file, so positions are reassigned. End result will be a single "file" that may contain groups within groups. It's pretty flexible so you can express similar things in various ways:
```
# commands to make a 6ch with segmented intro + loop:
introA_2ch.at3
mainA_2ch.at3
introB_2ch.at3
mainB_2ch.at3
introC_2ch.at3
mainC_2ch.at3
# - group intro/main pairs as segments, starting from 1 and repeating for A/B/C
group = S2R
# - play all as layer (can't set loop_start_segment in this case)
mode = layers
# you could also set: group = L and mode = mixed, same thing
```
`group` can go anywhere in the .txtp, as many times as needed (groups are read and kept in an list that is applied in order at the end). Format is `(position)(type)(count)(repeat)`:
- `position`: file start (optional, default is 1 = first)
- `type`: group as S=segments or L=layers
- `count`: number of files in group (optional, default is all)
- `repeat`: R=repeat group of `count` files until end (optional, default is no repeat)
Examples:
- `L`: take all files as layers (equivalent to `mode = layers`)
- `S`: take all files as segments (equivalent to `mode = segments`)
- `3L2`: layer 2 files starting from file 3
- `2L3R`: group every 3 files from position 2 as layers
- `1S1`: segment of one file (useless thus ignored)
- `1L1`: layer of one file (same)
- `9999L`: absurd values are ignored
Segments and layer settings and rules still apply, so you can't make segments of files with different total channels. To do it you can use commands to "downmix" the group, as well as giving it some config (explained later):
```
# this doesn't need to be grouped
intro_2ch.at3
# this is grouped into a single 4ch file, then downmixed to stereo
mainA_2ch.at3
mainB_2ch.at3
group = 2L2 #@layer-v 2
# finally resulting layers are played as segments (2ch, 2ch)
# (could set a group = S and ommit S here, too)
mode = segments
# if the last group joins all as segments you can use loop_start
loop_start_segment = 3 #refers to final group at position 2
loop_mode = keep
```
## TXTP COMMANDS
You can set file commands by adding multiple `#(command)` after the name. `# (anything)` is considered a comment and ignored, as well as any command not understood.
@ -91,9 +176,7 @@ bgm.sxd2#12
#bgm.sxd2#s12 # "sN" is alt for subsong
# single files loop normally by default
# if loop segment is defined it forces a full loop (0..num_samples)
#loop_start_segment = 1
# single files loop normally by default (see below to force looping)
```
### Play segmented subsong ranges as one
@ -554,6 +637,19 @@ segment2.hca#m0}1:00+10.0
# it would work ok it they were layers, but still, better to use commands with the resulting file
```
Combine with groups or extra complex cases:
```
BGM_SUMMON_0001_02-Intro.hca # 2ch file
BGM_SUMMON_0001_02-Intro2.hca # 2ch file
BGM_SUMMON_0001_02.hca
BGM_SUMMON_0001_02-Vocal.hca
group = 3L2 #@layer-v 2 # layer Main+Vocal as 4ch then downmix to 2ch
loop_start_segment = 3 #refers to new group at position 3
loop_mode = keep
```
Note how order subtly affects end results:
```
# after silencing channel 1 mixing is meaningless

View File

@ -43,6 +43,7 @@ static const char* extension_list[] = {
"ahv",
"ai",
//"aif", //common
"aif-Loop",
"aifc", //common?
"aifcl", //fake extension for .aif???
//"aiff", //common
@ -212,6 +213,9 @@ static const char* extension_list[] = {
"l",
"laac", //fake extension for .aac (tri-Ace)
"laif", //fake extension for .aif (various)
"laiff", //fake extension for .aiff
"laifc", //fake extension for .aifc
"lac3", //fake extension for .ac3, FFmpeg/not parsed
"lasf", //fake extension for .asf (various)
"leg",

View File

@ -23,9 +23,10 @@ void render_vgmstream_flat(sample_t * buffer, int32_t sample_count, VGMSTREAM *
samples_to_do = vgmstream_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
if (samples_to_do == 0) {
VGM_LOG("layout_flat: wrong samples_to_do found\n");
VGM_LOG("layout_flat: wrong samples_to_do 0 found\n"); /* could happen when calling render at EOF? */
//VGM_LOG("layout_flat: tb=%i sib=%i, spf=%i\n", samples_this_block, vgmstream->samples_into_block, samples_per_frame);
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
break;
}

View File

@ -87,11 +87,15 @@ int setup_layout_layered(layered_layout_data* data) {
for (i = 0; i < data->layer_count; i++) {
int layer_input_channels, layer_output_channels;
if (!data->layers[i])
if (data->layers[i] == NULL) {
VGM_LOG("layered: no vgmstream in %i\n", i);
goto fail;
}
if (data->layers[i]->num_samples <= 0)
if (data->layers[i]->num_samples <= 0) {
VGM_LOG("layered: no samples in %i\n", i);
goto fail;
}
/* different layers may have different input/output channels */
mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels);
@ -103,12 +107,12 @@ int setup_layout_layered(layered_layout_data* data) {
if (i > 0) {
/* a bit weird, but no matter */
if (data->layers[i]->sample_rate != data->layers[i-1]->sample_rate) {
VGM_LOG("layered layout: layer %i has different sample rate\n", i);
VGM_LOG("layered: layer %i has different sample rate\n", i);
}
/* also weird */
if (data->layers[i]->coding_type != data->layers[i-1]->coding_type) {
VGM_LOG("layered layout: layer %i has different coding type\n", i);
VGM_LOG("layered: layer %i has different coding type\n", i);
}
}

View File

@ -135,15 +135,21 @@ int setup_layout_segmented(segmented_layout_data* data) {
for (i = 0; i < data->segment_count; i++) {
int segment_input_channels, segment_output_channels;
if (!data->segments[i])
if (data->segments[i] == NULL) {
VGM_LOG("segmented: no vgmstream in segment %i\n", i);
goto fail;
}
if (data->segments[i]->num_samples <= 0)
if (data->segments[i]->num_samples <= 0) {
VGM_LOG("segmented: no samples in segment %i\n", i);
goto fail;
}
/* disable so that looping is controlled by render_vgmstream_segmented */
if (data->segments[i]->loop_flag != 0) {
VGM_LOG("segmented layout: segment %i is looped\n", i);
VGM_LOG("segmented: segment %i is looped\n", i);
data->segments[i]->loop_flag = 0;
}
@ -159,12 +165,14 @@ int setup_layout_segmented(segmented_layout_data* data) {
int prev_output_channels;
mixing_info(data->segments[i-1], NULL, &prev_output_channels);
if (segment_output_channels != prev_output_channels)
if (segment_output_channels != prev_output_channels) {
VGM_LOG("segmented: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
goto fail;
}
/* a bit weird, but no matter */
if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) {
VGM_LOG("segmented layout: segment %i has different sample rate\n", i);
VGM_LOG("segmented: segment %i has different sample rate\n", i);
}
/* perfectly acceptable */

View File

@ -10,8 +10,8 @@ VGMSTREAM * init_vgmstream_aif_asobo(STREAMFILE *streamFile) {
/* checks */
/* aif: standard, aiffl: for plugins? */
if ( !check_extensions(streamFile,"aif,aiffl") )
/* aif: standard, .laif/aiffl: for plugins */
if ( !check_extensions(streamFile,"aif,laif,aiffl") )
goto fail;
if ((uint16_t)read_16bitLE(0x00,streamFile) != 0x69) /* Xbox codec */
goto fail;

View File

@ -67,20 +67,21 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
/* checks */
/* .aif: common (AIFF or AIFC), .aiff: common AIFF, .aifc: common AIFC
* .laif/laifc/laiff: for plugins
* .aifcl/aiffl: for plugins?
* .cbd2: M2 games
* .bgm: Super Street Fighter II Turbo (3DO)
* .acm: Crusader - No Remorse (SAT)
* .adp: Sonic Jam (SAT)
* .ai: Dragon Force (SAT)
* .aifcl/aiffl: for plugins? */
if (check_extensions(streamFile, "aif")) {
* .ai: Dragon Force (SAT) */
if (check_extensions(streamFile, "aif,laif")) {
is_aifc_ext = 1;
is_aiff_ext = 1;
}
else if (check_extensions(streamFile, "aifc,aifcl,afc,cbd2,bgm")) {
else if (check_extensions(streamFile, "aifc,laifc,aifcl,afc,cbd2,bgm")) {
is_aifc_ext = 1;
}
else if (check_extensions(streamFile, "aiff,acm,adp,ai,aiffl")) {
else if (check_extensions(streamFile, "aiff,laiff,acm,adp,ai,aiffl")) {
is_aiff_ext = 1;
}
else {

View File

@ -299,7 +299,8 @@ static int parse_awc_header(STREAMFILE* streamFile, awc_header* awc) {
if ((awc->num_samples && !(awc->num_samples >= num_samples - 10 && awc->num_samples <= num_samples + 10)) ||
(awc->sample_rate && awc->sample_rate != sample_rate) ||
(awc->codec && awc->codec != codec)) {
VGM_LOG("AWC: found header diffs between channels\n");
VGM_LOG("AWC: found header diffs in channel %i, ns=%i vs %i, sr=%i vs %i, c=%i vs %i\n",
ch, awc->num_samples, num_samples, awc->sample_rate, sample_rate, awc->codec, codec);
goto fail;
}

View File

@ -310,8 +310,9 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
* .adx: KID [Remember11 (PC)]
* .rof: The Rhythm of Fighters (Mobile)
* .acm: Planescape Torment Enhanced Edition (PC)
* .sod: Zone 4 (PC) */
if (check_extensions(streamFile,"ogg,logg,adx,rof,acm,sod")) {
* .sod: Zone 4 (PC)
* .aif/laif/aif-Loop: Psychonauts (PC) raw extractions (named) */
if (check_extensions(streamFile,"ogg,logg,adx,rof,acm,sod,aif,laif,aif-Loop")) {
is_ogg = 1;
} else if (check_extensions(streamFile,"um3")) {
is_um3 = 1;

File diff suppressed because it is too large Load Diff

View File

@ -548,12 +548,13 @@ void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) {
mix_command_data mix = {0};
int ok;
if (ch_dst <= 1) return; /* can't kill from 1 */
if (ch_dst <= 0) return; /* can't kill from first channel */
if (!data || ch_dst >= data->output_channels) return;
mix.command = MIX_KILLMIX;
mix.ch_dst = ch_dst;
//;VGM_LOG("MIX: killmix %i\n", ch_dst);
ok = add_mixing(vgmstream, &mix);
if (ok) {
data->output_channels = ch_dst; /* clamp channels */

View File

@ -2640,7 +2640,9 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea
mixing_update_channel(opened_vgmstream); /* notify of new channel hacked-in */
}
return;
fail:
close_vgmstream(new_vgmstream);
return;
}

View File

@ -10,7 +10,7 @@ enum { PATH_LIMIT = 32768 };
enum { STREAM_NAME_SIZE = 255 };
enum { VGMSTREAM_MAX_CHANNELS = 64 };
enum { VGMSTREAM_MIN_SAMPLE_RATE = 300 }; /* 300 is Wwise min */
enum { VGMSTREAM_MAX_SAMPLE_RATE = 128000 };
enum { VGMSTREAM_MAX_SAMPLE_RATE = 192000 }; /* found in some FSB5 */
enum { VGMSTREAM_MAX_SUBSONGS = 65535 };
#include "streamfile.h"

View File

@ -874,7 +874,7 @@ static void add_extension(int length, char * dst, const char * ext) {
ext_upp[j] = '\0';
/* copy new extension + double null terminate */
/* ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
/* ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
written = sprintf(buf, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0');
for (j = 0; j < written; i++,j++)
dst[i] = buf[j];
@ -1716,7 +1716,7 @@ __declspec (dllexport) int winampGetExtendedFileInfoW(wchar_t *filename, char *m
/* return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox)
* if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! */
__declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) {
return 0;
return 0;
}
winamp_song_config ext_config;
@ -1731,6 +1731,7 @@ int ext_output_channels = 0;
static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate)
{
VGMSTREAM *ext_vgmstream = NULL;
in_char filename[PATH_LIMIT];
int stream_index = 0;
@ -1739,7 +1740,7 @@ static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps,
parse_fn_int(fn, wa_L("$s"), &stream_index);
/* open the stream */
VGMSTREAM *ext_vgmstream = init_vgmstream_winamp(filename, stream_index);
ext_vgmstream = init_vgmstream_winamp(filename, stream_index);
if (!ext_vgmstream) {
return NULL;
}
@ -1902,7 +1903,7 @@ __declspec(dllexport) void winampGetExtendedRead_close(void *handle)
__declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
/* may uninstall without restart as we aren't subclassing */
return IN_PLUGIN_UNINSTALL_NOW;
return IN_PLUGIN_UNINSTALL_NOW;
}
/* winamp sekrit exports: */