diff --git a/src/coding/coding.h b/src/coding/coding.h index 8dd3cd67..44ed2787 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -36,6 +36,7 @@ void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format); size_t ima_bytes_to_samples(size_t bytes, int channels); size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels); size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index 1db3f407..da3ff67b 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -935,6 +935,80 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci stream->adpcm_step_index = step_index; } +/* IMA with variable frame formats controlled by the block layout. The original code uses + * tables mapping all standard IMA combinations (to optimize calculations), but decodes the same. + * Based on HCS's and Nisto's reverse engineering in h4m_audio_decode. */ +void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format) { + int i, samples_done = 0; + int32_t hist1 = stream->adpcm_history1_32; + int step_index = stream->adpcm_step_index; + size_t header_size; + int is_stereo = (channelspacing > 1); + + /* external interleave (blocked, should call 1 frame) */ + + /* custom header, per channel */ + if (first_sample == 0) { + int channel_pos = is_stereo ? (1 - channel) : channel; /* R hist goes first */ + switch(frame_format) { + case 1: /* combined hist+index */ + hist1 = read_16bitBE(stream->offset + 0x02*channel_pos + 0x00,stream->streamfile) & 0xFFFFFF80; + step_index = (uint8_t)read_8bit(stream->offset + 0x02*channel_pos + 0x01,stream->streamfile) & 0x7f; + break; + case 3: /* separate hist+index */ + hist1 = read_16bitBE(stream->offset + 0x03*channel_pos + 0x00,stream->streamfile); + step_index = (uint8_t)read_8bit(stream->offset + 0x03*channel_pos + 0x02,stream->streamfile); + break; + case 2: /* no hist/index (continues from previous frame) */ + default: + break; + } + + /* write header sample (last nibble is skipped) */ + if (frame_format == 1 || frame_format == 3) { + outbuf[samples_done * channelspacing] = (short)hist1; + samples_done++; + samples_to_do--; + } + + /* clamp corrupted data just in case */ + if (step_index < 0) step_index = 0; + if (step_index > 88) step_index = 88; + } + else { + /* offset adjust for header sample */ + if (frame_format == 1 || frame_format == 3) { + first_sample--; + } + } + + /* offset adjust */ + switch(frame_format) { + case 1: header_size = (channelspacing*0x02); break; + case 3: header_size = (channelspacing*0x03); break; + default: header_size = 0; break; + } + + /* decode block nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + off_t byte_offset = is_stereo ? + stream->offset + header_size + i : /* stereo: one nibble per channel */ + stream->offset + header_size + i/2; /* mono: consecutive nibbles */ + int nibble_shift = is_stereo ? + (!(channel&1) ? 0:4) : /* stereo: L=low, R=high */ + (!(i&1) ? 0:4); /* mono: low first */ + + std_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index); + + outbuf[samples_done * channelspacing] = (short)(hist1); + samples_done++; + } + + stream->adpcm_history1_32 = hist1; + stream->adpcm_step_index = step_index; +} + +/* ************************************************************* */ size_t ima_bytes_to_samples(size_t bytes, int channels) { /* 2 samples per byte (2 nibbles) in stereo or mono config */ diff --git a/src/formats.c b/src/formats.c index 4647ce66..e0d82f01 100644 --- a/src/formats.c +++ b/src/formats.c @@ -552,6 +552,8 @@ static const coding_info coding_info_list[] = { {coding_AWC_IMA, "Rockstar AWC 4-bit IMA ADPCM"}, {coding_UBI_IMA, "Ubisoft 4-bit IMA ADPCM"}, + {coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"}, + {coding_MSADPCM, "Microsoft 4-bit ADPCM"}, {coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"}, {coding_WS, "Westwood Studios VBR ADPCM"}, diff --git a/src/layout/blocked_h4m.c b/src/layout/blocked_h4m.c index 14f64cc8..a328292e 100644 --- a/src/layout/blocked_h4m.c +++ b/src/layout/blocked_h4m.c @@ -42,38 +42,19 @@ void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream) { block_size = 0x08 + frame_size; block_samples = frame_samples; - /* skip data from other audio tracks */ - if (vgmstream->num_streams) { + if (vgmstream->num_streams > 1 && vgmstream->stream_index > 1) { uint32_t audio_bytes = frame_size - 0x04; - block_skip += (audio_bytes / vgmstream->num_streams) * vgmstream->stream_index; + block_skip += (audio_bytes / vgmstream->num_streams) * (vgmstream->stream_index-1); } - //VGM_ASSERT(frame_format < 1 && frame_format > 3, "H4M: unknown frame_format %x at %lx\n", frame_format, block_offset); VGM_ASSERT(frame_format == 1, "H4M: unknown frame_format %x at %lx\n", frame_format, block_offset); - //todo handle in the decoder? - //todo right channel first? - /* get ADPCM hist (usually every new block) */ - for (i = 0; i < vgmstream->channels; i++) { - if (frame_format == 1) { /* combined hist+index */ - vgmstream->ch[i].adpcm_history1_32 = read_16bitBE(block_offset + block_skip + 0x02*i + 0x00,streamFile) & 0xFFFFFF80; - vgmstream->ch[i].adpcm_step_index = read_8bit(block_offset + block_skip + 0x02*i + 0x01,streamFile) & 0x7f; - vgmstream->ch[i].offset = block_offset + block_skip + 0x02*vgmstream->channels; - } - else if (frame_format == 3) { /* separate hist+index */ - vgmstream->ch[i].adpcm_history1_32 = read_16bitBE(block_offset + block_skip + 0x03*i + 0x00,streamFile); - vgmstream->ch[i].adpcm_step_index = read_8bit(block_offset + block_skip + 0x03*i + 0x02,streamFile); - vgmstream->ch[i].offset = block_offset + block_skip + 0x03*vgmstream->channels; - } - else if (frame_format == 2) { /* no hist/index */ - vgmstream->ch[i].offset = block_offset + block_skip; - } - } + /* pass current mode to the decoder */ + vgmstream->codec_version = (frame_format << 8) | (vgmstream->codec_version & 0xFF); - //todo temp hack, at it must write header sample and ignore the last nibble to get fully correct output - if (frame_format == 1 || frame_format == 3) { - block_samples--; + for (i = 0; i < vgmstream->channels; i++) { + vgmstream->ch[i].offset = block_offset + block_skip; } } else { diff --git a/src/meta/h4m.c b/src/meta/h4m.c index 47ec0ece..e077f34a 100644 --- a/src/meta/h4m.c +++ b/src/meta/h4m.c @@ -46,15 +46,16 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) { /* 0x3a: unk3A (0 or 0x12) */ /* 0x3b: unk3B (0) */ channel_count = read_8bit(0x3c,streamFile); - if (read_8bit(0x3d,streamFile) != 16) /* bitdepth */ //todo Pikmin not working - goto fail; - format = read_8bit(0x3e,streamFile); /* flags? */ + if (read_8bit(0x3d,streamFile) != 16) /* bitdepth */ + goto fail; //todo Pikmin (GC) is using some kind of variable blocks + format = (uint8_t)read_8bit(0x3e,streamFile); /* flags? */ extra_tracks = read_8bit(0x3f,streamFile); sample_rate = read_32bitBE(0x40,streamFile); loop_flag = 0; - total_subsongs = extra_tracks + 1; /* tracks for languages [Pokemon Channel], or sometimes used to fake multichannel [Tales of Symphonia] */ + /* tracks for languages [Pokemon Channel], or sometimes used to fake multichannel [Tales of Symphonia] */ + total_subsongs = extra_tracks + 1; if (target_subsong == 0) target_subsong = 1; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; @@ -72,9 +73,10 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) { switch(format & 0x7F) { case 0x00: - vgmstream->coding_type = coding_DVI_IMA; //todo H4M_IMA + vgmstream->coding_type = coding_H4M_IMA; break; - /* no games known to use this, h4m_audio_decode may decode them */ + + /* no games known to use these, h4m_audio_decode may decode them */ case 0x01: /* Uncompressed PCM */ case 0x04: /* 8-bit (A)DPCM */ default: @@ -93,6 +95,7 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) { vgmstream->num_samples += vgmstream->current_block_samples; } while (vgmstream->next_block_offset < get_streamfile_size(streamFile)); + vgmstream->full_block_size = 0; /* extra cleanup for H4M */ } block_update_h4m(start_offset, vgmstream); diff --git a/src/vgmstream.c b/src/vgmstream.c index d3d9343f..8eedf676 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1108,6 +1108,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return (0x800 - 0x04) * 2; case coding_RAD_IMA_mono: return 32; + case coding_H4M_IMA: + return 0; /* variable (block-controlled) */ case coding_XA: return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */ @@ -1282,6 +1284,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { return 0x24 * vgmstream->channels; case coding_APPLE_IMA4: return 0x22; + case coding_H4M_IMA: + return 0x00; /* variable (block-controlled) */ case coding_XA: return 0x80; @@ -1816,6 +1820,15 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to samples_to_do,chan); } break; + case coding_H4M_IMA: + for (chan=0;chanchannels;chan++) { + uint16_t frame_format = (uint16_t)((vgmstream->codec_version >> 8) & 0xFFFF); + + decode_h4m_ima(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan, + vgmstream->channels,vgmstream->samples_into_block, + samples_to_do, chan, frame_format); + } + break; case coding_WS: for (chan=0;chanchannels;chan++) { diff --git a/src/vgmstream.h b/src/vgmstream.h index 00f2dba7..924538e7 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -144,6 +144,8 @@ typedef enum { coding_AWC_IMA, /* Rockstar AWC IMA ADPCM */ coding_UBI_IMA, /* Ubisoft IMA ADPCM */ + coding_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */ + coding_MSADPCM, /* Microsoft ADPCM (stereo/mono) */ coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */ coding_WS, /* Westwood Studios VBR ADPCM */ @@ -860,7 +862,6 @@ typedef enum { VORBIS_OGL, /* Shin'en OGL: custom packet headers */ VORBIS_SK, /* Silicon Knights AUD: "OggS" replaced by "SK" */ VORBIS_VID1, /* Neversoft VID1: custom packet blocks/headers */ - //VORBIS_LYN /* Ubisoft LyN: two interleaved Ogg (including setup, duplicated) */ } vorbis_custom_t; /* config for Wwise Vorbis (3 types for flexibility though not all combinations exist) */ @@ -1129,9 +1130,6 @@ typedef enum { FFMPEG_STANDARD, /* default FFmpeg */ FFMPEG_SWITCH_OPUS, /* Opus without Ogg layer */ FFMPEG_EA_XMA, /* XMA with padding removed and custom streams in SNS blocks */ - //FFMPEG_EA_SCHL, /* Normal header+data (ex. ATRAC3) in SCxx blocks */ - //FFMPEG_SFH, /* ATRAC3plus header+data in SFH blocks */ - //FFMPEG_AWC_XMA, /* XMA data in AWC blocks, 1 streams per channel */ } ffmpeg_custom_t; /* config for the above modes */