From 60b2ecc21dead65189d0308f7db64425417a84b4 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 1 May 2020 16:04:23 +0200 Subject: [PATCH] Add VADPCM for AIFC + .n64 SDK/src samples --- src/coding/coding.h | 5 + src/coding/vadpcm_decoder.c | 155 +++++++++++++++++++++++++++++++ src/formats.c | 2 + src/libvgmstream.vcproj | 4 + src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 + src/meta/aifc.c | 121 +++++++++++++++++++----- src/vgmstream.c | 10 ++ src/vgmstream.h | 14 +-- 9 files changed, 284 insertions(+), 31 deletions(-) create mode 100644 src/coding/vadpcm_decoder.c diff --git a/src/coding/coding.h b/src/coding/coding.h index ea43eb47..7367d292 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -61,6 +61,11 @@ void decode_ngc_dtk(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspaci /* ngc_afc_decoder */ void decode_ngc_afc(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +/* vadpcm_decoder */ +void decode_vadpcm(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int order); +//int32_t vadpcm_bytes_to_samples(size_t bytes, int channels); +void vadpcm_read_coefs_be(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t offset, int order, int entries, int ch); + /* pcm_decoder */ void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); diff --git a/src/coding/vadpcm_decoder.c b/src/coding/vadpcm_decoder.c new file mode 100644 index 00000000..d2f59ad8 --- /dev/null +++ b/src/coding/vadpcm_decoder.c @@ -0,0 +1,155 @@ +#include "coding.h" +#include "../util.h" + + +/* Decodes Silicon Graphics' N64 VADPCM, big brother of GC ADPCM. + * Has external coefs like DSP, but allows tables up to 8 groups of 8 coefs (code book) and also + * up to 8 history samples (order). In practice order must be 2, while files use 2~4 tables, so it + * ends up being an overcomplex XA. Code respects this quirky configurable hist for doc purposes though. + * + * This code is based on N64SoundListTool decoding (by Ice Mario), with bits of the official SDK vadpcm tool + * decompilation. I can't get proper sound from the later though, so not too sure about accuracy of this + * implementation. Output sounds correct though. + */ + +void decode_vadpcm(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int order) { + uint8_t frame[0x09] = {0}; + off_t frame_offset; + int frames_in, sample_count = 0; + size_t bytes_per_frame, samples_per_frame; + int i, j, k, o; + int scale, index; + + int codes[16]; /* AKA ix */ + int16_t hist[8] = {0}; + int16_t out[16]; + int16_t* coefs; + + + VGM_ASSERT_ONCE(order != 2, "VADPCM: wrong order=%i\n", order); + if (order != 2) /* only 2 allowed "in the current implementation" */ + order = 2; + + /* up to 8 (hist[0]=oldest) but only uses latest 2 (order=2), so we don't save the whole thing ATM */ + hist[6] = stream->adpcm_history2_16; + hist[7] = stream->adpcm_history1_16; + + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x09; + samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 16 */ + 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; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + scale = (frame[0] >> 4) & 0xF; + index = (frame[0] >> 0) & 0xF; + + scale = 1 << scale; + + VGM_ASSERT_ONCE(index > 8, "DSP: incorrect index at %x\n", (uint32_t)frame_offset); + if (index > 8) /* assumed */ + index = 8; + coefs = &stream->vadpcm_coefs[index * (order*8) + 0]; + + + /* read and pre-scale all nibbles, since groups of 8 are needed */ + for (i = 0, j = 0; i < 16; i += 2, j++) { + int n0 = (frame[j+1] >> 4) & 0xF; + int n1 = (frame[j+1] >> 0) & 0xF; + + /* sign extend */ + if (n0 & 8) + n0 = n0 - 16; + if (n1 & 8) + n1 = n1 - 16; + + codes[i+0] = n0 * scale; + codes[i+1] = n1 * scale; + } + + /* decode 2 sub-frames of 8 samples (maybe like this since N64 MIPS has registers to spare?) */ + for (j = 0; j < 2; j++) { + /* SDK dec code and N64ST copy 8 codes to a tmp buf and has some complex logic to move out buf + * around, but since that's useless N64 asm would be much more optimized... hopefully */ + int* sf_codes = &codes[j*8]; + int16_t* sf_out = &out[j*8]; + + /* works with 8 samples at a time, related in twisted ways */ + for( i = 0; i < 8; i++) { + int sample, delta = 0; + + /* in practice: delta = coefs[0][i] * hist[6] + coefs[1][i] * hist[7], + * much like XA's coef1*hist1 + coef2*hist2 but with multi coefs */ + for (o = 0; o < order; o++) { + delta += coefs[o*8 + i] * hist[(8 - order) + o]; + } + + /* adds all previous samples */ + for (k = i-1; k > -1; k--) { + for (o = 1; o < order; o++) { /* assumed, since only goes coefs[1][k] */ + delta += sf_codes[(i-1) - k] * coefs[(o*8) + k]; + } + } + + /* scale-filter thing (also seen in DSP) */ + sample = (sf_codes[i] << 11); + sample = (sample + delta) >> 11; + if (sample > 32767) + sample = 32767; + else if (sample < -32768) + sample = -32768; + + sf_out[i] = sample; + } + + /* save subframe hist */ + for (i = 8 - order; i < 8; i++) { + hist[i] = sf_out[i]; + } + } + + + /* copy samples last, since the whole thing is kinda complex to worry about half copying and stuff */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + outbuf[sample_count] = out[i]; + sample_count += channelspacing; + } + + /* update hist once all frame is actually copied */ + if (first_sample + sample_count == samples_per_frame) { + stream->adpcm_history2_16 = hist[6]; + stream->adpcm_history1_16 = hist[7]; + } +} + +/* +int32_t vadpcm_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; + return bytes / channels / 0x09 * 16; +} +*/ + +/* Reads code book, linearly unlike original SDK, that does some strange reordering and pre-scaling + * to reduce some loops. Format is 8 coefs per 'order' per 'entries' (max 8, but order is always 2). So: + * - i: table index (selectable filter tables on every decoded frame) + * - j: order index (coefs for prev N hist samples) + * - k: coef index (multiplication coefficient for 8 samples in a sub-frame) + * coefs[i * (order*8) + j * 8 + k * order] = coefs[i][j][k] */ +void vadpcm_read_coefs_be(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t offset, int order, int entries, int ch) { + int i; + + if (entries > 8) + entries = 8; + VGM_ASSERT(order != 2, "VADPCM: wrong order %i found\n", order); + if (order != 2) + order = 2; + + /* assumes all channels use same coefs, never seen non-mono files */ + for (i = 0; i < entries * order * 8; i++) { + vgmstream->ch[ch].vadpcm_coefs[i] = read_s16be(offset + i*2, sf); + } + vgmstream->codec_config = order; +} diff --git a/src/formats.c b/src/formats.c index 4a28d3cf..f924c7e9 100644 --- a/src/formats.c +++ b/src/formats.c @@ -330,6 +330,7 @@ static const char* extension_list[] = { "mxst", "myspd", + "n64", "naac", "ndp", "ngca", @@ -685,6 +686,7 @@ static const coding_info coding_info_list[] = { {coding_NGC_DSP_subint, "Nintendo DSP 4-bit ADPCM (subinterleave)"}, {coding_NGC_DTK, "Nintendo DTK 4-bit ADPCM"}, {coding_NGC_AFC, "Nintendo AFC 4-bit ADPCM"}, + {coding_VADPCM, "Silicon Graphics VADPCM 4-bit ADPCM"}, {coding_G721, "CCITT G.721 4-bit ADPCM"}, diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 519a80ac..f80e4743 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -2197,6 +2197,10 @@ + + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 5ae1c0e5..c44ab4c9 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1309,6 +1309,9 @@ coding\Source Files + + coding\Source Files + coding\Source Files diff --git a/src/meta/aifc.c b/src/meta/aifc.c index a1a95aef..08a3b6e9 100644 --- a/src/meta/aifc.c +++ b/src/meta/aifc.c @@ -3,7 +3,8 @@ #include "../coding/coding.h" -/* for reading integers inexplicably packed into 80-bit ('double extended') floats */ +/* for reading integers inexplicably packed into 80-bit ('double extended') floats, AKA: + * "80 bit IEEE Standard 754 floating point number (Standard AppleNumeric Environment [SANE] data type Extended)" */ static uint32_t read_f80be(off_t offset, STREAMFILE* sf) { uint8_t buf[0x0a]; int32_t exponent; @@ -49,14 +50,27 @@ static uint32_t find_marker(STREAMFILE* sf, off_t mark_offset, int marker_id) { return -1; } +static int is_str(const char* str, int len, off_t offset, STREAMFILE* sf) { + uint8_t buf[0x100]; -/* AIFF/AIFF-C (Audio Interchange File Format) - Apple format, from Mac/3DO/other games */ + if (len == 0) + len = strlen(str); + + if (len > sizeof(buf)) + return 0; + if (read_streamfile(buf, offset, len, sf) != len) + return 0; + return memcmp(buf, str, len) == 0; /* memcmp to allow "AB\0\0" */ +} + + +/* AIFF/AIFF-C (Audio Interchange File Format - Compressed) - Apple format, from Mac/3DO/other games */ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - off_t start_offset = 0; + off_t start_offset = 0, coef_offset = 0; size_t file_size; coding_t coding_type = 0; - int channel_count = 0, sample_count = 0, sample_size = 0, sample_rate = 0; + int channels = 0, sample_count = 0, sample_size = 0, sample_rate = 0; int interleave = 0; int loop_flag = 0; int32_t loop_start = 0, loop_end = 0; @@ -76,12 +90,13 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { * .adp: Sonic Jam (SAT) * .ai: Dragon Force (SAT) * (extensionless: Doom (3DO) - * .fda: Homeworld 2 (PC) */ + * .fda: Homeworld 2 (PC) + * .n64: Turok (N64) src */ if (check_extensions(sf, "aif,laif,")) { is_aifc_ext = 1; is_aiff_ext = 1; } - else if (check_extensions(sf, "aifc,laifc,aifcl,afc,cbd2,bgm,fda")) { + else if (check_extensions(sf, "aifc,laifc,aifcl,afc,cbd2,bgm,fda,n64")) { is_aifc_ext = 1; } else if (check_extensions(sf, "aiff,laiff,acm,adp,ai,aiffl")) { @@ -96,6 +111,8 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { read_u32be(0x04,sf)+0x08 != file_size) goto fail; + /* AIFF originally allowed only PCM (non-compressed) audio, so newer AIFC was added, + * though some AIFF with other codecs exist */ if (read_u32be(0x08,sf) == 0x41494643) { /* "AIFC" */ if (!is_aifc_ext) goto fail; is_aifc = 1; @@ -127,14 +144,13 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { goto fail; switch(chunk_type) { - case 0x46564552: /* "FVER" (version info) */ + case 0x46564552: /* "FVER" (version info, required) */ if (fver_found) goto fail; if (is_aiff) goto fail; /* plain AIFF shouldn't have */ fver_found = 1; - /* specific size */ - if (chunk_size != 4) goto fail; - + if (chunk_size != 4) + goto fail; /* Version 1 of AIFF-C spec timestamp */ if (read_u32be(offset + 0x00,sf) != 0xA2805140) goto fail; @@ -144,44 +160,46 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { if (comm_found) goto fail; comm_found = 1; - channel_count = read_u16be(offset + 0x00,sf); - if (channel_count <= 0) goto fail; + channels = read_u16be(offset + 0x00,sf); + if (channels <= 0) goto fail; - sample_count = read_u32be(offset + 0x02,sf); /* sometimes number of blocks */ + sample_count = read_u32be(offset + 0x02,sf); /* sample_frames in theory, depends on codec */ sample_size = read_u16be(offset + 0x06,sf); sample_rate = read_f80be(offset + 0x08,sf); if (is_aifc) { uint32_t codec = read_u32be(offset + 0x12,sf); + /* followed by "pascal string": name size + human-readable name (full count padded to even size) */ + switch (codec) { case 0x53445832: /* "SDX2" [3DO games: Super Street Fighter II Turbo (3DO), etc] */ + /* "2:1 Squareroot-Delta-Exact compression" */ coding_type = coding_SDX2; interleave = 0x01; break; case 0x43424432: /* "CBD2" [M2 (arcade 3DO) games: IMSA Racing (M2), etc] */ + /* "2:1 Cuberoot-Delta-Exact compression" */ coding_type = coding_CBD2; interleave = 0x01; break; case 0x41445034: /* "ADP4" */ coding_type = coding_DVI_IMA_int; - if (channel_count != 1) break; /* don't know how stereo DVI is laid out */ + if (channels != 1) break; /* don't know how stereo DVI is laid out */ break; case 0x696D6134: /* "ima4" [Alida (PC), Lunar SSS (iOS)] */ + /* "IMA 4:1FLLR" */ coding_type = coding_APPLE_IMA4; interleave = 0x22; sample_count = sample_count * ((interleave-0x2)*2); break; case 0x434F4D50: { /* "COMP" (generic compression) */ - uint8_t comp_name[255] = {0}; - uint8_t comp_size = read_u8(offset + 0x16, sf); - if (comp_size >= sizeof(comp_name) - 1) goto fail; - - read_streamfile(comp_name, offset + 0x17, comp_size, sf); - if (memcmp(comp_name, "Relic Codec v1.6", comp_size) == 0) { /* Homeworld 2 (PC) */ + uint8_t name_size = read_u8(offset + 0x16, sf); + + if (is_str("Relic Codec v1.6", name_size, offset + 0x17, sf)) { coding_type = coding_RELIC; sample_count = sample_count * 512; } @@ -191,11 +209,19 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { break; } + case 0x56415043: { /* "VAPC" [N64 (SDK mainly but apparently may exist in ROMs)] */ + /* "VADPCM ~4-1" */ + coding_type = coding_VADPCM; + + /* N64 tools don't create FVER, but it's required by the spec (could skip the check though) */ + fver_found = 1; + break; + } + default: VGM_LOG("AIFC: unknown codec\n"); goto fail; } - /* string size and human-readable AIFF-C codec follows */ } else if (is_aiff) { switch (sample_size) { @@ -222,8 +248,9 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { if (data_found) goto fail; data_found = 1; + /* 00: offset (for aligment, usually 0) + * 04: block size (ex. XA: 0x914) */ start_offset = offset + 0x08 + read_u32be(offset + 0x00,sf); - /* when "APCM" XA frame size is at 0x0c, fixed to 0x914 */ break; case 0x4D41524B: /* "MARK" (loops) */ @@ -234,6 +261,33 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { inst_offset = offset; break; + case 0x4150504C: /* "APPL" (application specific) */ + if (is_str("stoc", 0, offset + 0x00, sf)) { + uint8_t name_size = read_u8(offset + 0x4, sf); + off_t next_offset = offset + 0x04 + align_size_to_block(0x1 + name_size, 0x02); + + /* chunks appears multiple times per substring */ + if (is_str("VADPCMCODES", name_size, offset + 0x05, sf)) { + coef_offset = next_offset; + } + else if (is_str("VADPCMLOOPS", name_size, offset + 0x05, sf)) { + /* goes with inst (spec says app chunks have less priority than inst+mark loops) */ + int version = read_u16be(next_offset + 0x00, sf); + int loops = read_u16be(next_offset + 0x02, sf); + if (version != 1 || loops != 1) goto fail; + + loop_start = read_u32be(next_offset + 0x04, sf); + loop_end = read_u32be(next_offset + 0x08, sf); + loop_flag = read_s32be(next_offset + 0x08, sf) != 0; /*-1 = infinite */ + /* 0x10: ADPCM state[16] (hists?) */ + } + else { + VGM_LOG("AIFC: unknown APPL chunk\n"); + goto fail; + } + } + break; + default: break; } @@ -282,7 +336,7 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; @@ -302,7 +356,7 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { int bitrate = read_u16be(start_offset, sf); start_offset += 0x02; - vgmstream->codec_data = init_relic(channel_count, bitrate, sample_rate); + vgmstream->codec_data = init_relic(channels, bitrate, sample_rate); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; @@ -310,8 +364,25 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) { break; } + case coding_VADPCM: + if (channels > 1) goto fail; /* unknown layout */ + if (coef_offset == 0) goto fail; + + vgmstream->layout_type = layout_none; + { + int version = read_u16be(coef_offset + 0x00, sf); + int order = read_u16be(coef_offset + 0x02, sf); + int entries = read_u16be(coef_offset + 0x04, sf); + if (version != 1) goto fail; + + vadpcm_read_coefs_be(vgmstream, sf, coef_offset + 0x06, order, entries, 0); + } + + //vgmstream->num_samples = vadpcm_bytes_to_samples(data_size, channels); /* unneeded */ + break; + default: - vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none; + vgmstream->layout_type = (channels > 1) ? layout_interleave : layout_none; vgmstream->interleave_block_size = interleave; break; } diff --git a/src/vgmstream.c b/src/vgmstream.c index 2729207c..dd33e8b7 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1156,6 +1156,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { case coding_NGC_DSP_subint: return 14; case coding_NGC_AFC: + case coding_VADPCM: return 16; case coding_NGC_DTK: return 28; @@ -1357,6 +1358,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_NGC_DSP_subint: return 0x08 * vgmstream->channels; case coding_NGC_AFC: + case coding_VADPCM: return 0x09; case coding_NGC_DTK: return 0x20; @@ -1722,6 +1724,14 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->channels,vgmstream->samples_into_block,samples_to_do); } break; + case coding_VADPCM: { + int order = vgmstream->codec_config; + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_vadpcm(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do, order); + } + break; + } case coding_PSX: for (ch = 0; ch < vgmstream->channels; ch++) { decode_psx(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, diff --git a/src/vgmstream.h b/src/vgmstream.h index f3be0a40..db5b6d25 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -95,6 +95,7 @@ typedef enum { coding_NGC_DSP_subint, /* Nintendo DSP ADPCM with frame subinterframe */ coding_NGC_DTK, /* Nintendo DTK ADPCM (hardware disc), also called TRK or ADP */ coding_NGC_AFC, /* Nintendo AFC ADPCM */ + coding_VADPCM, /* Silicon Graphics VADPCM */ coding_G721, /* CCITT G.721 */ @@ -786,14 +787,15 @@ typedef struct { /* format specific */ /* adpcm */ - int16_t adpcm_coef[16]; /* for formats with decode coefficients built in */ - int32_t adpcm_coef_3by32[0x60]; /* for Level-5 0x555 */ + int16_t adpcm_coef[16]; /* formats with decode coefficients built in (DSP, some ADX) */ + int32_t adpcm_coef_3by32[0x60]; /* Level-5 0x555 */ + int16_t vadpcm_coefs[8*2*8]; /* VADPCM: max 8 groups * max 2 order * fixed 8 subframe coefs */ union { - int16_t adpcm_history1_16; /* previous sample */ + int16_t adpcm_history1_16; /* previous sample */ int32_t adpcm_history1_32; }; union { - int16_t adpcm_history2_16; /* previous previous sample */ + int16_t adpcm_history2_16; /* previous previous sample */ int32_t adpcm_history2_32; }; union { @@ -808,8 +810,8 @@ typedef struct { double adpcm_history1_double; double adpcm_history2_double; - int adpcm_step_index; /* for IMA */ - int adpcm_scale; /* for MS ADPCM */ + int adpcm_step_index; /* for IMA */ + int adpcm_scale; /* for MS ADPCM */ /* state for G.721 decoder, sort of big but we might as well keep it around */ struct g72x_state g72x_state;