mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 08:20:54 +01:00
commit
c585f3f502
@ -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)
|
||||
|
@ -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):
|
||||
|
102
doc/TXTP.md
102
doc/TXTP.md
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
1039
src/meta/txtp.c
1039
src/meta/txtp.c
File diff suppressed because it is too large
Load Diff
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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: */
|
||||
|
Loading…
Reference in New Issue
Block a user