Merge pull request #1508 from bnnm/ue4riff-acx-misc

- Fix some .adpcm [2013: Infected Wars (iOS)]
- Fix some .acx [12Riven (PC)]
- Fix some .wave [Mighty Switch Force! (Wii)]
- Export loop_start/loop_end for foobar's playlist
- Fix seeking into the outro region when loop+outro is set
- Add TXTP frame_size for MSADPCM interleaved mode [Metal Gear Solid 2 (PC)]
- Remove unused TXTH codec PCM8_U_int (use PCM8_U + interleave)
This commit is contained in:
bnnm 2024-03-31 22:03:28 +02:00 committed by GitHub
commit 07ed5f11a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 365 additions and 255 deletions

View File

@ -92,8 +92,6 @@ as explained below, but often will use default values. Accepted codec strings:
# * Interleave is multiple of 0x1 (default)
# - PCM8_U PCM 8-bit unsigned
# * Variation with modified encoding
# - PCM8_U_int PCM 8-bit unsigned (interleave block)
# * Variation with modified encoding
# - PCM8_SB PCM 8-bit with sign bit
# * Variation with modified encoding
# * For few rare games [Sonic CD (SCD)]
@ -124,7 +122,7 @@ as explained below, but often will use default values. Accepted codec strings:
# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo)
# - MS_IMA Microsoft IMA ADPCM
# * For some PC games
# * Interleave (frame size) varies, often multiple of 0x100 [required]
# * frame_size (or interleave) varies, often multiple of 0x100 [required]
# - APPLE_IMA4 Apple Quicktime IMA ADPCM
# * For some Mac/iOS games
# - IMA_HV High Voltage's IMA ADPCM
@ -132,8 +130,9 @@ as explained below, but often will use default values. Accepted codec strings:
#
# - MSADPCM Microsoft ADPCM (mono/stereo)
# * For some PC games
# * Interleave (frame size) varies, often 0x2c/0x8c/0x100/0x400 and max 0x800 [required]
# * frame_size (or interleave) varies, often 0x2c/0x8c/0x100/0x400 and max 0x800 [required]
# * frame_size + interleave forces mono mode
#
# - AICA Yamaha AICA ADPCM (mono/stereo)
# * For some Dreamcast games, and some arcade (Naomi) games
# * Special interleave is multiple of 0x1
@ -152,11 +151,11 @@ as explained below, but often will use default values. Accepted codec strings:
#
# - ATRAC3 Sony ATRAC3
# * For some PS2 and PS3 games
# * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required]
# * frame_size (or interleave) can be 0x60/0x98/0xC0 * channels [required]
# * Should set skip_samples (around 1024+69 but varies)
# - ATRAC3PLUS Sony ATRAC3plus
# * For many PSP games and rare PS3 games
# * Interleave (frame size) can be: [required]
# * frame_size (or interleave) can be: [required]
# Mono: 0x0118|0178|0230|02E8
# Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800
# 6/8 channels: multiple of one of the above
@ -168,8 +167,9 @@ as explained below, but often will use default values. Accepted codec strings:
# * For later X360 games
#
# - AC3 AC3/SPDIF
# * For few PS2 games
# * For few PS2 games [Burnout (PS2)]
# * Should set skip_samples (around 256 but varies)
# * bytes-to-samples needs interleave, but only works for PS2-style AC3 (use sync work 0x72F8/0x770B rather than 0x0b77)
# - AAC Advanced Audio Coding (raw outside .mp4)
# * For some 3DS games and many iOS games
# * Should set skip_samples (typically 1024 but varies, 2112 is also common)
@ -206,9 +206,9 @@ codec = (codec string)
#### CODEC VARIATIONS
Changes the behavior of some codecs:
```
# - XBOX|EAXA: 0=standard (mono or stereo interleave), 1=force mono interleave mode
# - NGC_DSP: 0=normal interleave, 1=byte interleave, 2=no interleave
# - XMA1|XMA2: 0=dual multichannel (2ch xN), 1=single multichannel (1ch xN)
# - XBOX|EAXA: 0=standard (mono or stereo interleave), 1=force mono interleave mode
# - PCFX: 0=standard, 1='buggy encoder' mode, 2/3=same as 0/1 but with double volume
# - PCM4|PCM4_U: 0=low nibble first, 1=high nibble first
# - others: ignored
@ -226,11 +226,16 @@ value_add|value_+ = (value)
value_sub|value_- = (value)
```
#### INTERLEAVE / FRAME SIZE [REQUIRED depending on codec]
This value changes how data is read depending on the codec:
- For mono/interleaved codecs it's the amount of data between channels, and while optional (defaults described in the "codec" section) you'll often need to set it to get proper sound.
- For codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus) means frame size and is required.
- Interleave 0 means "stereo mode" for codecs marked as "mono/stereo", and setting it will usually force mono-interleaved mode.
#### INTERLEAVE [REQUIRED depending on codec]
This value changes how data is read, and while optional (defaults described in the "codec" section) you'll often need it to get proper sound.
Roughly speaking interleave is the separation between data of each channel (or the size of a data chunk). For example `interleave = 0x02` means there are 2 bytes of data from channel 1, then 2 bytes from channel 2, then bytes for other channels if any (this is common for PCM16LE, where 2 bytes = 1 sample). While `interleave = 0x1000` works the same but means there is a lot more data of one channel before next channel:
```
interleave = 0x02: | ch1 | ch2 | ch1 | ch2 | ...
interleave = 0x08: | ch1 ch1 ch1 ch1 | ch2 ch2 ch2 ch2 | ch1 ch1 ch1 ch1 | ch2 ch2 ch2 ch2 | ...
```
Incorrect interleave will usually sound like audio is "fragmented" or noisy (since channel data is misinterpreted), but it's usually easy enough to try a few common values until it sounds right. Interleave needs to be a multiple of some value (PCM16LE is multiple of 0x02 so can't use interleave 0x05).
Special values:
- `half_size`: sets interleave as data_size / channels automatically
@ -238,6 +243,28 @@ Special values:
interleave = (value)|half_size
```
##### Special cases
Depending on the codec itself interleave has certain implications:
- mono-interleaved codecs: uses default value or value in `interleave` as described above
- mono/stereo codecs: no default, setting interleave usually forces mono-interleaved mode (if known cases exists)
- codecs with frame sizes: if `interleave` is set but `frame_size` isn't set, it'll use the former as `frame_size` (see below)
- other codecs: ignored
Mono/stereo codecs are a bit particular in that they have two modes. For 1 channel files mono mode is always used. But for stereo files, different games may have either mono-interleave data or stereo data:
- stereo file uses mono data: set interleave (forces mono mode with interleaved chunks)
- stereo file uses stereo data: don't set interleave (forces stereo mode with linear chunks)
It's technically possible that a game could use stereo mode with interleave for multichannel, but isn't handled at the moment.
#### FRAME SIZE [REQUIRED depending on codec]
Codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus) need `frame_size`. "frame size" is the amount of data the decoder needs as a single unit. Conceptually it's similar to interleave, so to simplify usage you may set `interleave` instead of `frame_size`.
It's possible though rare (seen in some MSADPCM files) that a game needs a `frame_size` then sets another `interleave` value, for example has frame_size 0x100 with mono interleave 0x400. This means it reads multiple smaller 0x100 for one channel up to 0x400, then next channel, etc.
```
frame_size = (value)
```
#### INTERLEAVE IN THE LAST BLOCK
In some files with interleaved data the last block (`interleave * channels`) of data is smaller than normal, so `interleave` is smaller for that block. Setting this fixes decoding glitches at the end.
@ -887,6 +914,19 @@ num_samples = data_size
#@0x04 number of 0x800 sectors
```
#### Metal Gear Solid 2 Substance (PC) .txth
```
# cutscene/voices use mono interleave for stereo tracks, set both frame_size and interleave
codec = MSADPCM
frame_size = 0x400
interleave = 0x800
sample_rate = @0x06:BE$2
channels = @0x08$1
# 0a: codec (11=msapdcm)
start_offset = 0x10
num_samples = data_size
```
#### Colin McRae DiRT (PC) .wip.txth
```
# first check that value at 0x00 is really 0x00000000 (rarely needed though)

View File

@ -168,6 +168,13 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
if (get_description_tag(temp,description,"stream count: ")) p_info.meta_set("stream_count",temp);
if (get_description_tag(temp,description,"stream index: ")) p_info.meta_set("stream_index",temp);
if (get_description_tag(temp,description,"stream name: ")) p_info.meta_set("stream_name",temp);
if (loop_end) {
p_info.meta_set("loop_start", pfc::format_int(loop_start));
p_info.meta_set("loop_end", pfc::format_int(loop_end));
// has extra text info
//if (get_description_tag(temp,description,"loop start: ")) p_info.meta_set("loop_start",temp);
//if (get_description_tag(temp,description,"loop end: ")) p_info.meta_set("loop_end",temp);
}
/* get external file tags */
//todo optimize and don't parse tags again for this session (not sure how), seems foobar

View File

@ -5,13 +5,13 @@
#include "mixing.h"
#include "plugins.h"
static void seek_force_loop(VGMSTREAM* vgmstream, int loop_count) {
/* pretend decoder reached loop end so internal state is set like jumping to loop start
* (no effect in some layouts but that is ok) */
static void seek_force_loop_end(VGMSTREAM* vgmstream, int loop_count) {
/* only called after hit loop */
if (!vgmstream->hit_loop)
return;
/* pretend decoder reached loop end so state is set to loop start */
vgmstream->loop_count = loop_count - 1; /* seeking to first loop must become ++ > 0 */
vgmstream->current_sample = vgmstream->loop_end_sample;
decode_do_loop(vgmstream);
@ -33,45 +33,148 @@ static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
}
}
static void seek_body(VGMSTREAM* vgmstream, int32_t seek_sample) {
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_sample, vgmstream->current_sample);
int32_t decode_samples;
play_state_t* ps = &vgmstream->pstate;
bool is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
bool is_config = vgmstream->config_enabled;
int play_forever = vgmstream->config.play_forever;
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
/* seek can be in some part of the body, depending on looping/decoder's current position/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* non-looped seek before decoder's position: restart + consume (seekr=50s, curr=95 > restart + decode=50s) */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* non-looped seek after decoder's position: consume (seekr=95s, curr=50 > decode=95-50=45s) */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* after num_samples, can happen when body is set manually (seekr=120s) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* looped seek before decoder's position: restart + consume or just consume */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* looped seek after loop start: can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
//int32_t loop_outr = (vgmstream->num_samples - vgmstream->loop_end_sample);
int32_t loop_part = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* samples of 1 looped part */
int32_t loop_seek = (seek_relative - vgmstream->loop_start_sample); /* samples within loop region */
int loop_count = loop_seek / loop_part; /* not accurate when loop_target is set */
loop_seek = loop_seek % loop_part; /* clamp within single loop after calcs */
/* current must have reached loop start at some point, otherwise force it (NOTE: some layouts don't actually set hit_loop) */
if (!vgmstream->hit_loop) {
if (vgmstream->current_sample > vgmstream->loop_start_sample) { /* may be 0 */
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
int32_t skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: force loop region / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (may happen at start since it's smaller than loop_end) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: outside loop region / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop_end(vgmstream, 0);
}
//;VGM_LOG("SEEK: in loop region / seekr=%i, seekl=%i, loops=%i, dec_curr=%i\n", seek_relative, loop_seek, loop_count, loop_curr);
/* when "ignore fade" is set and seek falls into the outro part (loop count if bigged than expected), adjust seek
* to do a whole part + outro samples (should probably calculate correct loop_count before but...) */
if (vgmstream->loop_target && loop_count >= vgmstream->loop_target) {
loop_seek = loop_part + (seek_relative - vgmstream->loop_start_sample) - vgmstream->loop_target * loop_part;
loop_count = vgmstream->loop_target - 1; /* so seek_force_loop_end detection kicks in and adds +1 */
//;VGM_LOG("SEEK: outro outside / seek=%i, count=%i\n", decode_samples, loop_seek, loop_count);
}
int32_t loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
if (loop_seek < loop_curr) {
decode_samples = loop_seek;
seek_force_loop_end(vgmstream, loop_count);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples = (loop_seek - loop_curr);
vgmstream->loop_count = loop_count;
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (is_config && !play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
seek_force_decode(vgmstream, decode_samples);
//;VGM_LOG("SEEK: decode=%i, current=%i\n", decode_samples, vgmstream->current_sample);
}
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
play_state_t* ps = &vgmstream->pstate;
int play_forever = vgmstream->config.play_forever;
int32_t decode_samples = 0;
int loop_count = -1;
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
bool is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
bool is_config = vgmstream->config_enabled;
/* cleanup */
if (seek_sample < 0)
seek_sample = 0;
/* play forever can seek past max */
if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever)
if (is_config && seek_sample > ps->play_duration && !play_forever)
seek_sample = ps->play_duration;
#if 0 //todo move below, needs to clamp in decode part
/* optimize as layouts can seek faster internally */
if (vgmstream->layout_type == layout_segmented) {
seek_layout_segmented(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
else if (vgmstream->layout_type == layout_layered) {
seek_layout_layered(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
#endif
/* will decode and loop until seek sample, but slower */
//todo apply same loop logic as below, or pretend we have play_forever + settings?
if (!vgmstream->config_enabled) {
if (!is_config) {
int32_t decode_samples;
//;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample);
if (seek_sample < vgmstream->current_sample) {
decode_samples = seek_sample;
@ -104,142 +207,49 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
/* start/pad-begin: consume pad samples */
if (seek_sample < ps->pad_begin_duration) {
if (is_config && seek_sample < ps->pad_begin_duration) {
/* seek=3: pad=5-3=2 */
decode_samples = 0;
reset_vgmstream(vgmstream);
ps->pad_begin_left = ps->pad_begin_duration - seek_sample;
//;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples);
}
/* body: find position relative to decoder's current sample */
else if (play_forever || seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_relative, vgmstream->current_sample);
/* seek can be in some part of the body, depending on looped/decoder's current/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* seekr=50s, curr=95 > restart + decode=50s */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* seekr=95s, curr=50 > decode=95-50=45s */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* seekr=120s (outside decode, can happen when body is set manually) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* seekr=6s > 6-5+3 > seek=4s inside decoder < 20s: decode 4s from start, or 1s if current was at 3s */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* seek can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
int32_t loop_body, loop_seek, loop_curr;
/* current must have reached loop start at some point */
if (!vgmstream->hit_loop) {
int32_t skip_samples;
if (vgmstream->current_sample > vgmstream->loop_start_sample) { /* may be 0 */
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: must loop / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (shouldn't happen?) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: current outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop(vgmstream, 0);
}
loop_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
loop_seek = seek_relative - vgmstream->loop_start_sample;
loop_count = loop_seek / loop_body;
loop_seek = loop_seek % loop_body;
loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
/* when "ignore fade" is used and seek falls into non-fade part, this needs to seek right before it
so when calling seek_force_loop detection kicks in, and non-fade then decodes normally */
if (vgmstream->loop_target && vgmstream->loop_target == loop_count) {
loop_seek = loop_body;
}
//;VGM_LOG("SEEK: in loop / seekl=%i, loops=%i, cur=%i, dec=%i\n", loop_seek, loop_count, loop_curr, decode_samples);
if (loop_seek < loop_curr) {
decode_samples += loop_seek;
seek_force_loop(vgmstream, loop_count);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples += (loop_seek - loop_curr);
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (!play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
/* done at the end in case of reset */
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
//;VGM_LOG("SEEK: pad start / dec=%i\n", 0);
}
/* pad end and beyond: ignored */
else {
decode_samples = 0;
else if (is_config && !play_forever && seek_sample >= ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
if (!is_looped)
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: end silence / dec=%i\n", decode_samples);
//;VGM_LOG("SEEK: end silence / dec=%i\n", 0);
/* looping decoder state isn't changed (seek backwards could use current sample) */
}
/* body: seek relative to decoder's current sample */
else {
#if 0
//TODO calculate samples into loop number N, and into fade region (segmented layout can only seek to loop start)
seek_force_decode(vgmstream, decode_samples);
/* optimize as layouts can seek faster internally */
if (vgmstream->layout_type == layout_segmented) {
seek_layout_segmented(vgmstream, seek_sample);
}
else if (vgmstream->layout_type == layout_layered) {
seek_layout_layered(vgmstream, seek_sample);
}
else
#endif
seek_body(vgmstream, seek_sample);
vgmstream->pstate.play_position = seek_sample;
/* done at the end in case of reset (that restores these values) */
if (is_config) {
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
}
if (is_config)
vgmstream->pstate.play_position = seek_sample;
}

View File

@ -1248,7 +1248,6 @@ void load_acb_wave_info(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
/* done */
fail:
VGM_LOG("close %x\n", (uint32_t)acb.CueNames);
utf_close(acb.Header);
utf_close(acb.CueNames);

View File

@ -5,31 +5,49 @@
VGMSTREAM* init_vgmstream_acx(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
off_t subfile_offset;
size_t subfile_size;
uint32_t subfile_offset, subfile_size, subfile_id;
int total_subsongs, target_subsong = sf->stream_index;
/* checks */
if (!check_extensions(sf,"acx"))
goto fail;
if (read_u32be(0x00,sf) != 0x00000000)
goto fail;
return NULL;
/* simple container for sfx and rarely music [Burning Rangers (SAT)],
* mainly used until .csb was introduced */
total_subsongs = read_u32be(0x04,sf);
if (total_subsongs > 256 || total_subsongs == 0) /* arbitrary max */
return NULL;
if (!check_extensions(sf,"acx"))
return NULL;
init_vgmstream_t init_vgmstream = NULL;
const char* fake_ext = NULL;
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
subfile_offset = read_u32be(0x08 + (target_subsong-1) * 0x08 + 0x00,sf);
subfile_size = read_u32be(0x08 + (target_subsong-1) * 0x08 + 0x04,sf);
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "adx");
subfile_id = read_u32be(subfile_offset,sf);
if (subfile_id == get_id32be("OggS")) { /* 12Riven (PC) */
init_vgmstream = init_vgmstream_ogg_vorbis;
fake_ext = "ogg";
}
else if ((subfile_id & 0xFFFF0000) == 0x80000000) {
init_vgmstream = init_vgmstream_adx;
fake_ext = "adx";
}
else {
goto fail;
}
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, fake_ext);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_adx(temp_sf);
vgmstream = init_vgmstream(temp_sf);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_subsongs;

View File

@ -324,7 +324,7 @@ fail:
return 0;
}
static int is_ue4_msadpcm(STREAMFILE* sf, riff_fmt_chunk* fmt, int fact_sample_count, off_t start_offset);
static bool is_ue4_msadpcm(STREAMFILE* sf, riff_fmt_chunk* fmt, int fact_sample_count, off_t start_offset, uint32_t data_size);
static size_t get_ue4_msadpcm_interleave(STREAMFILE* sf, riff_fmt_chunk* fmt, off_t start, size_t size);
@ -865,7 +865,7 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
}
/* UE4 uses interleaved mono MSADPCM, try to autodetect without breaking normal MSADPCM */
if (fmt.coding_type == coding_MSADPCM && is_ue4_msadpcm(sf, &fmt, fact_sample_count, start_offset)) {
if (fmt.coding_type == coding_MSADPCM && is_ue4_msadpcm(sf, &fmt, fact_sample_count, start_offset, data_size)) {
vgmstream->coding_type = coding_MSADPCM_int;
vgmstream->codec_config = 1; /* mark as UE4 MSADPCM */
vgmstream->frame_size = fmt.block_size;
@ -873,6 +873,8 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
vgmstream->interleave_block_size = get_ue4_msadpcm_interleave(sf, &fmt, start_offset, data_size);
if (fmt.size == 0x36)
vgmstream->num_samples = read_s32le(fmt.offset+0x08+0x32, sf);
else if (fmt.size == 0x32)
vgmstream->num_samples = msadpcm_bytes_to_samples(data_size / fmt.channels, fmt.block_size, 1);
}
/* Dynasty Warriors 5 (Xbox) 6ch interleaves stereo frames, probably not official */
@ -937,53 +939,79 @@ fail:
return NULL;
}
/* UE4 MSADPCM is quite normal but has a few minor quirks we can use to detect it */
static int is_ue4_msadpcm(STREAMFILE* sf, riff_fmt_chunk* fmt, int fact_sample_count, off_t start) {
static bool is_ue4_msadpcm_blocks(STREAMFILE* sf, riff_fmt_chunk* fmt, uint32_t offset, uint32_t data_size) {
uint32_t max_offset = 10 * fmt->block_size; /* try N blocks */
if (max_offset > offset + data_size)
max_offset = offset + data_size;
/* multichannel ok */
if (fmt->channels < 2)
goto fail;
/* UE4 encoder doesn't calculate optimal coefs and uses certain values every frame.
* Implicitly this should reject stereo frames (not used in UE4), that have scale/coefs in different positions. */
while (offset < max_offset) {
uint8_t coefs = read_u8(offset+0x00, sf);
uint16_t scale = read_u16le(offset+0x01, sf);
/* UE4 class is "ADPCM", assume it's the extension too */
if (!check_extensions(sf, "adpcm"))
goto fail;
/* mono frames should only fill the lower bits (4b index) */
if (coefs > 0x07)
return false;
/* UE4 encoder doesn't add "fact" */
if (fact_sample_count != 0)
goto fail;
/* fixed block size */
if (fmt->block_size != 0x200)
goto fail;
/* later UE4 versions use 0x36 */
if (fmt->size != 0x32 && fmt->size != 0x36)
goto fail;
/* size 0x32 in older UE4 matches standard MSADPCM, so add extra detection */
if (fmt->size == 0x32) {
off_t offset = start;
off_t max_offset = 5 * fmt->block_size; /* try N blocks */
if (max_offset > get_streamfile_size(sf))
max_offset = get_streamfile_size(sf);
/* their encoder doesn't calculate optimal coefs and uses fixed values every frame
* (could do it for fmt size 0x36 too but maybe they'll fix it in the future) */
while (offset <= max_offset) {
if (read_u8(offset+0x00, sf) != 0 || read_u16le(offset+0x01, sf) != 0x00E6)
goto fail;
offset += fmt->block_size;
/* size 0x36 always uses scale 0x00E6 and coefs 0x00 while size 0x32 usually does, except for early
* games where it may use more standard values [2013: Infected Wars (iPhone)] */
if (fmt->block_size == 0x200) {
if (scale == 0x00E6 && coefs != 0x00)
return false;
}
else {
if (scale > 0x4000) { /* observed max (high scales exists) */
VGM_LOG("RIFF: unexpected UE4 MSADPCM scale=%x\n", scale);
return false;
}
}
offset += fmt->block_size;
}
return 1;
fail:
return 0;
return true;
}
/* UE4 MSADPCM has a few minor quirks we can use to detect it */
static bool is_ue4_msadpcm(STREAMFILE* sf, riff_fmt_chunk* fmt, int fact_sample_count, off_t start, uint32_t data_size) {
/* UE4 allows >=2ch (sample rate may be anything), while mono files are just regular MSADPCM */
if (fmt->channels < 2)
return false;
/* UE4 encoder doesn't add "fact" while regular encoders usually do (but not always) */
if (fact_sample_count != 0)
return false;
/* later UE4 versions use fmt size 0x36 (unlike standard MSADPCM's 0x32), and only certain block sizes */
if (fmt->size == 0x36) {
if (!(fmt->block_size == 0x200))
return false;
}
else if (fmt->size == 0x32) {
/* other than 0x200 is rarely used [2013: Infected Wars (iPhone)] */
if (!(fmt->block_size == 0x200 || fmt->block_size == 0x9b || fmt->block_size == 0x69))
return false;
/* could do it for fmt size 0x36 too but not important */
if (!is_ue4_msadpcm_blocks(sf, fmt, start, data_size))
return false;
}
else {
return false;
}
/* UE4's class is "ADPCM", assume it's the extension too (also safer since can't tell UE4 MSADPCM from .wav ADPCM in some cases) */
if (!check_extensions(sf, "adpcm"))
return false;
return true;
}
/* for maximum annoyance later UE4 versions (~v4.2x?) interleave single frames instead of
* half interleave, but don't have flags to detect so we need some heuristics. Most later
* games with 0x36 chunk size use v2_interleave but notably Travis Strikes Again doesn't */
* games with 0x36 chunk size use v2_interleave but notably Travis Strikes Again doesn't */
static size_t get_ue4_msadpcm_interleave(STREAMFILE* sf, riff_fmt_chunk* fmt, off_t start, size_t size) {
size_t v1_interleave = size / fmt->channels;
size_t v2_interleave = fmt->block_size;
@ -1009,23 +1037,19 @@ static size_t get_ue4_msadpcm_interleave(STREAMFILE* sf, riff_fmt_chunk* fmt, of
is_blank_full = memcmp(nibbles_full, empty, nibbles_size) == 0;
/* last frame is almost always padded, so should at half interleave */
if (!is_blank_half && !is_blank_full) {
if (!is_blank_half && !is_blank_full)
return v1_interleave;
}
/* last frame is padded, and half interleave is not: should be regular interleave*/
if (!is_blank_half && is_blank_full) {
/* last frame is padded, and half interleave is not: should be regular interleave */
if (!is_blank_half && is_blank_full)
return v2_interleave;
}
/* last frame is silent-ish, so should at half interleave (TSA's SML_DarknessLoop_01, TSA_CAD_YAKATA)
* this doesn't work too well b/c num_samples at 0x36 uses all data, may need adjustment */
{
int i;
int empty_nibbles_full = 1, empty_nibbles_half = 1;
for (i = 0; i < sizeof(nibbles_full); i++) {
for (int i = 0; i < sizeof(nibbles_full); i++) {
uint8_t n1 = ((nibbles_full[i] >> 0) & 0x0f);
uint8_t n2 = ((nibbles_full[i] >> 4) & 0x0f);
if ((n1 != 0x0 && n1 != 0xf && n1 != 0x1) || (n2 != 0x0 && n2 != 0xf && n2 != 0x1)) {
@ -1034,7 +1058,7 @@ static size_t get_ue4_msadpcm_interleave(STREAMFILE* sf, riff_fmt_chunk* fmt, of
}
}
for (i = 0; i < sizeof(nibbles_half); i++) {
for (int i = 0; i < sizeof(nibbles_half); i++) {
uint8_t n1 = ((nibbles_half[i] >> 0) & 0x0f);
uint8_t n2 = ((nibbles_half[i] >> 4) & 0x0f);
if ((n1 != 0x0 && n1 != 0xf && n1 != 0x1) || (n2 != 0x0 && n2 != 0xf && n2 != 0x1)) {
@ -1043,10 +1067,8 @@ static size_t get_ue4_msadpcm_interleave(STREAMFILE* sf, riff_fmt_chunk* fmt, of
}
}
if (empty_nibbles_full && empty_nibbles_half){
VGM_LOG("v1 b\n");
if (empty_nibbles_full && empty_nibbles_half)
return v1_interleave;
}
}
/* other tests? */

View File

@ -25,7 +25,6 @@ typedef enum {
AICA = 10, /* YAMAHA AICA ADPCM (Dreamcast games) */
MSADPCM = 11, /* MS ADPCM (Windows games) */
NGC_DSP = 12, /* NGC DSP (Nintendo games) */
PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */
PSX_bf = 14, /* PS-ADPCM with bad flags */
MS_IMA = 15, /* Microsoft IMA ADPCM */
PCM8_U = 16, /* 8-bit unsigned PCM */
@ -79,6 +78,7 @@ typedef struct {
uint32_t interleave_last;
uint32_t interleave_first;
uint32_t interleave_first_skip;
uint32_t frame_size;
uint32_t channels;
uint32_t sample_rate;
@ -264,7 +264,6 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case PCM16BE: coding = coding_PCM16BE; break;
case PCM8: coding = coding_PCM8; break;
case PCM8_U: coding = coding_PCM8_U; break;
case PCM8_U_int: coding = coding_PCM8_U_int; break;
case PCM8_SB: coding = coding_PCM8_SB; break;
case ULAW: coding = coding_ULAW; break;
case ALAW: coding = coding_ALAW; break;
@ -335,9 +334,6 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
/* codec specific (taken from GENH with minimal changes) */
switch (coding) {
case coding_PCM8_U_int:
vgmstream->layout_type = layout_none;
break;
case coding_PCM24LE:
case coding_PCM24BE:
case coding_PCM16LE:
@ -457,20 +453,24 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
break;
case coding_MS_IMA:
if (!txth.interleave) goto fail; /* creates garbage */
vgmstream->interleave_block_size = txth.interleave;
vgmstream->interleave_block_size = txth.frame_size ? txth.frame_size : txth.interleave;
vgmstream->layout_type = layout_none;
vgmstream->allow_dual_stereo = 1; //???
break;
case coding_MSADPCM:
if (vgmstream->channels > 2) goto fail;
if (!txth.interleave) goto fail;
vgmstream->frame_size = txth.interleave;
vgmstream->layout_type = layout_none;
if (vgmstream->channels > 2) goto fail; //can't handle
if (txth.interleave && txth.frame_size) {
coding = coding_MSADPCM_int;
vgmstream->frame_size = txth.frame_size;
vgmstream->interleave_block_size = txth.interleave;
vgmstream->layout_type = layout_interleave;
}
else {
vgmstream->frame_size = txth.frame_size ? txth.frame_size : txth.interleave;
vgmstream->layout_type = layout_none;
}
break;
case coding_XBOX_IMA:
@ -974,7 +974,6 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) {
else if (is_string(val,"PCM16LE")) return PCM16LE;
else if (is_string(val,"PCM8")) return PCM8;
else if (is_string(val,"PCM8_U")) return PCM8_U;
else if (is_string(val,"PCM8_U_int")) return PCM8_U_int;
else if (is_string(val,"PCM8_SB")) return PCM8_SB;
else if (is_string(val,"SDX2")) return SDX2;
else if (is_string(val,"DVI_IMA")) return DVI_IMA;
@ -1120,6 +1119,9 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
txth->data_size -= skip;
}
}
else if (is_string(key,"frame_size")) {
if (!parse_num(txth->sf_head,txth,val, &txth->frame_size)) goto fail;
}
/* BASE CONFIG */
else if (is_string(key,"channels")) {
@ -2026,6 +2028,7 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_
else if ((n = is_string_field(val,"interleave_last"))) value = txth->interleave_last;
else if ((n = is_string_field(val,"interleave_first"))) value = txth->interleave_first;
else if ((n = is_string_field(val,"interleave_first_skip")))value = txth->interleave_first_skip;
else if ((n = is_string_field(val,"frame_size"))) value = txth->frame_size;
else if ((n = is_string_field(val,"channels"))) value = txth->channels;
else if ((n = is_string_field(val,"sample_rate"))) value = txth->sample_rate;
else if ((n = is_string_field(val,"start_offset"))) value = txth->start_offset;
@ -2133,7 +2136,7 @@ fail:
static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
switch(txth->codec) {
case MS_IMA:
return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels);
return ms_ima_bytes_to_samples(bytes, txth->frame_size ? txth->frame_size : txth->interleave, txth->channels);
case XBOX:
return xbox_ima_bytes_to_samples(bytes, txth->channels);
case NGC_DSP:
@ -2150,7 +2153,6 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
case PCM16LE:
return pcm16_bytes_to_samples(bytes, txth->channels);
case PCM8:
case PCM8_U_int:
case PCM8_U:
case PCM8_SB:
case ULAW:
@ -2163,11 +2165,11 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
case TGC:
return pcm_bytes_to_samples(bytes, txth->channels, 4);
case MSADPCM:
return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels);
return msadpcm_bytes_to_samples(bytes, txth->frame_size ? txth->frame_size : txth->interleave, txth->channels);
case ATRAC3:
return atrac3_bytes_to_samples(bytes, txth->interleave);
return atrac3_bytes_to_samples(bytes, txth->frame_size ? txth->frame_size : txth->interleave);
case ATRAC3PLUS:
return atrac3plus_bytes_to_samples(bytes, txth->interleave);
return atrac3plus_bytes_to_samples(bytes, txth->frame_size ? txth->frame_size : txth->interleave);
case AAC:
return aac_get_samples(txth->sf_body, txth->start_offset, bytes);
#ifdef VGM_USE_MPEG
@ -2175,7 +2177,7 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
return mpeg_get_samples(txth->sf_body, txth->start_offset, bytes);
#endif
case AC3:
return ac3_bytes_to_samples(bytes, txth->interleave, txth->channels);
return ac3_bytes_to_samples(bytes, txth->frame_size ? txth->frame_size : txth->interleave, txth->channels);
case ASF:
return asf_bytes_to_samples(bytes, txth->channels);
case EAXA:

View File

@ -6,7 +6,7 @@
VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset, extradata_offset, interleave;
int channels, loop_flag, sample_rate, codec;
int channels, loop_flag, sample_rate, codec, version;
int32_t num_samples, loop_start, loop_end;
int big_endian;
read_u32_t read_u32;
@ -16,17 +16,16 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "VAW3") && /* Happy Feet Two (3DS) */
!is_id32be(0x00,sf, "WWAV") && /* Mighty Switch Force! (beta) (Wii) */
read_u32le(0x00,sf) != 0xE5B7ECFE && /* common LE (hashed something?) */
read_u32be(0x00,sf) != 0xE5B7ECFE &&
read_u32be(0x00,sf) != 0xC9FB0C03) /* NDS [Lalaloopsy, Adventure Time: HIKWYSOG (DS)] */
goto fail;
/* 0x04: version? common=0, VAW3=2 */
return NULL;
if (!check_extensions(sf, "wave"))
goto fail;
return NULL;
/* assumed */
big_endian = read_u32be(0x00,sf) == 0xE5B7ECFE;
big_endian = read_u32be(0x00,sf) == 0xE5B7ECFE || is_id32be(0x00,sf, "WWAV");
if (big_endian) {
read_u32 = read_u32be;
read_s32 = read_s32be;
@ -37,6 +36,7 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
read_f32 = read_f32le;
}
version = read_u32(0x04,sf); /* common=0x0000, VAW3=0x0002, 0x50000=WWAV */
if (read_u32(0x08,sf) != get_streamfile_size(sf))
goto fail;
@ -64,6 +64,11 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
loop_flag = 1;
}
/* normalize codec: WWAV uses codec 0x00 for DSP */
if (codec == 0x00 && version == 0x00050000 && start_offset > 0x40) {
codec = 0x02;
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
@ -76,7 +81,7 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
vgmstream->meta_type = meta_WAVE;
/* not sure if there are other codecs but anyway (based also see wave-segmented) */
/* not sure if there are other codecs but anyway (based on wave-segmented) */
switch(codec) {
case 0x02:
/* DS games use IMA, no apparent flag (could also test ID) */
@ -97,9 +102,16 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;
/* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */
dsp_read_coefs(vgmstream, sf, extradata_offset+0x00, 0x2c, big_endian);
dsp_read_hist(vgmstream, sf, extradata_offset+0x22, 0x2c, big_endian);
/* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2 + ?, per channel */
int head_spacing = 0x2c;
int hist_spacing = 0x22;
if (version == 0x00050000) { /* has an extra empty 16b after coefs */
head_spacing = 0x2e;
hist_spacing = 0x24;
}
dsp_read_coefs(vgmstream, sf, extradata_offset + 0x00, head_spacing, big_endian);
dsp_read_hist(vgmstream, sf, extradata_offset + hist_spacing, head_spacing, big_endian);
}
break;
default:

View File

@ -21,7 +21,7 @@ VGMSTREAM* init_vgmstream_zsnd(STREAMFILE* sf) {
/* .zss/zsm: standard
* .ens/enm: same for PS2
* .zsd: normal or compact [BMX XXX (Xbox), Aggresive Inline (Xbox), Dave Mirra Freestyle BMX (PS1/PS2)] */
* .zsd: normal or compact [BMX XXX (Xbox), Aggresive Inline (Xbox), Dave Mirra Freestyle BMX 1/2 (PS1/PS2)] */
if (!check_extensions(sf, "zss,zsm,ens,enm,zsd"))
return NULL;
/* probably zss=stream, zsm=memory; no diffs other than size */
@ -68,7 +68,7 @@ VGMSTREAM* init_vgmstream_zsnd(STREAMFILE* sf) {
* table1 may have more entries than table2/3, and sometimes isn't set
*/
/* V1 has no table heads, rare [Aggresive Inline (Xbox), Dave Mirra Freestyle BMX (PS1/PS2)]
/* V1 has no table heads, rare [Aggresive Inline (Xbox), Dave Mirra Freestyle BMX 1/2 (PS1/PS2)]
* no apparent flag but we can test if table heads offsets appear */
is_v1 = read_32bit(0x14,sf) <= read_32bit(0x1c,sf) &&
read_32bit(0x1c,sf) <= read_32bit(0x24,sf) &&
@ -185,7 +185,7 @@ VGMSTREAM* init_vgmstream_zsnd(STREAMFILE* sf) {
case 0x50533220: /* "PS2 " (also for PSP) */
case 0x50535820: /* "PSX " */
if (table2_entries == 0) {
/* rare, seen in MUSIC.ZSD but SFX*.ZSD do have headers [Dave Mirra Freestyle BMX (PS1/PS2)] */
/* rare, seen in MUSIC.ZSD but SFX*.ZSD do have headers [Dave Mirra Freestyle BMX 1/2 (PS1/PS2)] */
sample_rate = 0x1000;
layers = 0x02;
}