From 057d66cf99cf2ed9eb940426ba0043435174e303 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 29 Aug 2019 00:54:49 +0200 Subject: [PATCH] Add Platinum ADPCM for .wem [Bayonetta 2 (Switch)] --- README.md | 1 + src/coding/coding.h | 4 ++ src/coding/ptadpcm_decoder.c | 98 ++++++++++++++++++++++++++++++++ src/formats.c | 1 + src/libvgmstream.vcproj | 12 ++-- src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 + src/meta/wwise.c | 51 +++++++++++++---- src/vgmstream.c | 11 ++++ src/vgmstream.h | 1 + 10 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 src/coding/ptadpcm_decoder.c diff --git a/README.md b/README.md index 82ad825d..963239e0 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,7 @@ are used in few games. - Paradigm MC3 ADPCM - FMOD FADPCM 4-bit ADPCM - Konami XMD 4-bit ADPCM +- Platinum 4-bit ADPCM - Argonaut ASF 4-bit ADPCM - Ocean DSA 4-bit ADPCM - Circus XPCM ADPCM diff --git a/src/coding/coding.h b/src/coding/coding.h index 1c2b5c48..89bc1c25 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -183,6 +183,10 @@ void decode_pcfx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin void decode_oki16(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); size_t oki_bytes_to_samples(size_t bytes, int channels); +/* ptadpcm_decoder */ +void decode_ptadpcm(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size); +size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size); + /* ea_mt_decoder*/ ea_mt_codec_data *init_ea_mt(int channels, int type); ea_mt_codec_data *init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets); diff --git a/src/coding/ptadpcm_decoder.c b/src/coding/ptadpcm_decoder.c new file mode 100644 index 00000000..a866c392 --- /dev/null +++ b/src/coding/ptadpcm_decoder.c @@ -0,0 +1,98 @@ +#include "coding.h" + + +/* a somewhat IMA-like mix of step+step index all in one */ +static const int32_t ptadpcm_table[(16+16)*16] = { /* 16 of (step+index) + 16 values in a nibble */ + -14, 2, -10, 2, -7, 1, -5, 1, -3, 0, -2, 0, -1, 0, 0, 0, + 0, 0, 1, 0, 2, 0, 3, 0, 5, 1, 7, 1, 10, 2, 14, 2, + -28, 3, -20, 3, -14, 2, -10, 2, -7, 1, -5, 1, -3, 1, -1, 0, + 1, 0, 3, 1, 5, 1, 7, 1, 10, 2, 14, 2, 20, 3, 28, 3, + -56, 4, -40, 4, -28, 3, -20, 3, -14, 2, -10, 2, -6, 2, -2, 1, + 2, 1, 6, 2, 10, 2, 14, 2, 20, 3, 28, 3, 40, 4, 56, 4, + -112, 5, -80, 5, -56, 4, -40, 4, -28, 3, -20, 3, -12, 3, -4, 2, + 4, 2, 12, 3, 20, 3, 28, 3, 40, 4, 56, 4, 80, 5, 112, 5, + -224, 6, -160, 6, -112, 5, -80, 5, -56, 4, -40, 4, -24, 4, -8, 3, + 8, 3, 24, 4, 40, 4, 56, 4, 80, 5, 112, 5, 160, 6, 224, 6, + -448, 7, -320, 7, -224, 6, -160, 6, -112, 5, -80, 5, -48, 5, -16, 4, + 16, 4, 48, 5, 80, 5, 112, 5, 160, 6, 224, 6, 320, 7, 448, 7, + -896, 8, -640, 8, -448, 7, -320, 7, -224, 6, -160, 6, -96, 6, -32, 5, + 32, 5, 96, 6, 160, 6, 224, 6, 320, 7, 448, 7, 640, 8, 896, 8, + -1792, 9, -1280, 9, -896, 8, -640, 8, -448, 7, -320, 7, -192, 7, -64, 6, + 64, 6, 192, 7, 320, 7, 448, 7, 640, 8, 896, 8, 1280, 9, 1792, 9, + -3584, 10, -2560, 10, -1792, 9, -1280, 9, -896, 8, -640, 8, -384, 8, -128, 7, + 128, 7, 384, 8, 640, 8, 896, 8, 1280, 9, 1792, 9, 2560, 10, 3584, 10, + -7168, 11, -5120, 11, -3584, 10, -2560, 10, -1792, 9, -1280, 9, -768, 9, -256, 8, + 256, 8, 768, 9, 1280, 9, 1792, 9, 2560, 10, 3584, 10, 5120, 11, 7168, 11, + -14336, 11, -10240, 11, -7168, 11, -5120, 11, -3584, 10, -2560, 10, -1536, 10, -512, 9, + 512, 9, 1536, 10, 2560, 10, 3584, 10, 5120, 11, 7168, 11, 10240, 11, 14336, 11, + -28672, 11, -20480, 11, -14336, 11, -10240, 11, -7168, 11, -5120, 11, -3072, 11, -1024, 10, + 1024, 10, 3072, 11, 5120, 11, 7168, 11, 10240, 11, 14336, 11, 20480, 11, 28672, 11, + /* rest is 0s */ +}; + +/* Platinum "PtADPCM" custom ADPCM for Wwise (reverse engineered from .exes). */ +void decode_ptadpcm(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) { + off_t frame_offset; + int i, frames_in, sample_count = 0, samples_done = 0; + size_t bytes_per_frame, samples_per_frame; + int16_t hist1, hist2; + int index, step; + + /* external interleave (variable size), mono */ + bytes_per_frame = frame_size; + samples_per_frame = 2 + (frame_size - 0x05) * 2; + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; + + /* parse frame header */ + frame_offset = stream->offset + bytes_per_frame*frames_in; + hist2 = read_16bitLE(frame_offset+0x00,stream->streamfile); + hist1 = read_16bitLE(frame_offset+0x02,stream->streamfile); + index = (uint8_t)read_8bit(frame_offset+0x04,stream->streamfile); + + VGM_ASSERT_ONCE(index > 12, "PTADPCM: incorrect index at %x\n", (uint32_t)frame_offset); + + /* write header samples (needed) */ + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist2; + samples_done++; + } + sample_count++; + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist1; + samples_done++; + } + sample_count++; + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + int32_t new_sample; + uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x05 + i/2,stream->streamfile); + uint8_t nibble; + + nibble = !(i&1) ? /* low nibble first */ + (nibbles >> 0) & 0xF : + (nibbles >> 4) & 0xF; + + step = ptadpcm_table[2*(nibble + 16*index) + 0]; + index = ptadpcm_table[2*(nibble + 16*index) + 1]; + new_sample = clamp16(step + 2*hist1 - hist2); + + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = new_sample; + samples_done++; + } + sample_count++; + + hist2 = hist1; + hist1 = new_sample; + } + + //stream->adpcm_history1_32 = hist1; + //stream->adpcm_history2_32 = hist2; +} + +size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size) { + if (channels <= 0 || frame_size < 0x06) return 0; + return (bytes / channels / frame_size) * ((frame_size-0x05) * 2); +} diff --git a/src/formats.c b/src/formats.c index ee8d2d1c..30921d41 100644 --- a/src/formats.c +++ b/src/formats.c @@ -682,6 +682,7 @@ static const coding_info coding_info_list[] = { {coding_XMD, "Konami XMD 4-bit ADPCM"}, {coding_PCFX, "PC-FX 4-bit ADPCM"}, {coding_OKI16, "OKI 4-bit ADPCM (16-bit output)"}, + {coding_PTADPCM, "Platinum 4-bit ADPCM"}, {coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"}, {coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"}, diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 0f3970d7..02439560 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -2022,10 +2022,14 @@ RelativePath=".\coding\psv_decoder.c" > - - + + + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 5baeee79..aa5c52cc 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -564,6 +564,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 3fdecd7c..bbc2cce1 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1210,6 +1210,9 @@ coding\Source Files + + coding\Source Files + coding\Source Files diff --git a/src/meta/wwise.c b/src/meta/wwise.c index b0cdd6c2..37b7f8d8 100644 --- a/src/meta/wwise.c +++ b/src/meta/wwise.c @@ -8,7 +8,7 @@ * * Some info: https://www.audiokinetic.com/en/library/edge/ */ -typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9, OPUS } wwise_codec; +typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9, OPUSNX, OPUS, PTADPCM } wwise_codec; typedef struct { int big_endian; size_t file_size; @@ -73,7 +73,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { #if 0 /* Wwise's RIFF size is often wonky, seemingly depending on codec: - * - PCM, IMA, VORBIS, AAC, OPUS: correct + * - PCM, IMA/PTADPCM, VORBIS, AAC, OPUSNX/OPUS: correct * - DSP, XWMA, ATRAC9: almost always slightly smaller (around 0x50) * - HEVAG: very off * - XMA2: exact file size @@ -172,14 +172,17 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { case 0x0162: ww.codec = XWMA; break; /* WMAPro */ case 0x0165: ww.codec = XMA2; break; /* always with the "XMA2" chunk, Wwise doesn't use XMA1 */ case 0x0166: ww.codec = XMA2; break; - case 0x3039: ww.codec = OPUS; break; /* later renamed to "OPUSNX" */ - //case 0x3040: ww.codec = OPUS; break; /* same for other platforms, supposedly */ case 0xAAC0: ww.codec = AAC; break; case 0xFFF0: ww.codec = DSP; break; case 0xFFFB: ww.codec = HEVAG; break; case 0xFFFC: ww.codec = ATRAC9; break; case 0xFFFE: ww.codec = PCM; break; /* "PCM for Wwise Authoring" */ case 0xFFFF: ww.codec = VORBIS; break; + case 0x3039: ww.codec = OPUSNX; break; /* later renamed from "OPUS" */ +#if 0 + case 0x3040: ww.codec = OPUS; break; + case 0x8311: ww.codec = PTADPCM; break; +#endif default: goto fail; } @@ -193,12 +196,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { /* few older Wwise DSP with num_samples in extra_size [Tony Hawk: Shred (Wii)] */ ww.codec = DSP; } else if (ww.block_align == 0x104 * ww.channels) { - //ww.codec = SWITCH_ADPCM; - /* unknown codec, found in Bayonetta 2 (Switch) - * frames of 0x104 per ch, possibly frame header is hist1(2)/hist2(2)/index(1) - * (may write 2 header samples + FF*2 nibbles = 0x200 samples per block?) - * index only goes up to ~0xb, may be a shift/scale value */ - goto fail; + ww.codec = PTADPCM; /* Bayonetta 2 (Switch) */ } } @@ -561,7 +559,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { break; } - case OPUS: { /* Switch */ + case OPUSNX: { /* Switch */ size_t skip; /* values up to 0x14 seem fixed and similar to HEVAG's (block_align 0x02/04, bits_per_sample 0x10) */ @@ -587,6 +585,25 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { vgmstream->layout_type = layout_none; break; } + +#if 0 + case OPUS: { /* PC/etc */ + ffmpeg_codec_data * ffmpeg_data = NULL; + if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + + /* extra: size 0x12, unknown values, maybe num_samples/etc */ + + ffmpeg_data = init_ffmpeg_offset(streamFile, ww.data_offset,ww.data_size); + if (!ffmpeg_data) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = (int32_t)ffmpeg_data->totalSamples; + break; + } +#endif + #endif case HEVAG: /* PSV */ /* changed values, another bizarre Wwise quirk */ @@ -626,12 +643,24 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { break; } #endif + case PTADPCM: /* substitutes IMA as default ADPCM codec */ + if (ww.bits_per_sample != 4) goto fail; + if (ww.block_align != 0x24 * ww.channels && ww.block_align != 0x104 * ww.channels) goto fail; + + vgmstream->coding_type = coding_PTADPCM; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = ww.block_align / ww.channels; + //vgmstream->codec_endian = ww.big_endian; //? + + vgmstream->num_samples = ptadpcm_bytes_to_samples(ww.data_size, ww.channels, vgmstream->interleave_block_size); + break; default: goto fail; } + if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset) ) goto fail; return vgmstream; diff --git a/src/vgmstream.c b/src/vgmstream.c index 9411f7da..bcb54a53 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1249,6 +1249,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return 14; /* (0x08 - 0x1) * 2 */ case coding_XMD: return (vgmstream->interleave_block_size - 0x06)*2 + 2; + case coding_PTADPCM: + return (vgmstream->interleave_block_size - 0x05)*2 + 2; case coding_EA_MT: return 0; /* 432, but variable in looped files */ case coding_CRI_HCA: @@ -1431,6 +1433,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { return 0x08; case coding_XMD: return vgmstream->interleave_block_size; + case coding_PTADPCM: + return vgmstream->interleave_block_size; case coding_EA_MT: return 0; /* variable (frames of bit counts or PCM frames) */ #ifdef VGM_USE_ATRAC9 @@ -2078,6 +2082,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->interleave_block_size); } break; + case coding_PTADPCM: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_ptadpcm(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do, + vgmstream->interleave_block_size); + } + break; case coding_PCFX: for (ch = 0; ch < vgmstream->channels; ch++) { decode_pcfx(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, diff --git a/src/vgmstream.h b/src/vgmstream.h index 18ce2622..69238452 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -165,6 +165,7 @@ typedef enum { coding_XMD, /* Konami XMD 4-bit ADPCM */ coding_PCFX, /* PC-FX 4-bit ADPCM */ coding_OKI16, /* OKI 4-bit ADPCM with 16-bit output */ + coding_PTADPCM, /* Platinum 4-bit ADPCM */ /* others */ coding_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */