Merge pull request #791 from bnnm/nxa-ifs-etc

nxa ifs etc
This commit is contained in:
bnnm 2020-12-20 16:13:14 +01:00 committed by GitHub
commit 46467c974f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 745 additions and 314 deletions

View File

@ -112,12 +112,12 @@ automatically. You need to manually refresh it by selecting songs and doing
**shift + right click > Tagging > Reload info from file(s)**.
### Audacious plugin
*Installation*: needs to be manually built. Instructions can be found in the BUILD
document in vgmstream's source code.
*Installation*: needs to be manually built. Instructions can be found in doc/BUILD.md
document in vgmstream's source code (can be done with CMake or autotools).
### vgmstream123
*Installation*: needs to be manually built. Instructions can be found in the BUILD
document in vgmstream's source code.
*Installation*: needs to be manually built. Instructions can be found in doc/BUILD.md
document in vgmstream's source code (can be done with CMake or autotools).
Usage: `vgmstream123 [options] INFILE ...`
@ -156,7 +156,7 @@ multiple .txtp (explained below) to select one of the subsongs (like `bgm.sxd#10
You can use this python script to autogenerate one `.txtp` per subsong:
https://github.com/losnoco/vgmstream/tree/master/cli/txtp_maker.py
Put in the same dir as test.exe/vgmstream_cli, then to drag-and-drop files with
subsongs to `txtp_maker.py`.
subsongs to `txtp_maker.py` (it has CLI options to control output too).
### Renamed files
A few extensions that vgmstream supports clash with common ones. Since players
@ -189,8 +189,8 @@ internal loop info, or apply subtle fixes, but is also limited in some ways
may work as a last resort to make a file playable.
Some plugins have options that allow any extension (common or unknown) to be
played, making renaming unnecessary (may need to adjust plugin priority in
player's options).
played, making renaming unnecessary. You may need to adjust plugin priority in
player's options first.
Also be aware that some plugins can tell the player they handle some extension,
then not actually play it. This makes the file unplayable as vgmstream doesn't
@ -223,7 +223,7 @@ on the internet.
### Companion files
Some formats have companion files with external info, that should be left together:
- `.mus`: playlist for `.acm`
- `.mus`: playlist with `.acm`
- `.ogg.sli` or `.sli`: loop info for `.ogg`
- `.ogg.sfl` : loop info for `.ogg`
- `.opus.sli`: loop info for `.opus`
@ -250,12 +250,12 @@ Similarly some formats split header+body data in separate files, examples:
- `.wav`+`.dcs`
- `.wbh`+`.wbd`
Both are needed to play and must be together. The usual rule is you open the
bigger file (body), save a few formats where the smaller file is opened instead
for technical reasons (mainly some bank formats).
bigger file (body), save a few formats where the smaller (header) file is opened
instead for technical reasons (mainly some bank formats).
Generally companion files are named the same (`bgm.awb`+`bgm.acb`), or internally
point to another file `sfx.sb0`+`STREAM.sb0`. A few formats may have different names
which are hardcoded instead of being listed in the main file (e.g. `.mpf+.mus`).
which are hardcoded instead of being listed in the header file (e.g. `.mpf+.mus`).
In these cases, you can use *TXTM* format to specify associated companion files.
See *Artificial files* below for more information.
@ -304,7 +304,7 @@ a companion file:
- `.ahx`: `.ahxkey` (derived 6 byte start/mult/add key)
- `.hca`: `.hcakey` (8 byte decryption key, a 64-bit number)
- May be followed by 2 byte AWB scramble key for newer HCA
- `.fsb`: `.fsbkey` (decryption key, in hex)
- `.fsb`: `.fsbkey` (decryption key in hex, usually between 8-32 bytes)
- `.bnsf`: `.bnsfkey` (decryption key, a string up to 24 chars)
The key file can be `.(ext)key` (for the whole folder), or `(name).(ext)key"
@ -396,8 +396,10 @@ a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`.
Some of vgmstream's plugins support simple read-only tagging via external files.
Tags are loaded from a text/M3U-like file named *!tags.m3u* in the song folder.
You don't have to load your songs with that M3U though (but you can, for pre-made
ordering), the file itself just 'looks' like an M3U.
You don't have to load your songs with this M3U though, but you can (for pre-made
order). The format is meant to be both a quick playlist and tags, but the tagfile
itself just 'looks' like an M3U. you can load files manually or using other playlists
and still get tags.
Format is:
```
@ -416,14 +418,16 @@ or uppercase, separated by one or multiple spaces. Repeated tags overwrite previ
(ex.- may define *@COMPOSER* multiple times for "sections"). It only reads up to
current *filename* though, so any *@TAG* below would be ignored.
Playlist title formatting should follow player's config. ASCII or UTF-8 tags work.
*GLOBAL_COMMAND*s currently can be:
- *AUTOTRACK*: sets *%TRACK* tag automatically (1..N as files are encountered
in the tag file).
- *AUTOALBUM*: sets *%ALBUM* tag automatically using the containing dir as album.
- *EXACTMATCH*: disables matching .txtp with regular files (explained below).
Playlist title formatting (how tags are shown) should follow player's config, as
vgmstream simply passes tags to the player. It's better to name the file lowercase
`!tags.m3u` rather than `!Tags.m3u` (Windows accepts both but Linux is case sensitive).
Note that with global tags you don't need to put all files inside. This would be
a perfectly valid *!tags.m3u*:
```
@ -431,6 +435,21 @@ a perfectly valid *!tags.m3u*:
# @ARTIST Various Artists
```
### Non-English filenames and tags
Tags and filenames using extended characters (like Japanese) should work, as long
as `!tags.m3u` is saved as *"UTF-8 with BOM"* (UTF-8 is a way to define non-English
characters, and BOM is a helper "byte-order" mark). Windows' *notepad* creates files
*"with BOM"* when selecting UTF-8 encoding in *save as* dialog, or you may use other
programs like *notepad++.exe* to convert them.
More exactly, vgmstream matches filenames and reads tags assuming they are in UTF-8,
while foobar/winamp can only read UTF-8 Japanese/extended filenames in a `.m3u` if file
is saved *with BOM* (opening files manually or with a `playlist.m3u8` won't need BOM).
Other players may not need BOM (or CRLF), but for consistency use them when dealing
with non-ASCII names and tags.
### Tags with spaces
Some players like foobar accept tags with spaces. To use them surround the tag
with both characters.

View File

@ -548,6 +548,8 @@ typedef struct {
/* frame table */
off_t table_offset;
int table_count;
/* fixed frames */
uint16_t frame_size;
} opus_config;
ffmpeg_codec_data* init_ffmpeg_switch_opus_config(STREAMFILE* sf, off_t start_offset, size_t data_size, opus_config* cfg);
@ -557,6 +559,7 @@ ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_
ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg);
ffmpeg_codec_data* init_ffmpeg_fixed_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg);
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf);

View File

@ -17,7 +17,7 @@
* https://github.com/hcs64/ww2ogg
*/
typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X, OPUS_FSB, OPUS_WWISE } opus_type_t;
typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X, OPUS_FSB, OPUS_WWISE, OPUS_FIXED } opus_type_t;
static size_t make_oggs_first(uint8_t *buf, int buf_size, opus_config *cfg);
static size_t make_oggs_page(uint8_t *buf, int buf_size, size_t data_size, int page_sequence, int granule);
@ -36,6 +36,9 @@ typedef struct {
int table_count;
uint16_t* frame_table;
/* fixed frame size for variations that use this */
uint16_t frame_size;
/* state */
off_t logical_offset; /* offset that corresponds to physical_offset */
off_t physical_offset; /* actual file offset */
@ -130,6 +133,10 @@ static size_t opus_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t l
data_size = get_table_frame_size(data, data->sequence - 2);
skip_size = 0;
break;
case OPUS_FIXED:
data_size = data->frame_size;
skip_size = 0;
break;
default:
return 0;
}
@ -231,6 +238,10 @@ static size_t opus_io_size(STREAMFILE* sf, opus_io_data* data) {
data_size = get_table_frame_size(data, packet);
skip_size = 0x00;
break;
case OPUS_FIXED:
data_size = data->frame_size;
skip_size = 0;
break;
default:
return 0;
}
@ -295,7 +306,7 @@ static void opus_io_close(STREAMFILE* sf, opus_io_data* data) {
/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */
static STREAMFILE* setup_opus_streamfile(STREAMFILE* sf, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
static STREAMFILE* setup_opus_streamfile(STREAMFILE* sf, opus_config* cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
STREAMFILE* new_sf = NULL;
opus_io_data io_data = {0};
@ -308,6 +319,7 @@ static STREAMFILE* setup_opus_streamfile(STREAMFILE* sf, opus_config *cfg, off_t
io_data.physical_offset = stream_offset;
io_data.table_offset = cfg->table_offset;
io_data.table_count = cfg->table_count;
io_data.frame_size = cfg->frame_size;
io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg);
if (!io_data.head_size) goto fail;
@ -612,12 +624,16 @@ static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFI
skip_size = 0x02;
break;
#if 0 // needs data* for frame table, but num_samples should exist on header
#if 0 //needs data*, num_samples should exist on header
case OPUS_X:
case OPUS_WWISE:
data_size = get_table_frame_size(data, packet);
skip_size = 0x00;
break;
case OPUS_FIXED:
data_size = data->frame_size;
skip_size = 0;
break;
#endif
default:
return 0;
@ -661,6 +677,11 @@ static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE* sf, opus_t
case OPUS_WWISE:
skip_size = 0x00;
break;
#if 0 //should exist on header
case OPUS_FIXED:
skip_size = 0x00;
break;
#endif
default:
return 0;
}
@ -756,6 +777,9 @@ ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) {
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_WWISE);
}
ffmpeg_codec_data* init_ffmpeg_fixed_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) {
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_FIXED);
}
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) {
int read_samples, calc_samples;

View File

@ -97,23 +97,23 @@ static int ealayer3_is_empty_frame_v2p(STREAMFILE* sf, off_t offset);
/* **************************************************************************** */
/* init codec from an EALayer3 frame */
int mpeg_custom_setup_init_ealayer3(STREAMFILE* streamfile, off_t start_offset, mpeg_codec_data* data, coding_t *coding_type) {
int mpeg_custom_setup_init_ealayer3(STREAMFILE* sf, off_t start_offset, mpeg_codec_data* data, coding_t *coding_type) {
int ok;
ealayer3_buffer_t ib = {0};
ealayer3_frame_t eaf;
//;VGM_LOG("init at %lx\n", start_offset);
//;VGM_LOG("init at %lx, %x\n", start_offset, read_u32be(start_offset, sf));
/* get first frame for info */
{
ib.sf = streamfile;
ib.sf = sf;
ib.offset = start_offset;
ib.is.buf = ib.buf;
ok = ealayer3_parse_frame(data, -1, &ib, &eaf);
if (!ok) goto fail;
}
;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
*coding_type = coding_MPEG_ealayer3;
data->channels_per_frame = eaf.channels;
@ -385,11 +385,16 @@ static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf)
ok = ealayer3_parse_frame_common(ib, eaf);
if (!ok) goto fail;
}
else {
/* rarely frames contain PCM data only [FIFA 2014 World Cup Brazil (PS3)] */
eaf->channels = eaf->v2_stereo_flag + 1;
}
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples);
//VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples);
eaf->pcm_size = (2*eaf->v2_pcm_samples * eaf->channels);
eaf->pcm_size = (eaf->v2_pcm_samples * sizeof(int16_t) * eaf->channels);
eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size;
@ -408,14 +413,14 @@ fail:
/* parses an EALayer3 frame (common part) */
static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) {
/* index tables */
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
static const int sample_rates[4][4] = { /* [version_index][sample rate index] */
static const int version_table[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
static const int sample_rate_table[4][4] = { /* [version_index][sample rate index] */
{ 11025, 12000, 8000, -1}, /* MPEG2.5 */
{ -1, -1, -1, -1}, /* reserved */
{ 22050, 24000, 16000, -1}, /* MPEG2 */
{ 44100, 48000, 32000, -1}, /* MPEG1 */
};
static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */
static const int channel_table[4] = { 2,2,2, 1 }; /* [channel_mode] */
bitstream_t* is = &ib->is;
off_t start_b_off = is->b_off;
@ -441,9 +446,9 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t*
/* derived */
eaf->version = versions[eaf->version_index];
eaf->channels = channels[eaf->channel_mode];
eaf->sample_rate = sample_rates[eaf->version_index][eaf->sample_rate_index];
eaf->version = version_table[eaf->version_index];
eaf->channels = channel_table[eaf->channel_mode];
eaf->sample_rate = sample_rate_table[eaf->version_index][eaf->sample_rate_index];
eaf->mpeg1 = (eaf->version == 1);
if (eaf->version == -1 || eaf->sample_rate == -1) {

View File

@ -221,6 +221,7 @@ static const char* extension_list[] = {
"idwav",
"idx",
"idxma",
"ifs",
"ikm",
"ild",
"ilv", //txth/reserved [Star Wars Episode III (PS2)]
@ -341,7 +342,8 @@ static const char* extension_list[] = {
"n64",
"naac",
"ndp",
"nds",
"ndp", //fake extension/header id for .nds
"ngca",
"nlsd",
"nop",
@ -1321,6 +1323,8 @@ static const meta_info meta_info_list[] = {
{meta_DSP_SQEX, "Square Enix DSP header"},
{meta_DSP_WIIVOICE, "Koei Tecmo WiiVoice header"},
{meta_SBK, "Team17 SBK header"},
{meta_DSP_WIIADPCM, "Exient WIIADPCM header"},
{meta_DSP_CWAC, "CRI CWAC header"},
};
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {

View File

@ -1282,10 +1282,14 @@
RelativePath=".\meta\ps2_iab.c"
>
</File>
<File
RelativePath=".\meta\ikm.c"
>
</File>
<File
RelativePath=".\meta\ifs.c"
>
</File>
<File
RelativePath=".\meta\ikm.c"
>
</File>
<File
RelativePath=".\meta\ps2_ild.c"
>

View File

@ -441,6 +441,7 @@
<ClCompile Include="meta\ps2_gbts.c" />
<ClCompile Include="meta\ps2_gcm.c" />
<ClCompile Include="meta\ps2_hgc1.c" />
<ClCompile Include="meta\ifs.c" />
<ClCompile Include="meta\ikm.c" />
<ClCompile Include="meta\ps2_ild.c" />
<ClCompile Include="meta\raw_int.c" />

View File

@ -826,6 +826,9 @@
<ClCompile Include="meta\ps2_hgc1.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ifs.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ikm.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -1,7 +1,7 @@
#include "meta.h"
#include "../coding/coding.h"
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP } awb_type;
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC } awb_type;
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
@ -116,6 +116,10 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
type = DSP;
extension = "dsp";
}
else if (is_id32be(subfile_offset,sf, "CWAC")) { /* type 13 again */
type = CWAC;
extension = "dsp";
}
else {
VGM_LOG("AWB: unknown codec\n");
goto fail;
@ -150,6 +154,10 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
vgmstream = init_vgmstream_ngc_dsp_std(temp_sf);
if (!vgmstream) goto fail;
break;
case CWAC: /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */
vgmstream = init_vgmstream_dsp_cwac(temp_sf);
if (!vgmstream) goto fail;
break;
default:
goto fail;
}

View File

@ -19,12 +19,14 @@ typedef struct {
off_t stream_offset;
size_t stream_size;
off_t vorbis_offset;
off_t vorbis_offset[VGMSTREAM_MAX_CHANNELS];
} awc_header;
static int parse_awc_header(STREAMFILE* sf, awc_header* awc);
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc);
/* AWC - from RAGE (Rockstar Advanced Game Engine) audio [Red Dead Redemption, Max Payne 3, GTA5 (multi)] */
VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
@ -154,20 +156,26 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
break;
}
#endif
#ifdef VGM_USE_MPEG
#ifdef VGM_USE_VORBIS
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
vorbis_custom_config cfg = {0};
if (awc.is_music) {
vgmstream->layout_data = build_layered_awc(sf, &awc);
if (!vgmstream->layout_data) goto fail;
vgmstream->layout_type = layout_layered;
vgmstream->coding_type = coding_FFmpeg;
}
else {
vorbis_custom_config cfg = {0};
if (awc.is_music) goto fail; /* needs blocks */
cfg.channels = awc.channels;
cfg.sample_rate = awc.sample_rate;
cfg.header_offset = awc.vorbis_offset[0];
cfg.channels = awc.channels;
cfg.sample_rate = awc.sample_rate;
cfg.header_offset = awc.vorbis_offset;
vgmstream->codec_data = init_vorbis_custom(sf, awc.stream_offset, VORBIS_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
vgmstream->coding_type = coding_VORBIS_custom;
vgmstream->codec_data = init_vorbis_custom(sf, awc.stream_offset, VORBIS_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
vgmstream->coding_type = coding_VORBIS_custom;
}
break;
}
#endif
@ -195,7 +203,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
int i, ch, entries;
uint32_t flags, info_header, tag_count = 0, tags_skip = 0;
off_t off;
off_t offset;
int target_subsong = sf->stream_index;
@ -220,7 +228,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
entries = read_u32(0x08,sf);
//header_size = read_u32(0x0c,sf); /* after to stream id/tags, not including chunks */
off = 0x10;
offset = 0x10;
if ((flags & 0xFF00FFFF) != 0xFF000001 || (flags & 0x00F00000)) {
VGM_LOG("AWC: unknown flags 0x%08x\n", flags);
@ -228,7 +236,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
}
if (flags & 0x00010000) /* some kind of mini offset table */
off += 0x2 * entries;
offset += 0x2 * entries;
//if (flags % 0x00020000) /* seems to indicate chunks are not ordered (ie. header may go after data) */
// ...
//if (flags % 0x00040000) /* music/multichannel flag? (GTA5, not seen in RDR) */
@ -244,7 +252,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
/* Music when the first id is 0 (base/fake entry with info for all channels), sfx pack otherwise.
* sfx = N single streams, music = N-1 interleaved mono channels (even for MP3/XMA).
* Music seems layered (N-1/2 stereo pairs), maybe set with events? */
awc->is_music = (read_u32(off + 0x00,sf) & 0x1FFFFFFF) == 0x00000000;
awc->is_music = (read_u32(offset + 0x00,sf) & 0x1FFFFFFF) == 0x00000000;
if (awc->is_music) { /* all streams except id 0 is a channel */
awc->total_subsongs = 1;
target_subsong = 1; /* we only need id 0, though channels may have its own tags/chunks */
@ -258,46 +266,47 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
/* get stream base info */
for (i = 0; i < entries; i++) {
info_header = read_u32(off + 0x04*i, sf);
info_header = read_u32(offset + 0x04*i, sf);
tag_count = (info_header >> 29) & 0x7; /* 3b */
//id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
if (target_subsong-1 == i)
break;
tags_skip += tag_count; /* tags to skip to reach target's tags, in the next header */
}
off += 0x04*entries;
off += 0x08*tags_skip;
offset += 0x04*entries;
offset += 0x08*tags_skip;
/* get stream tags */
for (i = 0; i < tag_count; i++) {
uint64_t tag_header;
uint8_t tag;
size_t size;
off_t offset;
uint8_t tag_type;
size_t tag_size;
off_t tag_offset;
tag_header = read_u64(off + 0x08*i,sf);
tag = (uint8_t)((tag_header >> 56) & 0xFF); /* 8b */
size = (size_t)((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
offset = (off_t)((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
//;VGM_LOG("AWC: tag%i/%i at %lx: t=%x, o=%lx, s=%x\n", i, tag_count, off + 0x08*i, tag, offset, size);
tag_header = read_u64(offset + 0x08*i,sf);
tag_type = (uint8_t)((tag_header >> 56) & 0xFF); /* 8b */
tag_size = (size_t)((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
tag_offset = (off_t)((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
;VGM_LOG("AWC: tag%i/%i at %lx: t=%x, o=%lx, s=%x\n", i, tag_count, offset + 0x08*i, tag_type, tag_offset, tag_size);
/* Tags are apparently part of a hash derived from a word ("data", "format", etc).
* If music + 1ch, the header and data chunks can repeat for no reason (sometimes not even pointed). */
switch(tag) {
switch(tag_type) {
case 0x55: /* data */
awc->stream_offset = offset;
awc->stream_size = size;
awc->stream_offset = tag_offset;
awc->stream_size = tag_size;
break;
case 0x48: /* music header */
if (!awc->is_music) {
VGM_LOG("AWC: music header found in sfx\n");
goto fail;
}
/* 0x00(32): unknown (some count?) */
awc->block_chunk = read_u32(offset + 0x04,sf);
awc->channels = read_u32(offset + 0x08,sf);
awc->block_chunk = read_u32(tag_offset + 0x04,sf);
awc->channels = read_u32(tag_offset + 0x08,sf);
if (awc->channels != entries - 1) { /* not counting id-0 */
VGM_LOG("AWC: number of music channels doesn't match entries\n");
@ -307,10 +316,10 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
for (ch = 0; ch < awc->channels; ch++) {
int num_samples, sample_rate, codec;
/* 0x00): stream id (not always in the header entries order) */
num_samples = read_u32(offset + 0x0c + 0x10*ch + 0x04,sf);
num_samples = read_u32(tag_offset + 0x0c + 0x10*ch + 0x04,sf);
/* 0x08: headroom */
sample_rate = read_u16(offset + 0x0c + 0x10*ch + 0x0a,sf);
codec = read_u8(offset + 0x0c + 0x10*ch + 0x0c,sf);
sample_rate = read_u16(tag_offset + 0x0c + 0x10*ch + 0x0a,sf);
codec = read_u8(tag_offset + 0x0c + 0x10*ch + 0x0c,sf);
/* 0x0d(8): round size? */
/* 0x0e: unknown (zero/-1) */
@ -334,11 +343,6 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
awc->codec = codec;
}
if (awc->codec == 0x08) {
/* one vorbis header per channel, pasted together */
awc->vorbis_offset = offset + size;
}
break;
case 0xFA: /* sfx header */
@ -347,12 +351,12 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
goto fail;
}
awc->num_samples = read_u32(offset + 0x00,sf);
awc->num_samples = read_u32(tag_offset + 0x00,sf);
/* 0x04: -1? */
awc->sample_rate = read_u16(offset + 0x08,sf);
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
/* 0x0a: unknown x4 */
/* 0x12: null? */
awc->codec = read_u8(offset + 0x13, sf);
awc->codec = read_u8(tag_offset + 0x13, sf);
awc->channels = 1;
break;
@ -362,17 +366,17 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
goto fail;
}
awc->num_samples = read_u32(offset + 0x00,sf);
awc->num_samples = read_u32(tag_offset + 0x00,sf);
/* 0x04: -1? */
awc->sample_rate = read_u16(offset + 0x08,sf);
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
/* 0x0a: granule start? (negative) */
/* 0x0c: granule max? */
/* 0x10: unknown */
awc->codec = read_u8(offset + 0x1c, sf); /* 16b? */
awc->codec = read_u8(tag_offset + 0x1c, sf); /* 16b? */
/* 0x1e: vorbis header size */
awc->channels = 1;
awc->vorbis_offset = offset + 0x20;
awc->vorbis_offset[0] = tag_offset + 0x20;
break;
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block) */
@ -389,6 +393,19 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
goto fail;
}
/* vorbis offset table, somehow offsets are unordered and can go before tags */
if (awc->is_music && awc->codec == 0x08) {
offset += 0x08 * tag_count;
for (ch = 0; ch < awc->channels; ch++) {
awc->vorbis_offset[ch] = read_u16(offset + 0x08*ch + 0x00, sf);
/* 0x02: always 0xB000? */
/* 0x04: always 0x00CD? */
/* 0x06: always 0x7F00? */
}
}
/* In music mode, data is divided into blocks of block_chunk size with padding.
* Each block has a header/seek table and interleaved data for all channels */
{
@ -404,3 +421,164 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
fail:
return 0;
}
/* ************************************************************************* */
//TODO: this method won't work properly, needs internal handling of blocks.
//
// This setups a decoder per block, but seems Vorbis' uses first frame as setup so it
// returns samples (576 vs 1024), making num_samples count in each block being off + causing
// gaps. So they must be using a single encoder + setting decode_to_discard per block
// to ge the thing working.
//
// However since blocks are probably also used for seeking, maybe they aren't resetting
// the decoder when seeking? or they force first frame to be 1024?
//
// In case of Vorvis, when setting skip samples seems repeated data from last block is
// exactly last 0x800 bytes of that channel.
static VGMSTREAM* build_block_vgmstream(STREAMFILE* sf, awc_header* awc, int channel, int32_t num_samples, int32_t skip_samples, off_t block_start, size_t block_size) {
STREAMFILE* temp_sf = NULL;
VGMSTREAM* vgmstream = NULL;
int block_channels = 1;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(block_channels, 0);
if (!vgmstream) goto fail;
vgmstream->sample_rate = awc->sample_rate;
vgmstream->num_samples = num_samples - skip_samples;
vgmstream->stream_size = block_size;
vgmstream->meta_type = meta_AWC;
switch(awc->codec) {
#ifdef VGM_USE_VORBIS
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
vorbis_custom_config cfg = {0};
cfg.channels = 1;
cfg.sample_rate = awc->sample_rate;
cfg.header_offset = awc->vorbis_offset[channel];
//cfg.skip_samples = skip_samples; //todo
vgmstream->codec_data = init_vorbis_custom(sf, block_start, VORBIS_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
vgmstream->coding_type = coding_VORBIS_custom;
}
break;
#endif
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream, sf, block_start))
goto fail;
close_streamfile(temp_sf);
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, awc_header* awc, int channel) {
VGMSTREAM* vgmstream = NULL;
segmented_layout_data* data = NULL;
int i, ch;
int blocks = awc->stream_size / awc->block_chunk + (awc->stream_size % awc->block_chunk ? 1 : 0) ;
/* init layout */
data = init_layout_segmented(blocks);
if (!data) goto fail;
/* one segment per block of this channel */
for (i = 0; i < blocks; i++) {
off_t block_offset = awc->stream_offset + i * awc->block_chunk;
int32_t num_samples = 0, skip_samples = 0;
uint32_t header_skip = 0, block_skip = 0, block_start = 0, block_data = 0;
/* read stupid block crap to get proper offsets and whatnot, format:
* - per channel: number of channel entries + skip samples + num samples
* - per channel: seek table with N entries */
for (ch = 0; ch < awc->channels; ch++) {
/* 0x00: -1 */
int entries = read_u32le(block_offset + 0x18 * ch + 0x04, sf);
int32_t entry_skip = read_u32le(block_offset + 0x18 * ch + 0x08, sf);
int32_t entry_samples = read_u32le(block_offset + 0x18 * ch + 0x0c, sf);
if (ch == channel) {
num_samples = entry_samples;
skip_samples = entry_skip;
block_start = block_offset + block_skip;
block_data = entries * 0x800;
}
header_skip += 0x18 + entries * 0x04;
block_skip += entries * 0x800;
}
if (!block_start)
goto fail;
header_skip = align_size_to_block(header_skip, 0x800);
block_start += header_skip;
//;VGM_LOG("AWC: build ch%i, block=%i at %lx, o=%x, s=%x, ns=%i, ss=%i\n", channel, i, block_offset, block_start, block_data, num_samples, skip_samples);
data->segments[i] = build_block_vgmstream(sf, awc, channel, num_samples, skip_samples, block_start, block_data);
if (!data->segments[i]) goto fail;
}
/* setup VGMSTREAMs */
if (!setup_layout_segmented(data))
goto fail;
/* build the layout VGMSTREAM */
vgmstream = allocate_segmented_vgmstream(data, 0, 0, 0);
if (!vgmstream) goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
if (!vgmstream)
free_layout_segmented(data);
return NULL;
}
/* ************************************************************************* */
/* Make layers per channel for AWC's abhorrent blocks.
*
* File has N channels = N streams, that use their own mono decoder.
* Each block then has header + seek table for all channels. But in each block there is
* a "skip samples" value per channel, and blocks repeat some data from last block
* for this, so PCM must be discarded. Also, channels in a block don't need to have
* the same number of samples.
*/
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc) {
int i;
layered_layout_data* data = NULL;
/* init layout */
data = init_layout_layered(awc->channels);
if (!data) goto fail;
/* open each layer subfile */
for (i = 0; i < awc->channels; i++) {
data->layers[i] = build_blocks_vgmstream(sf, awc, i);
if (!data->layers[i]) goto fail;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
return data;
fail:
free_layout_layered(data);
return NULL;
}

View File

@ -1,45 +1,44 @@
#include "meta.h"
/* BMP - from Jubeat series (AC) */
VGMSTREAM * init_vgmstream_bmp_konami(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag, channel_count;
/* checks */
/* .bin: actual extension
* .lbin: for plugins */
if (!check_extensions(streamFile, "bin,lbin"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x424D5000) /* "BMP\0" "*/
goto fail;
channel_count = read_8bit(0x10,streamFile); /* assumed */
if (channel_count != 2) goto fail;
loop_flag = 0;
start_offset = 0x20;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_BMP_KONAMI;
vgmstream->num_samples = read_32bitBE(0x04,streamFile);
vgmstream->sample_rate = read_32bitBE(0x14, streamFile);
vgmstream->coding_type = coding_OKI4S;
vgmstream->layout_type = layout_none;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
#include "meta.h"
/* BMP - from Konami arcade games [drummania (AC), GITADORA (AC), Jubeat (AC)] */
VGMSTREAM* init_vgmstream_bmp_konami(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int loop_flag, channels;
/* checks */
/* .bin: actual extension
* .lbin: for plugins */
if (!check_extensions(sf, "bin,lbin"))
goto fail;
if (!is_id32be(0x00,sf, "BMP\0"))
goto fail;
channels = read_u8(0x10,sf); /* assumed */
if (channels != 2) goto fail;
loop_flag = 0;
start_offset = 0x20;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_BMP_KONAMI;
vgmstream->num_samples = read_u32be(0x04,sf);
vgmstream->sample_rate = read_u32be(0x14, sf);
vgmstream->coding_type = coding_OKI4S;
vgmstream->layout_type = layout_none;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

67
src/meta/ifs.c Normal file
View File

@ -0,0 +1,67 @@
#include "meta.h"
#include "../coding/coding.h"
/* .ifs - Konami arcade games container [drummania (AC), GITADORA (AC)] */
VGMSTREAM* init_vgmstream_ifs(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
off_t subfile_offset, subfile_size;
int total_subsongs, target_subsong = sf->stream_index;
/* checks */
if (!check_extensions(sf, "ifs"))
goto fail;
if (read_u32be(0x00,sf) != 0x6CAD8F89)
goto fail;
if (read_u16be(0x04,sf) != 0x0003)
goto fail;
/* .ifs format is a binary XML thing with types/fields/nodes/etc, that sometimes
* contains Konami's BMP as subsongs (may differ in num_samples). This is an
* abridged version of the whole thing, see:
* - https://github.com/mon/ifstools
* - https://github.com/mon/kbinxml
* - https://bitbucket.org/ahigerd/gitadora2wav/
*/
{
off_t offset, size, subfile_start;
subfile_start = read_u32be(0x10,sf);
/* skip root section and point to childs */
offset = 0x28 + 0x04 + read_u32be(0x28,sf);
size = read_u32be(offset + 0x00,sf);
offset += 0x04;
/* point to subfile offsets */
size = size - 0x04 - read_u32be(offset + 0x00,sf) - 0x04;
offset = offset + 0x04 + read_u32be(offset + 0x00,sf) + 0x04;
total_subsongs = size / 0x0c;
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
subfile_offset = read_u32be(offset + (target_subsong-1)*0x0c + 0x00,sf) + subfile_start;
subfile_size = read_u32be(offset + (target_subsong-1)*0x0c + 0x04,sf);
}
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "bin");
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_bmp_konami(temp_sf);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_subsongs;
/* subsongs have names but are packed in six-bit strings */
close_streamfile(temp_sf);
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -42,7 +42,7 @@ VGMSTREAM* init_vgmstream_wii_was(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_str_ig(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_xiii(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_cabelas(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_wii_ndp(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_ndp(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ngc_dsp_aaap(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_dspw(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ngc_dsp_iadp(STREAMFILE* sf);
@ -56,6 +56,8 @@ VGMSTREAM* init_vgmstream_dsp_ds2(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_itl(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_sqex(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_wiivoice(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_wiiadpcm(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_dsp_cwac(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile);
@ -930,4 +932,6 @@ VGMSTREAM* init_vgmstream_cpk_memory(STREAMFILE* sf, STREAMFILE* sf_acb);
VGMSTREAM *init_vgmstream_sbk(STREAMFILE *sf);
VGMSTREAM* init_vgmstream_ifs(STREAMFILE* sf);
#endif /*_META_H*/

View File

@ -7,19 +7,19 @@
* then this is actually how they are laid out in the file, albeit big-endian */
struct dsp_header {
uint32_t sample_count; /* 0x00 */
uint32_t nibble_count; /* 0x04 */
uint32_t sample_rate; /* 0x08 */
uint32_t nibble_count; /* 0x04 (includes frame headers) */
uint32_t sample_rate; /* 0x08 (generally 32/48kz but games like Wario World set 32028hz to adjust for GC's rate) */
uint16_t loop_flag; /* 0x0c */
uint16_t format; /* 0x0e */
uint16_t format; /* 0x0e (always 0 for ADPCM) */
uint32_t loop_start_offset; /* 0x10 */
uint32_t loop_end_offset; /* 0x14 */
uint32_t ca; /* 0x18 */
int16_t coef[16]; /* 0x1c (really 8x2) */
uint16_t gain; /* 0x3c */
uint16_t initial_ps; /* 0x3e */
uint32_t ca; /* 0x18 (always 0) */
int16_t coef[16]; /* 0x1c (eight pairs) */
uint16_t gain; /* 0x3c (always 0 for ADPCM) */
uint16_t initial_ps; /* 0x3e (predictor/scale in frame header) */
int16_t initial_hist1; /* 0x40 */
int16_t initial_hist2; /* 0x42 */
uint16_t loop_ps; /* 0x44 */
uint16_t loop_ps; /* 0x44 (predictor/scale in loop frame header) */
int16_t loop_hist1; /* 0x46 */
int16_t loop_hist2; /* 0x48 */
int16_t channel_count; /* 0x4a (DSPADPCM.exe ~v2.7 extension) */
@ -29,34 +29,35 @@ struct dsp_header {
/* read the above struct; returns nonzero on failure */
static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE* sf, int big_endian) {
int32_t (*get_32bit)(const uint8_t *) = big_endian ? get_32bitBE : get_32bitLE;
int16_t (*get_16bit)(const uint8_t *) = big_endian ? get_16bitBE : get_16bitLE;
uint32_t (*get_u32)(const uint8_t*) = big_endian ? get_u32be : get_u32le;
uint16_t (*get_u16)(const uint8_t*) = big_endian ? get_u16be : get_u16le;
int16_t (*get_s16)(const uint8_t*) = big_endian ? get_s16be : get_s16le;
int i;
uint8_t buf[0x4e];
uint8_t buf[0x60];
if (offset > get_streamfile_size(sf))
return 1;
if (read_streamfile(buf, offset, 0x4e, sf) != 0x4e)
if (read_streamfile(buf, offset, 0x60, sf) != 0x60)
return 1;
header->sample_count = get_32bit(buf+0x00);
header->nibble_count = get_32bit(buf+0x04);
header->sample_rate = get_32bit(buf+0x08);
header->loop_flag = get_16bit(buf+0x0c);
header->format = get_16bit(buf+0x0e);
header->loop_start_offset = get_32bit(buf+0x10);
header->loop_end_offset = get_32bit(buf+0x14);
header->ca = get_32bit(buf+0x18);
header->sample_count = get_u32(buf+0x00);
header->nibble_count = get_u32(buf+0x04);
header->sample_rate = get_u32(buf+0x08);
header->loop_flag = get_u16(buf+0x0c);
header->format = get_u16(buf+0x0e);
header->loop_start_offset = get_u32(buf+0x10);
header->loop_end_offset = get_u32(buf+0x14);
header->ca = get_u32(buf+0x18);
for (i=0; i < 16; i++)
header->coef[i] = get_16bit(buf+0x1c+i*0x02);
header->gain = get_16bit(buf+0x3c);
header->initial_ps = get_16bit(buf+0x3e);
header->initial_hist1 = get_16bit(buf+0x40);
header->initial_hist2 = get_16bit(buf+0x42);
header->loop_ps = get_16bit(buf+0x44);
header->loop_hist1 = get_16bit(buf+0x46);
header->loop_hist2 = get_16bit(buf+0x48);
header->channel_count = get_16bit(buf+0x4a);
header->block_size = get_16bit(buf+0x4c);
header->coef[i] = get_s16(buf+0x1c+i*0x02);
header->gain = get_u16(buf+0x3c);
header->initial_ps = get_u16(buf+0x3e);
header->initial_hist1 = get_s16(buf+0x40);
header->initial_hist2 = get_s16(buf+0x42);
header->loop_ps = get_u16(buf+0x44);
header->loop_hist1 = get_s16(buf+0x46);
header->loop_hist2 = get_s16(buf+0x48);
header->channel_count = get_s16(buf+0x4a);
header->block_size = get_s16(buf+0x4c);
return 0;
}
static int read_dsp_header(struct dsp_header *header, off_t offset, STREAMFILE* file) {
@ -91,6 +92,7 @@ typedef struct {
int fix_loop_start; /* weird files with bad loop start */
int single_header; /* all channels share header, thus totals are off */
int ignore_header_agreement; /* sometimes there are minor differences between headers */
int ignore_loop_ps; /* sometimes has bad loop start ps */
} dsp_meta;
#define COMMON_DSP_MAX_CHANNELS 6
@ -113,8 +115,10 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
/* load standard DSP header per channel */
{
for (i = 0; i < dspm->channel_count; i++) {
if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, sf, !dspm->little_endian))
if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, sf, !dspm->little_endian)) {
//;VGM_LOG("DSP: bad header\n");
goto fail;
}
}
}
@ -129,8 +133,10 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
/* check type==0 and gain==0 */
{
for (i = 0; i < dspm->channel_count; i++) {
if (ch_header[i].format || ch_header[i].gain)
if (ch_header[i].format || ch_header[i].gain) {
//;VGM_LOG("DSP: bad type/gain\n");
goto fail;
}
}
}
@ -156,26 +162,31 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
for (i = 0; i < channels; i++) {
off_t channel_offset = dspm->start_offset + i*dspm->interleave;
if (ch_header[i].initial_ps != (uint8_t)read_8bit(channel_offset, sf))
if (ch_header[i].initial_ps != read_u8(channel_offset, sf)) {
//;VGM_LOG("DSP: bad initial ps\n");
goto fail;
}
}
}
/* check expected loop predictor/scale */
if (ch_header[0].loop_flag) {
if (ch_header[0].loop_flag && !dspm->ignore_loop_ps) {
int channels = dspm->channel_count;
if (dspm->single_header)
channels = 1;
for (i = 0; i < channels; i++) {
off_t loop_offset = ch_header[i].loop_start_offset;
loop_offset = loop_offset / 0x8 * 0x8; /* loop points to a nibble, but we need closest frame header */
if (dspm->interleave) {
loop_offset = loop_offset / 16 * 8;
loop_offset = (loop_offset / dspm->interleave * dspm->interleave * channels) + (loop_offset % dspm->interleave);
}
if (ch_header[i].loop_ps != (uint8_t)read_8bit(dspm->start_offset + i*dspm->interleave + loop_offset,sf))
if (ch_header[i].loop_ps != read_u8(dspm->start_offset + i*dspm->interleave + loop_offset,sf)) {
//;VGM_LOG("DSP: bad loop ps: %x vs at %lx\n", ch_header[i].loop_ps, dspm->start_offset + i*dspm->interleave + loop_offset);
goto fail;
}
}
}
@ -199,7 +210,7 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
vgmstream->sample_rate = ch_header[0].sample_rate;
vgmstream->num_samples = ch_header[0].sample_count;
vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_header[0].loop_start_offset);
vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset)+1;
vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset) + 1;
vgmstream->meta_type = dspm->meta_type;
vgmstream->coding_type = coding_NGC_DSP;
@ -224,7 +235,7 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
}
}
/* don't know why, but it does happen*/
/* don't know why, but it does happen */
if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples)
vgmstream->loop_end_sample = vgmstream->num_samples;
@ -235,7 +246,7 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
}
if (!vgmstream_open_stream(vgmstream,sf,dspm->start_offset))
if (!vgmstream_open_stream(vgmstream, sf, dspm->start_offset))
goto fail;
return vgmstream;
@ -867,25 +878,28 @@ fail:
}
/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */
VGMSTREAM* init_vgmstream_wii_ndp(STREAMFILE* sf) {
VGMSTREAM* init_vgmstream_dsp_ndp(STREAMFILE* sf) {
dsp_meta dspm = {0};
/* checks */
if (!check_extensions(sf, "ndp"))
/* .nds: standard
* .ndp: header id */
if (!check_extensions(sf, "nds,ndp"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x4E445000) /* "NDP\0" */
if (!is_id32be(0x00,sf, "NDP\0"))
goto fail;
if (read_32bitLE(0x08,sf) + 0x18 != get_streamfile_size(sf))
if (read_u32le(0x08,sf) + 0x18 != get_streamfile_size(sf))
goto fail;
/* 0x0c: sample rate */
dspm.channel_count = read_32bitLE(0x10,sf);
dspm.channel_count = read_u32le(0x10,sf);
dspm.max_channels = 2;
dspm.header_offset = 0x18;
dspm.header_spacing = 0x60;
dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing;
dspm.interleave = 0x04;
dspm.ignore_loop_ps = 1; /* some files loops from 0 but loop ps is null */
dspm.meta_type = meta_WII_NDP;
return init_vgmstream_dsp_common(sf, &dspm);
@ -1094,8 +1108,6 @@ VGMSTREAM* init_vgmstream_dsp_sps_n1(STREAMFILE* sf) {
dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count;
dspm.interleave = 0;
dspm.fix_loop_start = 1;
dspm.meta_type = meta_DSP_VAG;
return init_vgmstream_dsp_common(sf, &dspm);
fail:
@ -1306,3 +1318,60 @@ VGMSTREAM* init_vgmstream_dsp_wiivoice(STREAMFILE* sf) {
fail:
return NULL;
}
/* WIIADPCM - Exient wrapper [Need for Speed: Hot Pursuit (Wii)] */
VGMSTREAM* init_vgmstream_dsp_wiiadpcm(STREAMFILE* sf) {
dsp_meta dspm = {0};
/* checks */
if (!check_extensions(sf, "adpcm"))
goto fail;
if (!is_id32be(0x00,sf, "WIIA") && !is_id32be(0x00,sf, "DPCM"))
goto fail;
dspm.interleave = read_u32be(0x08,sf); /* interleave offset */
if (dspm.interleave) {
dspm.interleave -= 0x10;
}
/* 0x0c: 0 when RAM (2 DSP headers), interleave size when stream (2 WIIADPCM headers) */
dspm.channel_count = (dspm.interleave ? 2 : 1);
dspm.max_channels = 2;
dspm.header_offset = 0x10;
dspm.header_spacing = dspm.interleave;
dspm.start_offset = dspm.header_offset + 0x60;
dspm.meta_type = meta_DSP_WIIADPCM;
return init_vgmstream_dsp_common(sf, &dspm);
fail:
return NULL;
}
/* CWAC - CRI wrapper [Mario & Sonic at the Rio 2016 Olympic Games (WiiU)] */
VGMSTREAM* init_vgmstream_dsp_cwac(STREAMFILE* sf) {
dsp_meta dspm = {0};
/* checks */
/* .dsp: assumed */
if (!check_extensions(sf, "dsp"))
goto fail;
if (!is_id32be(0x00,sf, "CWAC"))
goto fail;
dspm.channel_count = read_u16be(0x04,sf);
dspm.header_offset = read_u32be(0x08,sf);
dspm.interleave = read_u32be(0x0c,sf) - dspm.header_offset;
dspm.max_channels = 2;
dspm.header_spacing = dspm.interleave;
dspm.start_offset = dspm.header_offset + 0x60;
dspm.ignore_loop_ps = 1; /* loop offset seems relative to CWAC? also interleave affects it */
dspm.meta_type = meta_DSP_CWAC;
return init_vgmstream_dsp_common(sf, &dspm);
fail:
return NULL;
}

View File

@ -1,58 +1,79 @@
#include "meta.h"
#include "../coding/coding.h"
/* Entergram NXA Opus [Higurashi no Naku Koro ni Hou (Switch)] */
VGMSTREAM * init_vgmstream_opus_nxa(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag = 0, channel_count;
size_t data_size, skip = 0;
/* checks */
if (!check_extensions(streamFile, "nxa"))
goto fail;
if (read_32bitBE(0x00, streamFile) != 0x4E584131) /* "NXA1" */
goto fail;
channel_count = read_16bitLE(0x10, streamFile);
skip = read_16bitLE(0x16, streamFile);
data_size = read_32bitLE(0x08, streamFile)-0x30;
start_offset = 0x30;
/* TODO: Determine if loop points are stored externally. No visible loop points in header */
loop_flag = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_NXA;
vgmstream->num_samples = read_32bitLE(0x20, streamFile);
vgmstream->sample_rate = read_32bitLE(0x0C, streamFile);
#ifdef VGM_USE_FFMPEG
{
vgmstream->codec_data = init_ffmpeg_switch_opus(streamFile, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
if (vgmstream->num_samples == 0) {
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip;
}
}
#else
goto fail;
#endif
/* open the file for reading */
if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
#include "meta.h"
#include "../coding/coding.h"
/* Entergram NXA Opus [Higurashi no Naku Koro ni Hou (Switch), Gensou Rougoku no Kaleidoscope (Switch)] */
VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int loop_flag, channels, type, sample_rate;
int32_t num_samples, loop_start, loop_end, skip;
size_t data_size, frame_size;
/* checks */
if (!check_extensions(sf, "nxa"))
goto fail;
if (!is_id32be(0x00, sf, "NXA1"))
goto fail;
start_offset = 0x30;
type = read_u32le(0x04, sf);
data_size = read_u32le(0x08, sf) - start_offset;
sample_rate = read_u32le(0x0C, sf);
channels = read_s16le(0x10, sf);
frame_size = read_u16le(0x12, sf);
/* 0x14: frame samples */
skip = read_s16le(0x16, sf);
num_samples = read_s32le(0x18, sf);
loop_start = read_s32le(0x1c, sf);
loop_end = read_s32le(0x20, sf);
/* rest: null */
loop_flag = (loop_start > 0);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_NXA;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
switch(type) {
#ifdef VGM_USE_FFMPEG
case 1: { /* Higurashi no Naku Koro ni Hou (Switch) */
vgmstream->codec_data = init_ffmpeg_switch_opus(sf, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
break;
}
case 2: { /* Gensou Rougoku no Kaleidoscope (Switch) */
opus_config cfg = {0};
cfg.channels = channels;
cfg.skip = skip;
cfg.frame_size = frame_size;
vgmstream->codec_data = init_ffmpeg_fixed_opus(sf, start_offset, data_size, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
break;
}
#endif
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -610,10 +610,22 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
sf_sub = setup_subfile_streamfile(txth->sf_body, txth->subfile_offset, txth->subfile_size, extension);
if (!sf_sub) goto fail;
sf_sub->stream_index = txth->sf->stream_index; /* in case of subfiles with subsongs */
sf_sub->stream_index = txth->sf->stream_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf_sub);
if (!vgmstream) goto fail;
if (!vgmstream) {
/* In case of subfiles with subsongs pass subsong N by default (ex. subfile is a .fsb with N subsongs).
* But if the subfile is a single-subsong subfile (ex. subfile is a .fsb with 1 subsong) try again
* without passing index (as it would fail first trying to open subsong N). */
if (sf_sub->stream_index > 1) {
sf_sub->stream_index = 0;
vgmstream = init_vgmstream_from_STREAMFILE(sf_sub);
if (!vgmstream) goto fail;
}
else {
goto fail;
}
}
/* apply some fields */
if (txth->sample_rate)

View File

@ -3,72 +3,76 @@
#include "../util.h"
/* BNS - Wii "Banner Sound" disc jingle */
VGMSTREAM * init_vgmstream_wii_bns(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t BNS_offset;
uint32_t info_offset=0,data_offset=0;
VGMSTREAM* init_vgmstream_wii_bns(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t bns_offset;
uint32_t info_offset = 0, data_offset = 0;
uint32_t channel_info_offset_list_offset;
int channel_count;
int loop_flag;
uint16_t sample_rate;
int channels, loop_flag, sample_rate;
uint32_t sample_count, loop_start;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("bns",filename_extension(filename))) goto fail;
/* checks */
/* .bin: actual extension
* .bns: header id */
if (!check_extensions(sf, "bin,lbin,bns"))
goto fail;
// check header
BNS_offset = 0;
if (read_32bitBE(BNS_offset,streamFile) == 0x494D4435) // IMD5
{
// Skip IMD5 header if present
BNS_offset = 0x20;
bns_offset = 0;
if (is_id32be(bns_offset + 0x40, sf, "IMET")) {
/* regular .bnr, find sound.bin offset */
bns_offset = read_u32be(bns_offset + 0x44,sf);
/* tables, probably not ok for all cases */
bns_offset += read_u32be(bns_offset + 0x54,sf);
}
if (read_32bitBE(BNS_offset+0x00,streamFile) != 0x424E5320) goto fail; // "BNS "
if ((uint32_t)read_32bitBE(BNS_offset+0x04,streamFile) != 0xFEFF0100u) goto fail;
if (is_id32be(bns_offset + 0x00, sf, "IMD5")) {
/* skip IMD5 header if present */
bns_offset += 0x20;
}
// find chunks, verify
if (!is_id32be(bns_offset + 0x00,sf, "BNS "))
goto fail;
if (read_u32be(bns_offset + 0x04,sf) != 0xFEFF0100u)
goto fail;
/* find chunks */
{
// file size as claimed by header
uint32_t header_file_size = read_32bitBE(BNS_offset+0x08,streamFile);
uint32_t header_size = read_16bitBE(BNS_offset+0xc,streamFile);
uint16_t chunk_count = read_16bitBE(BNS_offset+0xe,streamFile);
uint32_t file_size = read_u32be(bns_offset+0x08,sf);
uint32_t header_size = read_u16be(bns_offset+0x0c,sf);
uint16_t chunk_count = read_u16be(bns_offset+0x0e,sf);
int i;
// assume BNS is the last thing in the file
if (header_file_size + BNS_offset != get_streamfile_size(streamFile))
/* assume BNS is the last thing in the file */
if (file_size + bns_offset != get_streamfile_size(sf))
goto fail;
for (i = 0; i < chunk_count; i++) {
uint32_t chunk_info_offset = BNS_offset+0x10+i*8;
uint32_t chunk_info_offset = bns_offset+0x10+i*0x08;
uint32_t chunk_offset, chunk_size;
// ensure chunk info is within header
if (chunk_info_offset+8 > BNS_offset+header_size) goto fail;
if (chunk_info_offset+8 > bns_offset+header_size) goto fail;
chunk_offset = BNS_offset + (uint32_t)read_32bitBE(chunk_info_offset,streamFile);
chunk_size = (uint32_t)read_32bitBE(chunk_info_offset+4,streamFile);
chunk_offset = bns_offset + (uint32_t)read_32bitBE(chunk_info_offset,sf);
chunk_size = (uint32_t)read_32bitBE(chunk_info_offset+4,sf);
// ensure chunk is within file
if (chunk_offset < BNS_offset+header_size ||
chunk_offset+chunk_size > BNS_offset+header_file_size) goto fail;
if (chunk_offset < bns_offset+header_size ||
chunk_offset+chunk_size > bns_offset+file_size) goto fail;
// ensure chunk size in header matches that listed in chunk
// Note: disabled for now, as the Homebrew Channel BNS has a DATA
// chunk that doesn't include the header size
//if ((uint32_t)read_32bitBE(chunk_offset+4,streamFile) != chunk_size) goto fail;
//if ((uint32_t)read_32bitBE(chunk_offset+4,sf) != chunk_size) goto fail;
// handle each chunk type
switch (read_32bitBE(chunk_offset,streamFile)) {
switch (read_32bitBE(chunk_offset,sf)) {
case 0x494E464F: // INFO
info_offset = chunk_offset+8;
info_offset = chunk_offset+0x08;
break;
case 0x44415441: // DATA
data_offset = chunk_offset+8;
data_offset = chunk_offset+0x08;
break;
default:
goto fail;
@ -82,72 +86,65 @@ VGMSTREAM * init_vgmstream_wii_bns(STREAMFILE *streamFile) {
/* parse out basic stuff in INFO */
{
/* only seen this zero, specifies DSP format? */
if (read_8bit(info_offset+0x00,streamFile) != 0) goto fail;
if (read_8bit(info_offset+0x00,sf) != 0) goto fail;
loop_flag = read_8bit(info_offset+0x01,streamFile);
channel_count = read_8bit(info_offset+0x02,streamFile);
loop_flag = read_8bit(info_offset+0x01,sf);
channels = read_8bit(info_offset+0x02,sf);
/* only seen zero, padding? */
if (read_8bit(info_offset+0x03,streamFile) != 0) goto fail;
if (read_8bit(info_offset+0x03,sf) != 0) goto fail;
sample_rate = (uint16_t)read_16bitBE(info_offset+0x04,streamFile);
sample_rate = read_u16be(info_offset+0x04,sf);
/* only seen this zero, padding? */
if (read_16bitBE(info_offset+0x06,streamFile) != 0) goto fail;
if (read_u16be(info_offset+0x06,sf) != 0) goto fail;
loop_start = read_32bitBE(info_offset+0x08,streamFile);
loop_start = read_s32be(info_offset+0x08,sf);
sample_count = read_32bitBE(info_offset+0x0c,streamFile);
sample_count = read_s32be(info_offset+0x0c,sf);
channel_info_offset_list_offset = info_offset + (uint32_t)read_32bitBE(info_offset+0x10,streamFile);
channel_info_offset_list_offset = info_offset + read_u32be(info_offset+0x10,sf);
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->channels = channel_count;
vgmstream->sample_rate = sample_rate;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->num_samples = sample_count;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_WII_BNS;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = sample_count;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = sample_count;
if (loop_flag)
{
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = sample_count;
}
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_none;
//todo cleanup
/* open the file for reading */
{
int i;
for (i=0;i<channel_count;i++) {
uint32_t channel_info_offset = info_offset + read_32bitBE(channel_info_offset_list_offset+4*i,streamFile);
uint32_t channel_data_offset = data_offset + (uint32_t)read_32bitBE(channel_info_offset+0,streamFile);
uint32_t channel_dsp_offset = info_offset + (uint32_t)read_32bitBE(channel_info_offset+4,streamFile);
int j;
int i, j;
for (i = 0; i < channels; i++) {
uint32_t channel_info_offset = info_offset + read_u32be(channel_info_offset_list_offset + 0x04*i,sf);
uint32_t channel_data_offset = data_offset + read_u32be(channel_info_offset + 0x00,sf);
uint32_t channel_dsp_offset = info_offset + read_u32be(channel_info_offset + 0x04,sf);
/* always been 0... */
if (read_32bitBE(channel_info_offset+8,streamFile) != 0) goto fail;
if (read_u32be(channel_info_offset + 0x8,sf) != 0) goto fail;
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
vgmstream->ch[i].streamfile = reopen_streamfile(sf, 0);
if (!vgmstream->ch[i].streamfile) goto fail;
vgmstream->ch[i].channel_start_offset = vgmstream->ch[i].offset = channel_data_offset;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=channel_data_offset;
for (j=0;j<16;j++)
vgmstream->ch[i].adpcm_coef[j] =
read_16bitBE(channel_dsp_offset+j*2,streamFile);
for (j = 0; j < 16; j++) {
vgmstream->ch[i].adpcm_coef[j] = read_s16be(channel_dsp_offset + j*0x02,sf);
}
}
}
//if (!vgmstream_open_stream(vgmstream, sf, start_offset))
// goto fail;
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -664,7 +664,7 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
* pre1 start1 end1 post1
* - when pre and post are set nothing is done (fade is exact and multiple fades may overlap)
* - when previous fade's post or current fade's pre are negative (meaning file end/start)
* they should cancel each other (to allow changing fade-in + fade-out + fade-in + etc):
* they should cancel each other (to allow chaining fade-in + fade-out + fade-in + etc):
* <----------|----------|----------| |----------|----------|---------->
* pre1 start1 end1 post1 pre2 start2 end2 post2
* - other cases (previous fade is actually after/in-between current fade) are ignored

View File

@ -81,7 +81,7 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
strncpy(buf, pos, buf_len);
/* name without extension */
if (cfg->remove_extension) {
if (cfg && cfg->remove_extension) {
pos2 = strrchr(buf, '.');
if (pos2 && strlen(pos2) < 15) /* too big extension = file name probably has a dot in the middle */
pos2[0] = '\0';

View File

@ -307,6 +307,10 @@ static inline int min_s32(int32_t a, int32_t b) { return a < b ? a : b; }
//align32, align16, clamp16, etc
#endif
static inline const int is_id32be(off_t offset, STREAMFILE* sf, const char* s) {
return read_u32be(offset, sf) == get_id32be(s);
}
//TODO: maybe move to streamfile.c
/* guess byte endianness from a given value, return true if big endian and false if little endian */
static inline int guess_endianness16bit(off_t offset, STREAMFILE* sf) {

View File

@ -98,6 +98,10 @@ static inline int clamp16(int32_t val) {
else return val;
}
static inline const uint32_t get_id32be(const char* s) {
return (uint32_t)(s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0);
}
/* less common functions, no need to inline */
int round10(int val);

View File

@ -200,7 +200,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_redspark,
init_vgmstream_ivaud,
init_vgmstream_wii_wsd,
init_vgmstream_wii_ndp,
init_vgmstream_dsp_ndp,
init_vgmstream_ps2_sps,
init_vgmstream_ps2_xa2_rrp,
init_vgmstream_nds_hwas,
@ -513,6 +513,9 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_cpk,
init_vgmstream_opus_nsopus,
init_vgmstream_sbk,
init_vgmstream_dsp_wiiadpcm,
init_vgmstream_dsp_cwac,
init_vgmstream_ifs,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */

View File

@ -742,6 +742,8 @@ typedef enum {
meta_DSP_SQEX,
meta_DSP_WIIVOICE,
meta_SBK,
meta_DSP_WIIADPCM,
meta_DSP_CWAC,
} meta_t;
/* standard WAVEFORMATEXTENSIBLE speaker positions */