Skip to content

Commit

Permalink
Merge pull request #1480 from bnnm/aifc-misc
Browse files Browse the repository at this point in the history
- Add .caf AIFC/RIFF extension [Topple (iOS)]
- Add .ogg loops [The Rumble Fish + (Switch)]
- Allow buggy dual stereo [Harvest Moon: Tree of Tranquility (Wii)]
  • Loading branch information
bnnm authored Feb 4, 2024
2 parents 610c48d + ebaaaba commit 6c0e361
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 82 deletions.
4 changes: 2 additions & 2 deletions doc/FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ different internally (encrypted, different versions, etc) and not always can be
- **aifc.c**
- Apple AIFF-C header [*AIFC*]
- Apple AIFF header [*AIFF*]
- *aifc*: `.aif .laif .wav .lwav .(extensionless) .aifc .laifc .afc .cbd2 .bgm .fda .n64 .xa .aiff .laiff .acm .adp .ai .pcm`
- *aifc*: `.aif .laif .wav .lwav .(extensionless) .aifc .laifc .afc .cbd2 .bgm .fda .n64 .xa .caf .aiff .laiff .acm .adp .ai .pcm`
- Codecs: SDX2 CBD2 DVI_IMA_int APPLE_IMA4 RELIC VADPCM PCM8 PCM16BE XA
- **str_snds.c**
- 3DO SNDS header [*STR_SNDS*]
Expand Down Expand Up @@ -271,7 +271,7 @@ different internally (encrypted, different versions, etc) and not always can be
- RIFF WAVE header (ctrl looping) [*RIFF_WAVE_MWV*]
- RIFX WAVE header [*RIFX_WAVE*]
- RIFX WAVE header (smpl looping) [*RIFX_WAVE_smpl*]
- *riff*: `.wav .lwav .xwav .mwv .da .dax .cd .med .snd .adx .adp .xss .xsew .adpcm .adw .wd .(extensionless) .sbv .wvx .str .at3 .rws .aud .at9 .ckd .saf .ima .nsa .pcm .xvag .ogg .logg .p1d .xms .mus .dat .ldat .wma .lwma`
- *riff*: `.wav .lwav .xwav .mwv .da .dax .cd .med .snd .adx .adp .xss .xsew .adpcm .adw .wd .(extensionless) .sbv .wvx .str .at3 .rws .aud .at9 .ckd .saf .ima .nsa .pcm .xvag .ogg .logg .p1d .xms .mus .dat .ldat .wma .lwma .caf`
- *rifx*: `.wav .lwav`
- Codecs: AICA_int PCM32LE PCM24LE PCM16BE PCM16LE PCM8_U MSADPCM IMA PCMFLOAT MS_IMA AICA MPEG_custom XBOX_IMA MS_IMA_3BIT DVI_IMA L5_555 OGG_VORBIS ATRAC9 ATRAC3 MPEG MSADPCM_int
- **nwa.c**
Expand Down
2 changes: 1 addition & 1 deletion src/base/seek.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
if (!vgmstream->hit_loop) {
int32_t skip_samples;

if (vgmstream->current_sample >= vgmstream->loop_start_sample) {
if (vgmstream->current_sample > vgmstream->loop_start_sample) { /* may be 0 */
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
Expand Down
30 changes: 18 additions & 12 deletions src/coding/libs/utkdec.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <string.h>
#include "utkdec.h"


// AKA 'UTALKSTATE'
struct utk_context_t {
/* config */
utk_type_t type;
Expand Down Expand Up @@ -35,12 +35,12 @@ struct utk_context_t {
};


/* bit mask; (1 << count) - 1 is probably faster now but OG code uses a table */
/* AKA 'bitmask'; (1 << count) - 1 is probably faster now but OG code uses a table */
static const uint8_t mask_table[8] = {
0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF
};

/* reflection coefficients, rounded that correspond to hex values in exes (actual float is longer)
/* AKA 'coeff_table', reflection coefficients (rounded) that correspond to hex values in exes (actual float is longer)
* note this table is mirrored: for (i = 1 .. 32) t[64 - i] = -t[i]) */
static const float utk_rc_table[64] = {
/* 6b index start */
Expand All @@ -63,6 +63,7 @@ static const float utk_rc_table[64] = {
+0.977431f, +0.983879f, +0.990327f, +0.996776f,
};

// AKA 'index_table'
static const uint8_t utk_codebooks[2][256] = {
/* normal model */
{
Expand Down Expand Up @@ -109,6 +110,7 @@ enum {
MDL_LARGEPULSE = 1
};

// AKA 'decode_table'
static const struct {
int next_model;
int code_size;
Expand Down Expand Up @@ -202,7 +204,7 @@ static uint8_t peek_bits(struct bitreader_t* br, int count) {
return br->bits_value & mask;
}

/* assumes count <= 8, which is always true since sizes are known and don't depend on the bitstream. */
/* aka 'getbits', LSB style and assumes count <= 8, which is always true since sizes are known and don't depend on the bitstream. */
static uint8_t read_bits(struct bitreader_t* br, int count) {
uint8_t mask = mask_table[count - 1];
uint8_t ret = br->bits_value & mask;
Expand All @@ -218,7 +220,7 @@ static uint8_t read_bits(struct bitreader_t* br, int count) {
return ret;
}

/* for clarity, as found in OG code (no return) */
/* AKA 'discardbits', as found in OG code (no return) */
static void consume_bits(struct bitreader_t* br, int count) {
read_bits(br, count);
}
Expand Down Expand Up @@ -307,19 +309,19 @@ static void decode_excitation(utk_context_t* ctx, bool use_multipulse, float* ou
int bits = 0;
float val = 0.0f;

/* peek + partial consume code (odd to use 2 codes for 0.0 but seen in multiple exes) */
/* peek + partial consume code (bitreader is LSB so this is equivalent to reading bit by bit, but OG handles it like this) */
int huffman_code = peek_bits(&ctx->br, 2); /* variable-length, may consume less */
switch (huffman_code) {
case 0: //code: 0
case 2: //code: 1 (maybe meant to be -0.0?)
case 0: //value 00 = h.code: 0
case 2: //value 10 = h.code: 0
val = 0.0f;
bits = 1;
break;
case 1: //code: 01
case 1: //value 01 = h.code: 10
val = -2.0f;
bits = 2;
break;
case 3: //code: 11
case 3: //value 11 = h.code: 11
val = 2.0f;
bits = 2;
break;
Expand All @@ -334,6 +336,7 @@ static void decode_excitation(utk_context_t* ctx, bool use_multipulse, float* ou
}
}

// AKA ref_to_lpc
static void rc_to_lpc(const float* rc_data, float* lpc) {
int j;
float tmp1[12];
Expand Down Expand Up @@ -364,6 +367,7 @@ static void rc_to_lpc(const float* rc_data, float* lpc) {
}
}

// AKA 'filter'
static void lp_synthesis_filter(utk_context_t* ctx, int offset, int blocks) {
int i, j, k;
float lpc[12];
Expand Down Expand Up @@ -393,7 +397,7 @@ static void lp_synthesis_filter(utk_context_t* ctx, int offset, int blocks) {
}
}

/* OG sometimes inlines this (sx3, not B&B/CBX) */
// AKA 'interpolate', OG sometimes inlines this (sx3, not B&B/CBX) */
static void interpolate_rest(float* excitation) {
for (int i = 0; i < 108; i += 2) {
float tmp1 = (excitation[i - 5] + excitation[i + 5]) * 0.01803268f;
Expand All @@ -403,6 +407,7 @@ static void interpolate_rest(float* excitation) {
}
}

// AKA 'decodemut'
static void decode_frame_main(utk_context_t* ctx) {
bool use_multipulse = false;
float excitation[5 + 108 + 5]; /* extra +5*2 for interpolation */
Expand Down Expand Up @@ -436,7 +441,8 @@ static void decode_frame_main(utk_context_t* ctx) {
rc_delta[i] = (utk_rc_table[idx] - ctx->rc_data[i]) * 0.25f;
}

/* decode four subframes */

/* decode four subframes (AKA 'readsamples' but inline'd) */
for (int i = 0; i < 4; i++) {
int pitch_lag = read_bits(&ctx->br, 8);
int pitch_value = read_bits(&ctx->br, 4);
Expand Down
4 changes: 3 additions & 1 deletion src/coding/libs/utkdec.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* EA classifies MT as MT10:1 (smaller frames) and MT5:1 (bigger frames), but both are the same
* with different encoding parameters. Later revisions may have PCM blocks (rare). This codec was
* also reused by Traveller Tales in CBX (same devs?) with minor modifications.
* Internally it's sometimes called "UTalk" too.
*
* TODO:
* - lazy/avoid peeking/overreading when no bits left (OG code does it though, shouldn't matter)
Expand All @@ -25,9 +26,10 @@ typedef enum {
/* opaque struct */
typedef struct utk_context_t utk_context_t;

/* inits UTK (must be externally created + init here) */
/* inits UTK */
utk_context_t* utk_init(utk_type_t type);

/* frees UTK */
void utk_free(utk_context_t*);

/* reset/flush */
Expand Down
28 changes: 16 additions & 12 deletions src/meta/aifc.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,35 +82,39 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) {

/* checks */
if (!is_id32be(0x00,sf, "FORM"))
goto fail;

/* .aif: common (AIFF or AIFC), .aiff: common AIFF, .aifc: common AIFC
* .laif/laiff/laifc: for plugins
return NULL;

/* .aif: common (AIFF or AIFC)
* .wav: SimCity 3000 (Mac) (both AIFF and AIFC)
* (extensionless): Doom (3DO)
*
* .aifc: renamed AIFC?
* .afc: ?
* .cbd2: M2 games
* .bgm: Super Street Fighter II Turbo (3DO)
* .fda: Homeworld 2 (PC)
* .n64: Turok (N64) src
* .xa: SimCity 3000 (Mac)
* .caf: Topple (iOS)
*
* .aiff: renamed AIFF?
* .acm: Crusader - No Remorse (SAT)
* .adp: Sonic Jam (SAT)
* .ai: Dragon Force (SAT)
* (extensionless: Doom (3DO)
* .fda: Homeworld 2 (PC)
* .n64: Turok (N64) src
* .pcm: Road Rash (SAT)
* .wav: SimCity 3000 (Mac) (both AIFC and AIFF)
* .lwav: for media players that may confuse this format with the usual RIFF WAVE file.
* .xa: SimCity 3000 (Mac)
*/
if (check_extensions(sf, "aif,laif,wav,lwav,")) {
is_aifc_ext = 1;
is_aiff_ext = 1;
}
else if (check_extensions(sf, "aifc,laifc,afc,cbd2,bgm,fda,n64,xa")) {
else if (check_extensions(sf, "aifc,laifc,afc,cbd2,bgm,fda,n64,xa,caf")) {
is_aifc_ext = 1;
}
else if (check_extensions(sf, "aiff,laiff,acm,adp,ai,pcm")) {
is_aiff_ext = 1;
}
else {
goto fail;
return NULL;
}

file_size = get_streamfile_size(sf);
Expand Down
72 changes: 35 additions & 37 deletions src/meta/ogg_vorbis.c
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ static int _init_vgmstream_ogg_vorbis_tests(STREAMFILE* sf, ogg_vorbis_io_config
}
}

/* .um3: Ultramarine / Bruns Engine files */
/* .um3: Ultramarine / Bruns Engine files */
if (check_extensions(sf,"um3")) {
if (!is_id32be(0x00,sf, "OggS")) {
ovmi->decryption_callback = um3_ogg_decryption_callback;
Expand Down Expand Up @@ -644,69 +644,68 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start,
while (ogg_vorbis_get_comment(data, &comment)) {
;VGM_LOG("OGG: user_comment=%s\n", comment);

if (strstr(comment,"loop_start=") == comment || /* Phantasy Star Online: Blue Burst (PC) (no loop_end pair) */
strstr(comment,"LOOP_START=") == comment || /* Phantasy Star Online: Blue Burst (PC), common */
strstr(comment,"LOOPPOINT=") == comment || /* Sonic Robo Blast 2 */
strstr(comment,"COMMENT=LOOPPOINT=") == comment ||
strstr(comment,"LOOPSTART=") == comment ||
strstr(comment,"um3.stream.looppoint.start=") == comment ||
strstr(comment,"LOOP_BEGIN=") == comment || /* Hatsune Miku: Project Diva F (PS3) */
strstr(comment,"LoopStart=") == comment || /* Capcom games [Devil May Cry 4 (PC)] */
strstr(comment,"LOOP=") == comment || /* Duke Nukem 3D: 20th Anniversary World Tour */
strstr(comment,"XIPH_CUE_LOOPSTART=") == comment) { /* DeNa games [Super Mario Run (Android), FF Record Keeper (Android)] */
if ( strstr(comment,"loop_start=") == comment /* Phantasy Star Online: Blue Burst (PC) (no loop_end pair) */
|| strstr(comment,"LOOP_START=") == comment /* Phantasy Star Online: Blue Burst (PC), common */
|| strstr(comment,"LOOPPOINT=") == comment /* Sonic Robo Blast 2 (PC) */
|| strstr(comment,"COMMENT=LOOPPOINT=") == comment
|| strstr(comment,"LOOPSTART=") == comment /* common? */
|| strstr(comment,"um3.stream.looppoint.start=") == comment /* Ultramarine / Bruns Engine files */
|| strstr(comment,"LOOP_BEGIN=") == comment /* Hatsune Miku: Project Diva F (PS3) */
|| strstr(comment,"LoopStart=") == comment /* Capcom games [Devil May Cry 4 (PC)] */
|| strstr(comment,"LOOP=") == comment /* Duke Nukem 3D: 20th Anniversary World Tour */
|| strstr(comment,"XIPH_CUE_LOOPSTART=") == comment /* DeNa games [Super Mario Run (Android), FF Record Keeper (Android)] */
|| strstr(comment,"LOOPS=") == comment /* The Rumble Fish + (Switch) */
) {
loop_start = atol(strrchr(comment,'=')+1);
loop_flag = (loop_start >= 0);
}
else if (strstr(comment,"LOOPLENGTH=") == comment) {/* (LOOPSTART pair) */
else if (strstr(comment,"LOOPLENGTH=") == comment) { /* (LOOPSTART pair) */
loop_length = atol(strrchr(comment,'=')+1);
loop_length_found = 1;
}
else if (strstr(comment,"title=-lps") == comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */
else if ( strstr(comment,"LoopEnd=") == comment /* (LoopStart pair) */
|| strstr(comment,"LOOP_END=") == comment /* (LOOP_START/LOOP_BEGIN pair) */
|| strstr(comment, "XIPH_CUE_LOOPEND=") == comment /* (XIPH_CUE_LOOPSTART pair) */
|| strstr(comment, "LOOPE=") == comment /* (LOOPS pair) */
) {
loop_end = atol(strrchr(comment, '=') + 1);
loop_end_found = 1;
loop_flag = 1;
}
else if (strstr(comment,"title=-lps") == comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */
loop_start = atol(comment+10);
loop_flag = (loop_start >= 0);
}
else if (strstr(comment,"album=-lpe") == comment) { /* (title=-lps pair) */
else if (strstr(comment,"album=-lpe") == comment) { /* (title=-lps pair) */
loop_end = atol(comment+10);
loop_end_found = 1;
loop_flag = 1;
}
else if (strstr(comment,"LoopEnd=") == comment) { /* (LoopStart pair) */
loop_end = atol(strrchr(comment,'=')+1);
loop_end_found = 1;
}
else if (strstr(comment,"LOOP_END=") == comment) { /* (LOOP_START/LOOP_BEGIN pair) */
loop_end = atol(strrchr(comment,'=')+1);
loop_end_found = 1;
}
else if (strstr(comment,"lp=") == comment) {
sscanf(strrchr(comment,'=')+1,"%d,%d", &loop_start,&loop_end);
loop_end_found = 1;
loop_flag = 1;
}
else if (strstr(comment,"LOOPDEFS=") == comment) { /* Fairy Fencer F: Advent Dark Force */
else if (strstr(comment,"LOOPDEFS=") == comment) { /* Fairy Fencer F: Advent Dark Force */
sscanf(strrchr(comment,'=')+1,"%d,%d", &loop_start,&loop_end);
loop_flag = 1;
loop_end_found = 1;
}
else if (strstr(comment,"COMMENT=loop(") == comment) { /* Zero Time Dilemma (PC) */
sscanf(strrchr(comment,'(')+1,"%d,%d", &loop_start,&loop_end);
loop_flag = 1;
loop_end_found = 1;
}
else if (strstr(comment, "XIPH_CUE_LOOPEND=") == comment) { /* (XIPH_CUE_LOOPSTART pair) */
loop_end = atol(strrchr(comment, '=') + 1);
else if (strstr(comment,"COMMENT=loop(") == comment) { /* Zero Time Dilemma (PC) */
sscanf(strrchr(comment,'(')+1,"%d,%d", &loop_start,&loop_end);
loop_end_found = 1;
loop_flag = 1;
}
else if (strstr(comment, "omment=") == comment) { /* Air (Android) */
else if (strstr(comment, "omment=") == comment) { /* Air (Android) */
sscanf(strstr(comment, "=LOOPSTART=") + 11, "%d,LOOPEND=%d", &loop_start, &loop_end);
loop_flag = 1;
loop_end_found = 1;
loop_flag = 1;
}
else if (strstr(comment,"MarkerNum=0002") == comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) flag */
else if (strstr(comment,"MarkerNum=0002") == comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) flag */
/* uses LoopStart=-1 LoopEnd=-1, then 3 secuential comments: "MarkerNum" + "M=7F(start)" + "M=7F(end)" */
loop_flag = 1;
}
else if (strstr(comment,"M=7F") == comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) start/end */
else if (strstr(comment,"M=7F") == comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) start/end */
if (loop_flag && loop_start < 0) { /* LoopStart should set as -1 before */
sscanf(comment,"M=7F%x", &loop_start);
}
Expand All @@ -715,7 +714,7 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start,
loop_end_found = 1;
}
}
else if (strstr(comment,"LOOPMS=") == comment) { /* Sonic Robo Blast 2 (PC) */
else if (strstr(comment,"LOOPMS=") == comment) { /* Sonic Robo Blast 2 (PC) */
loop_start = atol(strrchr(comment,'=')+1) * sample_rate / 1000; /* ms to samples */
loop_flag = (loop_start >= 0);
}
Expand All @@ -730,8 +729,7 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start,
* loopTime nor have wrong granules though) */
force_seek = 1;
}

else if (strstr(comment,"COMMENT=*loopsample,") == comment) { /* Tsuki ni Yorisou Otome no Sahou (PC) */
else if (strstr(comment,"COMMENT=*loopsample,") == comment) { /* Tsuki ni Yorisou Otome no Sahou (PC) */
int unk0; // always 0 (delay?)
int unk1; // always -1 (loop flag? but non-looped files have no comment)
int m = sscanf(comment,"COMMENT=*loopsample,%d,%d,%d,%d", &unk0, &loop_start, &loop_end, &unk1);
Expand Down
9 changes: 5 additions & 4 deletions src/meta/riff.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,12 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {

/* checks*/
if (!is_id32be(0x00,sf,"RIFF"))
goto fail;
return NULL;

riff_size = read_u32le(0x04,sf);

if (!is_id32be(0x08,sf, "WAVE"))
goto fail;
return NULL;

file_size = get_streamfile_size(sf);

Expand Down Expand Up @@ -398,9 +398,10 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
* .mus: Burnout Legends/Dominator (PSP)
* .dat/ldat: RollerCoaster Tycoon 1/2 (PC)
* .wma/lwma: SRS: Street Racing Syndicate (Xbox), Fast and the Furious (Xbox)
* .caf: Topple (iOS)
*/
if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat,wma,lwma")) {
goto fail;
if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat,wma,lwma,caf")) {
return NULL;
}

/* some games have wonky sizes, selectively fix to catch bad rips and new mutations */
Expand Down
Loading

0 comments on commit 6c0e361

Please sign in to comment.