From cbd831e65809e41b4cadd55b896514e8a0480b87 Mon Sep 17 00:00:00 2001 From: bnnm <bananaman255@gmail.com> Date: Fri, 23 Sep 2022 22:53:45 +0200 Subject: [PATCH] Add .bigrp [BM Zero 2 (SW), Gunvolt 3 (SW)] --- doc/FORMATS.md | 1 + src/coding/coding.h | 10 + src/coding/ice_decoder.c | 138 +++ src/coding/ice_decoder_icelib.c | 1433 ++++++++++++++++++++++++++++++ src/coding/ice_decoder_icelib.h | 67 ++ src/decode.c | 22 + src/formats.c | 4 + src/libvgmstream.vcxproj | 4 + src/libvgmstream.vcxproj.filters | 12 + src/meta/bigrp.c | 110 +++ src/meta/meta.h | 2 + src/vgmstream.c | 1 + src/vgmstream.h | 3 + 13 files changed, 1807 insertions(+) create mode 100644 src/coding/ice_decoder.c create mode 100644 src/coding/ice_decoder_icelib.c create mode 100644 src/coding/ice_decoder_icelib.h create mode 100644 src/meta/bigrp.c diff --git a/doc/FORMATS.md b/doc/FORMATS.md index 06ea7402..645d37e0 100644 --- a/doc/FORMATS.md +++ b/doc/FORMATS.md @@ -315,6 +315,7 @@ are used in few games. - Xiph CELT (FSB) - Musepack - FLAC +- Inti Creates' Range / DCT codecs - Others Sometimes standard codecs come in non-standard layouts that aren't normally diff --git a/src/coding/coding.h b/src/coding/coding.h index 71368fd1..3621fe30 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -353,6 +353,16 @@ void seek_tac(tac_codec_data* data, int32_t num_sample); void free_tac(tac_codec_data* data); +/* ice_decoder */ +typedef struct ice_codec_data ice_codec_data; + +ice_codec_data* init_ice(STREAMFILE* sf, int subsong); +void decode_ice(ice_codec_data* data, sample_t* outbuf, int32_t samples_to_do); +void reset_ice(ice_codec_data* data); +void seek_ice(ice_codec_data* data, int32_t num_sample); +void free_ice(ice_codec_data* data); + + #ifdef VGM_USE_VORBIS /* ogg_vorbis_decoder */ typedef struct ogg_vorbis_codec_data ogg_vorbis_codec_data; diff --git a/src/coding/ice_decoder.c b/src/coding/ice_decoder.c new file mode 100644 index 00000000..3dd77e89 --- /dev/null +++ b/src/coding/ice_decoder.c @@ -0,0 +1,138 @@ +#include "coding.h" +#include "ice_decoder_icelib.h" + + +typedef struct { + STREAMFILE* sf; + int offset; +} icelib_io_t; + +struct ice_codec_data { + STREAMFILE* sf; + int channels; + icesnd_handle_t* ctx; + icelib_io_t io; +}; + +static void icelib_set_callbacks(icesnd_callback_t* cb, STREAMFILE* sf, icelib_io_t* io); + +ice_codec_data* init_ice(STREAMFILE* sf, int subsong) { + ice_codec_data* data = NULL; + + data = calloc(1, sizeof(ice_codec_data)); + if (!data) goto fail; + + data->sf = reopen_streamfile(sf, 0); + if (!data->sf) goto fail; + + { + icesnd_callback_t cb = {0}; + icesnd_info_t info = {0}; + int err; + + icelib_set_callbacks(&cb, data->sf, &data->io); + + data->ctx = icesnd_init(subsong, &cb); + if (!data->ctx) goto fail; + + err = icesnd_info(data->ctx, &info); + if (err < ICESND_RESULT_OK) goto fail; + + data->channels = info.channels; + } + + return data; +fail: + free_ice(data); + return NULL; +} + +void decode_ice(ice_codec_data* data, sample_t* outbuf, int32_t samples_to_do) { + int channels = data->channels; + + while (samples_to_do > 0) { + int done = icesnd_decode(data->ctx, outbuf, samples_to_do); + if (done <= 0) goto decode_fail; + + outbuf += done * channels; + samples_to_do -= done; + } + + return; + +decode_fail: + VGM_LOG("ICE: decode error\n"); + memset(outbuf, 0, samples_to_do * channels * sizeof(sample_t)); +} + +void reset_ice(ice_codec_data* data) { + if (!data) return; + + icesnd_reset(data->ctx, 0); +} + +void seek_ice(ice_codec_data* data, int32_t num_sample) { + if (!data) return; + + //todo discard (this should only be called when looping) + icesnd_reset(data->ctx, 1); +} + +void free_ice(ice_codec_data* data) { + if (!data) return; + + close_streamfile(data->sf); + icesnd_free(data->ctx); + free(data); +} + +/* ************************* */ + +static int icelib_read(void* dst, int size, int n, void* arg) { + icelib_io_t* io = arg; + int bytes_read, items_read; + + bytes_read = read_streamfile(dst, io->offset, size * n, io->sf); + items_read = bytes_read / size; + io->offset += bytes_read; + + return items_read; +} + +static int icelib_seek(void* arg, int offset, int whence) { + icelib_io_t* io = arg; + int base_offset, new_offset; + + switch (whence) { + case ICESND_SEEK_SET: + base_offset = 0; + break; + case ICESND_SEEK_CUR: + base_offset = io->offset; + break; + case ICESND_SEEK_END: + base_offset = get_streamfile_size(io->sf); + break; + default: + return -1; + break; + } + + new_offset = base_offset + offset; + if (new_offset < 0 /*|| new_offset > get_streamfile_size(config->sf)*/) { + return -1; /* unseekable */ + } + else { + io->offset = new_offset; + return 0; + } +} + +static void icelib_set_callbacks(icesnd_callback_t* cb, STREAMFILE* sf, icelib_io_t* io) { + io->offset = 0; + io->sf = sf; + + cb->arg = io; + cb->read = icelib_read; + cb->seek = icelib_seek; +} diff --git a/src/coding/ice_decoder_icelib.c b/src/coding/ice_decoder_icelib.c new file mode 100644 index 00000000..1b6508aa --- /dev/null +++ b/src/coding/ice_decoder_icelib.c @@ -0,0 +1,1433 @@ +/* Decodes Inti Creates' BIGRP files with custom codecs, found in games using their current + * "Inti Creates Engine" ("ICE") / "Imperial Engine" ('IMP'?). Engine's name is said to be + * the latter (ICE being the earlier iteration) but debug info still shows the former. + * Reverse engineered from various exes (if you use this as a base credit it, please). + * + * This code tries to follow the original closely for documentation purposes, with some extra + * error control (original doesn't check zlib errors or buf sizes) plus a few extra structs/functions + * that were likely inline'd (such as bitreaders). */ + +//TODO change to streaming decoder +// Currently lib expects most data in memory. Due to how format is designed it's not the +// easiest thing to change, to be fixed it later: +// - data is divided into 2 blocks (intro+body) that are decoded separatedly +// (streaming should read up to block max) +// - code data isn't divided into frames, just keeps reading from the file buf +// - "range" decoder has linear data, and should be easy enough to stream, but it's rarely used. +// - "dct" decoder has a big chunk (+30%) of codebook data at the beginning of each block, then +// code data *but* decoder reads simultaneously from both places. Files can be rather big +// (2 mins = 6mb, codebooks = ~1.5mb). Would need to pre-read all the codebooks (still big) then +// stream data, or seek around to codebooks (thrashes FILE buffers). + + +#include <math.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#include "ice_decoder_icelib.h" + +/* use miniz (API-compatible) to avoid adding external zlib just for this codec + * - https://github.com/richgel999/miniz */ +#include "../util/miniz.h" +//#include "zlib.h" + +#define ICESND_MAX_CHANNELS 2 + + +/* ************************************************************ */ +/* COMMON */ +/* ************************************************************ */ + +static inline uint8_t get_u8(const uint8_t* p) { + uint8_t ret; + ret = ((uint16_t)(const uint8_t)p[0]) << 0; + return ret; +} + +static inline uint16_t get_u16le(const uint8_t* p) { + uint16_t ret; + ret = ((uint16_t)(const uint8_t)p[0]) << 0; + ret |= ((uint16_t)(const uint8_t)p[1]) << 8; + return ret; +} + +static inline uint32_t get_u32le(const uint8_t* p) { + uint32_t ret; + ret = ((uint32_t)(const uint8_t)p[0]) << 0; + ret |= ((uint32_t)(const uint8_t)p[1]) << 8; + ret |= ((uint32_t)(const uint8_t)p[2]) << 16; + ret |= ((uint32_t)(const uint8_t)p[3]) << 24; + return ret; +} + +/* bigrp entry info as read from header */ +typedef struct { + uint32_t hash1; /* usually matches filename, different files vary on bytes, seems internally used to identify files */ + uint32_t hash2; /* group id? repeated in several files, doesn't seem used */ + uint32_t codec; /* 00: range, 01: metadata, 02: midi, 03: DCT */ + + /* rest of header varies per codec (padded until ~0x40 for all) */ + /* codec 01 entry: */ + /* - offset */ + /* - size */ + + /* codec 02 entry: */ + /* - config? (big value, same for all entries) */ + /* - midi offset */ + /* - midi size */ + /* - midi config? (~0x30C0, possibly some size) */ + /* - midi config? (~0x4036, then divided by 180.0) */ + + /* codec 00/03 entry */ + int sample_rate; + int channels; + int spf; /* always 16, internally used for pcm size calculations */ + int unknown; /* some kind of low-ish value, volume? (seen 0x40~0x7F) */ + + int loop_flag; + int frame_codes; /* 0x64 in codec 00, 0x00 in codec 03 (possibly "RangeBlockSize") */ + + /* has one "intro" block then one "body" block; intro block may be zero in full loops/no loops */ + uint32_t intro_samples; + uint32_t intro_zsize; + uint32_t intro_offset; + uint32_t body_samples; + uint32_t body_zsize; + uint32_t body_offset; + + /* rest: padding until entry_size */ + +} bigrp_entry_t; + +/* base bigrp header and extra config */ +typedef struct { + uint32_t head_size; + uint32_t entry_size; + int total_subsongs; + uint32_t dummy; + +} bigrp_header_t; + +/* block/data format (handled later): + * + * codec 00 (range): + * - zlib block (32b deflated size + zlib data) + * + * codec 03 (dct) + * - codeinfo block (see codeinfo_parse) + * - zlib tables + * - data + * + * codec 01 (data?) + * - ? + * Not parsed as audio in the IcePlayer init. Usually paired with midis, has several sections and some + * point to entries using hash1, so probably soundfont config, but not all bigrp with midis have this. + * + * codec 02 (midi) + * - standard midi (MThd) + */ + + +/* OG code casts buffer to this struct, read in a more portable fashion */ +static int bigrp_entry_parse(bigrp_entry_t* entry, const uint8_t* buf, int buf_size) { + + if (buf_size < 0x34) + goto fail; + + entry->hash1 = get_u32le(buf + 0x00); + entry->hash2 = get_u32le(buf + 0x04); + entry->codec = get_u32le(buf + 0x08); /* u8 in decoder, read fully to validate */ + + switch (entry->codec) { + case ICESND_CODEC_RANGE: + case ICESND_CODEC_DCT: + entry->sample_rate = get_u32le(buf + 0x0c); + entry->channels = get_u8 (buf + 0x10); + entry->spf = get_u8 (buf + 0x11); + entry->unknown = get_u16le(buf + 0x12); + entry->loop_flag = get_u32le(buf + 0x14); /* u8 in decoder, read fully to validate */ + entry->frame_codes = get_u32le(buf + 0x18); /* codec 00 only (includes N channels) */ + + entry->intro_samples = get_u32le(buf + 0x1c); + entry->intro_zsize = get_u32le(buf + 0x20); + entry->intro_offset = get_u32le(buf + 0x24); + entry->body_samples = get_u32le(buf + 0x28); + entry->body_zsize = get_u32le(buf + 0x2c); + entry->body_offset = get_u32le(buf + 0x30); + + if (entry->sample_rate < 2000 || entry->sample_rate > 48000) /* just in case */ + goto fail; + if (entry->channels < 1 || entry->channels > 2 || entry->spf != 16) /* not seen */ + goto fail; + if (entry->frame_codes != 0 && entry->frame_codes != 0x64) + goto fail; + if (entry->frame_codes % entry->channels != 0) /* assumed that N codes = N/chs samples */ + goto fail; + if (entry->loop_flag != 0 && entry->loop_flag != 1) + goto fail; + + /* probably wouldn't matter, same with other sizes */ + if (entry->intro_samples == 0 && entry->body_samples == 0) + goto fail; + + if (entry->channels > 1 && entry->codec == ICESND_CODEC_RANGE) /* not seen */ + goto fail; + break; + + case ICESND_CODEC_DATA: + case ICESND_CODEC_MIDI: + default: + goto fail; + } + + return ICESND_RESULT_OK; +fail: + return ICESND_ERROR_HEADER; +} + +/* read main .bigrp header. Earlier games used standard Nintendo's BFGRP and BCGRP, + * and this format seems kind of inspired by it, so presumably BIGRP = Binary Inti Group */ +static int bigrp_header_parse(bigrp_header_t* hdr, const uint8_t* buf, int buf_size, int subsong) { + + if (buf_size < 0x0c) + goto fail; + + + /* read base header */ + hdr->head_size = get_u32le(buf + 0x00); + hdr->entry_size = get_u32le(buf + 0x04); + hdr->total_subsongs = get_u32le(buf + 0x08); + + if (hdr->head_size > buf_size) + goto fail; + + if (hdr->head_size >= 0x10) + hdr->dummy = get_u32le(buf + 0x0c); + else + hdr->dummy = 0x00; + + /* 0x0c: Bloodstained COTM (Vita/3DS), Mighty Gunvolt Burst (PC); 0x10: rest */ + if (hdr->head_size != 0x0c && hdr->head_size != 0x10) + goto fail; + /* same (no changes, after 0x34 is padding) */ + if (hdr->entry_size != 0x34 && hdr->entry_size != 0x40) + goto fail; + if (hdr->dummy != 0x00) + goto fail; + + if (subsong < 1 || subsong > hdr->total_subsongs) + goto fail; + + return ICESND_RESULT_OK; +fail: + return ICESND_ERROR_HEADER; +} + +/* original also tries to call's zlib free is needed (not set though) */ +static void zlib_end(z_stream* strm, int* p_zlib_init) { + if (*p_zlib_init) { + inflateEnd(strm); + + /* original also tries to call's zlib free if needed (not set though) */ + + *p_zlib_init = 0; + } +} + +static int zlib_init(z_stream* strm, int* p_zlib_init, const uint8_t* buf, int buf_size) { + int err; + + zlib_end(strm, p_zlib_init); + + strm->zalloc = 0; + strm->zfree = 0; + strm->opaque = 0; + strm->next_in = 0; + strm->avail_in = 0; + err = inflateInit(strm); + if (err < 0) return ICESND_ERROR_SETUP; + *p_zlib_init = 1; + + /* zlib data starts with the decompressed size */ + strm->next_in = buf + 0x04; + strm->avail_in = buf_size - 0x04; + + return ICESND_RESULT_OK; +} + + +/* ************************************************************ */ +/* RANGE */ +/* ************************************************************ */ + +/* Inti Creates's "range" decoder, internally IceSoundCodecDecoderRange class. Seems similar to Sony's + * "adaptive dynamic range coding" (ADRC) compression (not what's usually called "range coding"). + * + * Data is zlibbed (though doesn't save much) then divided into VBR frames. Each frame has a 24-bit LE + * header that defines a "range" (min..max) and quantized codes' bits (often 6 or 7), then 100 codes. + * Unsigned codes just map to a relative value within the range, and final sample is range-min + range-value. + * Stereo alternates L-R header then L R L R ... samples + * + * For example, if a range = [0, 6000], and bits = 6: + * - code=0 > value=0 (min), code=63 > value=6000 (max), code=31 > value=2952 (in-between), ... + */ + +#define RANGE_DECODE_BUFFER 0x800 + +typedef struct { + const uint8_t* inbuf; + int inbuf_size; + int max_samples; + int frame_codes; + int samples_done; + uint32_t outbuf_pos; + uint8_t outbuf[RANGE_DECODE_BUFFER]; + z_stream strm; + int codes_left; + int bitpos; /* within curr_byte */ + int16_t range_min[ICESND_MAX_CHANNELS]; + int16_t range_max[ICESND_MAX_CHANNELS]; + uint16_t range_bits[ICESND_MAX_CHANNELS]; + uint16_t range_mask[ICESND_MAX_CHANNELS]; + uint8_t curr_byte; + uint8_t spf; /* not needed to decode though */ + uint8_t channels; + int zlib_init; + + /* extra */ + uint32_t outbuf_max; /* original has all data in memory, but we read zlib in chunks that may be smaller */ + +} range_handle_t; + + +static range_handle_t* range_decoder_open() { + range_handle_t* ctx = NULL; + + //ctx = calloc(1, sizeof(range_handle_t)); + ctx = malloc(sizeof(range_handle_t)); + if (!ctx) goto fail; + + ctx->inbuf = NULL; + ctx->inbuf_size = 0; + ctx->max_samples = 0; + ctx->frame_codes = 0; + ctx->spf = 0; + ctx->zlib_init = 0; + + return ctx; +fail: + return NULL; +} + +static void range_decoder_close(range_handle_t* ctx) { + if (!ctx) + return; + + zlib_end(&ctx->strm, &ctx->zlib_init); + free(ctx); +} + +static int range_decoder_reset(range_handle_t* ctx) { + int err; + + err = zlib_init(&ctx->strm, &ctx->zlib_init, ctx->inbuf, ctx->inbuf_size); + if (err < ICESND_RESULT_OK) return err; + + ctx->outbuf_pos = 0xFFFFFFFF; //sizeof(ctx->outbuf); /* force init */ + ctx->samples_done = 0; + ctx->codes_left = 0; + ctx->bitpos = 0; + + ctx->outbuf_max = 0; + + return ICESND_RESULT_OK; +} + +static int range_decoder_block_setup(range_handle_t* ctx, const uint8_t* buf, int buf_size, bigrp_entry_t* etr, int max_samples) { + int err; + + ctx->inbuf = buf; + ctx->inbuf_size = buf_size; + ctx->max_samples = max_samples; + + ctx->frame_codes = etr->frame_codes; + ctx->spf = etr->spf; + ctx->channels = etr->channels; + + err = range_decoder_reset(ctx); + if (err < ICESND_RESULT_OK) return err; + + return ICESND_RESULT_OK; +} + +#if 0 +static void fill_zlib(z_stream* strm, data_t* data) { + + /* zlib sets this to 0 once consumed */ + if (strm->avail_in > 0) + return; + + /* buffer is smaller than size, so this will be called N times */ + if (data->data_left) { + int bytes = data->buf_size; + if (bytes > data->data_left) + bytes = data->data_left; + + strm->next_in = data->buf; + strm->avail_in = data->cb.read(data->buf, 1, bytes, data->cb.arg); + + data->data_left -= strm->avail_in; + } +} +#endif + +/* get next byte from the zlib stream */ +static void range_load_byte(range_handle_t* ctx) { + + if (ctx->outbuf_pos >= ctx->outbuf_max) { /* OG: >= sizeof(ctx->outbuf) */ + //fill_zlib(&ctx->strm, ctx->data); + + ctx->strm.avail_out = sizeof(ctx->outbuf); + ctx->strm.next_out = ctx->outbuf; + inflate(&ctx->strm, Z_NO_FLUSH); + //if (err < Z_OK) return ICESND_ERROR_DECODER; /* OG: no error control (shouldn't matter) */ + + ctx->outbuf_pos = 0; + ctx->outbuf_max = sizeof(ctx->outbuf) - ctx->strm.avail_out; + } + + ctx->curr_byte = ctx->outbuf[ctx->outbuf_pos]; + ctx->outbuf_pos++; +} + +/* read 24-bit header, decompressing bytes if necessary */ +static void range_load_header(range_handle_t* ctx, int ch) { + uint32_t frame_header; + + range_load_byte(ctx); + frame_header = ctx->curr_byte; + + range_load_byte(ctx); + frame_header = (ctx->curr_byte << 8) | frame_header; + + range_load_byte(ctx); + frame_header = (ctx->curr_byte << 16) | frame_header; + + /* get signed range and quantized bits for this frame */ + ctx->range_min[ch] = (frame_header >> 3) << 5; /* & 0xFFE0, upper 11 bits (signed) */ + ctx->range_max[ch] = (frame_header >> 14) << 6; /* & 0xFFC0, upper 10 bits (signed) */ //(frame_header >> 8) & 0xffc0; + ctx->range_bits[ch] = (frame_header & 7) + 1; + ctx->range_mask[ch] = (1 << ctx->range_bits[ch]) - 1; +} + +/* decode next bitstream's code, decompressing bytes if necessary */ +static int16_t range_get_sample(range_handle_t* ctx, int ch) { + int32_t code; + int16_t delta; + uint16_t mask = ctx->range_mask[ch]; + + /* get next code of N-bits (doesn't seem to use an actual bitstream class) */ + if (ctx->bitpos == 0) { + range_load_byte(ctx); + } + code = (ctx->curr_byte >> (ctx->bitpos)) & mask; + + if (ctx->bitpos + ctx->range_bits[ch] > 8) { + range_load_byte(ctx); + code |= (ctx->curr_byte << (8 - ctx->bitpos)) & mask; + + ctx->bitpos = (ctx->bitpos + ctx->range_bits[ch]) - 8; + } + else { + ctx->bitpos = (ctx->bitpos + ctx->range_bits[ch]) & 7; + } + + /* calculate code's range value and final sample */ + delta = code * (ctx->range_max[ch] - ctx->range_min[ch]) / mask; + return ctx->range_min[ch] + delta; /* no clamp */ +} + +/* decode N samples and copy to sbuf. + * Internally decodes N samples at a time, and if asked for non-multiple number of samples it'll + * stop and resume properly from last copied sample of those 16. Return 1 if no more samples left. */ +static int range_decoder_decode(range_handle_t* ctx, int16_t* sbuf, const int max_done, int* p_done) { + int ch; + + + *p_done = 0; + + while (ctx->samples_done < ctx->max_samples) { + + /* read frame header */ + if (ctx->codes_left == 0) { + for (ch = 0; ch < ctx->channels; ch++) { + range_load_header(ctx, ch); + } + + ctx->codes_left = ctx->frame_codes; + if (ctx->samples_done + ctx->frame_codes > ctx->max_samples) + ctx->codes_left = ctx->max_samples - ctx->samples_done; + ctx->bitpos = 0; + } + + /* decode frame samples */ + while (ctx->codes_left) { + for (ch = 0; ch < ctx->channels; ch++) { + *sbuf++ = range_get_sample(ctx, ch); + } + + ctx->samples_done++; + ctx->codes_left--; + + (*p_done)++; + if (*p_done >= max_done) /* samples left */ + return ctx->samples_done >= ctx->max_samples; /* block done */ + } + } + + return ctx->samples_done >= ctx->max_samples; /* block done */ +} + + +/* ************************************************************ */ +/* DCT */ +/* ************************************************************ */ + +/* Inti Creates's "dct" decoder, internally IceSoundCodecDecoderDCT class, a pretty simple DCT codec. + * + * Header stores one codebook per band (max 16 * channels) of quantized bits, in zlibbed chunks of 4-bit nibbles. + * Data is just a bitstream of up to 16 bands (L then R) of variable bit codes (max 16-bit) that depend on previous + * 16 bands, dequantized using iDCT. Uses mid-side stereo but otherwise no other features like frames, scalefactors + * or overlaps/delay. Samples are encoded directly as +-32768, 16 at a time. + */ + +#define DCT_MAX_BANDS 16 +#define DCT_MAX_TRANSFORM 8 +#define DCT_MAX_PREV 4 +#define DCT_MAX_PREV_MASK 0x3 +#define DCT_CODEBOOK_BUFFER 0x100 + +typedef struct { + const uint8_t* buf; + int bitpos; + int bitstart; + int max_bits; +} dct_bitreader_t; + +typedef struct { + uint32_t table_size; + uint8_t init_scale; + uint8_t bands; + uint8_t channels; + uint8_t unused; + uint32_t max_samples; + uint32_t cbk_offset[ICESND_MAX_CHANNELS][DCT_MAX_BANDS]; + uint32_t cbk_size[ICESND_MAX_CHANNELS][DCT_MAX_BANDS]; + uint32_t data_start; + uint32_t data_size; +} dct_codeinfo_t; + +typedef struct { + z_stream strm; + uint8_t outbuf[DCT_CODEBOOK_BUFFER]; + dct_bitreader_t br; + int zlib_init; +} dct_codebook_t; + +typedef struct { + dct_codeinfo_t codeinfo_mem; /* OG code just casts a pointer to this, keep struct around */ + dct_codeinfo_t* codeinfo; + int samples_done; + dct_codebook_t codebook[ICESND_MAX_CHANNELS][DCT_MAX_BANDS]; + dct_bitreader_t br; + float transform[DCT_MAX_TRANSFORM][DCT_MAX_BANDS]; + float unused[DCT_MAX_TRANSFORM][DCT_MAX_BANDS]; /* ? */ + int16_t spectra[ICESND_MAX_CHANNELS][DCT_MAX_PREV][DCT_MAX_BANDS]; + int spectra_curr; + int16_t sbuf_tmp[DCT_MAX_BANDS * ICESND_MAX_CHANNELS]; /* interleaved */ + float scales[16]; +} dct_handle_t; + + +/* ****************************** */ +/* IceSoundCodecDecoder(Bitreader) */ + +/* OG bitreader uses u32 buf and reads u32le at a time (to simplify shifting), and aligns for 32-bit, + * since blocks pointer can start in the middle (whole file loaded in memory + non-padded blocks) */ + +static void dct_bitreader_init(dct_bitreader_t* ctx) { + ctx->buf = NULL; + ctx->bitpos = 0; + ctx->bitstart = 0; + ctx->max_bits = 0; +} + +static void dct_bitreader_set(dct_bitreader_t* ctx, const uint8_t* buf, int buf_size) { + //unsigned int* buf32 = (unsigned int *)(buf); + //ctx->buf32 = (uin32_t*)((uint32_t)buf32 & 0xFFFFFFFC); /* align to 32-bit boundary */ + //ctx->bitstart = 8 * ((uint8_t)buf32 & 3); /* align to pointer start */ + + ctx->buf = buf; + ctx->bitstart = 8 * 0; /* non-aligned ok */ + ctx->bitpos = ctx->bitstart; + ctx->max_bits = 8 * buf_size; +} + +static int dct_bitreader_is_over(dct_bitreader_t* ctx) { + return ctx->bitpos >= ctx->bitstart + ctx->max_bits; +} + +static uint32_t dct_bitreader_get(dct_bitreader_t* ctx, int bits) { + uint32_t code32; + uint8_t shift; + int pos; + uint32_t mask; + + if (ctx->bitpos + bits > ctx->bitstart + ctx->max_bits) /* ? */ + return 0; + + pos = ctx->bitpos >> 3; + shift = ctx->bitpos & 0x7; /* within u8 */ + + code32 = ctx->buf[pos] >> shift; + if (bits + shift > 8) { + code32 |= ctx->buf[pos+1] << (8u - shift); + if (bits + shift > 16) { + code32 |= ctx->buf[pos+2] << (16u - shift); + if (bits + shift > 24) { + code32 |= ctx->buf[pos+3] << (24u - shift); + if (bits + shift > 32) { + code32 |= ctx->buf[pos+4] << (32u - shift); + } + } + } + } + + //pos = ctx->br.bitpos >> 5; + //shift = ctx->br.bitpos & 0x1f; /* within u32 */ + + //code32 = ctx->buf32[pos] >> shift; + //if (qbits + shift > 32) { + // code32 |= ctx->buf32[pos+1] << (32u - shift); + //} + + ctx->bitpos += bits; + + mask = (((1 << bits)) - 1); + return code32 & mask; +} + +/* ****************************** */ +/* IceSoundCodecDecoder(Codebook) */ + +static void dct_codebook_init(dct_codebook_t* ctx) { + /* no alloc needed (part of dct_handle_t) */ + + dct_bitreader_init(&ctx->br); + + ctx->zlib_init = 0; +} + +static void dct_codebook_close(dct_codebook_t* ctx) { + if (!ctx) + return; + + zlib_end(&ctx->strm, &ctx->zlib_init); + /* no free needed */ +} + +static int dct_codebook_reset(dct_codebook_t* ctx, const uint8_t* buf, int buf_size) { + int err; + + err = zlib_init(&ctx->strm, &ctx->zlib_init, buf, buf_size); + if (err < ICESND_RESULT_OK) return err; + + dct_bitreader_init(&ctx->br); + + return ICESND_RESULT_OK; +} + +/* Read next quantized value's bits from zlibbed codebook. Data is in LSB order and codes 4-bits, like: + * 0x10,0x32,0x54,0x76,0x98... = 0 1 2 3 4 5 6 7 8 9... */ +static uint8_t dct_codebook_get_qbits(dct_codebook_t* ctx) { + uint32_t qbits; + + if (dct_bitreader_is_over(&ctx->br)) { + ctx->strm.avail_out = sizeof(ctx->outbuf); + ctx->strm.next_out = ctx->outbuf; + inflate(&ctx->strm, Z_NO_FLUSH); + //if (err < Z_OK) return ICESND_ERROR_DECODER; /* OG: no error control (shouldn't matter) */ + + dct_bitreader_set(&ctx->br, ctx->outbuf, sizeof(ctx->outbuf) - ctx->strm.avail_out); + } + + qbits = dct_bitreader_get(&ctx->br, 4); + + return qbits; +} + +/* *********************** */ +/* IceSoundCodecDecoderDCT */ + +static dct_handle_t* dct_decoder_open() { + int i, ch, band; + dct_handle_t* ctx = NULL; + + //ctx = calloc(1, sizeof(dct_handle_t)); + ctx = malloc(sizeof(dct_handle_t)); + if (!ctx) goto fail; + + ctx->codeinfo = NULL; + + /* init all codebook's base values */ + for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) { + for (band = 0; band < DCT_MAX_BANDS; band++) { + dct_codebook_t* codebook = &ctx->codebook[ch][band]; + + dct_codebook_init(codebook); + } + } + + dct_bitreader_init(&ctx->br); + + for (i = 0; i < DCT_MAX_BANDS; i++) { + ctx->scales[i] = 1.0f; + } + + return ctx; +fail: + return NULL; +} + +static void dct_decoder_close(dct_handle_t* ctx) { + int ch, band; + + + /* setup all codebook's zlib streams (even if not used, since we can close before anything is set) */ + for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) { + for (band = 0; band < DCT_MAX_BANDS; band++) { + dct_codebook_t* codebook = &ctx->codebook[ch][band]; + + dct_codebook_close(codebook); + } + } + + free(ctx); +} + +static int dct_decoder_reset(dct_handle_t* ctx, const uint8_t* buf) { + int err; + int ch, band; + dct_codeinfo_t* ci = ctx->codeinfo; + + /* OG code doesn't pass buf (since reset is a virtual method), but rather codeinfo doubles as a pointer to data start */ + + /* close all codebook's zlib streams */ + for (ch = 0; ch < ci->channels; ch++) { + for (band = 0; band < ci->bands; band++) { + dct_codebook_t* codebook = &ctx->codebook[ch][band]; + + const uint8_t* cbk_start = buf + ci->cbk_offset[ch][band]; + int cbk_size = ci->cbk_size[ch][band]; + + err = dct_codebook_reset(codebook, cbk_start, cbk_size); + if (err < ICESND_RESULT_OK) return err; + } + } + + dct_bitreader_set(&ctx->br, buf + ci->data_start, ci->data_size); + + memset(ctx->spectra, 0, sizeof(ctx->spectra)); + ctx->samples_done = 0; + ctx->spectra_curr = 0; + + return ICESND_RESULT_OK; +} + +/* transform spectrum into samples (iDCT) */ +static void dct_decoder_transform(dct_handle_t* ctx, int16_t* sbuf_tmp, int channel, int pos) { + int i, band; + float fbuf[16] = {0}; /* no need to init as it's written in band 0 but gcc complains */ + float f_curr; + dct_codeinfo_t* ci = ctx->codeinfo; + + + for (band = 0; band < ci->bands; band++) { + /* scales seems fixed to 1.0, maybe a remnant */ + float coef = (float)ctx->spectra[channel][pos][band] * ctx->scales[band]; + + /* optimized butterfly ops? */ + switch (band) { + case 0: /* bits 0000 */ + f_curr = ctx->transform[0][band] * coef; + fbuf[0] = f_curr; + fbuf[1] = f_curr; + fbuf[2] = f_curr; + fbuf[3] = f_curr; + fbuf[4] = f_curr; + fbuf[5] = f_curr; + fbuf[6] = f_curr; + fbuf[7] = f_curr; + fbuf[8] = f_curr; + fbuf[9] = f_curr; + fbuf[10] = f_curr; + fbuf[11] = f_curr; + fbuf[12] = f_curr; + fbuf[13] = f_curr; + fbuf[14] = f_curr; + fbuf[15] = f_curr; + break; + + case 1: + case 3: + case 5: + case 7: + case 9: + case 11: + case 13: + case 15: /* bits xxx1 */ + f_curr = ctx->transform[0][band] * coef; + fbuf[0] += f_curr; + fbuf[15] -= f_curr; + + f_curr = ctx->transform[1][band] * coef; + fbuf[1] += f_curr; + fbuf[14] -= f_curr; + + f_curr = ctx->transform[2][band] * coef; + fbuf[2] += f_curr; + fbuf[13] -= f_curr; + + f_curr = ctx->transform[3][band] * coef; + fbuf[3] += f_curr; + fbuf[12] -= f_curr; + + f_curr = ctx->transform[4][band] * coef; + fbuf[4] += f_curr; + fbuf[11] -= f_curr; + + f_curr = ctx->transform[5][band] * coef; + fbuf[5] += f_curr; + fbuf[10] -= f_curr; + + f_curr = ctx->transform[6][band] * coef; + fbuf[6] += f_curr; + fbuf[9] -= f_curr; + + f_curr = ctx->transform[7][band] * coef; + fbuf[7] += f_curr; + fbuf[8] -= f_curr; + break; + + case 2u: + case 6u: + case 10: + case 14: /* bits xx10 */ + f_curr = ctx->transform[0][band] * coef; + fbuf[0] += f_curr; + fbuf[7] -= f_curr; + fbuf[8] -= f_curr; + fbuf[15] += f_curr; + + f_curr = ctx->transform[1][band] * coef; + fbuf[1] += f_curr; + fbuf[6] -= f_curr; + fbuf[9] -= f_curr; + fbuf[14] += f_curr; + + f_curr = ctx->transform[2][band] * coef; + fbuf[2] += f_curr; + fbuf[5] -= f_curr; + fbuf[10] -= f_curr; + fbuf[13] += f_curr; + + f_curr = ctx->transform[3][band] * coef; + fbuf[3] += f_curr; + fbuf[4] -= f_curr; + fbuf[11] -= f_curr; + fbuf[12] += f_curr; + break; + + case 4: + case 12: /* bits x100 */ + f_curr = ctx->transform[0][band] * coef; + fbuf[0] += f_curr; + fbuf[3] -= f_curr; + fbuf[4] -= f_curr; + fbuf[7] += f_curr; + fbuf[8] += f_curr; + fbuf[11] -= f_curr; + fbuf[12] -= f_curr; + fbuf[15] += f_curr; + + f_curr = ctx->transform[1][band] * coef; + fbuf[1] += f_curr; + fbuf[2] -= f_curr; + fbuf[5] -= f_curr; + fbuf[6] += f_curr; + fbuf[9] += f_curr; + fbuf[10] -= f_curr; + fbuf[13] -= f_curr; + fbuf[14] += f_curr; + break; + + case 8: /* bits 1000 */ + f_curr = ctx->transform[0][band] * coef; + fbuf[0] += f_curr; + fbuf[1] -= f_curr; + fbuf[2] -= f_curr; + fbuf[3] += f_curr; + fbuf[4] += f_curr; + fbuf[5] -= f_curr; + fbuf[6] -= f_curr; + fbuf[7] += f_curr; + fbuf[8] += f_curr; + fbuf[9] -= f_curr; + fbuf[10] -= f_curr; + fbuf[11] += f_curr; + fbuf[12] += f_curr; + fbuf[13] -= f_curr; + fbuf[14] -= f_curr; + fbuf[15] += f_curr; + break; + default: + break; + } + } + + /* copy float samples to sbuf samples */ + for (i = 0; i < DCT_MAX_BANDS; i++) { + float sample = roundf(fbuf[i]); + /* interleaved (L: 0,2,4,6,8... R:1,3,5,7...) */ + sbuf_tmp[channel + ci->channels * i] = (int16_t)sample; /* no clamp */ + } +} + +/* read current code from the bitstream, in LE byte order */ +static int16_t dct_decoder_get_code(dct_handle_t* ctx, uint8_t qbits) { + uint32_t code32; + int16_t code16; /* also ok as int32 */ + + /* get code from bitstream */ + if (qbits <= 0) { /* no resolution: 1-bit where 0 = 0 and 1 = -1 */ + code32 = dct_bitreader_get(&ctx->br, 1); + + code16 = (int16_t)((int16_t)code32 << 15) >> 15; /* sign extend */ + } + else { + code32 = dct_bitreader_get(&ctx->br, qbits); + + code16 = code32; /* qbits max 0..15 */ + if (code16 < (1 << (qbits - 1))) /* negative encoding */ + code16 = code16 - (1 << qbits); + } + + return code16; +} + +/* read codes for this channel */ +static void dct_decoder_dequantize(dct_handle_t* ctx, int channel, int pos) { + int band; + dct_codeinfo_t* ci = ctx->codeinfo; + + int16_t* spectra = ctx->spectra[channel][pos]; + int16_t* spectra_prev1 = ctx->spectra[channel][(pos - 1) & DCT_MAX_PREV_MASK]; + int16_t* spectra_prev2 = ctx->spectra[channel][(pos - 2) & DCT_MAX_PREV_MASK]; + + + for (band = 0; band < ci->bands; band++) { + uint8_t qbits; /* common 7~10 bits, sometimes 12 too */ + int16_t code; /* also ok as int32 */ + + /* get next code's resolution and code */ + qbits = dct_codebook_get_qbits(&ctx->codebook[channel][band]); + code = dct_decoder_get_code(ctx, qbits); + + /* calc final value based on previous */ + spectra[band] = code + (int16_t)(2 * spectra_prev1[band]) - spectra_prev2[band]; + } +} + +/* restore L/R bands based on mid channel + side differences, ratio 1.0 + copy to final buffer */ +static void dct_decoder_ms_stereo(dct_handle_t* ctx, int16_t* sbuf_tmp) { + int i; + dct_codeinfo_t* ci = ctx->codeinfo; + + for (i = 0; i < DCT_MAX_BANDS; i++) { + int16_t sample_l = sbuf_tmp[0 + ci->channels * i]; + int16_t sample_r = sbuf_tmp[1 + ci->channels * i]; + ctx->sbuf_tmp[0 + ci->channels * i] = sample_l + sample_r; + ctx->sbuf_tmp[1 + ci->channels * i] = sample_l - sample_r; + } +} + +/* decode N samples and copy to sbuf. + * Internally decodes 16 samples at a time, and if asked for non-multiple number of samples it'll + * stop and resume properly from last copied sample of those 16. Return 1 if no more samples left. */ +static int dct_decoder_decode(dct_handle_t* ctx, int16_t* sbuf, const int max_done, int* p_done) { + int ch; + int16_t sbuf_loc[DCT_MAX_BANDS * ICESND_MAX_CHANNELS]; /* interleaved */ + int16_t* sbuf_tmp; + dct_codeinfo_t* ci = ctx->codeinfo; + int samples_left; + + + *p_done = 0; + samples_left = max_done; + if (samples_left > ci->max_samples - ctx->samples_done) + samples_left = ci->max_samples - ctx->samples_done; + + /* 2ch uses a tmp buffer to handle MS stereo */ + if (ci->channels == 1) + sbuf_tmp = ctx->sbuf_tmp; + else + sbuf_tmp = sbuf_loc; + + while (ctx->samples_done < ci->max_samples) { + + if (!samples_left) + return ctx->samples_done >= ci->max_samples; + + /* decode 16 samples (every 16 samples) */ + if ((ctx->samples_done & 0xF) == 0) { + + for (ch = 0; ch < ci->channels; ch++) { + dct_decoder_dequantize(ctx, ch, ctx->spectra_curr); + + dct_decoder_transform(ctx, sbuf_tmp, ch, ctx->spectra_curr); + } + + ctx->spectra_curr = (ctx->spectra_curr + 1) & DCT_MAX_PREV_MASK; /* 0..3 and back to 0 */ + + if (ci->channels == 2) + dct_decoder_ms_stereo(ctx, sbuf_tmp); + } + + /* copy to output sbuf */ + { + int sample_start; + int samples_copied; + + /* start could be non-zero if max_done is non-multiple of 16 */ + sample_start = ctx->samples_done & 0xF; + samples_copied = 16 - sample_start; + if (samples_copied > samples_left) + samples_copied = samples_left; + + /* copy to output sbuf */ + memcpy(sbuf, &ctx->sbuf_tmp[sample_start * ci->channels], sizeof(int16_t) * ci->channels * samples_copied); + sbuf += samples_copied * ci->channels; + + ctx->samples_done += samples_copied; + samples_left -= samples_copied; + *p_done += samples_copied; + } + } + + return ctx->samples_done >= ci->max_samples; /* block done */ +} + + +/* OG code casts buffer to this struct, read in a more portable fashion */ +static int dct_codeinfo_parse(dct_codeinfo_t* ci, const uint8_t* buf, int buf_size) { + int ch, i, pos; + + if (buf_size < 0x114) + goto fail; + + ci->table_size = get_u32le(buf + 0x00); + ci->init_scale = get_u8 (buf + 0x04); + ci->bands = get_u8 (buf + 0x05); + ci->channels = get_u8 (buf + 0x06); + ci->unused = get_u8 (buf + 0x07); + ci->max_samples = get_u32le(buf + 0x08); + + pos = 0x0c; + + for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) { + for (i = 0; i < DCT_MAX_BANDS; i++) { + ci->cbk_offset[ch][i] = get_u32le(buf + pos); + pos += 0x04; + } + } + + for (ch = 0; ch < ICESND_MAX_CHANNELS; ch++) { + for (i = 0; i < DCT_MAX_BANDS; i++) { + ci->cbk_size[ch][i] = get_u32le(buf + pos); + pos += 0x04; + } + } + + ci->data_start = get_u32le(buf + 0x10c); + ci->data_size = get_u32le(buf + 0x110); + + if (ci->table_size > 0x114) + goto fail; + if (ci->bands < 1 || ci->bands > DCT_MAX_BANDS) + goto fail; + if (ci->channels < 1 || ci->channels > ICESND_MAX_CHANNELS) + goto fail; + if (ci->unused != 0x00) + goto fail; + + if (buf_size < ci->data_start + ci->data_size) + goto fail; + + return ICESND_RESULT_OK; +fail: + return ICESND_ERROR_SETUP; +} + +/* base DCT unique coefs, used below to init the full table (see opus' analysis.c) */ +static const float DCT_TRANSFORM_COEFS[16] = { + 0.25f, 0.35185099f, 0.34676f, 0.33832899f, + 0.32664099f, 0.31180599f, 0.29396901f, 0.27329999f, + 0.25f, 0.224292f, 0.19642401f, 0.166664f, + 0.135299f, 0.102631f, 0.068975002f, 0.034653999f, +}; + +static const float DCT_TRANSFORM_SCALES[16] = { + 4.0, 6.0, 8.0, 10.0, 12.0, 12.0, 13.0, 15.0, + 16.0, 16.0, 20.0, 24.0, 28.0, 35.0, 41.0, 41.0 +}; + +static const int DCT_TRANSFORM_STEPS[16] = { + 1, 8, 4, 8, 2, 8, 4, 8, + 1, 8, 4, 8, 2, 8, 4, 8, +}; + +/* re-calculate DCT table, that depends on a current intro/body chunk's scale value */ +static int dct_decoder_block_setup(dct_handle_t* ctx, const uint8_t* buf, int buf_size, bigrp_entry_t* etr) { + int i; + int err; + float scale; + float dct_coefs[DCT_MAX_BANDS]; + dct_codeinfo_t* ci = &ctx->codeinfo_mem; + + + /* portable init */ + err = dct_codeinfo_parse(ci, buf, buf_size); + if (err < ICESND_RESULT_OK) return err; + + /* pre-calculate scaled coefs (mini optimization?) */ + scale = ci->init_scale; + for (i = 0; i < DCT_MAX_BANDS; i++) { + dct_coefs[i] = DCT_TRANSFORM_COEFS[i] * scale; + } + + /* transform for N=16, k=0..8? */ + for (i = 0; i < DCT_MAX_BANDS; i++) { + int steps = DCT_TRANSFORM_STEPS[i]; + int step; + int pos = i; + + for (step = 0; step < steps; step++) { + float coef; + + switch ((pos >> 4) & 3) { + case 1: + coef = -dct_coefs[16 - (pos & 0xF)]; + break; + case 2: + coef = -dct_coefs[(pos & 0xF)]; + break; + case 3: + coef = +dct_coefs[16 - (pos & 0xF)]; + break; + default: + coef = +dct_coefs[(pos & 0xF)]; + break; + } + pos += 2 * i; + + //ctx->transform[step][i] = coef; /* somehow assigned twice originally? */ + ctx->transform[step][i] = DCT_TRANSFORM_SCALES[i] * coef; + } + } + + + /* rest of setup */ + ctx->codeinfo = ci; + + err = dct_decoder_reset(ctx, buf); + if (err < ICESND_RESULT_OK) return err; + + return ICESND_RESULT_OK; +} + +/* ************************************************************ */ +/* API */ +/* ************************************************************ */ +/* (not part of original code (but partially inspired by IceSSoundEng::IcePlayer) */ + +#define ICESND_BIGRP_SIZE 0x10 +#define ICESND_ENTRY_SIZE 0x34 +#define ICESND_BUF_SIZE 0x10000 + + +struct icesnd_handle_t { + /* config*/ + int target_subsong; + icesnd_callback_t cb; + + /* state */ + bigrp_header_t hdr; + bigrp_entry_t etr; + + void* decoder; + int is_range; + + int intro_init; + int body_init; + int intro_done; + + /* absolute offset */ + int intro_offset; + int body_offset; + + uint8_t* blkbuf; + int blkbuf_size; +}; + + +static int parse_header(icesnd_handle_t* ctx) { + int err; + uint8_t tmp[0x40]; + const uint8_t* buf; + int buf_size; + uint32_t offset; + + + /* read common header size */ + offset = 0x00; + if (ctx->cb.read) { + ctx->cb.seek(ctx->cb.arg, offset, SEEK_SET); + buf_size = ctx->cb.read(tmp, 1, 0x10, ctx->cb.arg); + buf = tmp; + } + else { + buf_size = ctx->cb.filebuf_size; + buf = ctx->cb.filebuf + offset; + } + + err = bigrp_header_parse(&ctx->hdr, buf, buf_size, ctx->target_subsong); + if (err < ICESND_RESULT_OK) goto fail; + + /* read target entry */ + offset = ctx->hdr.head_size + ctx->hdr.entry_size * (ctx->target_subsong - 1); + if (ctx->cb.read) { + ctx->cb.seek(ctx->cb.arg, offset, SEEK_SET); + buf_size = ctx->cb.read(tmp, 1, ctx->hdr.entry_size, ctx->cb.arg); + buf = tmp; + } + else { + if (offset > ctx->cb.filebuf_size) goto fail; + buf = ctx->cb.filebuf + offset; + buf_size = ctx->cb.filebuf_size - offset; + } + + err = bigrp_entry_parse(&ctx->etr, buf, buf_size); + if (err < ICESND_RESULT_OK) goto fail; + + if (ctx->etr.codec == ICESND_CODEC_RANGE || ctx->etr.codec == ICESND_CODEC_DCT) { + ctx->intro_offset = offset + ctx->etr.intro_offset; + ctx->body_offset = offset + ctx->etr.body_offset; + } + + + //TODO fix library later + // see comment at top, but basically format is rather annoying to adapt as a streaming + // decoder, and ran out of time. For now it reads a whole blocks (intro/body) at once. Sorry! + + /* prepare buf */ + if (ctx->cb.read) { + int block_size = ctx->etr.body_zsize; + if (block_size < ctx->etr.intro_zsize) + block_size = ctx->etr.intro_zsize; + if (block_size % 0x10 != 0) /* pad just in case */ + block_size = block_size + (0x10 - (block_size % 0x10)); + + ctx->blkbuf_size = block_size; + ctx->blkbuf = malloc(block_size); + if (!ctx->blkbuf) goto fail; + } + + return ICESND_RESULT_OK; +fail: + return ICESND_ERROR_SETUP; +} + +icesnd_handle_t* icesnd_init(int target_subsong, icesnd_callback_t* cb) { + icesnd_handle_t* ctx = NULL; + int err; + + ctx = calloc(1, sizeof(icesnd_handle_t)); + if (!ctx) goto fail; + + ctx->target_subsong = target_subsong; + ctx->cb = *cb; /* memcpy */ + + if (!cb->filebuf && !(cb->read && cb->seek)) + goto fail; + + err = parse_header(ctx); + if (err < ICESND_RESULT_OK) goto fail; + + ctx->is_range = ctx->etr.codec == 0x00; + if (ctx->is_range) + ctx->decoder = range_decoder_open(); + else + ctx->decoder = dct_decoder_open(); + if (!ctx->decoder) goto fail; + + icesnd_reset(ctx, 0); + + return ctx; +fail: + icesnd_free(ctx); + return NULL; + //return ICESND_ERROR_SETUP; +} + +void icesnd_free(icesnd_handle_t* ctx) { + if (!ctx) + return; + + if (ctx->decoder) { + if (ctx->is_range) + range_decoder_close(ctx->decoder); + else + dct_decoder_close(ctx->decoder); + } + + free(ctx->blkbuf); + free(ctx); +} + +int icesnd_info(icesnd_handle_t* ctx, icesnd_info_t* info) { + if (!ctx) + goto fail; + + info->total_subsongs = ctx->hdr.total_subsongs; + info->codec = ctx->etr.codec; + info->sample_rate = ctx->etr.sample_rate; + info->channels = ctx->etr.channels; + info->loop_start = ctx->etr.intro_samples; + info->num_samples = ctx->etr.intro_samples + ctx->etr.body_samples; + info->loop_flag = ctx->etr.loop_flag; + + return ICESND_RESULT_OK; +fail: + return ICESND_ERROR_DECODE; +} + +void icesnd_reset(icesnd_handle_t* ctx, int loop_start) { + if (!ctx || !ctx->decoder) + return; + + ctx->intro_init = 0; + ctx->body_init = 0; + ctx->intro_done = 0; + + /* skip intro block in some cases */ + if (ctx->etr.intro_samples == 0 || loop_start != 0) + ctx->intro_done = 1; + + /* no need to reset decoder as will be done when block is set, plus + * only reset properly when doing that */ +} + +static int setup_block(icesnd_handle_t* ctx, int intro) { + int err; + const uint8_t* buf; + int buf_size; + int block_offset = (intro ? ctx->intro_offset : ctx->body_offset); + int block_size = (intro ? ctx->etr.intro_zsize : ctx->etr.body_zsize); + int block_samples = (intro ? ctx->etr.intro_samples : ctx->etr.body_samples); + + if (ctx->cb.read) { + /* could optimize by ignoring calls (intro > body > body...) but this kinda simulates streamings */ + if (block_size > ctx->blkbuf_size) /* can't happen but anyway */ + return ICESND_ERROR_DECODE; + ctx->cb.seek(ctx->cb.arg, block_offset, SEEK_SET); + buf_size = ctx->cb.read(ctx->blkbuf, 1, block_size, ctx->cb.arg); + buf = ctx->blkbuf; + } + else { + if (ctx->cb.filebuf_size < block_offset + block_size) + return ICESND_ERROR_DECODE; + buf = ctx->cb.filebuf + block_offset; + buf_size = ctx->cb.filebuf_size - block_offset; + } + + + if (ctx->is_range) { + err = range_decoder_block_setup(ctx->decoder, buf, buf_size, &ctx->etr, block_samples); + } + else { + err = dct_decoder_block_setup(ctx->decoder, buf, buf_size, &ctx->etr); /* max_samples info is in codebook table */ + } + if (err < ICESND_RESULT_OK) return err; + + return ICESND_RESULT_OK; +} + +int icesnd_decode(icesnd_handle_t* ctx, int16_t* sbuf, int max_samples) { + int err; + int samples_done, block_end; + int samples_decoded = 0; + if (!ctx) + goto fail; + + while (max_samples > 0) { + + if (!ctx->intro_done) { + if (!ctx->intro_init) { + err = setup_block(ctx, 1); + if (err < ICESND_RESULT_OK) return err; + + ctx->intro_init = 1; + } + } + else { + if (!ctx->body_init) { + err = setup_block(ctx, 0); + ctx->body_init = 1; + } + } + + if (ctx->is_range) + block_end = range_decoder_decode(ctx->decoder, sbuf, max_samples, &samples_done); + else + block_end = dct_decoder_decode(ctx->decoder, sbuf, max_samples, &samples_done); + + max_samples -= samples_done; + samples_decoded += samples_done; + sbuf += samples_done * ctx->etr.channels; + //ctx->curr_sample += samples_done; /* original keeps this around to test if intro block is done */ + + if (block_end) { + /* after first block (could check if this is the first block but whatevs) */ + ctx->intro_done = 1; + + /* intro end, or after body end to allow loops on next calls */ + if (ctx->etr.loop_flag) + ctx->body_init = 0; + } + + /* could be possible on block end if not reset */ + if (samples_done == 0) + break; + + /* stop on on block boundary to ensure external caller may stop on loop end (could go on otherwise) */ + if (block_end) + break; + } + + + return samples_decoded; +fail: + return ICESND_ERROR_DECODE; +} diff --git a/src/coding/ice_decoder_icelib.h b/src/coding/ice_decoder_icelib.h new file mode 100644 index 00000000..bcfb40cd --- /dev/null +++ b/src/coding/ice_decoder_icelib.h @@ -0,0 +1,67 @@ +#ifndef _ICELIB_H_ +#define _ICELIB_H_ +#include <stdint.h> + +/* Decodes Inti Creates's "ICE" engine BIGRP sounds. */ + +#define ICESND_CODEC_RANGE 0x00 +#define ICESND_CODEC_DATA 0x01 +#define ICESND_CODEC_MIDI 0x02 +#define ICESND_CODEC_DCT 0x03 + +#define ICESND_RESULT_OK 0 +#define ICESND_ERROR_HEADER -1 +#define ICESND_ERROR_SETUP -2 +#define ICESND_ERROR_DECODE -3 + +typedef struct icesnd_handle_t icesnd_handle_t; + +#define ICESND_SEEK_SET 0 +#define ICESND_SEEK_CUR 1 +#define ICESND_SEEK_END 2 + +typedef struct { + /* whole file in memory (for testing) */ + const uint8_t* filebuf; + int filebuf_size; + + /* custom IO */ + void* arg; + int (*read)(void* dst, int size, int n, void* arg); + int (*seek)(void* arg, int offset, int whence); + +} icesnd_callback_t; + +/* Inits ICE lib with config. + * Original code expects all data in memory, but this allows setting read callbacks + * (making it feed-style was a bit complex due to how data is laid out) */ +icesnd_handle_t* icesnd_init(int target_subsong, icesnd_callback_t* cb); + +void icesnd_free(icesnd_handle_t* handle); + +/* resets the decoder. If loop_starts and file loops and + * (format is not seekable but separated into intro+body blocks) */ +void icesnd_reset(icesnd_handle_t* handle, int loop_start); + +/* Decodes up to samples for N channels into sbuf (interleaved). Returns samples done, + * 0 if not possible (non-looped files past end) or negative on error. + * May return less than wanted samples on block boundaries. + * + * It's designed to decode an arbitrary number of samples, as data isn't divided into frames (original + * player does sample_rate/60.0 at a time). Codec 0 is aligned to 100 samples and codec 3 to 16 though. */ +int icesnd_decode(icesnd_handle_t* handle, int16_t* sbuf, int max_samples); + +typedef struct { + int total_subsongs; + int codec; + int sample_rate; + int channels; + int loop_start; + int num_samples; + int loop_flag; +} icesnd_info_t; + +/* loads info */ +int icesnd_info(icesnd_handle_t* handle, icesnd_info_t* info); + +#endif diff --git a/src/decode.c b/src/decode.c index bdd53cef..aec74b23 100644 --- a/src/decode.c +++ b/src/decode.c @@ -39,6 +39,11 @@ void free_codec(VGMSTREAM* vgmstream) { free_tac(vgmstream->codec_data); } + if (vgmstream->coding_type == coding_ICE_RANGE || + vgmstream->coding_type == coding_ICE_DCT) { + free_ice(vgmstream->codec_data); + } + if (vgmstream->coding_type == coding_UBI_ADPCM) { free_ubi_adpcm(vgmstream->codec_data); } @@ -140,6 +145,11 @@ void seek_codec(VGMSTREAM* vgmstream) { seek_tac(vgmstream->codec_data, vgmstream->loop_current_sample); } + if (vgmstream->coding_type == coding_ICE_RANGE || + vgmstream->coding_type == coding_ICE_DCT) { + seek_ice(vgmstream->codec_data, vgmstream->loop_current_sample); + } + if (vgmstream->coding_type == coding_UBI_ADPCM) { seek_ubi_adpcm(vgmstream->codec_data, vgmstream->loop_current_sample); } @@ -246,6 +256,11 @@ void reset_codec(VGMSTREAM* vgmstream) { reset_tac(vgmstream->codec_data); } + if (vgmstream->coding_type == coding_ICE_RANGE || + vgmstream->coding_type == coding_ICE_DCT) { + reset_ice(vgmstream->codec_data); + } + if (vgmstream->coding_type == coding_UBI_ADPCM) { reset_ubi_adpcm(vgmstream->codec_data); } @@ -535,6 +550,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) { return 0; /* 1024 - delay/padding (which can be bigger than 1024) */ case coding_TAC: return 0; /* 1024 - delay/padding */ + case coding_ICE_RANGE: + case coding_ICE_DCT: + return 0; /* ~100 (range), ~16 (DCT) */ #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) case coding_MP4_AAC: return ((mp4_aac_codec_data*)vgmstream->codec_data)->samples_per_frame; @@ -1075,6 +1093,10 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_ case coding_TAC: decode_tac(vgmstream, buffer, samples_to_do); break; + case coding_ICE_RANGE: + case coding_ICE_DCT: + decode_ice(vgmstream->codec_data, buffer, samples_to_do); + break; #ifdef VGM_USE_FFMPEG case coding_FFmpeg: decode_ffmpeg(vgmstream, buffer, samples_to_do, vgmstream->channels); diff --git a/src/formats.c b/src/formats.c index 5520ba36..e9d95659 100644 --- a/src/formats.c +++ b/src/formats.c @@ -105,6 +105,7 @@ static const char* extension_list[] = { "bgm", "bgw", "bh2pcm", + "bigrp", "bik", "bika", //fake extension for .bik (to be removed) "bik2", @@ -861,6 +862,8 @@ static const coding_info coding_info_list[] = { {coding_RELIC, "Relic Codec"}, {coding_CRI_HCA, "CRI HCA"}, {coding_TAC, "tri-Ace Codec"}, + {coding_ICE_RANGE, "Inti Creates Range Codec"}, + {coding_ICE_DCT, "Inti Creates DCT Codec"}, #ifdef VGM_USE_VORBIS {coding_OGG_VORBIS, "Ogg Vorbis"}, @@ -1415,6 +1418,7 @@ static const meta_info meta_info_list[] = { {meta_TT_AD, "Traveller's Tales AUDIO_DATA header"}, {meta_SNDZ, "Sony SNDZ header"}, {meta_VAB, "Sony VAB header"}, + {meta_BIGRP, "Inti Creates .BIGRP header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index a66f39a9..ed81595c 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -83,6 +83,7 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="coding\hca_decoder_clhca.h" /> + <ClInclude Include="coding\ice_decoder_icelib.h" /> <ClInclude Include="coding\mpeg_bitreader.h" /> <ClInclude Include="coding\mpeg_decoder.h" /> <ClInclude Include="coding\vorbis_bitreader.h" /> @@ -328,6 +329,7 @@ <ClCompile Include="meta\baf.c" /> <ClCompile Include="meta\bgw.c" /> <ClCompile Include="meta\bik.c" /> + <ClCompile Include="meta\bigrp.c" /> <ClCompile Include="meta\bkhd.c" /> <ClCompile Include="meta\bmp_konami.c" /> <ClCompile Include="meta\bnk_relic.c" /> @@ -643,6 +645,8 @@ <ClCompile Include="coding\g7221_decoder_lib.c" /> <ClCompile Include="coding\hca_decoder.c" /> <ClCompile Include="coding\hca_decoder_clhca.c" /> + <ClCompile Include="coding\ice_decoder.c" /> + <ClCompile Include="coding\ice_decoder_icelib.c" /> <ClCompile Include="coding\ima_decoder.c" /> <ClCompile Include="coding\imuse_decoder.c" /> <ClCompile Include="coding\l5_555_decoder.c" /> diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index ff066c19..c0fd68fe 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -266,6 +266,9 @@ <ClInclude Include="coding\hca_decoder_clhca.h"> <Filter>coding\Header Files</Filter> </ClInclude> + <ClInclude Include="coding\hca_decoder_icelib.h"> + <Filter>coding\Header Files</Filter> + </ClInclude> <ClInclude Include="coding\mpeg_bitreader.h"> <Filter>coding\Header Files</Filter> </ClInclude> @@ -1369,6 +1372,12 @@ <ClCompile Include="coding\hca_decoder_clhca.c"> <Filter>coding\Source Files</Filter> </ClCompile> + <ClCompile Include="coding\ice_decoder.c"> + <Filter>coding\Source Files</Filter> + </ClCompile> + <ClCompile Include="coding\ice_decoder_icelib.c"> + <Filter>coding\Source Files</Filter> + </ClCompile> <ClCompile Include="coding\ima_decoder.c"> <Filter>coding\Source Files</Filter> </ClCompile> @@ -1879,6 +1888,9 @@ <ClCompile Include="meta\bik.c"> <Filter>meta\Source Files</Filter> </ClCompile> + <ClCompile Include="meta\bigrp.c"> + <Filter>meta\Source Files</Filter> + </ClCompile> <ClCompile Include="meta\bkhd.c"> <Filter>meta\Source Files</Filter> </ClCompile> diff --git a/src/meta/bigrp.c b/src/meta/bigrp.c new file mode 100644 index 00000000..38502044 --- /dev/null +++ b/src/meta/bigrp.c @@ -0,0 +1,110 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../coding/ice_decoder_icelib.h" + + +/* .BIGRP - from Inti Creates "ICE"/"Imperial" engine [Blaster Master Zero 2 (SW), Gunvolt 3 (SW)] */ +VGMSTREAM* init_vgmstream_bigrp(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + uint32_t header_size, entry_size, stream_size; + int total_subsongs, target_subsong = sf->stream_index; + int codec, channels, loop_flag, sample_rate; + int32_t loop_start, num_samples; + + + /* checks */ + + /* simple checks to avoid opening the lib if possible + * early games use smaller sizes [Bloodstained COTM (Vita), Mighty Gunvolt Burst (PC)] */ + header_size = read_u32le(0x00,sf); + if (read_u32le(0x00,sf) != 0x0c && read_u32le(0x00,sf) != 0x10) + goto fail; + entry_size = read_u32le(0x04,sf); + if (entry_size != 0x34 && entry_size != 0x40) + goto fail; + + if (!check_extensions(sf, "bigrp")) + goto fail; + + if (target_subsong == 0) target_subsong = 1; + total_subsongs = read_s32le(0x08,sf); + if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail; + + + /* could read all this from the lib, meh */ + { + uint32_t offset = header_size + entry_size * (target_subsong - 1); + + /* 00: hash */ + /* 04: group? */ + codec = read_u32le(offset + 0x08, sf); + + switch(codec) { + case 0x00: /* range [BMZ2 (SW), Bloodstained COTM (Vita), BMZ1 (PS4)] */ + case 0x03: + sample_rate = read_s32le(offset + 0x0c, sf); + channels = read_u8 (offset + 0x10, sf); + /* 0x11: spf */ + /* 0x12: unknown (volume?) */ + loop_flag = read_u32le(offset + 0x14, sf); + /* 0x18: frame codes */ + loop_start = read_s32le(offset + 0x1c, sf); + stream_size = read_u32le(offset + 0x20, sf); /* intro block */ + /* 0x24: intro offset */ + num_samples = read_s32le(offset + 0x28, sf) + loop_start; + stream_size = read_u32le(offset + 0x2c, sf) + stream_size; /* body block */ + /* 0x30: body offset */ + break; + + default: + /* dummy to avoid breaking some files that mix codecs with midi */ + channels = 1; + sample_rate = 48000; + loop_flag = 0; + loop_start = 0; + num_samples = sample_rate; + stream_size = 0; + break; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_BIGRP; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = num_samples; + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + + switch(codec) { + case 0x00: + case 0x03: + vgmstream->codec_data = init_ice(sf, target_subsong); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = codec == 0x00 ? coding_ICE_RANGE : coding_ICE_DCT; + vgmstream->layout_type = layout_none; + break; + + case 0x01: + case 0x02: + vgmstream->coding_type = coding_SILENCE; + snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "[%s]", (codec == 0x01 ? "data" : "midi")); + break; + + default: + goto fail; + } + + //if (!vgmstream_open_stream(vgmstream, sf, 0x00)) + // goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/meta.h b/src/meta/meta.h index 28ad4377..deaedab0 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -991,4 +991,6 @@ VGMSTREAM* init_vgmstream_sndz(STREAMFILE* sf); VGMSTREAM* init_vgmstream_vab(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_bigrp(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/src/vgmstream.c b/src/vgmstream.c index 14974ac6..58f7187e 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -525,6 +525,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_bw_riff_mp3, init_vgmstream_sndz, init_vgmstream_vab, + init_vgmstream_bigrp, /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */ init_vgmstream_agsc, diff --git a/src/vgmstream.h b/src/vgmstream.h index b28e266c..962db34e 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -185,6 +185,8 @@ typedef enum { coding_RELIC, /* Relic Codec (DCT-based) */ coding_CRI_HCA, /* CRI High Compression Audio (MDCT-based) */ coding_TAC, /* tri-Ace Codec (MDCT-based) */ + coding_ICE_RANGE, /* Inti Creates "range" codec */ + coding_ICE_DCT, /* Inti Creates "DCT" codec */ #ifdef VGM_USE_VORBIS coding_OGG_VORBIS, /* Xiph Vorbis with Ogg layer (MDCT-based) */ @@ -765,6 +767,7 @@ typedef enum { meta_TT_AD, meta_SNDZ, meta_VAB, + meta_BIGRP, } meta_t;