Merge pull request #1408 from bnnm/foo-adm2

- Add ADM2 .wem [The Grand Tour Game (PC)]
- Add looping .ogg [Tsuki ni Yorisou Otome no Sahou (PC)]
- Fix foobar .txtp to .ogg issues in rare cases
This commit is contained in:
bnnm 2023-08-19 23:43:25 +02:00 committed by GitHub
commit c361ffae0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 253 additions and 163 deletions

View File

@ -273,7 +273,7 @@ different internally (encrypted, different versions, etc) and not always can be
- 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`
- *rifx*: `.wav .lwav`
- Codecs: AICA_int PCM24LE PCM16BE PCM16LE PCM8_U MSADPCM IMA MS_IMA AICA MPEG_custom XBOX_IMA MS_IMA_3BIT DVI_IMA L5_555 OGG_VORBIS ATRAC9 ATRAC3 MPEG MSADPCM_int
- 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**
- VisualArt's NWA header (NWAINFO.INI looping) [*NWA_NWAINFOINI*]
- VisualArt's NWA header (Gameexe.ini looping) [*NWA_GAMEEXEINI*]
@ -1735,8 +1735,9 @@ different internally (encrypted, different versions, etc) and not always can be
- Eurocom ESF header [*ESF*]
- *esf*: `.esf`
- Codecs: DVI_IMA PCM8_U PCM16LE
- **adm3.c**
- Crankcase ADM3 header [*ADM3*]
- **adm.c**
- Crankcase ADMx header [*ADM*]
- *adm2*: `.wem`
- *adm3*: `.wem`
- Codecs: APPLE_IMA4
- **tt_ad.c**

View File

@ -7,6 +7,7 @@
#endif
#include <stdio.h>
#include <io.h>
#include <locale.h>
#include <foobar2000/SDK/foobar2000.h>
@ -351,12 +352,22 @@ bool input_vgmstream::g_is_our_path(const char * p_path, const char * p_extensio
VGMSTREAM* input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
VGMSTREAM* vgmstream = NULL;
/* Workaround for a foobar bug (mainly for complex TXTP):
* When converting to .ogg foobar calls oggenc, that calls setlocale(LC_ALL, "") to use system's locale.
* After that, text parsing using sscanf that expects US locale for "N.N" decimals fails in some locales,
* so reset it here just in case
* (maybe should be done on lib and/or restore original locale but it's not common to change it in C) */
//const char* original_locale = setlocale(LC_ALL, NULL);
setlocale(LC_ALL, "C");
STREAMFILE* sf = open_foo_streamfile(filename, &p_abort, &stats);
if (sf) {
sf->stream_index = p_subsong;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
}
//setlocale(LC_ALL, original_locale);
return vgmstream;
}

View File

@ -1402,7 +1402,7 @@ static const meta_info meta_info_list[] = {
{meta_SSPF, "Konami SSPF header"},
{meta_S3V, "Konami S3V header"},
{meta_ESF, "Eurocom ESF header"},
{meta_ADM3, "Crankcase ADM3 header"},
{meta_ADM, "Crankcase ADMx header"},
{meta_TT_AD, "Traveller's Tales AUDIO_DATA header"},
{meta_SNDZ, "Sony SNDZ header"},
{meta_VAB, "Sony VAB header"},

View File

@ -337,7 +337,7 @@
<ClCompile Include="meta\acb.c" />
<ClCompile Include="meta\acm.c" />
<ClCompile Include="meta\acx.c" />
<ClCompile Include="meta\adm3.c" />
<ClCompile Include="meta\adm.c" />
<ClCompile Include="meta\adpcm_capcom.c" />
<ClCompile Include="meta\adp_konami.c" />
<ClCompile Include="meta\adp_qd.c" />

View File

@ -832,7 +832,7 @@
<ClCompile Include="meta\acx.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\adm3.c">
<ClCompile Include="meta\adm.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\adpcm_capcom.c">

219
src/meta/adm.c Normal file
View File

@ -0,0 +1,219 @@
#include "meta.h"
#include "../coding/coding.h"
typedef struct {
int total_subsongs;
int target_subsong;
int version;
uint32_t stream_offset;
uint32_t stream_size;
int loop_flag;
int sample_rate;
int channels;
int32_t num_samples;
} adm_header_t;
static int parse_adm(adm_header_t* adm, STREAMFILE* sf);
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version);
/* ADM2 - Crankcase Audio REV plugin file [The Grand Tour Game (PC)] */
VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "ADM2"))
return NULL;
if (!check_extensions(sf, "wem"))
return NULL;
return init_vgmstream_adm(sf, 2);
}
/* ADM3 - Crankcase Audio REV plugin file [Cyberpunk 2077 (PC), MotoGP 21 (PC)] */
VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "ADM3"))
return NULL;
if (!check_extensions(sf, "wem"))
return NULL;
return init_vgmstream_adm(sf, 3);
}
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) {
VGMSTREAM* vgmstream = NULL;
adm_header_t adm = {0};
/* ADMx are files used with the Wwise Crankaudio plugin, that simulate engine noises with
* base internal samples and some internal RPM config (probably). Actual file seems to
* define some combo of samples, this only plays those separate samples.
* Decoder is basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float
* each sample during decode by multiplying by 0.000030518509 */
adm.target_subsong = sf->stream_index;
if (adm.target_subsong == 0) adm.target_subsong = 1;
adm.version = version;
if (!parse_adm(&adm, sf))
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(adm.channels, adm.loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_ADM;
vgmstream->sample_rate = adm.sample_rate;
vgmstream->num_samples = adm.num_samples; /* slightly lower than bytes-to-samples */
vgmstream->num_streams = adm.total_subsongs;
vgmstream->stream_size = adm.stream_size;
vgmstream->coding_type = coding_APPLE_IMA4;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x22;
if (!vgmstream_open_stream(vgmstream, sf, adm.stream_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
static int parse_type(adm_header_t* adm, STREAMFILE* sf, uint32_t offset) {
/* ADM2 chunks */
if (is_id32be(offset, sf, "GRN1")) {
/* 0x74: offset to floats? */
offset = read_u32le(offset + 0x78, sf); /* to SMP1 */
if (!parse_type(adm, sf, offset))
goto fail;
}
else if (is_id32be(offset, sf, "SMP1")) {
adm->total_subsongs++;
if (adm->target_subsong == adm->total_subsongs) {
/* 0x04 always 0 */
/* 0x08 version? (0x00030000) */
adm->channels = read_u16le(offset + 0x0c, sf);
/* 0x0e 0x0001? */
/* 0x10 header size (0x2c) */
adm->sample_rate = read_s32le(offset + 0x14, sf);
adm->num_samples = read_s32le(offset + 0x18, sf);
adm->stream_size = read_u32le(offset + 0x1c, sf);
adm->stream_offset = read_u32le(offset + 0x20, sf);
/* rest: null */
VGM_LOG("so=%x %x\n", adm->stream_size, adm->stream_offset);
}
}
/* ADM3 chunks */
else if (is_id32be(offset, sf, "RMP1")) {
offset = read_u32le(offset + 0x1c, sf);
if (!parse_type(adm, sf, offset))
goto fail;
/* 0x24: offset to GRN1 */
}
else if (is_id32be(offset, sf, "SMB1")) {
uint32_t table_count = read_u32le(offset + 0x10, sf);
uint32_t table_offset = read_u32le(offset + 0x18, sf);
int i;
for (i = 0; i < table_count; i++) {
uint32_t smp2_unk = read_u32le(table_offset + i * 0x08 + 0x00, sf);
uint32_t smp2_offset = read_u32le(table_offset + i * 0x08 + 0x04, sf);
if (smp2_unk != 1)
goto fail;
if (!parse_type(adm, sf, smp2_offset)) /* SMP2 */
goto fail;
}
}
else if (is_id32be(offset, sf, "SMP2")) {
adm->total_subsongs++;
if (adm->target_subsong == adm->total_subsongs) {
/* 0x04 always 0 */
/* 0x08 version? (0x00040000) */
adm->channels = read_u32le(offset + 0x0c, sf); /* usually 4, with different sounds*/
/* 0x10 float pitch? */
/* 0x14 int pitch? */
/* 0x18 0x0001? */
/* 0x1a header size (0x30) */
adm->sample_rate = read_s32le(offset + 0x1c, sf);
adm->num_samples = read_s32le(offset + 0x20, sf);
adm->stream_size = read_u32le(offset + 0x24, sf);
/* 0x28 1? */
adm->stream_offset = read_u32le(offset + 0x2c, sf);
}
}
else {
VGM_LOG("ADM: unknown at %x\n", offset);
goto fail;
}
return 1;
fail:
return 0;
}
static int parse_adm(adm_header_t* adm, STREAMFILE* sf) {
uint32_t offset;
/* 0x04: null */
/* 0x08: version? (ADM2: 0x00050000, ADM3: 0x00060000) */
/* 0x0c: header size */
/* 0x10: data start */
/* rest unknown, looks mostly the same between files (some floats and stuff) */
switch(adm->version) {
case 2:
/* low to high */
offset = read_u32le(0x104, sf);
if (!parse_type(adm, sf, offset)) goto fail; /* GRN1 */
/* high to low */
offset = read_u32le(0x108, sf);
if (!parse_type(adm, sf, offset)) goto fail; /* GRN1 */
/* idle engine */
offset = read_u32le(0x10c, sf);
if (!parse_type(adm, sf, offset)) goto fail; /* SMP1 */
break;
case 3:
/* higher ramp, N samples from low to high */
offset = read_u32le(0x0FC, sf);
if (!parse_type(adm, sf, offset)) goto fail; /* RMP1 */
if (read_u32le(0x100, sf) != 1) goto fail;
/* lower ramp, also N samples */
offset = read_u32le(0x104, sf);
if (!parse_type(adm, sf, offset)) goto fail; /* RMP1 */
if (read_u32le(0x108, sf) != 1) goto fail;
/* idle engine */
offset = read_u32le(0x10c, sf);
if (!parse_type(adm, sf, offset)) goto fail; /* SMP2 */
if (read_u32le(0x110, sf) != 1) goto fail;
break;
default:
goto fail;
}
if (adm->target_subsong < 0 || adm->target_subsong > adm->total_subsongs || adm->total_subsongs < 1)
goto fail;
return 1;
fail:
return 0;
}

View File

@ -1,152 +0,0 @@
#include "meta.h"
#include "../coding/coding.h"
typedef struct {
int total_subsongs;
int target_subsong;
uint32_t stream_offset;
uint32_t stream_size;
int loop_flag;
int sample_rate;
int channels;
int32_t num_samples;
} adm3_header_t;
static int parse_adm3(adm3_header_t* adm3, STREAMFILE* sf);
/* ADM3 - Crankcase Audio REV plugin file [Cyberpunk 2077 (PC), MotoGP 21 (PC)] */
VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
adm3_header_t adm3 = {0};
/* checks */
if (!is_id32be(0x00,sf, "ADM3"))
goto fail;
if (!check_extensions(sf, "wem"))
goto fail;
adm3.target_subsong = sf->stream_index;
if (adm3.target_subsong == 0) adm3.target_subsong = 1;
/* ADM3 are files used with the Wwise Crankaudio plugin, that simulate engine noises with
* base internal samples and some internal RPM config (probably). Actual file seems to
* define some combo of samples, this only plays those separate samples.
* Decoder is basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float
* each sample during decode by multiplying by 0.000030518509 */
if (!parse_adm3(&adm3, sf))
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(adm3.channels, adm3.loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_ADM3;
vgmstream->sample_rate = adm3.sample_rate;
vgmstream->num_samples = adm3.num_samples; /* slightly lower than bytes-to-samples */
vgmstream->num_streams = adm3.total_subsongs;
vgmstream->stream_size = adm3.stream_size;
vgmstream->coding_type = coding_APPLE_IMA4;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x22;
if (!vgmstream_open_stream(vgmstream, sf, adm3.stream_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
static int parse_type(adm3_header_t* adm3, STREAMFILE* sf, uint32_t offset) {
if (is_id32be(offset, sf, "RMP1")) {
offset = read_u32le(offset + 0x1c, sf);
if (!parse_type(adm3, sf, offset))
goto fail;
/* 0x24: offset to GRN1 */
}
else if (is_id32be(offset, sf, "SMB1")) {
uint32_t table_count = read_u32le(offset + 0x10, sf);
uint32_t table_offset = read_u32le(offset + 0x18, sf);
int i;
for (i = 0; i < table_count; i++) {
uint32_t smp2_unk = read_u32le(table_offset + i * 0x08 + 0x00, sf);
uint32_t smp2_offset = read_u32le(table_offset + i * 0x08 + 0x04, sf);
if (smp2_unk != 1)
goto fail;
if (!parse_type(adm3, sf, smp2_offset)) /* SMP2 */
goto fail;
}
}
else if (is_id32be(offset, sf, "SMP2")) {
adm3->total_subsongs++;
if (adm3->target_subsong == adm3->total_subsongs) {
/* 0x04 always 0 */
/* 0x08 always 0x00040000 */
adm3->channels = read_u32le(offset + 0x0c, sf);
/* 0x10 float pitch? */
/* 0x14 int pitch? */
/* 0x18 0x0001? */
/* 0x1a 0x0030? (header size?) */
adm3->sample_rate = read_s32le(offset + 0x1c, sf);
adm3->num_samples = read_s32le(offset + 0x20, sf);
adm3->stream_size = read_u32le(offset + 0x24, sf);
/* 0x28 1? */
adm3->stream_offset = read_u32le(offset + 0x2c, sf);
}
}
else {
VGM_LOG("ADM3: unknown at %x\n", offset);
goto fail;
}
return 1;
fail:
return 0;
}
static int parse_adm3(adm3_header_t* adm3, STREAMFILE* sf) {
uint32_t offset;
/* 0x04: null */
/* 0x08: version? */
/* 0x0c: header size */
/* 0x10: data start */
/* rest unknown, looks mostly the same between files */
/* higher ramp, N samples from low to high */
offset = read_u32le(0x0FC, sf);
if (!parse_type(adm3, sf, offset)) goto fail; /* RMP1 */
if (read_u32le(0x100, sf) != 1) goto fail;
/* lower ramp, also N samples */
offset = read_u32le(0x104, sf);
if (!parse_type(adm3, sf, offset)) goto fail; /* RMP1 */
if (read_u32le(0x108, sf) != 1) goto fail;
/* idle engine */
offset = read_u32le(0x10c, sf);
if (!parse_type(adm3, sf, offset)) goto fail; /* SMP2 */
if (read_u32le(0x110, sf) != 1) goto fail;
if (adm3->target_subsong < 0 || adm3->target_subsong > adm3->total_subsongs || adm3->total_subsongs < 1)
goto fail;
return 1;
fail:
return 0;
}

View File

@ -953,6 +953,7 @@ VGMSTREAM* init_vgmstream_s3v(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_esf(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_tt_ad(STREAMFILE* sf);

View File

@ -614,6 +614,7 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start,
const char* comment = NULL;
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 */
@ -702,18 +703,26 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start,
force_seek = 1;
}
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);
if (m == 4) {
loop_flag = 1;
loop_end_found = 1;
}
}
/* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files
* ENCODER tag is common but ogg_vorbis_encode looks unique enough
* (arcade ends with "2010-11-26" while consoles have "2011-02-07" */
if (strstr(comment, "ENCODER=ogg_vorbis_encode/") == comment) {
else if (strstr(comment, "ENCODER=ogg_vorbis_encode/") == comment) {
disable_reordering = 1;
}
if (strstr(comment, "TITLE=") == comment) {
else if (strstr(comment, "TITLE=") == comment) {
strncpy(name, comment + 6, sizeof(name) - 1);
}
;VGM_LOG("OGG: user_comment=%s\n", comment);
}
}

View File

@ -522,6 +522,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_squeakstream,
init_vgmstream_squeaksample,
init_vgmstream_snds,
init_vgmstream_adm2,
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
init_vgmstream_scd_pcm,

View File

@ -691,7 +691,7 @@ typedef enum {
meta_SSPF,
meta_S3V,
meta_ESF,
meta_ADM3,
meta_ADM,
meta_TT_AD,
meta_SNDZ,
meta_VAB,