mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-18 03:26:57 +01:00
commit
295e187452
16
doc/TXTP.md
16
doc/TXTP.md
@ -95,7 +95,19 @@ BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3
|
||||
|
||||
mode = layers
|
||||
```
|
||||
Note that the number of channels is the sum of all layers so three 2ch layers play as a 6ch file (you can manually downmix using mixing commands, described later, since vgmstream can't guess if the result should be stereo or 5.1 audio). If all layers share loop points they are automatically kept.
|
||||
|
||||
If all layers share loop points they are automatically kept.
|
||||
```
|
||||
BGM1a.adx # loops from 10.0 to 90.0
|
||||
BGM1b.adx # loops from 10.0 to 90.0
|
||||
mode = layers
|
||||
# resulting file loops from 10.0 to 90.0
|
||||
|
||||
# if layers *don't* share loop points this sets a full loop (0..max)
|
||||
loop_mode = auto
|
||||
```
|
||||
|
||||
Note that the number of channels is the sum of all layers so three 2ch layers play as a 6ch file (you can manually downmix using mixing commands, described later, since vgmstream can't guess if the result should be stereo or 5.1 audio).
|
||||
|
||||
|
||||
### Mixed groups
|
||||
@ -158,7 +170,7 @@ Examples:
|
||||
- `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)
|
||||
- `1S1`: segment of one file (mostly useless but allowed as internal file may have play config)
|
||||
- `1L1`: layer of one file (same)
|
||||
- `9999L`: absurd values are ignored
|
||||
|
||||
|
@ -7,7 +7,7 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t subfile_offset, chunk_offset, bank_offset, offset;
|
||||
size_t subfile_size, bank_size;
|
||||
int is_old = 0;
|
||||
uint32_t version = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
@ -18,6 +18,7 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
goto fail;
|
||||
if (read_u32be(0x08,sf) != 0x46455620) /* "FEV " */
|
||||
goto fail;
|
||||
version = read_u32le(0x14,sf); /* newer FEV have some kind of sub-version at 0x18 */
|
||||
|
||||
/* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to
|
||||
* form a .bank, which is the format we support here (regular .fev is complex and not very interesting).
|
||||
@ -42,15 +43,14 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
goto fail;
|
||||
|
||||
switch(chunk_type) {
|
||||
case 0x4C495354: /* "LIST" with "SNDH" (older) */
|
||||
case 0x4C495354: /* "LIST" with "SNDH" (usually v0x28 but also in Fall Guys's BNK_Music_RoundReveal) */
|
||||
if (read_u32be(offset + 0x04, sf) == 0x534E4448) {
|
||||
bank_offset = offset + 0x0c;
|
||||
bank_size = read_s32le(offset + 0x08,sf);
|
||||
is_old = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x534E4448: /* "SNDH" (newer) */
|
||||
case 0x534E4448: /* "SNDH" */
|
||||
bank_offset = offset;
|
||||
bank_size = chunk_size;
|
||||
break;
|
||||
@ -65,10 +65,18 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
if (bank_offset == 0)
|
||||
goto fail;
|
||||
|
||||
/* 0x00: unknown (version? ex LE: 0x00080003, 0x00080005) */
|
||||
/* 0x00: unknown (chunk version? ex LE: 0x00080003, 0x00080005) */
|
||||
{
|
||||
/* versions:
|
||||
* 0x28: Transistor (iOS) [+2015]
|
||||
* 0x50: Runic Rampage (PC), Forza 7 (PC), Shantae: Half Genie Hero (Switch) [+2017]
|
||||
* 0x58: Mana Spark (PC), Shantae and the Seven Sirens (PC) [+2018]
|
||||
* 0x63: Banner Saga 3 (PC) [+2018]
|
||||
* 0x64: Guacamelee! Super Turbo Championship Edition (Switch) [+2018]
|
||||
* 0x65: Carrion (Switch) [+2020]
|
||||
* 0x7D: Fall Guys (PC) [+2020] */
|
||||
size_t entry_size = version <= 0x28 ? 0x04 : 0x08;
|
||||
int banks;
|
||||
size_t entry_size = is_old ? 0x04 : 0x08;
|
||||
|
||||
/* multiple banks is possible but rare (only seen an extra "Silence" FSB5 in Guacamelee 2 (Switch),
|
||||
* which on PC is a regular subsong in the only FSB5) */
|
||||
@ -88,7 +96,7 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
if (bank_subsongs != 1) goto fail;
|
||||
}
|
||||
|
||||
if (is_old) {
|
||||
if (version <= 0x28) {
|
||||
subfile_offset = read_u32le(bank_offset+0x04,sf);
|
||||
subfile_size = /* meh */
|
||||
read_u32le(subfile_offset + 0x0C,sf) +
|
||||
|
@ -371,8 +371,8 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
||||
if (file_size != riff_size + 0x08) {
|
||||
uint16_t codec = read_16bitLE(0x14,sf);
|
||||
|
||||
if (riff_size + 0x08 + 0x01 == file_size)
|
||||
riff_size += 0x01; /* [Shikkoku no Sharnoth (PC)] */
|
||||
if (codec == 0x6771 && riff_size + 0x08 + 0x01 == file_size)
|
||||
riff_size += 0x01; /* [Shikkoku no Sharnoth (PC)] (Sony Sound Forge?) */
|
||||
|
||||
else if (codec == 0x0069 && riff_size == file_size)
|
||||
riff_size -= 0x08; /* [Dynasty Warriors 3 (Xbox), BloodRayne (Xbox)] */
|
||||
@ -386,12 +386,15 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
||||
else if (codec == 0x0000 && riff_size == file_size)
|
||||
riff_size -= 0x08; /* [Rayman 2 (DC)] */
|
||||
|
||||
else if (codec == 0x0000 && riff_size + 0x02 + 0x08 == file_size)
|
||||
else if (codec == 0x0000 && riff_size + 0x08 + 0x02 == file_size)
|
||||
riff_size -= 0x02; /* [Rayman 2 (DC)]-dcz */
|
||||
|
||||
else if (codec == 0x0300 && riff_size == file_size)
|
||||
riff_size -= 0x08; /* [Chrono Ma:gia (Android)] */
|
||||
|
||||
else if (codec == 0xFFFE && riff_size + 0x08 + 0x18 == file_size)
|
||||
riff_size += 0x18; /* [F1 2011 (Vita)] (adds a "pada" chunk but RIFF size wasn't updated) */
|
||||
|
||||
else if (mwv) {
|
||||
int channels = read_16bitLE(0x16, sf); /* [Dragon Quest VIII (PS2), Rogue Galaxy (PS2)] */
|
||||
size_t file_size_fixed = riff_size + 0x08 + 0x04 * (channels - 1);
|
||||
|
@ -266,14 +266,15 @@ static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int p
|
||||
//;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count);
|
||||
}
|
||||
|
||||
static int make_group_segment(txtp_header* txtp, int position, int count) {
|
||||
static int make_group_segment(txtp_header* txtp, int is_group, int position, int count) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
segmented_layout_data *data_s = NULL;
|
||||
int i, loop_flag = 0;
|
||||
|
||||
|
||||
if (count == 1) { /* nothing to do */
|
||||
//;VGM_LOG("TXTP: ignored segments of 1\n");
|
||||
/* allowed for actual groups (not final "mode"), otherwise skip to optimize */
|
||||
if (!is_group && count == 1) {
|
||||
//;VGM_LOG("TXTP: ignored single group\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -349,14 +350,15 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int make_group_layer(txtp_header* txtp, int position, int count) {
|
||||
static int make_group_layer(txtp_header* txtp, int is_group, int position, int count) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
layered_layout_data* data_l = NULL;
|
||||
int i;
|
||||
|
||||
|
||||
if (count == 1) { /* nothing to do */
|
||||
//;VGM_LOG("TXTP: ignored layer of 1\n");
|
||||
/* allowed for actual groups (not final mode), otherwise skip to optimize */
|
||||
if (!is_group && count == 1) {
|
||||
//;VGM_LOG("TXTP: ignored single group\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -392,6 +394,12 @@ static int make_group_layer(txtp_header* txtp, int position, int count) {
|
||||
}
|
||||
}
|
||||
|
||||
/* loop settings only make sense if this group becomes final vgmstream */
|
||||
if (position == 0 && txtp->vgmstream_count == count) {
|
||||
if (txtp->is_loop_auto && !vgmstream->loop_flag) {
|
||||
vgmstream_force_loop(vgmstream, 1, 0, vgmstream->num_samples);
|
||||
}
|
||||
}
|
||||
|
||||
/* set new vgmstream and reorder positions */
|
||||
update_vgmstream_list(vgmstream, txtp, position, count);
|
||||
@ -404,12 +412,13 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int make_group_random(txtp_header* txtp, int position, int count, int selected) {
|
||||
static int make_group_random(txtp_header* txtp, int is_group, int position, int count, int selected) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int i;
|
||||
|
||||
if (count == 1) { /* nothing to do */
|
||||
//;VGM_LOG("TXTP: ignored random of 1\n");
|
||||
/* allowed for actual groups (not final mode), otherwise skip to optimize */
|
||||
if (!is_group && count == 1) {
|
||||
//;VGM_LOG("TXTP: ignored single group\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -484,15 +493,15 @@ static int parse_groups(txtp_header* txtp) {
|
||||
//;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups);
|
||||
switch(grp->type) {
|
||||
case TXTP_GROUP_MODE_LAYERED:
|
||||
if (!make_group_layer(txtp, pos, grp->count))
|
||||
if (!make_group_layer(txtp, 1, pos, grp->count))
|
||||
goto fail;
|
||||
break;
|
||||
case TXTP_GROUP_MODE_SEGMENTED:
|
||||
if (!make_group_segment(txtp, pos, grp->count))
|
||||
if (!make_group_segment(txtp, 1, pos, grp->count))
|
||||
goto fail;
|
||||
break;
|
||||
case TXTP_GROUP_MODE_RANDOM:
|
||||
if (!make_group_random(txtp, pos, grp->count, grp->selected))
|
||||
if (!make_group_random(txtp, 1, pos, grp->count, grp->selected))
|
||||
goto fail;
|
||||
break;
|
||||
default:
|
||||
@ -506,11 +515,11 @@ static int parse_groups(txtp_header* txtp) {
|
||||
|
||||
/* final tweaks (should be integrated with the above?) */
|
||||
if (txtp->is_layered) {
|
||||
if (!make_group_layer(txtp, 0, txtp->vgmstream_count))
|
||||
if (!make_group_layer(txtp, 0, 0, txtp->vgmstream_count))
|
||||
goto fail;
|
||||
}
|
||||
if (txtp->is_segmented) {
|
||||
if (!make_group_segment(txtp, 0, txtp->vgmstream_count))
|
||||
if (!make_group_segment(txtp, 0, 0, txtp->vgmstream_count))
|
||||
goto fail;
|
||||
}
|
||||
if (txtp->is_single) {
|
||||
|
@ -373,7 +373,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
else {
|
||||
/* newer Wwise (>2012) */
|
||||
off_t extra_offset = ww.fmt_offset + 0x18; /* after flag + channels */
|
||||
int is_wem = check_extensions(sf,"wem");
|
||||
int is_wem = check_extensions(sf,"wem,bnk"); /* use extension as a guide for faster vorbis inits */
|
||||
|
||||
switch(ww.extra_size) {
|
||||
case 0x30:
|
||||
@ -382,7 +382,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
cfg.header_type = WWV_TYPE_2;
|
||||
cfg.packet_type = WWV_MODIFIED;
|
||||
|
||||
/* setup not detectable by header, so we'll try both; hopefully libvorbis will reject wrong codebooks
|
||||
/* setup not detectable by header, so we'll try both; libvorbis should reject wrong codebooks
|
||||
* - standard: early (<2012), ex. The King of Fighters XIII (X360)-2011/11, .ogg (cbs are from aoTuV, too)
|
||||
* - aoTuV603: later (>2012), ex. Sonic & All-Stars Racing Transformed (PC)-2012/11, .wem */
|
||||
cfg.setup_type = is_wem ? WWV_AOTUV603_CODEBOOKS : WWV_EXTERNAL_CODEBOOKS; /* aoTuV came along .wem */
|
||||
|
@ -737,6 +737,8 @@ VGMSTREAM* allocate_vgmstream(int channel_count, int loop_flag) {
|
||||
|
||||
mixing_init(vgmstream); /* pre-init */
|
||||
|
||||
/* BEWARE: try_dual_file_stereo does some free'ing too */
|
||||
|
||||
//vgmstream->stream_name_size = STREAM_NAME_SIZE;
|
||||
return vgmstream;
|
||||
fail:
|
||||
@ -1203,6 +1205,7 @@ static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, VG
|
||||
|
||||
/* discard the second VGMSTREAM */
|
||||
mixing_close(new_vgmstream);
|
||||
free(new_vgmstream->tmpbuf);
|
||||
free(new_vgmstream->start_vgmstream);
|
||||
free(new_vgmstream);
|
||||
|
||||
|
@ -1316,35 +1316,30 @@ void winamp_EQSet(int on, char data[10], int preamp) {
|
||||
/*****************************************************************************/
|
||||
/* MAIN DECODE (some used in extended part too, so avoid globals) */
|
||||
|
||||
static void setup_seek(winamp_state_t* state, int32_t max_samples, int play_forever) {
|
||||
|
||||
/* 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_sample > max_samples && !play_forever) {
|
||||
state->seek_sample = max_samples;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* reset if we need to seek backwards (causes funny cursor jumps though) */
|
||||
if (state->seek_sample < state->decode_pos_samples) {
|
||||
state->decode_pos_samples = 0;
|
||||
state->decode_pos_ms = 0;
|
||||
}
|
||||
/* seek done */
|
||||
else if (state->decode_pos_samples >= state->seek_sample) {
|
||||
state->seek_sample = -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void do_seek(winamp_state_t* state, VGMSTREAM* vgmstream) {
|
||||
/* could divide in N seeks (from pos) for slower files so cursor moves, but doesn't seem too necessary */
|
||||
seek_vgmstream(vgmstream, state->seek_sample);
|
||||
int play_forever = vgmstream_get_play_forever(vgmstream);
|
||||
int seek_sample = state->seek_sample; /* local due to threads/race conditions changing state->seek_sample elsewhere */
|
||||
|
||||
/* discard decoded samples and keep seeking */
|
||||
state->decode_pos_samples = state->seek_sample;
|
||||
/* ignore seeking past file, can happen using the right (->) key, ok if playing forever */
|
||||
if (state->seek_sample > state->length_samples && !play_forever) {
|
||||
state->seek_sample = -1;
|
||||
//state->seek_sample = state->length_samples;
|
||||
//seek_sample = state->length_samples;
|
||||
|
||||
state->decode_pos_samples = state->length_samples;
|
||||
state->decode_pos_ms = state->decode_pos_samples * 1000LL / vgmstream->sample_rate;
|
||||
return;
|
||||
}
|
||||
|
||||
/* could divide in N seeks (from pos) for slower files so cursor moves, but doesn't seem too necessary */
|
||||
seek_vgmstream(vgmstream, seek_sample);
|
||||
|
||||
state->decode_pos_samples = seek_sample;
|
||||
state->decode_pos_ms = state->decode_pos_samples * 1000LL / vgmstream->sample_rate;
|
||||
state->seek_sample = -1;
|
||||
|
||||
/* different sample: other seek may have been requested during seek_vgmstream */
|
||||
if (state->seek_sample == seek_sample)
|
||||
state->seek_sample = -1;
|
||||
}
|
||||
|
||||
static void apply_gain(winamp_state_t* state, int samples_to_do) {
|
||||
@ -1365,21 +1360,19 @@ static void apply_gain(winamp_state_t* state, int samples_to_do) {
|
||||
/* the decode thread */
|
||||
DWORD WINAPI __stdcall decode(void *arg) {
|
||||
const int max_buffer_samples = SAMPLE_BUFFER_SIZE;
|
||||
const int max_samples = state.length_samples;
|
||||
int play_forever = vgmstream_get_play_forever(vgmstream);
|
||||
|
||||
while (!state.decode_abort) {
|
||||
int samples_to_do;
|
||||
int output_bytes;
|
||||
|
||||
if (state.decode_pos_samples + max_buffer_samples > state.length_samples && !play_forever)
|
||||
if (state.decode_pos_samples + max_buffer_samples > state.length_samples && !play_forever) {
|
||||
samples_to_do = state.length_samples - state.decode_pos_samples;
|
||||
else
|
||||
if (samples_to_do < 0) /* just in case */
|
||||
samples_to_do = 0;
|
||||
}
|
||||
else {
|
||||
samples_to_do = max_buffer_samples;
|
||||
|
||||
/* seek setup */
|
||||
if (state.seek_sample >= 0) {
|
||||
setup_seek(&state, max_samples, play_forever);
|
||||
}
|
||||
|
||||
output_bytes = (samples_to_do * state.output_channels * sizeof(short));
|
||||
@ -1398,7 +1391,8 @@ DWORD WINAPI __stdcall decode(void *arg) {
|
||||
do_seek(&state, vgmstream);
|
||||
|
||||
/* flush Winamp buffers *after* fully seeking (allows to play buffered samples while we seek, feels a bit snappier) */
|
||||
input_module.outMod->Flush(state.decode_pos_ms);
|
||||
if (state.seek_sample < 0)
|
||||
input_module.outMod->Flush(state.decode_pos_ms);
|
||||
}
|
||||
else if (input_module.outMod->CanWrite() >= output_bytes) { /* decode */
|
||||
render_vgmstream(sample_buffer, samples_to_do, vgmstream);
|
||||
@ -1760,7 +1754,6 @@ __declspec(dllexport) void *winampGetExtendedRead_openW(const wchar_t *fn, int *
|
||||
/* decode len to dest buffer, called multiple times until file done or decoding is aborted */
|
||||
__declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *dest, size_t len, int *killswitch) {
|
||||
const int max_buffer_samples = SAMPLE_BUFFER_SIZE;
|
||||
const int max_samples = xstate.length_samples;
|
||||
unsigned copied = 0;
|
||||
int done = 0;
|
||||
VGMSTREAM* xvgmstream = handle;
|
||||
@ -1773,14 +1766,13 @@ __declspec(dllexport) size_t winampGetExtendedRead_getData(void *handle, char *d
|
||||
while (copied + (max_buffer_samples * xvgmstream->channels * sizeof(short)) < len && !done) {
|
||||
int samples_to_do;
|
||||
|
||||
if (xstate.decode_pos_samples + max_buffer_samples > xstate.length_samples && !play_forever)
|
||||
if (xstate.decode_pos_samples + max_buffer_samples > xstate.length_samples && !play_forever) {
|
||||
samples_to_do = xstate.length_samples - xstate.decode_pos_samples;
|
||||
else
|
||||
if (samples_to_do < 0) /* just in case */
|
||||
samples_to_do = 0;
|
||||
}
|
||||
else {
|
||||
samples_to_do = max_buffer_samples;
|
||||
|
||||
/* seek setup (max samples to skip if still seeking, mark done) */
|
||||
if (xstate.seek_sample != -1) {
|
||||
setup_seek(&xstate, max_samples, play_forever);
|
||||
}
|
||||
|
||||
if (!samples_to_do) { /* track finished */
|
||||
|
Loading…
x
Reference in New Issue
Block a user