mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-29 00:34:33 +01:00
Add XMD decoder [Silent Hill 4, Castlevania: Curse of Darkness (Xbox)
This commit is contained in:
parent
b43063f8a2
commit
61034484ab
@ -151,6 +151,9 @@ void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin
|
|||||||
/* asf_decoder */
|
/* asf_decoder */
|
||||||
void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||||
|
|
||||||
|
/* xmd_decoder */
|
||||||
|
void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size);
|
||||||
|
|
||||||
/* ea_mt_decoder*/
|
/* ea_mt_decoder*/
|
||||||
ea_mt_codec_data *init_ea_mt(int channel_count, int type);
|
ea_mt_codec_data *init_ea_mt(int channel_count, int type);
|
||||||
void decode_ea_mt(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
void decode_ea_mt(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||||
|
63
src/coding/xmd_decoder.c
Normal file
63
src/coding/xmd_decoder.c
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#include "coding.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Decodes Konami XMD from Xbox games (algorithm info from xmd2wav/xmddecode.dll). */
|
||||||
|
void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
|
||||||
|
off_t frame_offset;
|
||||||
|
int i, frames_in, sample_count = 0, samples_done = 0;
|
||||||
|
size_t bytes_per_frame, samples_per_frame;
|
||||||
|
int16_t hist1, hist2;
|
||||||
|
uint16_t scale;
|
||||||
|
|
||||||
|
/* external interleave (variable size), mono */
|
||||||
|
bytes_per_frame = frame_size;
|
||||||
|
samples_per_frame = 2 + (frame_size - 0x06) * 2;
|
||||||
|
frames_in = first_sample / samples_per_frame;
|
||||||
|
first_sample = first_sample % samples_per_frame;
|
||||||
|
|
||||||
|
/* parse frame header */
|
||||||
|
frame_offset = stream->offset + bytes_per_frame*frames_in;
|
||||||
|
hist2 = read_16bitLE(frame_offset+0x00,stream->streamfile);
|
||||||
|
hist1 = read_16bitLE(frame_offset+0x02,stream->streamfile);
|
||||||
|
scale = (uint16_t)read_16bitLE(frame_offset+0x04,stream->streamfile); /* scale doesn't go too high though */
|
||||||
|
|
||||||
|
|
||||||
|
/* write header samples (needed) */
|
||||||
|
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||||
|
outbuf[samples_done * channelspacing] = hist2;
|
||||||
|
samples_done++;
|
||||||
|
}
|
||||||
|
sample_count++;
|
||||||
|
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||||
|
outbuf[samples_done * channelspacing] = hist1;
|
||||||
|
samples_done++;
|
||||||
|
}
|
||||||
|
sample_count++;
|
||||||
|
|
||||||
|
/* decode nibbles */
|
||||||
|
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||||
|
int32_t new_sample;
|
||||||
|
uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x06 + i/2,stream->streamfile);
|
||||||
|
|
||||||
|
new_sample = i&1 ? /* low nibble first */
|
||||||
|
get_high_nibble_signed(nibbles):
|
||||||
|
get_low_nibble_signed(nibbles);
|
||||||
|
new_sample = (new_sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14;
|
||||||
|
/* Coefs are similar to XA's filter 2, but using those creates hissing in some songs.
|
||||||
|
* ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */
|
||||||
|
//new_sample = (int32_t)(new_sample*scale + hist1*1.796875 + hist2*-0.8125);
|
||||||
|
|
||||||
|
//new_sample = clamp16(new_sample); /* not needed */
|
||||||
|
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||||
|
outbuf[samples_done * channelspacing] = (int16_t)new_sample;
|
||||||
|
samples_done++;
|
||||||
|
}
|
||||||
|
sample_count++;
|
||||||
|
|
||||||
|
hist2 = hist1;
|
||||||
|
hist1 = new_sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
//stream->adpcm_history1_32 = hist1;
|
||||||
|
//stream->adpcm_history2_32 = hist2;
|
||||||
|
}
|
@ -437,6 +437,7 @@ static const char* extension_list[] = {
|
|||||||
"xvas",
|
"xvas",
|
||||||
"xwav",//fake, to be removed
|
"xwav",//fake, to be removed
|
||||||
"xwb",
|
"xwb",
|
||||||
|
"xmd",
|
||||||
"xwc",
|
"xwc",
|
||||||
"xwm", //FFmpeg, not parsed (XWMA)
|
"xwm", //FFmpeg, not parsed (XWMA)
|
||||||
"xwma", //FFmpeg, not parsed (XWMA)
|
"xwma", //FFmpeg, not parsed (XWMA)
|
||||||
@ -555,6 +556,7 @@ static const coding_info coding_info_list[] = {
|
|||||||
{coding_MC3, "Paradigm MC3 3-bit ADPCM"},
|
{coding_MC3, "Paradigm MC3 3-bit ADPCM"},
|
||||||
{coding_FADPCM, "FMOD FADPCM 4-bit ADPCM"},
|
{coding_FADPCM, "FMOD FADPCM 4-bit ADPCM"},
|
||||||
{coding_ASF, "Argonaut ASF 4-bit ADPCM"},
|
{coding_ASF, "Argonaut ASF 4-bit ADPCM"},
|
||||||
|
{coding_XMD, "Konami XMD 4-bit ADPCM"},
|
||||||
|
|
||||||
{coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"},
|
{coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"},
|
||||||
{coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"},
|
{coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"},
|
||||||
@ -1041,6 +1043,7 @@ static const meta_info meta_info_list[] = {
|
|||||||
{meta_H4M, "Hudson HVQM4 header"},
|
{meta_H4M, "Hudson HVQM4 header"},
|
||||||
{meta_OGG_MUS, "Ogg Vorbis (MUS header)"},
|
{meta_OGG_MUS, "Ogg Vorbis (MUS header)"},
|
||||||
{meta_ASF, "Argonaut ASF header"},
|
{meta_ASF, "Argonaut ASF header"},
|
||||||
|
{meta_XMD, "Konami XMD header"},
|
||||||
|
|
||||||
#ifdef VGM_USE_FFMPEG
|
#ifdef VGM_USE_FFMPEG
|
||||||
{meta_FFmpeg, "FFmpeg supported file format"},
|
{meta_FFmpeg, "FFmpeg supported file format"},
|
||||||
|
@ -1473,6 +1473,10 @@
|
|||||||
<File
|
<File
|
||||||
RelativePath=".\meta\xvag.c"
|
RelativePath=".\meta\xvag.c"
|
||||||
>
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\meta\xmd.c"
|
||||||
|
>
|
||||||
</File>
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\meta\xwb.c"
|
RelativePath=".\meta\xwb.c"
|
||||||
@ -1714,6 +1718,10 @@
|
|||||||
RelativePath=".\coding\xa_decoder.c"
|
RelativePath=".\coding\xa_decoder.c"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\coding\xmd_decoder.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\coding\vorbis_custom_decoder.c"
|
RelativePath=".\coding\vorbis_custom_decoder.c"
|
||||||
>
|
>
|
||||||
|
@ -442,6 +442,7 @@
|
|||||||
<ClCompile Include="meta\xnb.c" />
|
<ClCompile Include="meta\xnb.c" />
|
||||||
<ClCompile Include="meta\xss.c" />
|
<ClCompile Include="meta\xss.c" />
|
||||||
<ClCompile Include="meta\xvag.c" />
|
<ClCompile Include="meta\xvag.c" />
|
||||||
|
<ClCompile Include="meta\xmd.c" />
|
||||||
<ClCompile Include="meta\xwb.c" />
|
<ClCompile Include="meta\xwb.c" />
|
||||||
<ClCompile Include="meta\xwc.c" />
|
<ClCompile Include="meta\xwc.c" />
|
||||||
<ClCompile Include="meta\ydsp.c" />
|
<ClCompile Include="meta\ydsp.c" />
|
||||||
@ -487,6 +488,7 @@
|
|||||||
<ClCompile Include="coding\vorbis_custom_utils_wwise.c" />
|
<ClCompile Include="coding\vorbis_custom_utils_wwise.c" />
|
||||||
<ClCompile Include="coding\ws_decoder.c" />
|
<ClCompile Include="coding\ws_decoder.c" />
|
||||||
<ClCompile Include="coding\xa_decoder.c" />
|
<ClCompile Include="coding\xa_decoder.c" />
|
||||||
|
<ClCompile Include="coding\xmd_decoder.c" />
|
||||||
<ClCompile Include="layout\segmented.c" />
|
<ClCompile Include="layout\segmented.c" />
|
||||||
<ClCompile Include="layout\aix_layout.c" />
|
<ClCompile Include="layout\aix_layout.c" />
|
||||||
<ClCompile Include="layout\blocked_ast.c" />
|
<ClCompile Include="layout\blocked_ast.c" />
|
||||||
|
@ -895,6 +895,9 @@
|
|||||||
<ClCompile Include="meta\xvag.c">
|
<ClCompile Include="meta\xvag.c">
|
||||||
<Filter>meta\Source Files</Filter>
|
<Filter>meta\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="meta\xmd.c">
|
||||||
|
<Filter>meta\Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="meta\xwb.c">
|
<ClCompile Include="meta\xwb.c">
|
||||||
<Filter>meta\Source Files</Filter>
|
<Filter>meta\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -1027,6 +1030,9 @@
|
|||||||
<ClCompile Include="coding\xa_decoder.c">
|
<ClCompile Include="coding\xa_decoder.c">
|
||||||
<Filter>coding\Source Files</Filter>
|
<Filter>coding\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="coding\xmd_decoder.c">
|
||||||
|
<Filter>coding\Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="layout\segmented.c">
|
<ClCompile Include="layout\segmented.c">
|
||||||
<Filter>layout\Source Files</Filter>
|
<Filter>layout\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -765,4 +765,6 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile);
|
|||||||
|
|
||||||
VGMSTREAM * init_vgmstream_asf(STREAMFILE *streamFile);
|
VGMSTREAM * init_vgmstream_asf(STREAMFILE *streamFile);
|
||||||
|
|
||||||
|
VGMSTREAM * init_vgmstream_xmd(STREAMFILE *streamFile);
|
||||||
|
|
||||||
#endif /*_META_H*/
|
#endif /*_META_H*/
|
||||||
|
69
src/meta/xmd.c
Normal file
69
src/meta/xmd.c
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#include "meta.h"
|
||||||
|
#include "../coding/coding.h"
|
||||||
|
|
||||||
|
/* XMD - from Konami Xbox games [Silent Hill 4 (Xbox), Castlevania Curse of Darkness (Xbox)] */
|
||||||
|
VGMSTREAM * init_vgmstream_xmd(STREAMFILE *streamFile) {
|
||||||
|
VGMSTREAM * vgmstream = NULL;
|
||||||
|
off_t start_offset;
|
||||||
|
int loop_flag, channel_count, sample_rate;
|
||||||
|
size_t data_size, loop_start, frame_size;
|
||||||
|
|
||||||
|
|
||||||
|
/* checks (.xmd comes from bigfiles with filenames) */
|
||||||
|
if (!check_extensions(streamFile, "xmd"))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if ((read_32bitBE(0x00,streamFile) & 0xFFFFFF00) == 0x786D6400) { /* "xmd\0" */
|
||||||
|
/* v2 from Castlevania: Curse of Darkness */
|
||||||
|
channel_count = read_8bit(0x03,streamFile);
|
||||||
|
sample_rate = (uint16_t)read_16bitLE(0x04, streamFile);
|
||||||
|
data_size = read_32bitLE(0x06, streamFile);
|
||||||
|
loop_flag = read_8bit(0x0a,streamFile);
|
||||||
|
loop_start = read_32bitLE(0x0b, streamFile);
|
||||||
|
/* 0x0f(2): unknown+config? */
|
||||||
|
frame_size = 0x15;
|
||||||
|
start_offset = 0x11;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* v1 from Silent Hill 4 */
|
||||||
|
channel_count = read_8bit(0x00,streamFile);
|
||||||
|
sample_rate = (uint16_t)read_16bitLE(0x01, streamFile);
|
||||||
|
data_size = read_32bitLE(0x03, streamFile);
|
||||||
|
loop_flag = read_8bit(0x07,streamFile);
|
||||||
|
loop_start = read_32bitLE(0x08, streamFile);
|
||||||
|
|
||||||
|
frame_size = 0x0d;
|
||||||
|
start_offset = 0x0c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extra checks just in case */
|
||||||
|
if (data_size > get_streamfile_size(streamFile))
|
||||||
|
goto fail; /* v1 .xmd are sector-aligned with padding */
|
||||||
|
if (channel_count > 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
|
||||||
|
/* build the VGMSTREAM */
|
||||||
|
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||||
|
if (!vgmstream) goto fail;
|
||||||
|
|
||||||
|
vgmstream->sample_rate = sample_rate;
|
||||||
|
vgmstream->num_samples = data_size / frame_size / channel_count * ((frame_size-0x06)*2 + 2); /* bytes to samples */
|
||||||
|
if (loop_flag) {
|
||||||
|
vgmstream->loop_start_sample = loop_start / frame_size / channel_count * ((frame_size-0x06)*2 + 2); /* bytes to samples */
|
||||||
|
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
vgmstream->meta_type = meta_XMD;
|
||||||
|
vgmstream->coding_type = coding_XMD;
|
||||||
|
vgmstream->layout_type = layout_interleave;
|
||||||
|
vgmstream->interleave_block_size = frame_size;
|
||||||
|
|
||||||
|
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||||
|
goto fail;
|
||||||
|
return vgmstream;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
close_vgmstream(vgmstream);
|
||||||
|
return NULL;
|
||||||
|
}
|
@ -419,6 +419,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
|||||||
init_vgmstream_h4m,
|
init_vgmstream_h4m,
|
||||||
init_vgmstream_ps2_ads_container,
|
init_vgmstream_ps2_ads_container,
|
||||||
init_vgmstream_asf,
|
init_vgmstream_asf,
|
||||||
|
init_vgmstream_xmd,
|
||||||
|
|
||||||
init_vgmstream_txth, /* should go at the end (lower priority) */
|
init_vgmstream_txth, /* should go at the end (lower priority) */
|
||||||
#ifdef VGM_USE_FFMPEG
|
#ifdef VGM_USE_FFMPEG
|
||||||
@ -1147,6 +1148,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
|||||||
return 256; /* (0x8c - 0xc) * 2 */
|
return 256; /* (0x8c - 0xc) * 2 */
|
||||||
case coding_ASF:
|
case coding_ASF:
|
||||||
return 32; /* (0x11 - 0x1) * 2 */
|
return 32; /* (0x11 - 0x1) * 2 */
|
||||||
|
case coding_XMD:
|
||||||
|
return (vgmstream->interleave_block_size - 0x06)*2 + 2;
|
||||||
case coding_EA_MT:
|
case coding_EA_MT:
|
||||||
return 432;
|
return 432;
|
||||||
case coding_CRI_HCA:
|
case coding_CRI_HCA:
|
||||||
@ -1305,6 +1308,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
|||||||
return 0x8c;
|
return 0x8c;
|
||||||
case coding_ASF:
|
case coding_ASF:
|
||||||
return 0x11;
|
return 0x11;
|
||||||
|
case coding_XMD:
|
||||||
|
return vgmstream->interleave_block_size;
|
||||||
case coding_EA_MT:
|
case coding_EA_MT:
|
||||||
return 0; /* variable (frames of bit counts or PCM frames) */
|
return 0; /* variable (frames of bit counts or PCM frames) */
|
||||||
#ifdef VGM_USE_ATRAC9
|
#ifdef VGM_USE_ATRAC9
|
||||||
@ -1943,6 +1948,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
|||||||
samples_to_do);
|
samples_to_do);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case coding_XMD:
|
||||||
|
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||||
|
decode_xmd(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||||
|
vgmstream->channels,vgmstream->samples_into_block,
|
||||||
|
samples_to_do, vgmstream->interleave_block_size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case coding_EA_MT:
|
case coding_EA_MT:
|
||||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||||
decode_ea_mt(vgmstream, buffer+samples_written*vgmstream->channels+chan,
|
decode_ea_mt(vgmstream, buffer+samples_written*vgmstream->channels+chan,
|
||||||
|
@ -156,6 +156,7 @@ typedef enum {
|
|||||||
coding_MC3, /* Paradigm MC3 3-bit ADPCM */
|
coding_MC3, /* Paradigm MC3 3-bit ADPCM */
|
||||||
coding_FADPCM, /* FMOD FADPCM 4-bit ADPCM */
|
coding_FADPCM, /* FMOD FADPCM 4-bit ADPCM */
|
||||||
coding_ASF, /* Argonaut ASF 4-bit ADPCM */
|
coding_ASF, /* Argonaut ASF 4-bit ADPCM */
|
||||||
|
coding_XMD, /* Konami XMD 4-bit ADPCM */
|
||||||
|
|
||||||
/* others */
|
/* others */
|
||||||
coding_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */
|
coding_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */
|
||||||
@ -683,6 +684,7 @@ typedef enum {
|
|||||||
meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
|
meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
|
||||||
meta_OGG_MUS, /* Ogg Vorbis with encryption [Redux - Dark Matters (PC)] */
|
meta_OGG_MUS, /* Ogg Vorbis with encryption [Redux - Dark Matters (PC)] */
|
||||||
meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */
|
meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */
|
||||||
|
meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */
|
||||||
|
|
||||||
#ifdef VGM_USE_FFMPEG
|
#ifdef VGM_USE_FFMPEG
|
||||||
meta_FFmpeg,
|
meta_FFmpeg,
|
||||||
|
Loading…
Reference in New Issue
Block a user