Fix some .adpcm [2013: Infected Wars (iOS)]

This commit is contained in:
bnnm 2024-03-31 16:09:36 +02:00
parent 5ad05ba736
commit 94282e0514
2 changed files with 231 additions and 196 deletions

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,155 @@ static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
}
}
static void seek_layout_standard(VGMSTREAM* vgmstream, int32_t seek_sample) {
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_sample, vgmstream->current_sample);
int32_t decode_samples = 0;
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;
if (is_config)
seek_relative = seek_relative - 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 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_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
int32_t loop_seek = (seek_relative - vgmstream->loop_start_sample) % loop_body;
int loop_count = loop_seek / loop_body;
/* 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: must loop / 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) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
;VGM_LOG("SEEK: outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop_end(vgmstream, 0);
}
int32_t loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
//;VGM_LOG("SEEK: in loop / seekr=%i, seekl=%i, loops=%i, cur=%i, dec=%i\n", seek_relative, loop_seek, loop_count, loop_curr, decode_samples);
/* 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_end detection kicks in, and non-fade then decodes normally */
if (vgmstream->loop_target && vgmstream->loop_target == loop_count) {
loop_seek = loop_body;
//decode_samples += loop_seek;
//VGM_LOG("outside!: %i\n", loop_body);
}
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 - 1; //todo
//;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);
}
}
/* done at the end in case of reset */
if (is_config) {
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
seek_force_decode(vgmstream, decode_samples);
;VGM_LOG("SEEK: force=%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,9 +214,8 @@ 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;
@ -114,121 +223,8 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t 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;
}
/* 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)
@ -238,8 +234,25 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
/* looping decoder state isn't changed (seek backwards could use current sample) */
}
/* body: seek relative to decoder's current sample */
else {
#if 0
//TODO issues: handles seek into loops number N correctly, and into fade region
//- handle looping within
//- handle
/* 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_layout_standard(vgmstream, seek_sample);
}
seek_force_decode(vgmstream, decode_samples);
vgmstream->pstate.play_position = seek_sample;
}

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? */