From 94282e0514c0e6c313e7336dbff6965d738e38f5 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 16:09:36 +0200 Subject: [PATCH 1/8] Fix some .adpcm [2013: Infected Wars (iOS)] --- src/base/seek.c | 303 +++++++++++++++++++++++++----------------------- src/meta/riff.c | 124 ++++++++++++-------- 2 files changed, 231 insertions(+), 196 deletions(-) diff --git a/src/base/seek.c b/src/base/seek.c index 5c79c6f9..aa5407d7 100644 --- a/src/base/seek.c +++ b/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,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; } diff --git a/src/meta/riff.c b/src/meta/riff.c index 0f1bb0e3..0f5166f7 100644 --- a/src/meta/riff.c +++ b/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? */ From 9721fa22e9b004987c17bdedc25972caee26f176 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 16:09:50 +0200 Subject: [PATCH 2/8] Fix some .acx [12Riven (PC)] --- src/meta/acx.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/meta/acx.c b/src/meta/acx.c index 430ea448..2effb31b 100644 --- a/src/meta/acx.c +++ b/src/meta/acx.c @@ -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; From a8b140389dfee5b30e6119a454647678a3461a48 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 16:10:00 +0200 Subject: [PATCH 3/8] Fix some .wave [Mighty Switch Force! (Wii)] --- src/meta/wave.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/meta/wave.c b/src/meta/wave.c index 8e93089f..921c8a71 100644 --- a/src/meta/wave.c +++ b/src/meta/wave.c @@ -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: From 318a0c70ebf3809201fc998fabcfca8dfb8528df Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 16:10:18 +0200 Subject: [PATCH 4/8] doc/cleanup --- src/meta/acb.c | 1 - src/meta/zsnd.c | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/meta/acb.c b/src/meta/acb.c index 4b462837..6f7663c0 100644 --- a/src/meta/acb.c +++ b/src/meta/acb.c @@ -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); diff --git a/src/meta/zsnd.c b/src/meta/zsnd.c index 4928cb5d..a50952ad 100644 --- a/src/meta/zsnd.c +++ b/src/meta/zsnd.c @@ -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; } From 8097e772f479d9de9914971e14152583eaa0618c Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 16:21:55 +0200 Subject: [PATCH 5/8] Export loop_start/loop_end for foobar's playlist --- fb2k/foo_vgmstream.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index 2f19c340..5fc3443b 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -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 From 843a0e83a4fa2c855b9729a929e25df585de3a11 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 16:22:17 +0200 Subject: [PATCH 6/8] Fix seeking into the outro region when loop+outro is set --- src/base/seek.c | 79 ++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/src/base/seek.c b/src/base/seek.c index aa5407d7..cfa80b0d 100644 --- a/src/base/seek.c +++ b/src/base/seek.c @@ -34,10 +34,10 @@ static void seek_force_decode(VGMSTREAM* vgmstream, int samples) { } -static void seek_layout_standard(VGMSTREAM* vgmstream, int32_t seek_sample) { +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 = 0; + 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 */ @@ -45,13 +45,9 @@ static void seek_layout_standard(VGMSTREAM* vgmstream, int32_t seek_sample) { 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; + 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 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) */ @@ -92,9 +88,12 @@ static void seek_layout_standard(VGMSTREAM* vgmstream, int32_t seek_sample) { } 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; + //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) { @@ -104,40 +103,39 @@ static void seek_layout_standard(VGMSTREAM* vgmstream, int32_t seek_sample) { } 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); + //;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) */ + /* 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 area / curr=%i, ls=%i, le=%i\n", vgmstream->current_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); } - int32_t loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample; + //;VGM_LOG("SEEK: in loop region / seekr=%i, seekl=%i, loops=%i, dec_curr=%i\n", seek_relative, loop_seek, loop_count, loop_curr); - //;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 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 */ - /* 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); + //;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; + 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 + 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); } @@ -147,18 +145,13 @@ static void seek_layout_standard(VGMSTREAM* vgmstream, int32_t seek_sample) { && 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); + //;VGM_LOG("SEEK: decode=%i, current=%i\n", decode_samples, vgmstream->current_sample); } @@ -220,7 +213,7 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) { reset_vgmstream(vgmstream); ps->pad_begin_left = ps->pad_begin_duration - seek_sample; - //;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples); + //;VGM_LOG("SEEK: pad start / dec=%i\n", 0); } /* pad end and beyond: ignored */ @@ -230,16 +223,15 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) { 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 issues: handles seek into loops number N correctly, and into fade region - //- handle looping within - //- handle + //TODO calculate samples into loop number N, and into fade region (segmented layout can only seek to loop start) + /* optimize as layouts can seek faster internally */ if (vgmstream->layout_type == layout_segmented) { seek_layout_segmented(vgmstream, seek_sample); @@ -249,10 +241,15 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) { } else #endif - seek_layout_standard(vgmstream, seek_sample); + seek_body(vgmstream, 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; + } } - - vgmstream->pstate.play_position = seek_sample; + if (is_config) + vgmstream->pstate.play_position = seek_sample; } From 35ef9d62056bf3e03b689c8358e39a0ac9484137 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 20:01:17 +0200 Subject: [PATCH 7/8] Add TXTP frame_size for MSADPCM interleaved mode [Metal Gear Solid 2 (PC)] --- doc/TXTH.md | 66 ++++++++++++++++++++++++++++++++++++++++--------- src/meta/txth.c | 35 ++++++++++++++++---------- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/doc/TXTH.md b/doc/TXTH.md index 8ec905fe..91e62759 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -124,7 +124,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 +132,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 +153,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 +169,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 +208,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 +228,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 +245,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 +916,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) diff --git a/src/meta/txth.c b/src/meta/txth.c index 3e001604..0d83b7ec 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -79,6 +79,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; @@ -457,20 +458,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: @@ -1120,6 +1125,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 +2034,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 +2142,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: @@ -2163,11 +2172,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 +2184,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: From fc4113a1ea5e731e864b865fe0b43951e9cd3a3c Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 31 Mar 2024 20:07:55 +0200 Subject: [PATCH 8/8] Remove unused TXTH codec PCM8_U_int (use PCM8_U + interleave) --- doc/TXTH.md | 2 -- src/meta/txth.c | 7 ------- 2 files changed, 9 deletions(-) diff --git a/doc/TXTH.md b/doc/TXTH.md index 91e62759..22b41ff5 100644 --- a/doc/TXTH.md +++ b/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)] diff --git a/src/meta/txth.c b/src/meta/txth.c index 0d83b7ec..7ef7d939 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -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 */ @@ -265,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; @@ -336,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: @@ -979,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; @@ -2159,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: