mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
Merge pull request #82 from bnnm/wwise-3
Wwise 3 - A Lizard and a Bat, What's Up With That?
This commit is contained in:
commit
ebcf4ec8aa
@ -27,7 +27,9 @@ CODING_OBJS=coding/adx_decoder.o \
|
||||
coding/hca_decoder.o \
|
||||
coding/ffmpeg_decoder.o \
|
||||
coding/ffmpeg_decoder_utils.o \
|
||||
coding/fsb_vorbis_decoder.o
|
||||
coding/fsb_vorbis_decoder.o \
|
||||
coding/wwise_vorbis_decoder.o \
|
||||
coding/wwise_vorbis_utils.o
|
||||
|
||||
LAYOUT_OBJS=layout/ast_blocked.o \
|
||||
layout/blocked.o \
|
||||
|
@ -32,5 +32,7 @@ libcoding_la_SOURCES += mtaf_decoder.c
|
||||
libcoding_la_SOURCES += g719_decoder.c
|
||||
libcoding_la_SOURCES += hca_decoder.c
|
||||
libcoding_la_SOURCES += fsb_vorbis_decoder.c
|
||||
libcoding_la_SOURCES += wwise_vorbis_decoder.c
|
||||
libcoding_la_SOURCES += wwise_vorbis_utils.c
|
||||
|
||||
EXTRA_DIST = coding.h g72x_state.h clHCA.h
|
||||
|
@ -27,6 +27,8 @@ void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelsp
|
||||
void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
|
||||
void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels);
|
||||
|
||||
/* ngc_dsp_decoder */
|
||||
void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
@ -126,6 +128,15 @@ void decode_fsb_vorbis(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_t
|
||||
void free_fsb_vorbis(vorbis_codec_data *data);
|
||||
void reset_fsb_vorbis(VGMSTREAM *vgmstream);
|
||||
void seek_fsb_vorbis(VGMSTREAM *vgmstream, int32_t num_sample);
|
||||
|
||||
/* wwise_vorbis_decoder */
|
||||
vorbis_codec_data * init_wwise_vorbis_codec_data(STREAMFILE *streamfile, off_t start_offset, int channels, int sample_rate, int blocksize_0_exp, int blocksize_1_exp,
|
||||
wwise_setup_type setup_type, wwise_header_type header_type, wwise_packet_type packet_type, int big_endian);
|
||||
void decode_wwise_vorbis(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels);
|
||||
|
||||
void free_wwise_vorbis(vorbis_codec_data *data);
|
||||
void reset_wwise_vorbis(VGMSTREAM *vgmstream);
|
||||
void seek_wwise_vorbis(VGMSTREAM *vgmstream, int32_t num_sample);
|
||||
#endif
|
||||
|
||||
/* mpeg_decoder */
|
||||
|
@ -411,7 +411,14 @@ void free_fsb_vorbis(vorbis_codec_data * data) {
|
||||
}
|
||||
|
||||
void reset_fsb_vorbis(VGMSTREAM *vgmstream) {
|
||||
seek_fsb_vorbis(vgmstream, 0);
|
||||
#if FSB_VORBIS_ON
|
||||
vorbis_codec_data *data = vgmstream->codec_data;
|
||||
|
||||
/* Seeking is provided by the Ogg layer, so with raw vorbis we need seek tables instead.
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void seek_fsb_vorbis(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
@ -422,6 +429,7 @@ void seek_fsb_vorbis(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = num_sample;
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
|
||||
#endif
|
||||
}
|
||||
|
@ -604,3 +604,57 @@ void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
|
||||
void decode_wwise_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
int i, sample_count = 0;
|
||||
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_index = stream->adpcm_step_index;
|
||||
|
||||
//internal interleave (configurable size), block-interleave multichannel (ex. if block is 0xD8 in 6ch: 6 blocks of 4+0x20)
|
||||
int block_samples = (vgmstream->interleave_block_size - 4*vgmstream->channels) * 2 / vgmstream->channels;
|
||||
first_sample = first_sample % block_samples;
|
||||
|
||||
//block-interleaved header (1 header per channel block); can be LE or BE
|
||||
if (first_sample == 0) {
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_16bitBE : read_16bitLE;
|
||||
off_t header_offset = stream->offset + (vgmstream->interleave_block_size / vgmstream->channels)*channel;
|
||||
|
||||
hist1 = read_16bit(header_offset,stream->streamfile);
|
||||
step_index = read_8bit(header_offset+2,stream->streamfile);
|
||||
if (step_index < 0) step_index=0;
|
||||
if (step_index > 88) step_index=88;
|
||||
|
||||
//must write history from header as last nibble/sample in block is almost always 0 / not encoded
|
||||
outbuf[sample_count] = (short)(hist1);
|
||||
sample_count += channelspacing;
|
||||
first_sample += 1;
|
||||
samples_to_do -= 1;
|
||||
}
|
||||
|
||||
for (i=first_sample; i < first_sample + samples_to_do; i++) { /* first_sample + samples_to_do should be block_samples at most */
|
||||
off_t byte_offset = stream->offset + (vgmstream->interleave_block_size / vgmstream->channels)*channel + 4 + (i-1)/2;
|
||||
int nibble_shift = ((i-1)&1?4:0); //low nibble first
|
||||
|
||||
//last nibble/sample in block is ignored (next header sample contains it)
|
||||
if (i < block_samples) {
|
||||
ms_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index);
|
||||
outbuf[sample_count] = (short)(hist1);
|
||||
sample_count+=channelspacing;
|
||||
//todo atenuation: apparently from hcs's analysis Wwise IMA decodes nibbles slightly different, reducing dbs
|
||||
}
|
||||
}
|
||||
|
||||
//internal interleave: increment offset on complete frame
|
||||
if (i == block_samples) stream->offset += vgmstream->interleave_block_size;
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
|
||||
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) {
|
||||
/* MS IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */
|
||||
return (bytes / block_align) * (block_align - 4 * channels) * 2 / channels;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ mpeg_codec_data *init_mpeg_codec_data(STREAMFILE *streamfile, off_t start_offset
|
||||
|
||||
rc = mpg123_decode(main_m, data->buffer,data->buffer_size, NULL,0, &bytes_done);
|
||||
if (rc != MPG123_OK && rc != MPG123_NEW_FORMAT && rc != MPG123_NEED_MORE) {
|
||||
VGM_LOG("MPEG: unable to set up mpp123 @ 0x%08lx to 0x%08lx\n", start_offset, read_offset);
|
||||
VGM_LOG("MPEG: unable to set up mpg123 @ 0x%08lx to 0x%08lx\n", start_offset, read_offset);
|
||||
goto fail; //todo handle MPG123_DONE?
|
||||
}
|
||||
if (read_offset > 0x5000) { /* don't hang in some incorrectly detected formats */
|
||||
@ -172,7 +172,12 @@ mpeg_codec_data *init_mpeg_codec_data_interleaved(STREAMFILE *streamfile, off_t
|
||||
/* get frame size from the first frame as it can be used externally to calc interleave */
|
||||
if ( !update_frame_sizes(data, streamfile, start_offset) )
|
||||
goto fail;
|
||||
//VGM_LOG("ch=%i, sr=%i, spf=%i, v=%i, l=%i\n", data->channels_per_frame, data->sample_rate_per_frame, data->samples_per_frame, info.version, info.layer);
|
||||
|
||||
//mpg123_handle *main_m = data->m;
|
||||
//struct mpg123_frameinfo mi;
|
||||
//mpg123_info(main_m,&mi);
|
||||
//VGM_LOG("mi.framesize=%x\n", mi.framesize);
|
||||
/* unlikely, can fixed with bigger buffer or a feed loop */
|
||||
if (info.frame_size > data->buffer_size)
|
||||
goto fail;
|
||||
@ -406,12 +411,16 @@ static void decode_mpeg_interleave_samples(VGMSTREAMCHANNEL *stream, mpeg_codec_
|
||||
/* decode samples from 1 full frame */
|
||||
do {
|
||||
int rc;
|
||||
//int first_frame = stream->offset == stream->channel_start_offset;
|
||||
|
||||
/* padded frame stuff */
|
||||
rc = update_frame_sizes(data, stream->streamfile, stream->offset);
|
||||
/* ignore any errors and continue; mpg123 will probably sync */
|
||||
VGM_ASSERT(rc==0, "MPEG: frame error @ 0x%08lx (prev size=0x%x / padding=0x%x)\n", stream->offset, data->current_frame_size, data->current_padding);
|
||||
|
||||
|
||||
|
||||
|
||||
/* extra EOF check for edge cases when the caller tries to read more samples than possible */
|
||||
if (stream->offset > stream_size) {
|
||||
memset(data->frame_buffer, 0, data->frame_buffer_size);
|
||||
@ -423,6 +432,7 @@ static void decode_mpeg_interleave_samples(VGMSTREAMCHANNEL *stream, mpeg_codec_
|
||||
/* read more raw data (only 1 frame, to check interleave block end) */
|
||||
if (!data->buffer_full) {
|
||||
data->bytes_in_buffer = read_streamfile(data->buffer,stream->offset,data->current_frame_size,stream->streamfile);
|
||||
//VGM_LOG("read raw: cfs=%x, cp=%x, tot=%x, off=%x\n", data->current_frame_size, data->current_padding, data->current_frame_size + data->current_padding, stream->offset);
|
||||
|
||||
/* end of stream, fill frame buffer with 0s but continue normally with other streams */
|
||||
if (!data->bytes_in_buffer) {
|
||||
@ -438,16 +448,37 @@ static void decode_mpeg_interleave_samples(VGMSTREAMCHANNEL *stream, mpeg_codec_
|
||||
if (block_size && ((stream->offset - stream->channel_start_offset) % block_size==0)) {
|
||||
stream->offset += block_size * (data->ms_size-1); /* skip a block per stream if block done */
|
||||
}
|
||||
|
||||
/*
|
||||
/// TODO: skip very first frame but add fake silence?
|
||||
//todo skip only if first frame is fully decodable? (ie.- blank below)
|
||||
//todo for multich files idem
|
||||
if (first_frame) {
|
||||
data->buffer_full = 0;
|
||||
|
||||
VGM_LOG("skip first frame @ %x - %x\n", stream->offset, stream->channel_start_offset);
|
||||
memset(data->frame_buffer, 0, data->frame_buffer_size);
|
||||
bytes_done = data->frame_buffer_size;
|
||||
break;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/* feed new raw data to the decoder if needed, copy decoded results to frame buffer output */
|
||||
if (!data->buffer_used) {
|
||||
//VGM_LOG("feed unused: cfs=%x, cp=%x, tot=%x, off=%x\n", data->current_frame_size, data->current_padding, data->current_frame_size + data->current_padding, stream->offset);
|
||||
//getchar();
|
||||
|
||||
rc = mpg123_decode(m,
|
||||
data->buffer, data->bytes_in_buffer,
|
||||
(unsigned char *)data->frame_buffer, data->frame_buffer_size,
|
||||
&bytes_done);
|
||||
data->buffer_used = 1;
|
||||
} else {
|
||||
//VGM_LOG("feed used: cfs=%x, cp=%x, tot=%x, off=%x\n", data->current_frame_size, data->current_padding, data->current_frame_size + data->current_padding, stream->offset);
|
||||
//getchar();
|
||||
|
||||
rc = mpg123_decode(m,
|
||||
NULL,0,
|
||||
(unsigned char *)data->frame_buffer, data->frame_buffer_size,
|
||||
@ -517,6 +548,9 @@ static int update_frame_sizes(mpeg_codec_data * data, STREAMFILE *streamfile, of
|
||||
data->current_padding = (data->current_frame_size % data->fsb_padding) ?
|
||||
data->fsb_padding - (data->current_frame_size % data->fsb_padding) : 0;
|
||||
}
|
||||
|
||||
//VGM_LOG("off=%lx, ch=%i, sr=%i, spf=%i, fs=%x, v=%i, l=%i\n", offset, info.channels, info.sample_rate, info.frame_samples, info.frame_size, info.version, info.layer);
|
||||
//getchar();
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -651,7 +685,6 @@ void reset_mpeg(VGMSTREAM *vgmstream) {
|
||||
int i;
|
||||
for (i=0; i < data->ms_size; i++) {
|
||||
mpg123_feedseek(data->ms[i],0,SEEK_SET,&input_offset);
|
||||
vgmstream->loop_ch[i].offset = vgmstream->loop_ch[i].channel_start_offset + input_offset;
|
||||
}
|
||||
|
||||
data->bytes_in_interleave_buffer = 0;
|
||||
@ -667,12 +700,14 @@ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
/* seek multistream */
|
||||
if (!data->interleaved) {
|
||||
mpg123_feedseek(data->m, num_sample,SEEK_SET,&input_offset);
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + input_offset;
|
||||
} else {
|
||||
int i;
|
||||
/* re-start from 0 */
|
||||
for (i=0; i < data->ms_size; i++) {
|
||||
mpg123_feedseek(data->ms[i],0,SEEK_SET,&input_offset);
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[i].offset = vgmstream->loop_ch[i].channel_start_offset;
|
||||
}
|
||||
/* manually add skip samples, since we don't really know the correct offset */
|
||||
@ -795,6 +830,9 @@ static int mpeg_get_frame_info(STREAMFILE *streamfile, off_t offset, mpeg_frame_
|
||||
case 576: info->frame_size = (72l * info->bit_rate * 1000l / info->sample_rate + padding); break;
|
||||
case 1152: info->frame_size = (144l * info->bit_rate * 1000l / info->sample_rate + padding); break;
|
||||
}
|
||||
//int fs2 = (info->frame_samples / 8 * info->bit_rate * 1000l) / info->sample_rate + padding;
|
||||
//VGM_LOG("fs=%x, fs2=%x, fsp=%i, br=%i\n", info->frame_size, fs2, info->frame_samples, info->bit_rate);
|
||||
//getchar();
|
||||
|
||||
info->coding_type = coding_types[info->version-1][info->layer-1];
|
||||
|
||||
|
8747
src/coding/wwise_vorbis_data.h
Normal file
8747
src/coding/wwise_vorbis_data.h
Normal file
File diff suppressed because it is too large
Load Diff
320
src/coding/wwise_vorbis_decoder.c
Normal file
320
src/coding/wwise_vorbis_decoder.c
Normal file
@ -0,0 +1,320 @@
|
||||
#include "coding.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
#define WWISE_VORBIS_ON 1 //todo remove once battle-tested
|
||||
#if WWISE_VORBIS_ON
|
||||
#include "wwise_vorbis_utils.h"
|
||||
|
||||
|
||||
#define WWISE_VORBIS_DEFAULT_BUFFER_SIZE 0x8000 /* should be at least the size of the setup header, ~0x2000 */
|
||||
|
||||
static void pcm_convert_float_to_16(vorbis_codec_data * data, sample * outbuf, int samples_to_do, float ** pcm);
|
||||
static int vorbis_make_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long);
|
||||
static int vorbis_make_header_comment(uint8_t * buf, size_t bufsize);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Inits a raw Wwise vorbis stream.
|
||||
*
|
||||
* Vorbis packets are stored in .ogg, which is divided into ogg pages/packets, and the first packets contain necessary
|
||||
* vorbis setup. For raw vorbis the setup is stored it elsewhere (i.e.- in the .exe), presumably to shave off some kb
|
||||
* per stream and codec startup time. We'll read the external setup and raw data and decode it with libvorbis.
|
||||
*
|
||||
* Wwise stores a reduced setup, and the raw vorbis packets have mini headers with the block size,
|
||||
* and vorbis packets themselves may reduced as well. The format evolved over time so there are some variations.
|
||||
* The Wwise implementation uses Tremor (fixed-point Vorbis) but shouldn't matter.
|
||||
*
|
||||
* Format reverse engineered by hcs in ww2ogg (https://github.com/hcs64/ww2ogg).
|
||||
* Info from the official docs (https://www.xiph.org/vorbis/doc/libvorbis/overview.html).
|
||||
*/
|
||||
vorbis_codec_data * init_wwise_vorbis_codec_data(STREAMFILE *streamFile, off_t start_offset, int channels, int sample_rate, int blocksize_0_exp, int blocksize_1_exp,
|
||||
wwise_setup_type setup_type, wwise_header_type header_type, wwise_packet_type packet_type, int big_endian) {
|
||||
#if WWISE_VORBIS_ON
|
||||
vorbis_codec_data * data = NULL;
|
||||
size_t header_size, packet_size;
|
||||
|
||||
/* init stuff */
|
||||
data = calloc(1,sizeof(vorbis_codec_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->buffer_size = WWISE_VORBIS_DEFAULT_BUFFER_SIZE;
|
||||
data->buffer = calloc(sizeof(uint8_t), data->buffer_size);
|
||||
if (!data->buffer) goto fail;
|
||||
|
||||
data->setup_type = setup_type;
|
||||
data->header_type = header_type;
|
||||
data->packet_type = packet_type;
|
||||
|
||||
/* init vorbis stream state, using 3 fake Ogg setup packets (info, comments, setup/codebooks)
|
||||
* libvorbis expects parsed Ogg pages, but we'll fake them with our raw data instead */
|
||||
vorbis_info_init(&data->vi);
|
||||
vorbis_comment_init(&data->vc);
|
||||
|
||||
data->op.packet = data->buffer;
|
||||
data->op.b_o_s = 1; /* fake headers start */
|
||||
|
||||
if (setup_type == HEADER_TRIAD) {
|
||||
/* read 3 Wwise packets with triad (id/comment/setup), each with a Wwise header */
|
||||
off_t offset = start_offset;
|
||||
|
||||
/* normal identificacion packet */
|
||||
header_size = wwise_vorbis_get_header(streamFile, offset, data->header_type, (int*)&data->op.granulepos, &packet_size, big_endian);
|
||||
if (!header_size || packet_size > data->buffer_size) goto fail;
|
||||
if (read_streamfile(data->buffer,offset+header_size,packet_size, streamFile)!=packet_size) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
offset += header_size + packet_size;
|
||||
|
||||
/* normal comment packet */
|
||||
header_size = wwise_vorbis_get_header(streamFile, offset, data->header_type, (int*)&data->op.granulepos, &packet_size, big_endian);
|
||||
if (!header_size || packet_size > data->buffer_size) goto fail;
|
||||
if (read_streamfile(data->buffer,offset+header_size,packet_size, streamFile)!=packet_size) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
offset += header_size + packet_size;
|
||||
|
||||
/* modified setup packet */ //todo doesn't seem needed, may be copied as -is
|
||||
header_size = wwise_vorbis_get_header(streamFile, offset, data->header_type, (int*)&data->op.granulepos, &packet_size, big_endian);
|
||||
data->op.bytes = wwise_vorbis_rebuild_setup(data->buffer, data->buffer_size, streamFile, offset, data, big_endian, channels);
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
offset += header_size + packet_size;
|
||||
}
|
||||
else {
|
||||
/* rebuild headers */
|
||||
|
||||
/* new identificacion packet */
|
||||
data->op.bytes = vorbis_make_header_identification(data->buffer, data->buffer_size, channels, sample_rate, blocksize_0_exp, blocksize_1_exp);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */
|
||||
|
||||
/* new comment packet */
|
||||
data->op.bytes = vorbis_make_header_comment(data->buffer, data->buffer_size);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */
|
||||
|
||||
/* rebuild setup packet */
|
||||
data->op.bytes = wwise_vorbis_rebuild_setup(data->buffer, data->buffer_size, streamFile, start_offset, data, big_endian, channels);
|
||||
if (!data->op.bytes) goto fail;
|
||||
if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */
|
||||
}
|
||||
|
||||
data->op.b_o_s = 0; /* end of fake headers */
|
||||
|
||||
/* init vorbis global and block state */
|
||||
if (vorbis_synthesis_init(&data->vd,&data->vi) != 0) goto fail;
|
||||
if (vorbis_block_init(&data->vd,&data->vb) != 0) goto fail;
|
||||
|
||||
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
free_wwise_vorbis(data);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes raw Wwise Vorbis
|
||||
*/
|
||||
void decode_wwise_vorbis(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels) {
|
||||
#if WWISE_VORBIS_ON
|
||||
VGMSTREAMCHANNEL *stream = &vgmstream->ch[0];
|
||||
vorbis_codec_data * data = vgmstream->codec_data;
|
||||
size_t stream_size = get_streamfile_size(stream->streamfile);
|
||||
//data->op.packet = data->buffer;/* implicit from init */
|
||||
int samples_done = 0;
|
||||
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
|
||||
/* extra EOF check for edge cases */
|
||||
if (stream->offset > stream_size) {
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (data->samples_full) { /* read more samples */
|
||||
int samples_to_get;
|
||||
float **pcm;
|
||||
|
||||
/* get PCM samples from libvorbis buffers */
|
||||
samples_to_get = vorbis_synthesis_pcmout(&data->vd, &pcm);
|
||||
if (!samples_to_get) {
|
||||
data->samples_full = 0; /* request more if empty*/
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data->samples_to_discard) {
|
||||
/* discard samples for looping */
|
||||
if (samples_to_get > data->samples_to_discard)
|
||||
samples_to_get = data->samples_to_discard;
|
||||
data->samples_to_discard -= samples_to_get;
|
||||
}
|
||||
else {
|
||||
/* get max samples and convert from Vorbis float pcm to 16bit pcm */
|
||||
if (samples_to_get > samples_to_do - samples_done)
|
||||
samples_to_get = samples_to_do - samples_done;
|
||||
pcm_convert_float_to_16(data, outbuf + samples_done * channels, samples_to_get, pcm);
|
||||
samples_done += samples_to_get;
|
||||
}
|
||||
|
||||
/* mark consumed samples from the buffer
|
||||
* (non-consumed samples are returned in next vorbis_synthesis_pcmout calls) */
|
||||
vorbis_synthesis_read(&data->vd, samples_to_get);
|
||||
}
|
||||
else { /* read more data */
|
||||
int rc;
|
||||
size_t header_size, packet_size;
|
||||
|
||||
data->op.packetno++; /* not actually needed, but feels nicer */
|
||||
|
||||
/* reconstruct a Wwise packet, if needed; final bytes may be bigger than packet_size so we get the header offsets here */
|
||||
header_size = wwise_vorbis_get_header(stream->streamfile, stream->offset, data->header_type, (int*)&data->op.granulepos, &packet_size, vgmstream->codec_endian);
|
||||
if (!header_size || packet_size > data->buffer_size) {
|
||||
VGM_LOG("Wwise Vorbis: wrong packet (0x%x) @ %lx\n", packet_size, stream->offset);
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
data->op.bytes = wwise_vorbis_rebuild_packet(data->buffer, data->buffer_size, stream->streamfile,stream->offset, data, vgmstream->codec_endian);
|
||||
stream->offset += header_size + packet_size; /* update first to avoid infinite loops */
|
||||
if (!data->op.bytes || data->op.bytes >= 0xFFFF) {
|
||||
VGM_LOG("Wwise Vorbis: wrong bytes (0x%lx) @ %lx\n", data->op.bytes, stream->offset);
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
/* parse the fake ogg packet into a logical vorbis block */
|
||||
rc = vorbis_synthesis(&data->vb,&data->op);
|
||||
if (rc == OV_ENOTAUDIO) {
|
||||
VGM_LOG("Wwise Vorbis: not audio packet @ %lx\n",stream->offset);
|
||||
continue; /* bad packet parsing */
|
||||
} else if (rc != 0) {
|
||||
VGM_LOG("Wwise Vorbis: cannot parse Vorbis block @ %lx\n",stream->offset);
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
/* finally decode the logical block into samples */
|
||||
rc = vorbis_synthesis_blockin(&data->vd,&data->vb);
|
||||
if (rc != 0) {
|
||||
VGM_LOG("Wwise Vorbis: cannot decode Vorbis block @ %lx\n",stream->offset);
|
||||
goto decode_fail; /* ? */
|
||||
}
|
||||
|
||||
data->samples_full = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
decode_fail:
|
||||
/* on error just put some 0 samples */
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WWISE_VORBIS_ON
|
||||
|
||||
static void pcm_convert_float_to_16(vorbis_codec_data * data, sample * outbuf, int samples_to_do, float ** pcm) {
|
||||
/* mostly from Xiph's decoder_example.c */
|
||||
int i,j;
|
||||
|
||||
/* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc)
|
||||
* to 16 bit signed PCM ints (host order) and interleave + fix clipping */
|
||||
for (i = 0; i < data->vi.channels; i++) {
|
||||
sample *ptr = outbuf + i;
|
||||
float *mono = pcm[i];
|
||||
for (j = 0; j < samples_to_do; j++) {
|
||||
int val = floor(mono[j] * 32767.f + .5f);
|
||||
if (val > 32767) val = 32767;
|
||||
if (val < -32768) val = -32768;
|
||||
|
||||
*ptr = val;
|
||||
ptr += data->vi.channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* *************************************************** */
|
||||
|
||||
static int vorbis_make_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_0_exp, int blocksize_1_exp) {
|
||||
int bytes = 0x1e;
|
||||
uint8_t blocksizes;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
|
||||
blocksizes = (blocksize_0_exp << 4) | (blocksize_1_exp);
|
||||
|
||||
put_8bit (buf+0x00, 0x01); /* packet_type (id) */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */
|
||||
put_8bit (buf+0x0b, channels); /* audio_channels */
|
||||
put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */
|
||||
put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */
|
||||
put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */
|
||||
put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */
|
||||
put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */
|
||||
put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int vorbis_make_header_comment(uint8_t * buf, size_t bufsize) {
|
||||
int bytes = 0x19;
|
||||
|
||||
if (bytes > bufsize) return 0;
|
||||
|
||||
put_8bit (buf+0x00, 0x03); /* packet_type (comments) */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_32bitLE(buf+0x07, 0x09); /* vendor_length */
|
||||
memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */
|
||||
put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */
|
||||
put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
void free_wwise_vorbis(vorbis_codec_data * data) {
|
||||
#if WWISE_VORBIS_ON
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
/* internal decoder cleanp */
|
||||
vorbis_info_clear(&data->vi);
|
||||
vorbis_comment_clear(&data->vc);
|
||||
vorbis_dsp_clear(&data->vd);
|
||||
|
||||
free(data->buffer);
|
||||
free(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset_wwise_vorbis(VGMSTREAM *vgmstream) {
|
||||
#if WWISE_VORBIS_ON
|
||||
vorbis_codec_data *data = vgmstream->codec_data;
|
||||
|
||||
/* Seeking is provided by the Ogg layer, so with raw vorbis we need seek tables instead.
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void seek_wwise_vorbis(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
#if WWISE_VORBIS_ON
|
||||
vorbis_codec_data *data = vgmstream->codec_data;
|
||||
|
||||
/* Seeking is provided by the Ogg layer, so with raw vorbis we need seek tables instead.
|
||||
* To avoid having to parse different formats we'll just discard until the expected sample */
|
||||
vorbis_synthesis_restart(&data->vd);
|
||||
data->samples_to_discard = num_sample;
|
||||
if (vgmstream->loop_ch) /* this func is only using for looping though */
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
1220
src/coding/wwise_vorbis_utils.c
Normal file
1220
src/coding/wwise_vorbis_utils.c
Normal file
File diff suppressed because it is too large
Load Diff
11
src/coding/wwise_vorbis_utils.h
Normal file
11
src/coding/wwise_vorbis_utils.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef _WWISE_VORBIS_UTILS_H_
|
||||
#define _WWISE_VORBIS_UTILS_H_
|
||||
|
||||
#include "coding.h"
|
||||
|
||||
int wwise_vorbis_get_header(STREAMFILE *streamFile, off_t offset, wwise_header_type header_type, int * granulepos, size_t * packet_size, int big_endian);
|
||||
int wwise_vorbis_rebuild_packet(uint8_t * obuf, size_t obufsize, STREAMFILE *streamFile, off_t offset, vorbis_codec_data * data, int big_endian);
|
||||
int wwise_vorbis_rebuild_setup(uint8_t * obuf, size_t obufsize, STREAMFILE *streamFile, off_t offset, vorbis_codec_data * data, int big_endian, int channels);
|
||||
|
||||
|
||||
#endif/*_WWISE_VORBIS_UTILS_H_ */
|
@ -420,6 +420,7 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_SNDS_IMA, "Heavy Iron .snds 4-bit IMA ADPCM"},
|
||||
{coding_OTNS_IMA, "Omikron: The Nomad Soul 4-bit IMA ADPCM"},
|
||||
{coding_FSB_IMA, "FSB multichannel 4-bit IMA ADPCM"},
|
||||
{coding_WWISE_IMA, "Audiokinetic Wwise 4-bit IMA ADPCM"},
|
||||
{coding_WS, "Westwood Studios ADPCM"},
|
||||
{coding_ACM, "InterPlay ACM"},
|
||||
{coding_NWA0, "NWA DPCM Level 0"},
|
||||
@ -438,6 +439,7 @@ static const coding_info coding_info_list[] = {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{coding_ogg_vorbis, "Ogg Vorbis"},
|
||||
{coding_fsb_vorbis, "FSB Vorbis"},
|
||||
{coding_wwise_vorbis, "Wwise Vorbis"},
|
||||
#endif
|
||||
#ifdef VGM_USE_MPEG
|
||||
{coding_fake_MPEG2_L2, "MPEG-2 Layer II Audio"},
|
||||
|
@ -1366,6 +1366,14 @@
|
||||
RelativePath=".\coding\sdx2_decoder.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\wwise_vorbis_decoder.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\wwise_vorbis_utils.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\ws_decoder.c"
|
||||
>
|
||||
|
@ -421,6 +421,8 @@
|
||||
<ClCompile Include="coding\SASSC_decoder.c" />
|
||||
<ClCompile Include="coding\sdx2_decoder.c" />
|
||||
<ClCompile Include="coding\ws_decoder.c" />
|
||||
<ClCompile Include="coding\wwise_vorbis_decoder.c" />
|
||||
<ClCompile Include="coding\wwise_vorbis_utils.c" />
|
||||
<ClCompile Include="coding\xa_decoder.c" />
|
||||
<ClCompile Include="layout\aax_layout.c" />
|
||||
<ClCompile Include="layout\aix_layout.c" />
|
||||
|
@ -808,6 +808,12 @@
|
||||
<ClCompile Include="coding\ws_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\wwise_vorbis_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\wwise_vorbis_utils.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\xa_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
@ -362,6 +362,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x4A554E4B: /* JUNK */
|
||||
/* JUNK is an optional Wwise chunk, and Wwise hijacks the MSADPCM/MS_IMA/XBOX IMA ids (how nice).
|
||||
* To ensure their stuff is parsed in wwise.c we reject their JUNK, which they put almost always. */
|
||||
goto fail;
|
||||
default:
|
||||
/* ignorance is bliss */
|
||||
break;
|
||||
@ -687,6 +691,10 @@ VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) {
|
||||
if (chunk_size != 4) break;
|
||||
//fact_sample_count = read_32bitBE(current_chunk+8, streamFile);
|
||||
break;
|
||||
case 0x4A554E4B: /* JUNK */
|
||||
/* JUNK is an optional Wwise chunk, and Wwise hijacks the MSADPCM/MS_IMA/XBOX IMA ids (how nice).
|
||||
* To ensure their stuff is parsed in wwise.c we reject their JUNK, which they put almost always. */
|
||||
goto fail;
|
||||
default:
|
||||
/* ignorance is bliss */
|
||||
break;
|
||||
|
277
src/meta/wwise.c
277
src/meta/wwise.c
@ -12,6 +12,7 @@ typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9 } wwise_cod
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
size_t file_size;
|
||||
int truncated;
|
||||
|
||||
/* chunks references */
|
||||
off_t fmt_offset;
|
||||
@ -46,8 +47,8 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) {
|
||||
|
||||
/* basic checks */
|
||||
/* .wem (Wwise Encoded Media) is "newer Wwise", used after the 2011.2 SDK (~july)
|
||||
* .wav (ex. Shadowrun X360) and .ogg (ex. KOF XII X360) are used only in older Wwise */
|
||||
if (!check_extensions(streamFile,"wem,wav,lwav,ogg,logg")) goto fail; /* .xma may be possible? */
|
||||
* .wav (ex. Shadowrun X360) and .ogg (ex. KOF XII X360), .xma (ex. Tron Evolution X360) are used in older Wwise */
|
||||
if (!check_extensions(streamFile,"wem,wav,lwav,ogg,logg,xma")) goto fail;
|
||||
|
||||
if ((read_32bitBE(0x00,streamFile) != 0x52494646) && /* "RIFF" (LE) */
|
||||
(read_32bitBE(0x00,streamFile) != 0x52494658)) /* "RIFX" (BE) */
|
||||
@ -87,11 +88,6 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) {
|
||||
if (!find_chunk(streamFile, 0x666d7420,first_offset,0, &ww.fmt_offset,&ww.fmt_size, ww.big_endian, 0)) goto fail; /*"fmt "*/
|
||||
if (!find_chunk(streamFile, 0x64617461,first_offset,0, &ww.data_offset,&ww.data_size, ww.big_endian, 0)) goto fail; /*"data"*/
|
||||
|
||||
if (ww.data_size > ww.file_size) {
|
||||
VGM_LOG("WWISE: bad data size (real=0x%x > riff=0x%x)\n", ww.data_size, ww.file_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* base fmt */
|
||||
if (ww.fmt_size < 0x12) goto fail;
|
||||
ww.format = (uint16_t)read_16bit(ww.fmt_offset+0x00,streamFile);
|
||||
@ -140,8 +136,8 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) {
|
||||
switch(ww.format) {
|
||||
case 0x0001: ww.codec = PCM; break; /* older Wwise */
|
||||
case 0x0002: ww.codec = IMA; break; /* newer Wwise (conflicts with MSADPCM, probably means "platform's ADPCM") */
|
||||
case 0x0011: ww.codec = IMA; break; /* older Wwise */
|
||||
case 0x0069: ww.codec = IMA; break; /* older Wwise */
|
||||
//case 0x0011: ww.codec = IMA; break; /* older Wwise (used?) */
|
||||
case 0x0069: ww.codec = IMA; break; /* older Wwise (Spiderman Web of Shadows X360) */
|
||||
case 0x0161: ww.codec = XWMA; break;
|
||||
case 0x0162: ww.codec = XWMA; break;
|
||||
case 0x0165: ww.codec = XMA2; break; /* always with the "XMA2" chunk, Wwise doesn't use XMA1 */
|
||||
@ -161,6 +157,17 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) {
|
||||
ww.codec = DSP;
|
||||
}
|
||||
|
||||
/* Some Wwise files (ex. Oddworld PSV, Bayonetta 2 WiiU sfx) are truncated mirrors of another file.
|
||||
* They come in RAM banks, probably to play the beginning while the rest of the real stream loads.
|
||||
* We'll add basic support to avoid complaints of this or that .wem not playing */
|
||||
if (ww.data_size > ww.file_size) {
|
||||
//VGM_LOG("WWISE: truncated data size (prefetch): (real=0x%x > riff=0x%x)\n", ww.data_size, ww.file_size);
|
||||
if (ww.codec == IMA || ww.codec == VORBIS) /* only seen in those, probably other exist */
|
||||
ww.truncated = 1;
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(ww.channels,ww.loop_flag);
|
||||
@ -186,83 +193,167 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) {
|
||||
break;
|
||||
|
||||
case IMA: /* common */
|
||||
/* slightly modified MS-IMA.
|
||||
* Original research by hcs in ima_rejigger (https://github.com/hcs64/vgm_ripping/tree/master/demux/ima_rejigger5) */
|
||||
#if 0
|
||||
/* slightly modified MS IMA with interleaved sub-blocks and LE/BE header */
|
||||
|
||||
/* Wwise uses common codecs (ex. 0x0002 MSADPCM) so this parser should go AFTER riff.c avoid misdetection */
|
||||
|
||||
if (ww.bits_per_sample != 4) goto fail;
|
||||
vgmstream->coding_type = coding_WWISE_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->interleave_block_size = ww.block_align;
|
||||
vgmstream->codec_endian = ww.big_endian;
|
||||
|
||||
vgmstream->num_samples = (ww.data_size / ww.block_align) * (ww.block_align - 4 * vgmstream->channels) * 2 /vgmstream->channels;//todo ms_ima_bytes_to_samples
|
||||
if (ww.truncated) /* enough to get real samples */
|
||||
ww.data_size = ww.file_size - ww.data_offset;
|
||||
|
||||
vgmstream->num_samples = ms_ima_bytes_to_samples(ww.data_size, ww.block_align, ww.channels);
|
||||
break;
|
||||
#endif
|
||||
VGM_LOG("WWISE: IMA found (unsupported)\n");
|
||||
goto fail;
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case VORBIS: { /* common */
|
||||
/* Wwise uses custom Vorbis, which changed over time (config must be detected to pass to the decoder).
|
||||
* Original research by hcs in ww2ogg (https://github.com/hcs64/ww2ogg) */
|
||||
#if 0
|
||||
off_t vorb_offset;
|
||||
size_t vorb_size;
|
||||
/* Wwise uses custom Vorbis, which changed over time (config must be detected to pass to the decoder). */
|
||||
off_t vorb_offset, data_offsets, block_offsets;
|
||||
size_t vorb_size, setup_offset, audio_offset;
|
||||
|
||||
int packet_header_type = 0; /* 1 = size 8 (4-granule + 4-size), 2 = size 6 (4-granule + 2-size) or 3 = size 2 (2-size) */
|
||||
int packet_type = 0; /* 1 = standard, 2 = modified vorbis packets */
|
||||
int setup_type = 0; /* 1: triad, 2 = inline codebooks, 3 = external codebooks, 4 = external aoTuV codebooks */
|
||||
int blocksize_0_pow = 0, blocksize_1_pow = 0;
|
||||
wwise_setup_type setup_type;
|
||||
wwise_header_type header_type;
|
||||
wwise_packet_type packet_type;
|
||||
int blocksize_0_exp = 0, blocksize_1_exp = 0;
|
||||
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */
|
||||
|
||||
/* autodetect format */
|
||||
/* autodetect format (field are mostly common, see the end of the file) */
|
||||
if (find_chunk(streamFile, 0x766F7262,first_offset,0, &vorb_offset,&vorb_size, ww.big_endian, 0)) { /*"vorb"*/
|
||||
/* older Wwise (~2011) */
|
||||
switch (vorb_size) {
|
||||
case 0x28:
|
||||
case 0x2A:
|
||||
case 0x2C:
|
||||
case 0x32:
|
||||
case 0x34:
|
||||
default: goto fail;
|
||||
/* older Wwise (~<2012) */
|
||||
|
||||
switch(vorb_size) {
|
||||
#if 0
|
||||
case 0x28: /* early (~2009), some Divinity II files? */
|
||||
case 0x2C: /* early (~2009), some EVE Online Apocrypha files? */
|
||||
data_offsets = 0x18;
|
||||
block_offsets = 0; /* no need, full headers are present */
|
||||
header_type = TYPE_8;
|
||||
packet_type = STANDARD;
|
||||
setup_type = HEADER_TRIAD;
|
||||
break;
|
||||
|
||||
case 0x32: /* ? */
|
||||
#endif
|
||||
case 0x34: /* common (2010~2011) */
|
||||
data_offsets = 0x18;
|
||||
block_offsets = 0x30;
|
||||
header_type = TYPE_6;
|
||||
packet_type = STANDARD;
|
||||
/* setup_type: autodetect later */
|
||||
break;
|
||||
case 0x2a: /* infamous 2 (mid 2011) */
|
||||
data_offsets = 0x10;
|
||||
block_offsets = 0x28;
|
||||
header_type = TYPE_2;
|
||||
packet_type = MODIFIED;
|
||||
setup_type = EXTERNAL_CODEBOOKS;
|
||||
break;
|
||||
default:
|
||||
VGM_LOG("WWISE: unknown vorb size 0x%x\n", vorb_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_samples = read_32bit(vorb_offset + 0x00, streamFile);
|
||||
setup_offset = read_32bit(vorb_offset + data_offsets + 0x00, streamFile); /* within data (0 = no seek table) */
|
||||
audio_offset = read_32bit(vorb_offset + data_offsets + 0x04, streamFile); /* within data */
|
||||
if (block_offsets) {
|
||||
blocksize_1_exp = read_8bit(vorb_offset + block_offsets + 0x00, streamFile); /* small */
|
||||
blocksize_0_exp = read_8bit(vorb_offset + block_offsets + 0x01, streamFile); /* big */
|
||||
}
|
||||
|
||||
/* detect setup type:
|
||||
* - full inline: ~2009, ex. The King of Fighters XII X360, The Saboteur PC
|
||||
* - trimmed inline: ~2010, ex. Army of Two: 40 days X360 (some multiplayer files)
|
||||
* - external: ~2010, ex. Assassin's Creed Brotherhood X360, Dead Nation X360 */
|
||||
if (vorb_size == 0x34) {
|
||||
size_t setup_size = (uint16_t)read_16bit(start_offset + setup_offset, streamFile);
|
||||
uint32_t id = (uint32_t)read_32bitBE(start_offset + setup_offset + 0x06, streamFile);
|
||||
|
||||
/* if the setup after header starts with "(data)BCV" it's an inline codebook) */
|
||||
if ((id & 0x00FFFFFF) == 0x00424356) { /* 0"BCV" */
|
||||
setup_type = FULL_SETUP;
|
||||
}
|
||||
/* if the setup is suspiciously big it's probably trimmed inline codebooks */
|
||||
else if (setup_size > 0x200) { /* an external setup it's ~0x100 max + some threshold */
|
||||
setup_type = INLINE_CODEBOOKS;
|
||||
}
|
||||
else {
|
||||
/* newer Wwise (~2012+) */
|
||||
if (ww.extra_size != 0x30) goto fail; //todo some 0x2a exist
|
||||
setup_type = EXTERNAL_CODEBOOKS;
|
||||
}
|
||||
}
|
||||
|
||||
packet_header_type = 3; /* size 2 */
|
||||
packet_type = 1; //todo mod packets false on certain configs
|
||||
setup_type = 4; /* aoTuV */
|
||||
vgmstream->codec_data = init_wwise_vorbis_codec_data(streamFile, start_offset + setup_offset, ww.channels, ww.sample_rate, blocksize_0_exp,blocksize_1_exp,
|
||||
setup_type,header_type,packet_type, ww.big_endian);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
}
|
||||
else {
|
||||
/* newer Wwise (>2012) */
|
||||
off_t extra_offset = ww.fmt_offset + 0x18; /* after flag + channels */
|
||||
int is_wem = check_extensions(streamFile,"wem");
|
||||
|
||||
/* 0x12 (2): unk (00,10,18) not related to seek table*/
|
||||
/* 0x14 (4): channel config */
|
||||
vgmstream->num_samples = read_32bit(ww.fmt_offset + 0x18, streamFile);
|
||||
/* 0x20 (4): config, 0xcb/0xd9/etc */
|
||||
/* 0x24 (4): ? samples? */
|
||||
/* 0x28 (4): seek table size (setup packet offset within data) */
|
||||
/* 0x2c (4): setup size (first audio packet offset within data) */
|
||||
/* 0x30 (2): ? */
|
||||
/* 0x32 (2): ? */
|
||||
/* 0x34 (4): ? */
|
||||
/* 0x38 (4): ? */
|
||||
/* 0x3c (4): uid */ //todo same as external crc32?
|
||||
blocksize_0_pow = read_8bit(ww.fmt_offset + 0x40, streamFile);
|
||||
blocksize_1_pow = read_8bit(ww.fmt_offset + 0x41, streamFile);
|
||||
switch(ww.extra_size) {
|
||||
case 0x30:
|
||||
data_offsets = 0x10;
|
||||
block_offsets = 0x28;
|
||||
header_type = TYPE_2;
|
||||
packet_type = MODIFIED;
|
||||
//packet_type = STANDARD;
|
||||
|
||||
/* setup not detectable by header, so we'll try both; hopefully libvorbis will reject wrong codebooks
|
||||
* - standard: early (<2012), ex. The King of Fighters XIII X360 (2011/11), .ogg
|
||||
* - aoTuV603: later (>2012), ex. Sonic & All-Stars Racing Transformed PC (2012/11), .wem */
|
||||
setup_type = is_wem ? AOTUV603_CODEBOOKS : EXTERNAL_CODEBOOKS; /* aoTuV came along .wem */
|
||||
break;
|
||||
default:
|
||||
//todo apparently some 0x2a + non mod_packets exist
|
||||
//todo TYPE_06 possibly detectable by checking setup's granule (should be 0)
|
||||
VGM_LOG("WWISE: unknown extra size 0x%x\n", vorb_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_samples = read_32bit(extra_offset + 0x00, streamFile);
|
||||
setup_offset = read_32bit(extra_offset + data_offsets + 0x00, streamFile); /* within data*/
|
||||
audio_offset = read_32bit(extra_offset + data_offsets + 0x04, streamFile); /* within data */
|
||||
blocksize_1_exp = read_8bit(extra_offset + block_offsets + 0x00, streamFile); /* small */
|
||||
blocksize_0_exp = read_8bit(extra_offset + block_offsets + 0x01, streamFile); /* big */
|
||||
|
||||
vgmstream->codec_data = init_wwise_vorbis_codec_data(streamFile, start_offset, vgmstream->channels, vgmstream->sample_rate);//pass endianness too
|
||||
/* Normal packets are used rarely (ex. Oddworld New 'n' Tasty! PSV). They are hard to detect (decoding
|
||||
* will mostly work with garbage results) but we'll try. Setup size and "fmt" bitrate fields may matter too. */
|
||||
if (ww.extra_size == 0x30) {
|
||||
/* all blocksizes I've seen are 0x08+0x0B except Oddworld PSV, that uses 0x09+0x09
|
||||
* (maybe lower spec machines = needs simpler packets) */
|
||||
if (blocksize_0_exp == blocksize_1_exp)
|
||||
packet_type = STANDARD;
|
||||
}
|
||||
|
||||
|
||||
/* try with the selected codebooks */
|
||||
vgmstream->codec_data = init_wwise_vorbis_codec_data(streamFile, start_offset + setup_offset, ww.channels, ww.sample_rate, blocksize_0_exp,blocksize_1_exp,
|
||||
setup_type,header_type,packet_type, ww.big_endian);
|
||||
if (!vgmstream->codec_data) {
|
||||
/* codebooks failed: try again with the other type */
|
||||
setup_type = is_wem ? EXTERNAL_CODEBOOKS : AOTUV603_CODEBOOKS;
|
||||
vgmstream->codec_data = init_wwise_vorbis_codec_data(streamFile, start_offset + setup_offset, ww.channels, ww.sample_rate, blocksize_0_exp,blocksize_1_exp,
|
||||
setup_type,header_type,packet_type, ww.big_endian);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
}
|
||||
}
|
||||
vgmstream->coding_type = coding_wwise_vorbis;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->codec_endian = ww.big_endian;
|
||||
|
||||
start_offset = start_offset + audio_offset;
|
||||
|
||||
/* Vorbis is VBR so this is very approximate, meh */
|
||||
if (ww.truncated)
|
||||
vgmstream->num_samples = 500;// vgmstream->num_samples * (ww.file_size - start_offset) / ww.data_size;
|
||||
|
||||
break;
|
||||
#endif
|
||||
VGM_LOG("WWISE: VORBIS found (unsupported)\n");
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -324,8 +415,9 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) {
|
||||
|
||||
vgmstream->num_samples = ww.num_samples; /* set while parsing XMAWAVEFORMATs */
|
||||
|
||||
/* "XMAc": rare Wwise extension, XMA2 physical loop regions (loop_start_b, loop_end_b, loop_subframe_data) */
|
||||
VGM_ASSERT(find_chunk(streamFile, 0x584D4163,first_offset,0, NULL,NULL, ww.big_endian, 0), "WWISE: XMAc chunk found\n");
|
||||
/* "XMAc": rare Wwise extension, XMA2 physical loop regions (loop_start_b, loop_end_b, loop_subframe_data)
|
||||
* Can appear even in the file doesn't loop, maybe it's meant to be the playable physical region */
|
||||
//VGM_ASSERT(find_chunk(streamFile, 0x584D4163,first_offset,0, NULL,NULL, ww.big_endian, 0), "WWISE: XMAc chunk found\n");
|
||||
/* other chunks: "seek", regular XMA2 seek table */
|
||||
|
||||
break;
|
||||
@ -415,3 +507,68 @@ fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* VORBIS FORMAT RESEARCH */
|
||||
/*
|
||||
- old format
|
||||
"fmt" size 0x28, extra size 0x16 / size 0x18, extra size 0x06
|
||||
0x12 (2): flag? (00,10,18): not related to seek table, codebook type, chunk count, looping
|
||||
0x14 (4): channel config
|
||||
0x18-24 (16): ? (fixed: 0x01000000 00001000 800000AA 00389B71) [removed when extra size is 0x06]
|
||||
|
||||
"vorb" size 0x34
|
||||
0x00 (4): num_samples
|
||||
0x04 (4): skip samples?
|
||||
0x08 (4): ? (small if loop, 0 otherwise)
|
||||
0x0c (4): loop offset after seek table+setup (offset after setup if file doesn't loop)
|
||||
0x10 (4): ? (small, 0..~0x400)
|
||||
0x14 (4): approximate data size without seek table? (almost setup+packets)
|
||||
0x18 (4): setup_offset within data (0 = no seek table)
|
||||
0x1c (4): audio_offset within data
|
||||
0x20 (4): biggest packet size (not including header)?
|
||||
0x24 (4): ? (mid, 0~0x5000)
|
||||
0x28 (4): ? (mid, 0~0x5000)
|
||||
0x2c (4): parent bank/event id? (shared by several .wem a game, but not all need to share it)
|
||||
0x30 (1): blocksize_1_exp (small)
|
||||
0x31 (1): blocksize_0_exp (large)
|
||||
0x32 (2): empty
|
||||
|
||||
"vorb" size 0x2a
|
||||
0x00 (4): num_samples
|
||||
0x04 (4): loop offset after seek table+setup (offset after setup if file doesn't loop)
|
||||
0x08 (4): data size without seek table (setup+packets)
|
||||
0x0c (2): ? (small, 0..~0x400)
|
||||
0x10 (4): setup_offset within data (0 = no seek table)
|
||||
0x14 (4): audio_offset within data
|
||||
0x18 (2): ? (small, 0..~0x400)
|
||||
0x1a (2): ? (small, N..~0x100)
|
||||
0x1c (4): ? (mid, 0~0x5000)
|
||||
0x20 (4): ? (mid, 0~0x5000)
|
||||
0x24 (4): parent bank/event id? (shared by several .wem a game, but not all need to share it)
|
||||
0x28 (1): blocksize_1_exp (small)
|
||||
0x29 (1): blocksize_0_exp (large)
|
||||
|
||||
|
||||
- new format:
|
||||
"fmt" size 0x42, extra size 0x30
|
||||
0x12 (2): flag? (00,10,18): not related to seek table, codebook type, chunk count, looping, etc
|
||||
0x14 (4): channel config
|
||||
0x18 (4): num_samples
|
||||
0x1c (4): loop offset after seek table+setup (offset after setup if file doesn't loop)
|
||||
0x20 (4): data size without seek table (setup+packets)
|
||||
0x24 (2): ?1 (small, 0..~0x400)
|
||||
0x26 (2): ?2 (small, N..~0x100): not related to seek table, codebook type, chunk count, looping, packet size, samples, etc
|
||||
0x28 (4): setup offset within data (0 = no seek table)
|
||||
0x2c (4): audio offset within data
|
||||
0x30 (2): biggest packet size (not including header)
|
||||
0x32 (2): ?4 (small, 0..~0x100): may be same than ?2 (something related to the avg bitrate?)
|
||||
0x34 (4): bitrate config? (mid, 0~0x5000)
|
||||
0x38 (4): bitrate config? (mid, 0~0x5000) (2 byte with max/min?)
|
||||
0x40 (1): blocksize_1_exp (small)
|
||||
0x41 (1): blocksize_0_exp (large)
|
||||
|
||||
Wwise encoder options, unknown fields above may be reflect these:
|
||||
https://www.audiokinetic.com/library/edge/?source=Help&id=vorbis_encoder_parameters
|
||||
*/
|
||||
|
@ -134,7 +134,6 @@ static int parse_header(xma_header_data * xma, STREAMFILE *streamFile) {
|
||||
int big_endian = 0;
|
||||
enum {
|
||||
id_RIFF = UINT32_C(0x52494646), /* "RIFF" */
|
||||
id_RIFX = UINT32_C(0x52494658), /* "RIFX" */
|
||||
id_NXMA = UINT32_C(0x786D6100), /* "xma\0" */
|
||||
id_PASX = UINT32_C(0x50415358), /* "PASX" */
|
||||
};
|
||||
@ -145,7 +144,6 @@ static int parse_header(xma_header_data * xma, STREAMFILE *streamFile) {
|
||||
switch (id) {
|
||||
case id_RIFF:
|
||||
break;
|
||||
case id_RIFX:
|
||||
case id_NXMA:
|
||||
case id_PASX:
|
||||
big_endian = 1;
|
||||
@ -168,7 +166,7 @@ static int parse_header(xma_header_data * xma, STREAMFILE *streamFile) {
|
||||
xma->file_size = streamFile->get_size(streamFile);
|
||||
|
||||
/* find offsets */
|
||||
if (id == id_RIFF || id == id_RIFX) { /* regular RIFF header / RIFX (BE, wwsize?) */
|
||||
if (id == id_RIFF) { /* regular RIFF header */
|
||||
off_t current_chunk = 0xc;
|
||||
off_t fmt_offset = 0, xma2_offset = 0;
|
||||
size_t riff_size = 0, fmt_size = 0, xma2_size = 0;
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "streamtypes.h"
|
||||
|
||||
int check_sample_rate(int32_t sr) {
|
||||
return !(sr<1000 || sr>96000);
|
||||
return !(sr<300 || sr>96000);
|
||||
}
|
||||
|
||||
const char * filename_extension(const char * filename) {
|
||||
|
@ -479,6 +479,10 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
|
||||
if (vgmstream->coding_type==coding_fsb_vorbis) {
|
||||
reset_fsb_vorbis(vgmstream);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type==coding_wwise_vorbis) {
|
||||
reset_wwise_vorbis(vgmstream);
|
||||
}
|
||||
#endif
|
||||
if (vgmstream->coding_type==coding_CRI_HCA) {
|
||||
hca_codec_data *data = vgmstream->codec_data;
|
||||
@ -682,6 +686,11 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
|
||||
free_fsb_vorbis(vgmstream->codec_data);
|
||||
vgmstream->codec_data = NULL;
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type==coding_wwise_vorbis) {
|
||||
free_wwise_vorbis(vgmstream->codec_data);
|
||||
vgmstream->codec_data = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (vgmstream->coding_type==coding_CRI_HCA) {
|
||||
@ -1003,6 +1012,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case coding_ogg_vorbis:
|
||||
case coding_fsb_vorbis:
|
||||
case coding_wwise_vorbis:
|
||||
#endif
|
||||
#ifdef VGM_USE_MPEG
|
||||
case coding_fake_MPEG2_L2:
|
||||
@ -1073,6 +1083,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
return 64;
|
||||
case coding_MS_IMA:
|
||||
case coding_RAD_IMA:
|
||||
case coding_WWISE_IMA:
|
||||
return (vgmstream->interleave_block_size-4*vgmstream->channels)*2/vgmstream->channels;
|
||||
case coding_RAD_IMA_mono:
|
||||
return 32;
|
||||
@ -1162,6 +1173,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
case coding_RAD_IMA:
|
||||
case coding_NDS_IMA:
|
||||
case coding_DAT4_IMA:
|
||||
case coding_WWISE_IMA:
|
||||
return vgmstream->interleave_block_size;
|
||||
case coding_RAD_IMA_mono:
|
||||
return 0x14;
|
||||
@ -1487,6 +1499,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
buffer+samples_written*vgmstream->channels,samples_to_do,
|
||||
vgmstream->channels);
|
||||
break;
|
||||
|
||||
case coding_wwise_vorbis:
|
||||
decode_wwise_vorbis(vgmstream,
|
||||
buffer+samples_written*vgmstream->channels,samples_to_do,
|
||||
vgmstream->channels);
|
||||
break;
|
||||
#endif
|
||||
case coding_CRI_HCA:
|
||||
decode_hca(vgmstream->codec_data,
|
||||
@ -1587,6 +1605,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
samples_to_do,chan);
|
||||
}
|
||||
break;
|
||||
case coding_WWISE_IMA:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
decode_wwise_ima(vgmstream,&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||
vgmstream->channels,vgmstream->samples_into_block,
|
||||
samples_to_do,chan);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_WS:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
@ -1801,6 +1826,10 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) {
|
||||
if (vgmstream->coding_type==coding_fsb_vorbis) {
|
||||
seek_fsb_vorbis(vgmstream, vgmstream->loop_start_sample);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type==coding_wwise_vorbis) {
|
||||
seek_wwise_vorbis(vgmstream, vgmstream->loop_start_sample);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
@ -121,9 +121,10 @@ typedef enum {
|
||||
coding_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */
|
||||
coding_OTNS_IMA, /* Omikron The Nomad Soul IMA ADPCM */
|
||||
coding_FSB_IMA, /* FMOD's FSB multichannel IMA ADPCM */
|
||||
coding_WWISE_IMA, /* Audiokinetic Wwise IMA ADPCM */
|
||||
|
||||
coding_WS, /* Westwood Studios VBR ADPCM */
|
||||
coding_MSADPCM, /* Microsoft ADPCM */
|
||||
coding_WS, /* Westwood Studios VBR ADPCM */
|
||||
coding_AICA, /* Yamaha AICA ADPCM */
|
||||
coding_L5_555, /* Level-5 0x555 ADPCM */
|
||||
coding_SASSC, /* Activision EXAKT SASSC DPCM */
|
||||
@ -150,6 +151,7 @@ typedef enum {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
coding_ogg_vorbis, /* Xiph Vorbis (MDCT-based) */
|
||||
coding_fsb_vorbis, /* FMOD's Vorbis without Ogg layer */
|
||||
coding_wwise_vorbis, /* Audiokinetic's Vorbis without Ogg layer */
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
@ -721,6 +723,8 @@ typedef struct {
|
||||
off_t loop_next_block_offset; /* saved from next_block_offset */
|
||||
|
||||
/* decoder specific */
|
||||
int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */
|
||||
|
||||
uint8_t xa_channel; /* XA ADPCM: selected channel */
|
||||
int32_t xa_sector_length; /* XA ADPCM: XA block */
|
||||
uint8_t xa_headerless; /* XA ADPCM: headerless XA block */
|
||||
@ -768,6 +772,11 @@ typedef struct {
|
||||
ogg_vorbis_streamfile ov_streamfile;
|
||||
} ogg_vorbis_codec_data;
|
||||
|
||||
/* config for Wwise Vorbis */
|
||||
typedef enum { HEADER_TRIAD, FULL_SETUP, INLINE_CODEBOOKS, EXTERNAL_CODEBOOKS, AOTUV603_CODEBOOKS } wwise_setup_type;
|
||||
typedef enum { TYPE_8, TYPE_6, TYPE_2 } wwise_header_type;
|
||||
typedef enum { STANDARD, MODIFIED } wwise_packet_type;
|
||||
|
||||
/* any raw Vorbis without Ogg layer */
|
||||
typedef struct {
|
||||
vorbis_info vi; /* stream settings */
|
||||
@ -780,6 +789,16 @@ typedef struct {
|
||||
size_t buffer_size;
|
||||
size_t samples_to_discard; /* for looping purposes */
|
||||
int samples_full; /* flag, samples available in vorbis buffers */
|
||||
|
||||
/* Wwise Vorbis config */
|
||||
wwise_setup_type setup_type;
|
||||
wwise_header_type header_type;
|
||||
wwise_packet_type packet_type;
|
||||
/* saved data to reconstruct modified packets */
|
||||
uint8_t mode_blockflag[64+1]; /* max 6b+1; flags 'n stuff */
|
||||
int mode_bits; /* bits to store mode_number */
|
||||
uint8_t prev_blockflag; /* blockflag in the last decoded packet */
|
||||
|
||||
} vorbis_codec_data;
|
||||
#endif
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
/*
|
||||
** vgmstream for XMPlay
|
||||
**
|
||||
** 11/11/2009 - started. this is hilariously buggy and doesnt support much yet. [unknownfile]
|
||||
*/
|
||||
/**
|
||||
* vgmstream for XMPlay
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
@ -156,7 +154,7 @@ int32_t totalFrames, framesDone, framesLength, framesFade;
|
||||
#define APP_NAME "vgmstream plugin"
|
||||
#define PLUGIN_DESCRIPTION "vgmstream plugin " VERSION " " __DATE__
|
||||
|
||||
void __stdcall XMPAbout(HWND hwParent) {
|
||||
void __stdcall XMP_About(HWND hwParent) {
|
||||
MessageBox(hwParent,
|
||||
PLUGIN_DESCRIPTION "\n"
|
||||
"by hcs, FastElbja, manakoAT, bxaimc, kode54, and PSXGamerPro1\n\n"
|
||||
@ -164,8 +162,9 @@ void __stdcall XMPAbout(HWND hwParent) {
|
||||
,"about xmp-vgmstream",MB_OK);
|
||||
}
|
||||
|
||||
void __stdcall Stop() {
|
||||
void __stdcall XMP_Close() {
|
||||
close_vgmstream(vgmstream);
|
||||
vgmstream = NULL;
|
||||
}
|
||||
|
||||
BOOL __stdcall XMP_CheckFile(const char *filename, XMPFILE file) {
|
||||
@ -200,7 +199,7 @@ DWORD __stdcall XMP_GetFileInfo(const char *filename, XMPFILE file, float **leng
|
||||
return 1;
|
||||
}
|
||||
|
||||
DWORD __stdcall LoadVgmStream(const char *filename, XMPFILE file) {
|
||||
DWORD __stdcall XMP_Open(const char *filename, XMPFILE file) {
|
||||
if (file) vgmstream = init_vgmstream_from_xmpfile(file, filename);
|
||||
else vgmstream = init_vgmstream(filename);
|
||||
|
||||
@ -220,7 +219,7 @@ DWORD __stdcall LoadVgmStream(const char *filename, XMPFILE file) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
DWORD __stdcall XMP_Buffer(float* buffer, DWORD bufsize) {
|
||||
DWORD __stdcall XMP_Process(float* buffer, DWORD bufsize) {
|
||||
INT16 buf[1024];
|
||||
UINT32 i, j, todo, done;
|
||||
|
||||
@ -323,45 +322,80 @@ double __stdcall XMP_SetPosition(DWORD pos) {
|
||||
return cpos;
|
||||
}
|
||||
|
||||
void __stdcall XMP_GetInfoText(char* txt, char* length) {
|
||||
if (txt)
|
||||
sprintf(txt,"vgmstream!");
|
||||
/* main panel info text (short file info) */
|
||||
void __stdcall XMP_GetInfoText(char* format, char* length) {
|
||||
if (!format)
|
||||
return;
|
||||
|
||||
sprintf(format,"vgmstream");
|
||||
/* length is the file time */
|
||||
}
|
||||
|
||||
void __stdcall GetAdditionalFields(char* blerp) {
|
||||
sprintf(blerp,"oh god how did this get here I am not good with computers\n");
|
||||
/* info for the "General" window/tab (buf is ~40K) */
|
||||
void __stdcall XMP_GetGeneralInfo(char* buf) {
|
||||
int i;
|
||||
char description[1024];
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
if (!vgmstream)
|
||||
return;
|
||||
|
||||
description[0] = '\0';
|
||||
describe_vgmstream(vgmstream,description,sizeof(description));
|
||||
|
||||
/* tags are divided with a tab and lines with carriage return so we'll do some guetto fixin' */
|
||||
for (i = 0; i < 1024; i++) {
|
||||
int tag_done = 0;
|
||||
|
||||
if (description[i] == '\0')
|
||||
break;
|
||||
|
||||
if (description[i] == ':' && !tag_done) { /* to ignore multiple ':' in a line*/
|
||||
description[i] = ' ';
|
||||
description[i+1] = '\t';
|
||||
tag_done = 1;
|
||||
}
|
||||
|
||||
if (description[i] == '\n') {
|
||||
description[i] = '\r';
|
||||
tag_done = 0;
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(buf,"vgmstream\t\r%s\r", description);
|
||||
}
|
||||
|
||||
|
||||
/* XMPlay extension list, only needed to associate extensions in Windows */
|
||||
/* todo: as of v3.8.2.17, any more than 1024 will crash XMplay's file list screen (but not using the non-native Winamp plugin...) */
|
||||
#define EXTENSION_LIST_SIZE 1024 /*VGM_EXTENSION_LIST_CHAR_SIZE * 2*/
|
||||
char working_extension_list[EXTENSION_LIST_SIZE] = {0};
|
||||
|
||||
static int add_extension(int length, char * dst, const char * src);
|
||||
static void build_extension_list();
|
||||
|
||||
/* XMPlay extension list, only needed to associate extensions in Windows */
|
||||
/* todo: as of v3.8.2.17, any more than ~1000 will crash XMplay's file list screen (but not using the non-native Winamp plugin...) */
|
||||
#define EXTENSION_LIST_SIZE 1000 /*VGM_EXTENSION_LIST_CHAR_SIZE * 2*/
|
||||
char working_extension_list[EXTENSION_LIST_SIZE] = {0};
|
||||
|
||||
/* plugin defs, see xmpin.h */
|
||||
XMPIN vgmstream_intf = {
|
||||
XMPIN_FLAG_CANSTREAM,
|
||||
"vgmstream for XMPlay",
|
||||
working_extension_list,
|
||||
XMPAbout,
|
||||
NULL,
|
||||
XMP_About,
|
||||
NULL,//XMP_Config
|
||||
XMP_CheckFile,
|
||||
XMP_GetFileInfo,
|
||||
LoadVgmStream,
|
||||
Stop,
|
||||
XMP_Open,
|
||||
XMP_Close,
|
||||
NULL,
|
||||
XMP_SetFormat,
|
||||
XMP_GetTags, // Actually mandatory
|
||||
XMP_GetTags, //(OPTIONAL) --actually mandatory
|
||||
XMP_GetInfoText,
|
||||
GetAdditionalFields,
|
||||
NULL,
|
||||
XMP_GetGeneralInfo,
|
||||
NULL,//GetMessage - text for the "Message" tab window/tab (OPTIONAL)
|
||||
XMP_SetPosition,
|
||||
XMP_GetGranularity,
|
||||
NULL,
|
||||
XMP_Buffer,
|
||||
XMP_Process,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
|
Loading…
Reference in New Issue
Block a user