From 8b4bf139b305895e4f9d8269a8500545686da978 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 22 Sep 2022 23:17:20 +0200 Subject: [PATCH 1/5] Fix some .ccc --- src/formats.c | 2 +- src/meta/ps2_ccc.c | 4 ++-- src/vgmstream.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/formats.c b/src/formats.c index 97c88ea8..5520ba36 100644 --- a/src/formats.c +++ b/src/formats.c @@ -130,7 +130,7 @@ static const char* extension_list[] = { "caf", "cbd2", - "ccc", + "ccc", //fake extension (to be removed) "cd", "cfn", //fake extension for CAF (renamed, to be removed?) "chd", //txth/reserved [Donkey Konga (GC), Star Fox Assault (GC)] diff --git a/src/meta/ps2_ccc.c b/src/meta/ps2_ccc.c index ab2a4013..d0745a66 100644 --- a/src/meta/ps2_ccc.c +++ b/src/meta/ps2_ccc.c @@ -33,10 +33,10 @@ VGMSTREAM * init_vgmstream_ps2_ccc(STREAMFILE *streamFile) { vgmstream->channels = channel_count; vgmstream->sample_rate = read_32bitLE(0x04,streamFile); vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = (read_32bitLE(0x08,streamFile))/channel_count/32*28; + vgmstream->num_samples = (read_32bitLE(0x08,streamFile))/channel_count/0x10*28; if (loop_flag) { vgmstream->loop_start_sample = 0; - vgmstream->loop_end_sample = (read_32bitLE(0x08,streamFile))/channel_count/32*28; + vgmstream->loop_end_sample = (read_32bitLE(0x08,streamFile))/channel_count/0x10*28; } vgmstream->layout_type = layout_interleave; diff --git a/src/vgmstream.c b/src/vgmstream.c index 53c0d061..14974ac6 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -129,7 +129,6 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_idsp_ie, init_vgmstream_ngc_ymf, init_vgmstream_sadl, - init_vgmstream_ps2_ccc, init_vgmstream_fag, init_vgmstream_ps2_mihb, init_vgmstream_ngc_pdt_split, @@ -555,6 +554,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_ngc_ulw, /* .ulw raw u-Law */ init_vgmstream_exakt_sc, /* .sc raw PCM */ init_vgmstream_zwdsp, /* fake format */ + init_vgmstream_ps2_ccc, /* fake format, to be removed */ init_vgmstream_ps2_adm, /* weird non-constant PSX blocks */ init_vgmstream_baf_badrip, /* crap, to be removed */ init_vgmstream_rxws_badrip, /* crap, to be removed */ From 78eafcba703c2efa9c6e8d17f33b02022d650904 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 22 Sep 2022 23:18:41 +0200 Subject: [PATCH 2/5] Show multiple cue names in SQEX .sab Also allow dummy entries for easier cue<>wave matching --- src/meta/sqex_sead.c | 234 +++++++++++++++++++++++++------------------ 1 file changed, 138 insertions(+), 96 deletions(-) diff --git a/src/meta/sqex_sead.c b/src/meta/sqex_sead.c index 7aa4547a..0112fa82 100644 --- a/src/meta/sqex_sead.c +++ b/src/meta/sqex_sead.c @@ -1,5 +1,6 @@ #include "meta.h" #include "../coding/coding.h" +#include "../util/endianness.h" #include "sqex_streamfile.h" @@ -20,65 +21,67 @@ typedef struct { int sample_rate; int loop_start; int loop_end; - off_t mtrl_offset; - off_t extradata_offset; - size_t extradata_size; - size_t stream_size; + uint32_t mtrl_offset; + uint32_t extradata_offset; + uint32_t extradata_size; + uint32_t stream_size; uint16_t extradata_id; - off_t filename_offset; - size_t filename_size; - off_t muscname_offset; - size_t muscname_size; - off_t sectname_offset; - size_t sectname_size; - off_t modename_offset; - size_t modename_size; - off_t instname_offset; - size_t instname_size; - off_t sndname_offset; - size_t sndname_size; + uint32_t filename_offset; + uint32_t filename_size; + uint32_t muscname_offset; + uint32_t muscname_size; + uint32_t sectname_offset; + uint32_t sectname_size; + uint32_t modename_offset; + uint32_t modename_size; + uint32_t instname_offset; + uint32_t instname_size; + //uint32_t sndname_offset; + //uint32_t sndname_size; - off_t sections_offset; - off_t snd_section_offset; - off_t seq_section_offset; - off_t trk_section_offset; - off_t musc_section_offset; - off_t inst_section_offset; - off_t mtrl_section_offset; + uint32_t sections_offset; + uint32_t snd_section_offset; + uint32_t seq_section_offset; + uint32_t trk_section_offset; + uint32_t musc_section_offset; + uint32_t inst_section_offset; + uint32_t mtrl_section_offset; char readable_name[STREAM_NAME_SIZE]; -} sead_header; +} sead_header_t; -static int parse_sead(sead_header *sead, STREAMFILE *sf); +static int parse_sead(sead_header_t* sead, STREAMFILE* sf); /* SABF/MABF - Square Enix's "sead" audio games [Dragon Quest Builders (PS3), Dissidia Opera Omnia (mobile), FF XV (PS4)] */ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - sead_header sead = {0}; + sead_header_t sead = {0}; off_t start_offset; int target_subsong = sf->stream_index; - uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL; - uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL; + read_u32_t read_u32 = NULL; + read_u16_t read_u16 = NULL; /* checks */ + if (is_id32be(0x00, sf, "sabf")) { + sead.is_sab = 1; + } + else if (is_id32be(0x00, sf, "mabf")) { + sead.is_mab = 1; + } + else { + goto fail; + } + /* .sab: sound/bgm * .mab: music * .sbin: Dissidia Opera Omnia .sab */ if (!check_extensions(sf,"sab,mab,sbin")) goto fail; - if (read_u32be(0x00, sf) == 0x73616266) { /* "sabf" */ - sead.is_sab = 1; - } else if (read_u32be(0x00, sf) == 0x6D616266) { /* "mabf" */ - sead.is_mab = 1; - } else { - goto fail; - } - /* SEAD handles both sab/mab in the same lib (libsead), and other similar files (config, engine, etc). * Has some chunks pointing to sections, and each section entry (usually starting with section * version/reserved/size) is always padded to 0x10. Most values are unsigned. @@ -89,7 +92,7 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { * or is a separate audio lib derived from this "SEAD Engine". */ - sead.big_endian = guess_endianness16bit(0x06, sf); /* no flag, use size */ + sead.big_endian = guess_endian16(0x06, sf); /* no flag, use size */ if (sead.big_endian) { read_u32 = read_u32be; read_u16 = read_u16be; @@ -115,6 +118,14 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { strcpy(vgmstream->stream_name, sead.readable_name); switch(sead.codec) { + case 0x00: /* NONE */ + vgmstream->coding_type = coding_SILENCE; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = sead.sample_rate; + + start_offset = 0; + break; case 0x01: { /* PCM [Chrono Trigger (PC) sfx] */ start_offset = sead.extradata_offset + sead.extradata_size; @@ -157,7 +168,7 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { #ifdef VGM_USE_VORBIS case 0x03: { /* VORBIS (Ogg subfile) [Final Fantasy XV Benchmark sfx (PC)] */ - VGMSTREAM *ogg_vgmstream = NULL; + VGMSTREAM* ogg_vgmstream = NULL; ogg_vorbis_meta_info_t ovmi = {0}; off_t subfile_offset = sead.extradata_offset + sead.extradata_size; @@ -254,8 +265,8 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { #endif case 0x07: { /* HCA (subfile) [Dissidia Opera Omnia (Mobile), Final Fantaxy XV (PS4)] */ - VGMSTREAM *temp_vgmstream = NULL; - STREAMFILE *temp_sf = NULL; + VGMSTREAM* temp_vgmstream = NULL; + STREAMFILE* temp_sf = NULL; off_t subfile_offset = sead.extradata_offset + 0x10; size_t subfile_size = sead.stream_size + sead.extradata_size - 0x10; @@ -299,11 +310,10 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { } } - case 0x00: /* NONE / dummy entry */ case 0x05: /* XMA2 (extradata may be a XMA2 fmt extra chunk) */ case 0x08: /* SWITCHOPUS (no extradata?) */ default: - VGM_LOG("SQEX SEAD: unknown codec %x\n", sead.codec); + vgm_logi("SQEX SEAD: unknown codec %x\n", sead.codec); goto fail; } @@ -319,54 +329,76 @@ fail: return NULL; } +//todo safeops, avoid recalc lens +static void sead_cat(char* dst, int dst_max, const char* src) { + int dst_len = strlen(dst); + int src_len = strlen(dst); + if (dst_len + src_len > dst_max - 1) + return; + strcat(dst, src); +} -static void build_readable_name(char * buf, size_t buf_size, sead_header *sead, STREAMFILE *sf) { +static void build_readable_sab_name(sead_header_t* sead, STREAMFILE* sf, uint32_t sndname_offset, uint32_t sndname_size) { + char * buf = sead->readable_name; + int buf_size = sizeof(sead->readable_name); + char descriptor[255], name[255]; - if (sead->is_sab) { - char descriptor[255], name[255]; + if (sead->filename_size > 255 || sndname_size > 255) + goto fail; - if (sead->filename_size > 255 || sead->sndname_size > 255) goto fail; - - read_string(descriptor,sead->filename_size+1,sead->filename_offset, sf); - read_string(name,sead->sndname_size+1,sead->sndname_offset, sf); + if (buf[0] == '\0') { /* init */ + read_string(descriptor,sead->filename_size+1, sead->filename_offset, sf); + read_string(name, sndname_size+1, sndname_offset, sf); snprintf(buf,buf_size, "%s/%s", descriptor, name); } - else { - char descriptor[255], name[255], mode[255]; + else { /* add */ + read_string(name, sndname_size+1, sndname_offset, sf); - if (sead->filename_size > 255 || sead->muscname_size > 255 || sead->sectname_size > 255 || sead->modename_size > 255) goto fail; - - read_string(descriptor,sead->filename_size+1,sead->filename_offset, sf); - //read_string(filename,sead->muscname_size+1,sead->muscname_offset, sf); /* same as filename, not too interesting */ - if (sead->sectname_offset) - read_string(name,sead->sectname_size+1,sead->sectname_offset, sf); - else if (sead->instname_offset) - read_string(name,sead->instname_size+1,sead->instname_offset, sf); - else - strcpy(name, "?"); - if (sead->modename_offset > 0) - read_string(mode,sead->modename_size+1,sead->modename_offset, sf); - - /* default mode in most files */ - if (sead->modename_offset == 0 || strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) - snprintf(buf,buf_size, "%s/%s", descriptor, name); - else - snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); + sead_cat(buf, buf_size, "; "); + sead_cat(buf, buf_size, name); } + return; +fail: + VGM_LOG("SEAD: bad sab name found\n"); +} + +static void build_readable_mab_name(sead_header_t* sead, STREAMFILE* sf) { + char * buf = sead->readable_name; + int buf_size = sizeof(sead->readable_name); + char descriptor[255], name[255], mode[255]; + + if (sead->filename_size > 255 || sead->muscname_size > 255 || sead->sectname_size > 255 || sead->modename_size > 255) + goto fail; + + read_string(descriptor,sead->filename_size+1,sead->filename_offset, sf); + //read_string(filename,sead->muscname_size+1,sead->muscname_offset, sf); /* same as filename, not too interesting */ + if (sead->sectname_offset) + read_string(name,sead->sectname_size+1,sead->sectname_offset, sf); + else if (sead->instname_offset) + read_string(name,sead->instname_size+1,sead->instname_offset, sf); + else + strcpy(name, "?"); + if (sead->modename_offset > 0) + read_string(mode,sead->modename_size+1,sead->modename_offset, sf); + + /* default mode in most files */ + if (sead->modename_offset == 0 || strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) + snprintf(buf,buf_size, "%s/%s", descriptor, name); + else + snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); return; fail: - VGM_LOG("SEAD: bad name found\n"); + VGM_LOG("SEAD: bad mab name found\n"); } -static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) { - uint32_t (*read_u32)(off_t,STREAMFILE*) = sead->big_endian ? read_u32be : read_u32le; - uint16_t (*read_u16)(off_t,STREAMFILE*) = sead->big_endian ? read_u16be : read_u16le; +static void parse_sead_mab_name(sead_header_t* sead, STREAMFILE* sf) { + read_u32_t read_u32 = sead->big_endian ? read_u32be : read_u32le; + read_u16_t read_u16 = sead->big_endian ? read_u16be : read_u16le; int i, j, k, entries; - off_t target_muscname_offset = 0, target_sectname_offset = 0; - size_t target_muscname_size = 0, target_sectname_size = 0; - + uint32_t target_muscname_offset = 0, target_sectname_offset = 0; + uint32_t target_muscname_size = 0, target_sectname_size = 0; /* find names referencing to our material stream, usually: * - music > sections > layers (<> meters) > material index @@ -608,9 +640,9 @@ static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) { } } -static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { - uint32_t (*read_u32)(off_t,STREAMFILE*) = sead->big_endian ? read_u32be : read_u32le; - uint16_t (*read_u16)(off_t,STREAMFILE*) = sead->big_endian ? read_u16be : read_u16le; +static void parse_sead_sab_name(sead_header_t* sead, STREAMFILE* sf) { + read_u32_t read_u32 = sead->big_endian ? read_u32be : read_u32le; + read_u16_t read_u16 = sead->big_endian ? read_u16be : read_u16le; int i, j, entries; /* find names referencing to our material stream, usually: @@ -627,9 +659,9 @@ static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { /* parse sounds */ entries = read_u16(sead->snd_section_offset + 0x04, sf); for (i = 0; i < entries; i++) { - off_t snd_offset = sead->snd_section_offset + read_u32(sead->snd_section_offset + 0x10 + i*0x04, sf); - size_t snd_size, sndname_size; - off_t seqi_start, seqi_offset, sndname_offset; + uint32_t snd_offset = sead->snd_section_offset + read_u32(sead->snd_section_offset + 0x10 + i*0x04, sf); + uint32_t snd_size, sndname_size; + uint32_t seqi_start, seqi_offset, sndname_offset; int snd_version, seqi_count; snd_version = read_u8(snd_offset + 0x00, sf); @@ -677,8 +709,8 @@ static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { /* parse sequence */ { - off_t seq_offset = sead->seq_section_offset + read_u32(sead->seq_section_offset + 0x10 + seq_index*0x04, sf); - off_t cmnd_start, cmnd_offset; + uint32_t seq_offset = sead->seq_section_offset + read_u32(sead->seq_section_offset + 0x10 + seq_index*0x04, sf); + uint32_t cmnd_start, cmnd_offset; int seq_version; seq_version = read_u8(seq_offset + 0x00, sf); @@ -724,7 +756,7 @@ static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { /* parse track */ { - off_t trk_offset = sead->trk_section_offset + read_u32(sead->trk_section_offset + 0x10 + trk_index*0x04, sf); + uint32_t trk_offset = sead->trk_section_offset + read_u32(sead->trk_section_offset + 0x10 + trk_index*0x04, sf); uint8_t trk_type; uint16_t mtrl_index; @@ -743,8 +775,9 @@ static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { /* assumes same bank, not sure if bank info is even inside .sab */ if (mtrl_index == sead->mtrl_index) { - sead->sndname_offset = sndname_offset; - sead->sndname_size = sndname_size; + build_readable_sab_name(sead, sf, sndname_offset, sndname_size); + //sead->sndname_offset = sndname_offset; + //sead->sndname_size = sndname_size; } } else if (trk_type == 0x02) { @@ -760,8 +793,10 @@ static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { /* commands normally end when a type 0=none is found */ if (cmnd_type <= 0x00 || cmnd_type > 0x08) break; - if (sead->sndname_offset > 0) - break; + + /* keep reading names as N sounds may point to current material */ + //if (sead->sndname_offset > 0) + // break; } } @@ -770,9 +805,9 @@ static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { } -static int parse_sead(sead_header* sead, STREAMFILE* sf) { - uint32_t (*read_u32)(off_t,STREAMFILE*) = sead->big_endian ? read_u32be : read_u32le; - uint16_t (*read_u16)(off_t,STREAMFILE*) = sead->big_endian ? read_u16be : read_u16le; +static int parse_sead(sead_header_t* sead, STREAMFILE* sf) { + read_u32_t read_u32 = sead->big_endian ? read_u32be : read_u32le; + read_u16_t read_u16 = sead->big_endian ? read_u16be : read_u16le; /** base header **/ /* 0x00: id */ @@ -849,9 +884,9 @@ static int parse_sead(sead_header* sead, STREAMFILE* sf) { for (i = 0; i < entries; i++) { off_t entry_offset = sead->mtrl_section_offset + read_u32(sead->mtrl_section_offset + 0x10 + i*0x04, sf); - if (read_u8(entry_offset + 0x05, sf) == 0) { - continue; /* codec 0 when dummy (see stream header) */ - } + //if (read_u8(entry_offset + 0x05, sf) == 0) { + // continue; /* codec 0 when dummy (see stream header) */ + //} sead->total_subsongs++; if (!sead->mtrl_offset && sead->total_subsongs == sead->target_subsong) { @@ -882,17 +917,24 @@ static int parse_sead(sead_header* sead, STREAMFILE* sf) { sead->extradata_id = read_u16(sead->mtrl_offset + 0x1c, sf); /* 0x1e: reserved */ + if (sead->codec == 0x00) { + /* dummy entries have null fields, put defaults to allow playing them */ + sead->channels = 1; + sead->sample_rate = 48000; + } + sead->loop_flag = (sead->loop_end > 0); sead->extradata_offset = sead->mtrl_offset + 0x20; if (sead->is_sab) { parse_sead_sab_name(sead, sf); + /* sab name is added during process */ } else if (sead->is_mab) { parse_sead_mab_name(sead, sf); + build_readable_mab_name(sead, sf); } - build_readable_name(sead->readable_name, sizeof(sead->readable_name), sead, sf); return 1; fail: From 47381e7e3db5c09575b6110fd49e279a90847fa1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 22 Sep 2022 23:19:46 +0200 Subject: [PATCH 3/5] Reduce internal path limit for more common cases --- src/vgmstream.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vgmstream.h b/src/vgmstream.h index 46407abe..b28e266c 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -7,7 +7,10 @@ /* reasonable limits */ enum { - PATH_LIMIT = 32768, + /* Windows generally only allows 260 chars in path, but other OSs have higher limits, and we handle + * UTF-8 (that typically uses 2-bytes for common non-latin codepages) plus player may append protocols + * to paths, so it should be a bit higher. Most people wouldn't use huge paths though. */ + PATH_LIMIT = 4096, /* (256 * 8) * 2 = ~max_path * (other_os+extra) * codepage_bytes */ STREAM_NAME_SIZE = 255, VGMSTREAM_MAX_CHANNELS = 64, VGMSTREAM_MIN_SAMPLE_RATE = 300, /* 300 is Wwise min */ From 791a7de02a4ea7344190fe79eca745a76d7890b4 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 22 Sep 2022 23:20:36 +0200 Subject: [PATCH 4/5] Cleanup/doc --- doc/USAGE.md | 19 +++++++ src/coding/vorbis_custom_utils_fsb.c | 77 +++++++++----------------- src/coding/vorbis_custom_utils_wwise.c | 8 ++- src/meta/akb.c | 2 - src/meta/awb.c | 2 +- src/plugins.c | 1 - 6 files changed, 52 insertions(+), 57 deletions(-) diff --git a/doc/USAGE.md b/doc/USAGE.md index 1c855d2b..9d36dfa9 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -630,6 +630,25 @@ If your main motivation for extracting is to rename or have loose files, remembe you can simply use TXTP to point to a subsong, and name that `.txtp` whatever you want, without having to touch original data or needing custom extractors. +### Cue formats +Some formats that vgmstream supports (SQEX's .sab, CRI's .acb+awb, Wwise's .bnk+wem, +Microsoft's .xss+.xwb....) are "cue" formats. The way these work is (more or less), +they have a bunch of named audio "cues"/"events" in a section of the file, that are +called to play one or multiple audio "waves"/"materials" in another section. + +Rather than handling cues, vgmstream shows and plays waves, then assigns cue names +that point to the wave if possible, since vgmstream mainly deals with streamed/wave +audio and simulating cues is out of scope. Figuring out a whole cue format can be a +*huge* time investment, so handling waves only is often enough. + +Cues can be *very* complex, like N cues pointing to 1 wave with varying pitch, or +1 cue playing one random wave out of 3. Sometimes not all waves are referenced by +cues, or cues do undesirable effects that make only playing waves a good compromise. +Simulating cues is better handled with external tools that allow more flexibility +(for example, this project simulates Wwise's extremely complex cues/events by creating +.TXTP telling vgmstream which config and waves to play, and one can filter desired +cues/TXTP: https://github.com/bnnm/wwiser). + ## Logged errors and unplayable supported files Some formats should normally play, but somehow don't. In those cases plugins can print vgmstream's error info to console (for example, `.fsb` with an unknown diff --git a/src/coding/vorbis_custom_utils_fsb.c b/src/coding/vorbis_custom_utils_fsb.c index 7845291e..2a25a565 100644 --- a/src/coding/vorbis_custom_utils_fsb.c +++ b/src/coding/vorbis_custom_utils_fsb.c @@ -17,8 +17,9 @@ static int build_header_identification(uint8_t* buf, size_t bufsize, int channel static int build_header_comment(uint8_t* buf, size_t bufsize); static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); -static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); -static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); +#if !(FSB_VORBIS_USE_PRECOMPILED_FVS) +static int load_fvs_file(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); +#endif static int load_fvs_array(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); @@ -58,7 +59,7 @@ int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL* stream, vorbis_custom_codec size_t bytes; /* get next packet size from the FSB 16b header (doesn't count this 16b) */ - data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile); + data->op.bytes = read_u16le(stream->offset, stream->streamfile); stream->offset += 2; if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */ @@ -108,16 +109,16 @@ static int build_header_identification(uint8_t* buf, size_t bufsize, int channel } blocksizes = (exp_blocksize_0 << 4) | (exp_blocksize_1); - put_8bit (buf+0x00, 0x01); /* packet_type (id) */ + put_u8 (buf+0x00, 0x01); /* packet_type (id) */ memcpy (buf+0x01, "vorbis", 6); /* id */ - put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */ - put_8bit (buf+0x0b, channels); /* audio_channels */ - put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */ - put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */ - put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */ - put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */ - put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */ - put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */ + put_u32le(buf+0x07, 0x00); /* vorbis_version (fixed) */ + put_u8 (buf+0x0b, channels); /* audio_channels */ + put_s32le(buf+0x0c, sample_rate); /* audio_sample_rate */ + put_u32le(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */ + put_u32le(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */ + put_u32le(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */ + put_u8 (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */ + put_u8 (buf+0x1d, 0x01); /* framing_flag (fixed) */ return bytes; } @@ -127,12 +128,12 @@ static int build_header_comment(uint8_t* buf, size_t bufsize) { if (bytes > bufsize) return 0; - put_8bit (buf+0x00, 0x03); /* packet_type (comments) */ - memcpy (buf+0x01, "vorbis", 6); /* id */ - put_32bitLE(buf+0x07, 0x09); /* vendor_length */ - memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */ - put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */ - put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */ + put_u8 (buf+0x00, 0x03); /* packet_type (comments) */ + memcpy (buf+0x01, "vorbis", 6); /* id */ + put_u32le(buf+0x07, 0x09); /* vendor_length */ + memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */ + put_u32le(buf+0x14, 0x00); /* user_comment_list_length */ + put_u8 (buf+0x18, 0x01); /* framing_flag (fixed) */ return bytes; } @@ -145,49 +146,20 @@ static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, S if (bytes) return bytes; +#if !(FSB_VORBIS_USE_PRECOMPILED_FVS) /* try to load from external files */ - bytes = load_fvs_file_single(buf, bufsize, setup_id, sf); - if (bytes) - return bytes; - - bytes = load_fvs_file_multi(buf, bufsize, setup_id, sf); + bytes = load_fvs_file(buf, bufsize, setup_id, sf); if (bytes) return bytes; +#endif /* not found */ VGM_LOG("FSB Vorbis: setup_id %08x not found\n", setup_id); return 0; } -static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { - STREAMFILE* sf_setup = NULL; - - /* get from artificial external file (used if compiled without codebooks) */ - { - char setupname[0x20]; - - snprintf(setupname, sizeof(setupname), ".fvs_%08x", setup_id); - sf_setup = open_streamfile_by_filename(sf, setupname); - } - - /* get codebook and copy to buffer */ - if (sf_setup) { - size_t bytes = sf_setup->get_size(sf_setup); - if (bytes > bufsize) goto fail; - - if (read_streamfile(buf, 0, bytes, sf_setup) != bytes) - goto fail; - - close_streamfile(sf_setup); - return bytes; - } - -fail: - close_streamfile(sf_setup); - return 0; -} - -static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { +#if !(FSB_VORBIS_USE_PRECOMPILED_FVS) +static int load_fvs_file(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { STREAMFILE* sf_setup = NULL; /* from to get from artificial external file (used if compiled without codebooks) */ @@ -230,6 +202,7 @@ fail: if (sf_setup) sf_setup->close(sf_setup); return 0; } +#endif static int load_fvs_array(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { #if FSB_VORBIS_USE_PRECOMPILED_FVS diff --git a/src/coding/vorbis_custom_utils_wwise.c b/src/coding/vorbis_custom_utils_wwise.c index e2190075..3ffe00a7 100644 --- a/src/coding/vorbis_custom_utils_wwise.c +++ b/src/coding/vorbis_custom_utils_wwise.c @@ -34,7 +34,9 @@ static int ww2ogg_generate_vorbis_packet(bitstream_t* ow, bitstream_t* iw, wpack static int ww2ogg_generate_vorbis_setup(bitstream_t* ow, bitstream_t* iw, vorbis_custom_codec_data* data, size_t packet_size, STREAMFILE* sf); static int load_wvc(uint8_t* ibuf, size_t ibufsize, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE* sf); +#if !(WWISE_VORBIS_USE_PRECOMPILED_WVC) static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STREAMFILE* sf); +#endif static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, wwise_setup_t setup_type); @@ -1133,16 +1135,19 @@ static int load_wvc(uint8_t* ibuf, size_t ibufsize, uint32_t codebook_id, wwise_ if (bytes) return bytes; +#if !(WWISE_VORBIS_USE_PRECOMPILED_WVC) /* try to load from external file (ignoring type, just use file if found) */ bytes = load_wvc_file(ibuf, ibufsize, codebook_id, sf); if (bytes) return bytes; +#endif /* not found */ VGM_LOG("Wwise Vorbis: codebook_id %04x not found\n", codebook_id); return 0; } +#if !(WWISE_VORBIS_USE_PRECOMPILED_WVC) static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STREAMFILE* sf) { STREAMFILE* sf_setup = NULL; size_t wvc_size = 0; @@ -1185,6 +1190,7 @@ fail: close_streamfile(sf_setup); return 0; } +#endif static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, wwise_setup_t setup_type) { #if WWISE_VORBIS_USE_PRECOMPILED_WVC @@ -1217,7 +1223,7 @@ static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, ww } } - // this can be used if the lists contained a 1:1 dump of the codebook files + // this can be used with 1:1 dump of the codebook file #if 0 if (wvc == NULL) goto fail; /* find codebook and copy to buffer */ diff --git a/src/meta/akb.c b/src/meta/akb.c index 97ab50d6..327f1ec4 100644 --- a/src/meta/akb.c +++ b/src/meta/akb.c @@ -138,10 +138,8 @@ VGMSTREAM* init_vgmstream_akb(STREAMFILE* sf) { /* enable encryption */ if (version >= 3 && (flags & 8)) { - VGM_LOG("temp1\n"); temp_sf = setup_sqex_streamfile(sf, start_offset, stream_size, 1, 0x00, 0x00, "ogg"); if (!temp_sf) goto fail; - VGM_LOG("temp2\n"); ogg_vgmstream = init_vgmstream_ogg_vorbis_config(temp_sf, 0x00, &ovmi); close_streamfile(temp_sf); diff --git a/src/meta/awb.c b/src/meta/awb.c index 274ec4cf..0210ae56 100644 --- a/src/meta/awb.c +++ b/src/meta/awb.c @@ -78,7 +78,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { subfile_size = subfile_next - subfile_offset; } - ;VGM_LOG("awb: subfile offset=%x + %x\n", subfile_offset, subfile_size); + //;VGM_LOG("awb: subfile offset=%x + %x\n", subfile_offset, subfile_size); /* autodetect as there isn't anything, plus can mix types * (waveid<>codec info is usually in the companion .acb) */ diff --git a/src/plugins.c b/src/plugins.c index 8b2a352b..a30412f7 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -406,7 +406,6 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { } else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) { tags->exact_match = 1; - VGM_LOG("exact\n"); } continue; /* not an actual tag */ From cbd831e65809e41b4cadd55b896514e8a0480b87 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 23 Sep 2022 22:53:45 +0200 Subject: [PATCH 5/5] 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 +#include +#include +#include +#include + +#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 + +/* 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 @@ + @@ -328,6 +329,7 @@ + @@ -643,6 +645,8 @@ + + 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 @@ coding\Header Files + + coding\Header Files + coding\Header Files @@ -1369,6 +1372,12 @@ coding\Source Files + + coding\Source Files + + + coding\Source Files + coding\Source Files @@ -1879,6 +1888,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files 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;