mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-24 06:50:20 +01:00
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:
commit
07ed5f11a4
68
doc/TXTH.md
68
doc/TXTH.md
@ -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)
|
||||
|
@ -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
|
||||
|
306
src/base/seek.c
306
src/base/seek.c
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
124
src/meta/riff.c
124
src/meta/riff.c
@ -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? */
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user