Updated VGMStream to r1050-3468-gc3ed1fad
parent
24231ecdbb
commit
4ef5283fa8
|
@ -551,7 +551,7 @@ ffmpeg_codec_data* init_ffmpeg_ue4_opus(STREAMFILE* sf, off_t start_offset, size
|
|||
ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
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 table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_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);
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac
|
|||
#endif
|
||||
|
||||
/* EA XA v1 (mono/stereo) */
|
||||
void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel, int is_stereo) {
|
||||
void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) {
|
||||
uint8_t frame_info;
|
||||
int32_t coef1, coef2;
|
||||
int i, sample_count, shift;
|
||||
|
@ -194,8 +194,9 @@ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing
|
|||
int frame_samples = 28;
|
||||
first_sample = first_sample % frame_samples;
|
||||
|
||||
/* header */
|
||||
if (is_stereo) {
|
||||
/* header (coefs ch0+ch1 + shift ch0+ch1) */
|
||||
/* coefs ch0+ch1 + shift ch0+ch1 */
|
||||
frame_info = read_8bit(stream->offset + 0x00, stream->streamfile);
|
||||
coef1 = EA_XA_TABLE[(hn ? frame_info >> 4 : frame_info & 0x0F) + 0];
|
||||
coef2 = EA_XA_TABLE[(hn ? frame_info >> 4 : frame_info & 0x0F) + 4];
|
||||
|
@ -203,7 +204,7 @@ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing
|
|||
frame_info = read_8bit(stream->offset + 0x01, stream->streamfile);
|
||||
shift = (hn ? frame_info >> 4 : frame_info & 0x0F) + 8;
|
||||
} else {
|
||||
/* header (coefs + shift ch0) */
|
||||
/* coefs + shift ch0 */
|
||||
frame_info = read_8bit(stream->offset + 0x00, stream->streamfile);
|
||||
coef1 = EA_XA_TABLE[(frame_info >> 4) + 0];
|
||||
coef2 = EA_XA_TABLE[(frame_info >> 4) + 4];
|
||||
|
@ -233,7 +234,7 @@ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing
|
|||
stream->offset += frame_size;
|
||||
}
|
||||
|
||||
/* Maxis EA-XA v1 (mono+stereo) with byte-interleave layout in stereo mode */
|
||||
/* Maxis EA-XA v1 (mono/stereo) with byte-interleave layout in stereo mode */
|
||||
void decode_maxis_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame_info;
|
||||
int32_t coef1, coef2;
|
||||
|
|
|
@ -490,11 +490,11 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
|
|||
if (mapping_family > 0) {
|
||||
int i;
|
||||
|
||||
/* internal mono/stereo streams (N mono/stereo streams form M channels) */
|
||||
/* internal mono/stereo streams (N mono/stereo streams that make M channels) */
|
||||
put_u8(buf+0x13, cfg->stream_count);
|
||||
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
|
||||
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled in 4 streams */
|
||||
put_u8(buf+0x14, cfg->coupled_count);
|
||||
/* mapping bits per channel? */
|
||||
/* mapping per channel (order of channels, ex: 0x000104050203) */
|
||||
for (i = 0; i < cfg->channels; i++) {
|
||||
put_u8(buf+0x15+i, cfg->channel_mapping[i]);
|
||||
}
|
||||
|
@ -753,8 +753,8 @@ ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int ta
|
|||
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
|
||||
return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_FSB);
|
||||
}
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip) {
|
||||
return init_ffmpeg_custom_table_opus(sf, table_offset, table_count, data_offset, data_size, channels, skip, 0, OPUS_WWISE);
|
||||
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);
|
||||
}
|
||||
|
||||
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) {
|
||||
|
|
|
@ -197,7 +197,7 @@ void decode_oki4s(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing
|
|||
stream->offset + i : /* stereo: one nibble per channel */
|
||||
stream->offset + i/2; /* mono: consecutive nibbles (assumed) */
|
||||
int nibble_shift =
|
||||
is_stereo ? (!(channel&1) ? 0:4) : (!(i&1) ? 0:4); /* even = low, odd = high */
|
||||
is_stereo ? ((channel&1) ? 0:4) : ((i&1) ? 0:4); /* even = high, odd = low */
|
||||
|
||||
oki4s_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index, &out_sample);
|
||||
outbuf[sample_count] = (out_sample);
|
||||
|
|
|
@ -341,6 +341,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
|
|||
case coding_SDX2:
|
||||
case coding_SDX2_int:
|
||||
case coding_CBD2:
|
||||
case coding_CBD2_int:
|
||||
case coding_ACM:
|
||||
case coding_DERF:
|
||||
case coding_WADY:
|
||||
|
@ -540,6 +541,7 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) {
|
|||
case coding_SDX2:
|
||||
case coding_SDX2_int:
|
||||
case coding_CBD2:
|
||||
case coding_CBD2_int:
|
||||
case coding_DERF:
|
||||
case coding_WADY:
|
||||
case coding_NWA:
|
||||
|
|
|
@ -25,7 +25,8 @@ static const char* extension_list[] = {
|
|||
"208",
|
||||
"2dx9",
|
||||
"2pfs",
|
||||
"4", // for Game.com audio
|
||||
"3do",
|
||||
"4", //for Game.com audio
|
||||
"8", //txth/reserved [Gungage (PS1)]
|
||||
"800",
|
||||
"9tav",
|
||||
|
@ -333,6 +334,7 @@ static const char* extension_list[] = {
|
|||
"musc",
|
||||
"musx",
|
||||
"mvb", //txth/reserved [Porsche Challenge (PS1)]
|
||||
"mwa", //txth/reserved [Fatal Frame (Xbox)]
|
||||
"mwv",
|
||||
"mxst",
|
||||
"myspd",
|
||||
|
@ -942,7 +944,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_DSP_WSI, "Alone in the Dark .WSI header"},
|
||||
{meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"},
|
||||
{meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"},
|
||||
{meta_STR_SNDS, "3DO .str header"},
|
||||
{meta_STR_SNDS, "3DO SNDS header"},
|
||||
{meta_WS_AUD, "Westwood Studios .aud header"},
|
||||
{meta_WS_AUD_old, "Westwood Studios .aud (old) header"},
|
||||
{meta_PS2_IVB, "IVB/BVII header"},
|
||||
|
|
|
@ -1,54 +1,39 @@
|
|||
#include "layout.h"
|
||||
#include "../vgmstream.h"
|
||||
|
||||
/* set up for the block at the given offset */
|
||||
void block_update_str_snds(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
off_t current_chunk;
|
||||
size_t file_size;
|
||||
|
||||
void block_update_str_snds(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
STREAMFILE* sf = vgmstream->ch[0].streamfile;
|
||||
uint32_t block_type, block_subtype, block_size, block_current;
|
||||
int i;
|
||||
STREAMFILE *streamfile;
|
||||
int FoundSSMP = 0;
|
||||
off_t SSMP_offset = -1;
|
||||
|
||||
current_chunk = block_offset;
|
||||
streamfile = vgmstream->ch[0].streamfile;
|
||||
file_size = get_streamfile_size(streamfile);
|
||||
/* EOF reads: signal we have nothing and let the layout fail */
|
||||
if (block_offset >= get_streamfile_size(sf)) {
|
||||
vgmstream->current_block_samples = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* we may have to skip some chunks */
|
||||
while (!FoundSSMP && current_chunk < file_size) {
|
||||
if (current_chunk+read_32bitBE(current_chunk+4,streamfile)>=file_size)
|
||||
break;
|
||||
switch (read_32bitBE(current_chunk,streamfile)) {
|
||||
case 0x534e4453: /* SNDS */
|
||||
/* SSMP */
|
||||
if (read_32bitBE(current_chunk+0x10,streamfile)==0x53534d50) {
|
||||
FoundSSMP = 1;
|
||||
SSMP_offset = current_chunk;
|
||||
}
|
||||
break;
|
||||
case 0x46494c4c: /* FILL, the main culprit */
|
||||
default:
|
||||
break;
|
||||
|
||||
block_type = read_u32be(block_offset + 0x00,sf);
|
||||
block_size = read_u32be(block_offset + 0x04,sf);
|
||||
|
||||
block_current = 0; /* ignore block by default (other chunks include MPVD + VHDR/FRAM and FILL) */
|
||||
if (block_type == 0x534e4453) { /* SNDS */
|
||||
block_subtype = read_u32be(block_offset + 0x10,sf); /* SNDS */
|
||||
if (block_subtype == 0x53534d50) {
|
||||
block_current = read_u32be(block_offset + 0x14, sf) / vgmstream->channels;
|
||||
}
|
||||
|
||||
current_chunk += read_32bitBE(current_chunk+4,streamfile);
|
||||
}
|
||||
|
||||
if (!FoundSSMP) {
|
||||
/* if we couldn't find it all we can do is try playing the current
|
||||
* block, which is going to suck */
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
}
|
||||
/* seen in Battle Tryst video frames */
|
||||
if (block_size % 0x04)
|
||||
block_size += 0x04 - (block_size % 0x04);
|
||||
|
||||
vgmstream->current_block_offset = SSMP_offset;
|
||||
vgmstream->current_block_size = (read_32bitBE(
|
||||
vgmstream->current_block_offset+4,
|
||||
vgmstream->ch[0].streamfile) - 0x18) / vgmstream->channels;
|
||||
vgmstream->next_block_offset = vgmstream->current_block_offset +
|
||||
read_32bitBE(vgmstream->current_block_offset+4,
|
||||
vgmstream->ch[0].streamfile);
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
vgmstream->next_block_offset = block_offset + block_size;
|
||||
vgmstream->current_block_size = block_current;
|
||||
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
vgmstream->ch[i].offset = vgmstream->current_block_offset + 0x18 + i * vgmstream->interleave_block_size;
|
||||
vgmstream->ch[i].offset = block_offset + 0x18 + i * vgmstream->interleave_block_size;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,18 @@ decode_fail:
|
|||
}
|
||||
|
||||
|
||||
void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
seek_vgmstream(data->layers[layer], seek_sample);
|
||||
}
|
||||
|
||||
vgmstream->current_sample = seek_sample;
|
||||
vgmstream->samples_into_block = seek_sample;
|
||||
}
|
||||
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
|
|
@ -59,7 +59,8 @@ segmented_layout_data* init_layout_segmented(int segment_count);
|
|||
int setup_layout_segmented(segmented_layout_data* data);
|
||||
void free_layout_segmented(segmented_layout_data* data);
|
||||
void reset_layout_segmented(segmented_layout_data* data);
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample);
|
||||
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment);
|
||||
|
||||
void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
|
@ -67,7 +68,8 @@ layered_layout_data* init_layout_layered(int layer_count);
|
|||
int setup_layout_layered(layered_layout_data* data);
|
||||
void free_layout_layered(layered_layout_data* data);
|
||||
void reset_layout_layered(layered_layout_data* data);
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample);
|
||||
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#define VGMSTREAM_MAX_SEGMENTS 1024
|
||||
#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192
|
||||
|
||||
static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written);
|
||||
|
||||
/* Decodes samples for segmented streams.
|
||||
* Chains together sequential vgmstreams, for data divided into separate sections or files
|
||||
|
@ -15,9 +16,10 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
|||
int samples_written = 0, samples_this_block;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
int use_internal_buffer = 0;
|
||||
int current_channels = 0;
|
||||
|
||||
/* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */
|
||||
if (vgmstream->channels != data->input_channels) {
|
||||
if (vgmstream->channels != data->input_channels || data->mixed_channels) {
|
||||
use_internal_buffer = 1;
|
||||
}
|
||||
|
||||
|
@ -27,6 +29,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
|||
}
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
|
@ -34,6 +37,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
|||
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
|
||||
/* handle looping (loop_layout has been called below, changes segments/state) */
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -50,6 +54,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
|||
reset_vgmstream(data->segments[data->current_segment]);
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
|
@ -73,10 +78,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
|||
data->segments[data->current_segment]);
|
||||
|
||||
if (use_internal_buffer) {
|
||||
int s;
|
||||
for (s = 0; s < samples_to_do * data->output_channels; s++) {
|
||||
outbuf[samples_written * data->output_channels + s] = data->buffer[s];
|
||||
}
|
||||
copy_samples(outbuf, data, current_channels, samples_to_do, samples_written);
|
||||
}
|
||||
|
||||
samples_written += samples_to_do;
|
||||
|
@ -89,7 +91,31 @@ decode_fail:
|
|||
memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written) {
|
||||
int ch_out = data->output_channels;
|
||||
int ch_in = current_channels;
|
||||
int pos = samples_written * ch_out;
|
||||
int s;
|
||||
if (ch_in == ch_out) { /* most common and probably faster */
|
||||
for (s = 0; s < samples_to_do * ch_out; s++) {
|
||||
outbuf[pos + s] = data->buffer[s];
|
||||
}
|
||||
}
|
||||
else {
|
||||
int ch;
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
for (ch = 0; ch < ch_in; ch++) {
|
||||
outbuf[pos + s*ch_out + ch] = data->buffer[s*ch_in + ch];
|
||||
}
|
||||
for (ch = ch_in; ch < ch_out; ch++) {
|
||||
outbuf[pos + s*ch_out + ch] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int segment, total_samples;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
|
@ -98,13 +124,13 @@ void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
|||
while (total_samples < vgmstream->num_samples) {
|
||||
int32_t segment_samples = vgmstream_get_samples(data->segments[segment]);
|
||||
|
||||
/* find if loop falls within segment's samples */
|
||||
if (loop_sample >= total_samples && loop_sample < total_samples + segment_samples) {
|
||||
int32_t loop_relative = loop_sample - total_samples;
|
||||
/* find if sample falls within segment's samples */
|
||||
if (seek_sample >= total_samples && seek_sample < total_samples + segment_samples) {
|
||||
int32_t seek_relative = seek_sample - total_samples;
|
||||
|
||||
seek_vgmstream(data->segments[segment], loop_relative);
|
||||
seek_vgmstream(data->segments[segment], seek_relative);
|
||||
data->current_segment = segment;
|
||||
vgmstream->samples_into_block = loop_relative;
|
||||
vgmstream->samples_into_block = seek_relative;
|
||||
break;
|
||||
}
|
||||
total_samples += segment_samples;
|
||||
|
@ -112,10 +138,14 @@ void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
|||
}
|
||||
|
||||
if (segment == data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: can't find loop segment\n");
|
||||
VGM_LOG("SEGMENTED: can't find seek segment\n");
|
||||
}
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
seek_layout_segmented(vgmstream, loop_sample);
|
||||
}
|
||||
|
||||
|
||||
segmented_layout_data* init_layout_segmented(int segment_count) {
|
||||
segmented_layout_data* data = NULL;
|
||||
|
@ -139,7 +169,7 @@ fail:
|
|||
}
|
||||
|
||||
int setup_layout_segmented(segmented_layout_data* data) {
|
||||
int i, max_input_channels = 0, max_output_channels = 0;
|
||||
int i, max_input_channels = 0, max_output_channels = 0, mixed_channels = 0;
|
||||
sample_t *outbuf_re = NULL;
|
||||
|
||||
|
||||
|
@ -170,8 +200,8 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
|||
}
|
||||
}
|
||||
|
||||
/* different segments may have different input channels, though output should be
|
||||
* the same for all (ex. 2ch + 1ch segments, but 2ch segment is downmixed to 1ch) */
|
||||
/* different segments may have different input or output channels, we
|
||||
* need to know maxs to properly handle */
|
||||
mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels);
|
||||
if (max_input_channels < segment_input_channels)
|
||||
max_input_channels = segment_input_channels;
|
||||
|
@ -183,11 +213,12 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
|||
|
||||
mixing_info(data->segments[i-1], NULL, &prev_output_channels);
|
||||
if (segment_output_channels != prev_output_channels) {
|
||||
VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
|
||||
goto fail;
|
||||
mixed_channels = 1;
|
||||
//VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
|
||||
//goto fail;
|
||||
}
|
||||
|
||||
/* a bit weird, but no matter */
|
||||
/* a bit weird, but no matter (should resample) */
|
||||
if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) {
|
||||
VGM_LOG("SEGMENTED: segment %i has different sample rate\n", i);
|
||||
}
|
||||
|
@ -214,6 +245,7 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
|||
|
||||
data->input_channels = max_input_channels;
|
||||
data->output_channels = max_output_channels;
|
||||
data->mixed_channels = mixed_channels;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
|
|
|
@ -105,7 +105,7 @@ VGMSTREAM * init_vgmstream_aax(STREAMFILE *streamFile) {
|
|||
}
|
||||
}
|
||||
|
||||
channel_count = data->segments[0]->channels;
|
||||
channel_count = data->output_channels;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
|
||||
static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE* sf_head, STREAMFILE* sf_data, off_t header_offset, off_t start_offset, meta_t meta_type, int standalone);
|
||||
static VGMSTREAM *parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t ast_offset);
|
||||
VGMSTREAM * init_vgmstream_gin_header(STREAMFILE* sf, off_t offset);
|
||||
|
||||
|
||||
/* .SNR+SNS - from EA latest games (~2005-2010), v0 header */
|
||||
|
@ -375,12 +374,13 @@ fail:
|
|||
/* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */
|
||||
VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
|
||||
int target_stream = sf->stream_index;
|
||||
uint32_t snr_offset, sns_offset, block_size;
|
||||
uint16_t sth_offset, sth_offset2;
|
||||
uint8_t userdata_size, total_sounds, block_id;
|
||||
off_t snr_offset, sns_offset, sth_offset, sth_offset2;
|
||||
size_t dat_size, block_size;
|
||||
STREAMFILE *datFile = NULL, *sthFile = NULL;
|
||||
size_t dat_size;
|
||||
STREAMFILE *sf_dat = NULL, *sf_sth = NULL;
|
||||
VGMSTREAM *vgmstream;
|
||||
int32_t(*read_32bit)(off_t, STREAMFILE*);
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE*);
|
||||
|
||||
/* 0x00: ID */
|
||||
/* 0x02: parameters (userdata size, ...) */
|
||||
|
@ -388,46 +388,46 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
|
|||
/* 0x04: sub-ID (used for different police voices in NFS games) */
|
||||
/* 0x08: sample repeat (alt number of files?) */
|
||||
/* 0x09: block size (always zero?) */
|
||||
/* 0x0A: number of blocks (related to size?) */
|
||||
/* 0x0C: number of sub-banks (always zero?) */
|
||||
/* 0x0E: padding */
|
||||
/* 0x0a: number of blocks (related to size?) */
|
||||
/* 0x0c: number of sub-banks (always zero?) */
|
||||
/* 0x0e: padding */
|
||||
/* 0x10: table start */
|
||||
|
||||
if (!check_extensions(sf, "hdr"))
|
||||
goto fail;
|
||||
|
||||
if (read_8bit(0x09, sf) != 0)
|
||||
if (read_u8(0x09, sf) != 0)
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x0c, sf) != 0)
|
||||
if (read_u32be(0x0c, sf) != 0)
|
||||
goto fail;
|
||||
|
||||
/* first offset is always zero */
|
||||
if (read_16bitBE(0x10, sf) != 0)
|
||||
if (read_u16be(0x10, sf) != 0)
|
||||
goto fail;
|
||||
|
||||
sthFile = open_streamfile_by_ext(sf, "sth");
|
||||
if (!sthFile)
|
||||
sf_sth = open_streamfile_by_ext(sf, "sth");
|
||||
if (!sf_sth)
|
||||
goto fail;
|
||||
|
||||
datFile = open_streamfile_by_ext(sf, "dat");
|
||||
if (!datFile)
|
||||
sf_dat = open_streamfile_by_ext(sf, "dat");
|
||||
if (!sf_dat)
|
||||
goto fail;
|
||||
|
||||
/* STH always starts with the first offset of zero */
|
||||
sns_offset = read_32bitBE(0x00, sthFile);
|
||||
sns_offset = read_u32be(0x00, sf_sth);
|
||||
if (sns_offset != 0)
|
||||
goto fail;
|
||||
|
||||
/* check if DAT starts with a correct SNS block */
|
||||
block_id = read_8bit(0x00, datFile);
|
||||
block_id = read_u8(0x00, sf_dat);
|
||||
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
|
||||
goto fail;
|
||||
|
||||
userdata_size = read_8bit(0x02, sf);
|
||||
total_sounds = read_8bit(0x03, sf);
|
||||
userdata_size = read_u8(0x02, sf) & 0x0F;
|
||||
total_sounds = read_u8(0x03, sf);
|
||||
|
||||
if (read_8bit(0x08, sf) > total_sounds)
|
||||
if (read_u8(0x08, sf) > total_sounds)
|
||||
goto fail;
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
|
@ -435,14 +435,14 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
|
|||
goto fail;
|
||||
|
||||
/* offsets in HDR are always big endian */
|
||||
sth_offset = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf);
|
||||
sth_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf);
|
||||
|
||||
#if 0
|
||||
snr_offset = sth_offset + 0x04;
|
||||
sns_offset = read_32bit(sth_offset + 0x00, sthFile);
|
||||
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
|
||||
#else
|
||||
/* we can't reliably detect byte endianness so we're going to find the sound the hacky way */
|
||||
dat_size = get_streamfile_size(datFile);
|
||||
/* overly intricate way to detect byte endianness because of the simplicity of HDR format */
|
||||
dat_size = get_streamfile_size(sf_dat);
|
||||
snr_offset = 0;
|
||||
sns_offset = 0;
|
||||
|
||||
|
@ -456,8 +456,8 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
|
|||
if (sns_offset >= dat_size)
|
||||
goto fail;
|
||||
|
||||
block_id = read_8bit(sns_offset, datFile);
|
||||
block_size = read_32bitBE(sns_offset, datFile) & 0x00FFFFFF;
|
||||
block_id = read_u8(sns_offset, sf_dat);
|
||||
block_size = read_u32be(sns_offset, sf_dat) & 0x00FFFFFF;
|
||||
if (block_size == 0)
|
||||
goto fail;
|
||||
|
||||
|
@ -470,36 +470,37 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
|
|||
break;
|
||||
}
|
||||
|
||||
sth_offset2 = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * 1, sf);
|
||||
if (sns_offset == read_32bitBE(sth_offset2, sthFile)) {
|
||||
read_32bit = read_32bitBE;
|
||||
} else if (sns_offset == read_32bitLE(sth_offset2, sthFile)) {
|
||||
read_32bit = read_32bitLE;
|
||||
sns_offset = align_size_to_block(sns_offset, 0x40);
|
||||
sth_offset2 = read_u16be(0x10 + (0x02 + userdata_size) * 1, sf);
|
||||
if (sns_offset == read_u32be(sth_offset2, sf_sth)) {
|
||||
read_u32 = read_u32be;
|
||||
} else if (sns_offset == read_u32le(sth_offset2, sf_sth)) {
|
||||
read_u32 = read_u32le;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snr_offset = sth_offset + 0x04;
|
||||
sns_offset = read_32bit(sth_offset + 0x00, sthFile);
|
||||
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
|
||||
}
|
||||
#endif
|
||||
|
||||
block_id = read_8bit(sns_offset, datFile);
|
||||
block_id = read_u8(sns_offset, sf_dat);
|
||||
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
|
||||
goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_eaaudiocore_header(sthFile, datFile, snr_offset, sns_offset, meta_EA_SNR_SNS, 0);
|
||||
vgmstream = init_vgmstream_eaaudiocore_header(sf_sth, sf_dat, snr_offset, sns_offset, meta_EA_SNR_SNS, 0);
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
|
||||
vgmstream->num_streams = total_sounds;
|
||||
close_streamfile(sthFile);
|
||||
close_streamfile(datFile);
|
||||
close_streamfile(sf_sth);
|
||||
close_streamfile(sf_dat);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(sthFile);
|
||||
close_streamfile(datFile);
|
||||
close_streamfile(sf_sth);
|
||||
close_streamfile(sf_dat);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -507,7 +508,7 @@ fail:
|
|||
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks) {
|
||||
static const char *const mapfile_pairs[][2] = {
|
||||
/* standard cases, replace map part with mus part (from the end to preserve prefixes) */
|
||||
{"game.mpf", "Game_Stream.mus"}, /* Skate */
|
||||
{"game.mpf", "Game_Stream.mus"}, /* Skate 1/2/3 */
|
||||
{"ipod.mpf", "Ipod_Stream.mus"},
|
||||
{"world.mpf", "World_Stream.mus"},
|
||||
{"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */
|
||||
|
@ -606,37 +607,41 @@ static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks)
|
|||
|
||||
/* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */
|
||||
VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
||||
uint32_t num_tracks, track_start, track_hash = 0, mus_sounds, mus_stream = 0;
|
||||
uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0;
|
||||
uint32_t tracks_table, samples_table, eof_offset, table_offset, entry_offset, snr_offset, sns_offset;
|
||||
uint16_t num_subbanks;
|
||||
uint8_t version, sub_version;
|
||||
off_t tracks_table, samples_table, eof_offset, table_offset, entry_offset, snr_offset, sns_offset;
|
||||
int32_t(*read_32bit)(off_t, STREAMFILE*);
|
||||
STREAMFILE *musFile = NULL;
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
int i;
|
||||
int target_stream = sf->stream_index, total_streams, is_ram = 0;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE *);
|
||||
uint16_t(*read_u16)(off_t, STREAMFILE *);
|
||||
|
||||
/* check extension */
|
||||
if (!check_extensions(sf, "mpf"))
|
||||
goto fail;
|
||||
|
||||
/* detect endianness */
|
||||
if (read_32bitBE(0x00, sf) == 0x50464478) { /* "PFDx" */
|
||||
read_32bit = read_32bitBE;
|
||||
} else if (read_32bitLE(0x00, sf) == 0x50464478) { /* "xDFP" */
|
||||
read_32bit = read_32bitLE;
|
||||
if (read_u32be(0x00, sf) == 0x50464478) { /* "PFDx" */
|
||||
read_u32 = read_u32be;
|
||||
read_u16 = read_u16be;
|
||||
} else if (read_u32le(0x00, sf) == 0x50464478) { /* "xDFP" */
|
||||
read_u32 = read_u32le;
|
||||
read_u16 = read_u16le;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
version = read_8bit(0x04, sf);
|
||||
sub_version = read_8bit(0x05, sf);
|
||||
version = read_u8(0x04, sf);
|
||||
sub_version = read_u8(0x05, sf);
|
||||
if (version != 5 || sub_version < 2 || sub_version > 3) goto fail;
|
||||
|
||||
num_tracks = read_8bit(0x0d, sf);
|
||||
num_tracks = read_u8(0x0d, sf);
|
||||
|
||||
tracks_table = read_32bit(0x2c, sf);
|
||||
samples_table = read_32bit(0x34, sf);
|
||||
eof_offset = read_32bit(0x38, sf);
|
||||
tracks_table = read_u32(0x2c, sf);
|
||||
samples_table = read_u32(0x34, sf);
|
||||
eof_offset = read_u32(0x38, sf);
|
||||
total_streams = (eof_offset - samples_table) / 0x08;
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
|
@ -644,24 +649,30 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
|||
goto fail;
|
||||
|
||||
for (i = num_tracks - 1; i >= 0; i--) {
|
||||
entry_offset = read_32bit(tracks_table + i * 0x04, sf) * 0x04;
|
||||
track_start = read_32bit(entry_offset + 0x00, sf);
|
||||
entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04;
|
||||
track_start = read_u32(entry_offset + 0x00, sf);
|
||||
|
||||
if (track_start == 0 && i != 0)
|
||||
continue; /* empty track */
|
||||
|
||||
if (track_start <= target_stream - 1) {
|
||||
track_hash = read_32bitBE(entry_offset + 0x08, sf);
|
||||
is_ram = (track_hash == 0xF1F1F1F1);
|
||||
num_subbanks = read_u16(entry_offset + 0x04, sf);
|
||||
track_checksum = read_u32be(entry_offset + 0x08, sf);
|
||||
is_ram = (num_subbanks != 0);
|
||||
|
||||
if (num_subbanks > 1) {
|
||||
VGM_LOG("EA MPF: Found EAAC MPF with more than 1 RAM sub-bank.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* checks to distinguish it from older versions */
|
||||
if (is_ram) {
|
||||
if (read_32bitBE(entry_offset + 0x0c, sf) != 0x00)
|
||||
if (read_u32(entry_offset + 0x0c, sf) != 0x00)
|
||||
goto fail;
|
||||
|
||||
track_hash = read_32bitBE(entry_offset + 0x14, sf);
|
||||
track_checksum = read_u32be(entry_offset + 0x14, sf);
|
||||
} else {
|
||||
if (read_32bitBE(entry_offset + 0x0c, sf) == 0x00)
|
||||
if (read_u32(entry_offset + 0x0c, sf) == 0x00)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -675,13 +686,13 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
|||
if (!musFile)
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00, musFile) != track_hash)
|
||||
if (read_u32be(0x00, musFile) != track_checksum)
|
||||
goto fail;
|
||||
|
||||
/* sample offsets table is still there but it just holds SNS offsets, it's of little use to us */
|
||||
/* MUS file has a header, however */
|
||||
if (sub_version == 2) {
|
||||
if (read_32bit(0x04, musFile) != 0x00)
|
||||
if (read_u32(0x04, musFile) != 0x00)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
|
@ -691,11 +702,11 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
|||
*/
|
||||
table_offset = 0x08;
|
||||
entry_offset = table_offset + mus_stream * 0x0c;
|
||||
snr_offset = read_32bit(entry_offset + 0x04, musFile);
|
||||
sns_offset = read_32bit(entry_offset + 0x08, musFile);
|
||||
snr_offset = read_u32(entry_offset + 0x04, musFile);
|
||||
sns_offset = read_u32(entry_offset + 0x08, musFile);
|
||||
} else if (sub_version == 3) {
|
||||
/* number of files is always little endian */
|
||||
mus_sounds = read_32bitLE(0x04, musFile);
|
||||
/* number of samples is always little endian */
|
||||
mus_sounds = read_u32le(0x04, musFile);
|
||||
if (mus_stream >= mus_sounds)
|
||||
goto fail;
|
||||
|
||||
|
@ -706,9 +717,9 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
|||
}
|
||||
|
||||
/*
|
||||
* 0x00: hash?
|
||||
* 0x00: checksum
|
||||
* 0x04: index
|
||||
* 0x06: zero
|
||||
* 0x06: sub-index
|
||||
* 0x08: SNR offset
|
||||
* 0x0c: SNS offset
|
||||
* 0x10: SNR size
|
||||
|
@ -717,8 +728,8 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
|||
*/
|
||||
table_offset = 0x28;
|
||||
entry_offset = table_offset + mus_stream * 0x1c;
|
||||
snr_offset = read_32bit(entry_offset + 0x08, musFile) * 0x10;
|
||||
sns_offset = read_32bit(entry_offset + 0x0c, musFile) * 0x80;
|
||||
snr_offset = read_u32(entry_offset + 0x08, musFile) * 0x10;
|
||||
sns_offset = read_u32(entry_offset + 0x0c, musFile) * 0x80;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
|
@ -741,6 +752,7 @@ fail:
|
|||
VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE* sf) {
|
||||
uint32_t num_sounds, sound_type, table_offset, data_offset, entry_offset, sound_offset;
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_sf = NULL;
|
||||
int target_stream = sf->stream_index;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE *);
|
||||
|
||||
|
@ -769,8 +781,12 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE* sf) {
|
|||
|
||||
switch (sound_type) {
|
||||
case 0x47494E20: /* "GIN " */
|
||||
vgmstream = init_vgmstream_gin_header(sf, sound_offset);
|
||||
temp_sf = setup_subfile_streamfile(sf, sound_offset, get_streamfile_size(sf) - sound_offset, "gin");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_gin(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
break;
|
||||
case 0x534E5220: /* "SNR " */
|
||||
vgmstream = init_vgmstream_eaaudiocore_header(sf, NULL, sound_offset, 0x00, meta_EA_SNR_SNS, 0);
|
||||
|
@ -784,6 +800,7 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE* sf) {
|
|||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -391,10 +391,10 @@ VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE* sf) {
|
|||
if (target_entry_offset == 0)
|
||||
goto fail;
|
||||
|
||||
/* 0x00: type (0x00 - normal, 0x01 - streamed, 0x02 - streamed looped) */
|
||||
/* 0x00: type (0x00 - RAM, 0x01 - streamed, 0x02 - streamed looped) */
|
||||
/* 0x01: priority */
|
||||
/* 0x02: padding */
|
||||
/* 0x04: index for normal sounds, offset for streamed sounds */
|
||||
/* 0x04: index for RAM sounds, offset for streamed sounds */
|
||||
/* 0x08: loop offset for streamed sounds */
|
||||
sound_type = read_8bit(target_entry_offset + 0x00, sf);
|
||||
|
||||
|
@ -474,15 +474,14 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* EA HDR/DAT v1 (2004-2005) - used for storing speech and other streamed sounds (except for music) */
|
||||
VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream;
|
||||
STREAMFILE* sf_dat = NULL;
|
||||
/* EA HDR/DAT v1 (2004-2005) - used for storing speech, sometimes streamed SFX */
|
||||
VGMSTREAM *init_vgmstream_ea_hdr_dat(STREAMFILE *sf) {
|
||||
VGMSTREAM *vgmstream;
|
||||
STREAMFILE *sf_dat = NULL, *temp_sf = NULL;
|
||||
int target_stream = sf->stream_index;
|
||||
uint32_t offset_mult;
|
||||
uint32_t offset_mult, sound_offset, sound_size;
|
||||
uint8_t userdata_size, total_sounds;
|
||||
size_t dat_size;
|
||||
off_t schl_offset;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "hdr"))
|
||||
|
@ -493,10 +492,10 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) {
|
|||
/* 0x02: sub-ID (used for different police voices in NFS games) */
|
||||
/* 0x04: parameters (userdata size, ...) */
|
||||
/* 0x05: number of files */
|
||||
/* 0x06: alt number of files? */
|
||||
/* 0x07: offset multiplier flag */
|
||||
/* 0x08: combined size of all sounds without padding divided by offset mult */
|
||||
/* 0x0a: zero */
|
||||
/* 0x06: sample repeat (alt number of files?) */
|
||||
/* 0x07: block size (offset multiplier) */
|
||||
/* 0x08: number of blocks (DAT size divided by block size) */
|
||||
/* 0x0a: number of sub-banks */
|
||||
/* 0x0c: table start */
|
||||
|
||||
/* no nice way to validate these so we do what we can */
|
||||
|
@ -507,12 +506,13 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) {
|
|||
if (read_u16be(0x0c, sf) != 0)
|
||||
goto fail;
|
||||
|
||||
/* must be accompanied by DAT file with SCHl sounds */
|
||||
/* must be accompanied by DAT file with SCHl or VAG sounds */
|
||||
sf_dat = open_streamfile_by_ext(sf, "dat");
|
||||
if (!sf_dat)
|
||||
goto fail;
|
||||
|
||||
if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER)
|
||||
if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER &&
|
||||
read_u32be(0x00, sf_dat) != 0x56414770)
|
||||
goto fail;
|
||||
|
||||
userdata_size = read_u8(0x04, sf) & 0x0F;
|
||||
|
@ -532,13 +532,23 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) {
|
|||
goto fail;
|
||||
|
||||
/* offsets are always big endian */
|
||||
schl_offset = read_u16be(0x0C + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult;
|
||||
if (read_32bitBE(schl_offset, sf_dat) != EA_BLOCKID_HEADER)
|
||||
goto fail;
|
||||
sound_offset = read_u16be(0x0C + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult;
|
||||
if (read_u32be(sound_offset, sf_dat) == EA_BLOCKID_HEADER) { /* "SCHl" */
|
||||
vgmstream = parse_schl_block(sf_dat, sound_offset, 0);
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
} else if (read_u32be(sound_offset, sf_dat) == 0x56414770) { /* "VAGp" */
|
||||
/* Need for Speed: Hot Pursuit 2 (PS2) */
|
||||
sound_size = read_u32be(sound_offset + 0x0c, sf_dat) + 0x30;
|
||||
temp_sf = setup_subfile_streamfile(sf_dat, sound_offset, sound_size, "vag");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = parse_schl_block(sf_dat, schl_offset, 0);
|
||||
if (!vgmstream)
|
||||
vgmstream = init_vgmstream_vag(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_sounds;
|
||||
close_streamfile(sf_dat);
|
||||
|
@ -546,6 +556,7 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) {
|
|||
|
||||
fail:
|
||||
close_streamfile(sf_dat);
|
||||
close_streamfile(temp_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -554,10 +565,9 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) {
|
|||
VGMSTREAM *vgmstream;
|
||||
STREAMFILE *sf_dat = NULL;
|
||||
int target_stream = sf->stream_index;
|
||||
uint32_t offset_mult;
|
||||
uint32_t offset_mult, sound_offset;
|
||||
uint8_t userdata_size, total_sounds;
|
||||
size_t dat_size;
|
||||
off_t schl_offset;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "hdr"))
|
||||
|
@ -570,9 +580,9 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) {
|
|||
/* 0x04: sub-ID (used for different police voices in NFS games) */
|
||||
/* 0x08: sample repeat (alt number of files?) */
|
||||
/* 0x09: block size (offset multiplier) */
|
||||
/* 0x0A: number of blocks (DAT size divided by block size) */
|
||||
/* 0x0C: number of sub-banks (always zero?) */
|
||||
/* 0x0E: padding */
|
||||
/* 0x0a: number of blocks (DAT size divided by block size) */
|
||||
/* 0x0c: number of sub-banks (always zero?) */
|
||||
/* 0x0e: padding */
|
||||
/* 0x10: table start */
|
||||
|
||||
/* no nice way to validate these so we do what we can */
|
||||
|
@ -608,11 +618,11 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) {
|
|||
goto fail;
|
||||
|
||||
/* offsets are always big endian */
|
||||
schl_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult;
|
||||
if (read_32bitBE(schl_offset, sf_dat) != EA_BLOCKID_HEADER)
|
||||
sound_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult;
|
||||
if (read_u32be(sound_offset, sf_dat) != EA_BLOCKID_HEADER)
|
||||
goto fail;
|
||||
|
||||
vgmstream = parse_schl_block(sf_dat, schl_offset, 0);
|
||||
vgmstream = parse_schl_block(sf_dat, sound_offset, 0);
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
|
||||
|
@ -733,6 +743,7 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks)
|
|||
}
|
||||
|
||||
/* EA MAP/MUS combo - used in older games for interactive music (for EA's PathFinder tool) */
|
||||
/* seen in Need for Speed II, Need for Speed III: Hot Pursuit, SSX */
|
||||
VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_mus = NULL;
|
||||
|
@ -758,14 +769,14 @@ VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE* sf) {
|
|||
* 0x04: version
|
||||
* 0x05: starting node
|
||||
* 0x06: number of nodes
|
||||
* 0x07: number of sections
|
||||
* 0x07: number of events
|
||||
* 0x08: three zeroes
|
||||
* 0x0b: number of events
|
||||
* 0x0b: number of sections
|
||||
* 0x0c: data start
|
||||
*/
|
||||
num_sounds = read_8bit(0x06, sf);
|
||||
num_sections = read_8bit(0x07, sf);
|
||||
num_events = read_8bit(0x0b, sf);
|
||||
num_events = read_8bit(0x07, sf);
|
||||
num_sections = read_8bit(0x0b, sf);
|
||||
section_offset = 0x0c;
|
||||
|
||||
/* section 1: nodes, contains information about segment playback order */
|
||||
|
@ -802,24 +813,25 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_mus = NULL;
|
||||
segmented_layout_data *data_s = NULL;
|
||||
uint32_t track_start, track_end = 0, track_hash = 0, tracks_table, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, off_mult, sound_offset;
|
||||
uint16_t num_nodes;
|
||||
uint32_t track_start, track_end = 0, track_checksum = 0;
|
||||
uint32_t tracks_table, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, off_mult, sound_offset;
|
||||
uint16_t num_nodes, num_subbanks = 0;
|
||||
uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num = 0;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE*);
|
||||
uint16_t(*read_u16)(off_t, STREAMFILE*);
|
||||
int i;
|
||||
int target_stream = sf->stream_index, total_streams, big_endian, is_bnk = 0;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE *);
|
||||
uint16_t(*read_u16)(off_t, STREAMFILE *);
|
||||
|
||||
/* check extension */
|
||||
if (!check_extensions(sf, "mpf"))
|
||||
goto fail;
|
||||
|
||||
/* detect endianness */
|
||||
if (read_32bitBE(0x00, sf) == 0x50464478) { /* "PFDx" */
|
||||
if (read_u32be(0x00, sf) == 0x50464478) { /* "PFDx" */
|
||||
read_u32 = read_u32be;
|
||||
read_u16 = read_u16be;
|
||||
big_endian = 1;
|
||||
} else if (read_32bitLE(0x00, sf) == 0x50464478) { /* "xDFP" */
|
||||
} else if (read_u32le(0x00, sf) == 0x50464478) { /* "xDFP" */
|
||||
read_u32 = read_u32le;
|
||||
read_u16 = read_u16le;
|
||||
big_endian = 0;
|
||||
|
@ -840,28 +852,47 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
num_vars = read_u8(0x11, sf);
|
||||
num_nodes = read_u16(0x12, sf);
|
||||
|
||||
/* HACK: number of sub-entries for nodes and events is stored in bitstreams that are different in LE and BE */
|
||||
/* I can't figure it out, so let's just use a workaround for now */
|
||||
/* Some structs here use C bitfields which are different on LE and BE AND their
|
||||
* implementation is compiler dependent, fun times.
|
||||
* Earlier versions don't have section offsets so we have to go through all of them
|
||||
* to get to the samples table. */
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
|
||||
if (version == 3)
|
||||
/* SSX Tricky (v3.1), Harry Potter and the Chamber of Secrets (v3.4) */ {
|
||||
/* we need to go through all the sections to get to the samples table */
|
||||
if (sub_version != 1 && sub_version != 2 && sub_version != 4)
|
||||
goto fail;
|
||||
|
||||
/* get the last entry offset */
|
||||
if (version == 3 && (sub_version == 1 || sub_version == 2))
|
||||
/* SSX Tricky, Sled Storm */ {
|
||||
section_offset = 0x24;
|
||||
entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
|
||||
if (sub_version == 1 || sub_version == 2) {
|
||||
subentry_num = read_u8(entry_offset + 0x0b, sf);
|
||||
} else if (sub_version == 4) {
|
||||
if (big_endian) {
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 19) & 0xFF;
|
||||
} else {
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 16) & 0xFF;
|
||||
}
|
||||
subentry_num = read_u8(entry_offset + 0x0b, sf);
|
||||
section_offset = entry_offset + 0x0c + subentry_num * 0x04;
|
||||
|
||||
section_offset += align_size_to_block(num_events * num_tracks * num_sections, 0x04);
|
||||
section_offset += num_routers * 0x04;
|
||||
section_offset += num_vars * 0x04;
|
||||
|
||||
tracks_table = read_u32(section_offset, sf) * 0x04;
|
||||
samples_table = tracks_table + num_tracks * 0x04;
|
||||
eof_offset = get_streamfile_size(sf);
|
||||
total_streams = (eof_offset - samples_table) / 0x08;
|
||||
off_mult = 0x04;
|
||||
|
||||
track_start = total_streams;
|
||||
|
||||
for (i = num_tracks - 1; i >= 0; i--) {
|
||||
track_end = track_start;
|
||||
track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
|
||||
track_start = (track_start - samples_table) / 0x08;
|
||||
if (track_start <= target_stream - 1)
|
||||
break;
|
||||
}
|
||||
} else if (version == 3 && sub_version == 4)
|
||||
/* Harry Potter and the Chamber of Secrets, Shox */ {
|
||||
section_offset = 0x24;
|
||||
entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
|
||||
if (big_endian) {
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 19) & 0x1F;
|
||||
} else {
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 16) & 0x1F;
|
||||
}
|
||||
section_offset = entry_offset + 0x0c + subentry_num * 0x04;
|
||||
|
||||
|
@ -870,14 +901,8 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
section_offset += num_vars * 0x04;
|
||||
|
||||
tracks_table = read_u32(section_offset, sf) * 0x04;
|
||||
if (sub_version == 1 || sub_version == 2)
|
||||
samples_table = tracks_table + num_tracks * 0x04;
|
||||
else if (sub_version == 4)
|
||||
samples_table = tracks_table + (num_tracks + 1) * 0x04;
|
||||
if (sub_version == 1 || sub_version == 2)
|
||||
eof_offset = get_streamfile_size(sf);
|
||||
else if (sub_version == 4)
|
||||
eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04;
|
||||
samples_table = tracks_table + (num_tracks + 1) * 0x04;
|
||||
eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04;
|
||||
total_streams = (eof_offset - samples_table) / 0x08;
|
||||
off_mult = 0x04;
|
||||
|
||||
|
@ -892,23 +917,20 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
}
|
||||
} else if (version == 4) {
|
||||
/* Need for Speed: Underground 2, SSX 3, Harry Potter and the Prisoner of Azkaban */
|
||||
/* we need to go through all the sections to get to the samples table */
|
||||
/* get the last entry offset */
|
||||
section_offset = 0x20;
|
||||
entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
|
||||
if (big_endian) {
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 15) & 0xFF;
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 15) & 0x0F;
|
||||
} else {
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 20) & 0xFF;
|
||||
subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 20) & 0x0F;
|
||||
}
|
||||
section_offset = entry_offset + 0x10 + subentry_num * 0x04;
|
||||
|
||||
/* get the last entry offset */
|
||||
entry_offset = read_u16(section_offset + (num_events - 1) * 0x02, sf) * 0x04;
|
||||
if (big_endian) {
|
||||
subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 10) & 0xFF;
|
||||
subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 10) & 0x3F;
|
||||
} else {
|
||||
subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 8) & 0xFF;
|
||||
subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 8) & 0x3F;
|
||||
}
|
||||
section_offset = entry_offset + 0x10 + subentry_num * 0x10;
|
||||
|
||||
|
@ -933,7 +955,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
break;
|
||||
}
|
||||
} else if (version == 5) {
|
||||
/* Need for Speed: Most Wanted, Need for Speed: Carbon */
|
||||
/* Need for Speed: Most Wanted, Need for Speed: Carbon, SSX on Tour */
|
||||
tracks_table = read_u32(0x2c, sf);
|
||||
samples_table = read_u32(0x34, sf);
|
||||
eof_offset = read_u32(0x38, sf);
|
||||
|
@ -951,8 +973,9 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
continue; /* empty track */
|
||||
|
||||
if (track_start <= target_stream - 1) {
|
||||
track_hash = read_u32be(entry_offset + 0x08, sf);
|
||||
is_bnk = (track_hash == 0xF1F1F1F1);
|
||||
num_subbanks = read_u16(entry_offset + 0x04, sf);
|
||||
track_checksum = read_u32be(entry_offset + 0x08, sf);
|
||||
is_bnk = (num_subbanks != 0);
|
||||
|
||||
/* checks to distinguish it from SNR/SNS version */
|
||||
if (is_bnk) {
|
||||
|
@ -1017,11 +1040,15 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
}
|
||||
|
||||
if (version == 5) {
|
||||
track_hash = read_u32be(entry_offset + 0x14 + 0x10 * bnk_index, sf);
|
||||
if (read_u32be(0x00, sf_mus) != track_hash)
|
||||
track_checksum = read_u32be(entry_offset + 0x14 + 0x10 * bnk_index, sf);
|
||||
if (read_u32be(0x00, sf_mus) != track_checksum)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (read_u32be(bnk_offset, sf_mus) != EA_BNK_HEADER_LE &&
|
||||
read_u32be(bnk_offset, sf_mus) != EA_BNK_HEADER_BE)
|
||||
goto fail;
|
||||
|
||||
/* play until the next entry in MPF track or the end of BNK */
|
||||
if (target_stream < track_end) {
|
||||
next_entry = read_u32(samples_table + (target_stream - 0) * 0x08 + 0x00, sf);
|
||||
|
@ -1050,11 +1077,11 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
|||
if (!vgmstream)
|
||||
goto fail;
|
||||
} else {
|
||||
if (version == 5 && read_u32be(0x00, sf_mus) != track_hash)
|
||||
if (version == 5 && read_u32be(0x00, sf_mus) != track_checksum)
|
||||
goto fail;
|
||||
|
||||
sound_offset *= off_mult;;
|
||||
if (read_32bitBE(sound_offset, sf_mus) != EA_BLOCKID_HEADER)
|
||||
if (read_u32be(sound_offset, sf_mus) != EA_BLOCKID_HEADER)
|
||||
goto fail;
|
||||
|
||||
vgmstream = parse_schl_block(sf_mus, sound_offset, 0);
|
||||
|
|
|
@ -1,26 +1,17 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset);
|
||||
|
||||
/* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */
|
||||
VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) {
|
||||
if (!check_extensions(streamFile, "gin"))
|
||||
goto fail;
|
||||
|
||||
return init_vgmstream_gin_header(streamFile, 0x00);
|
||||
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate, num_samples;
|
||||
|
||||
if (!check_extensions(streamFile, "gin"))
|
||||
goto fail;
|
||||
|
||||
/* checks */
|
||||
if (read_32bitBE(offset + 0x00, streamFile) != 0x476E7375) /* "Gnsu" */
|
||||
if (read_32bitBE(0x00, streamFile) != 0x476E7375) /* "Gnsu" */
|
||||
goto fail;
|
||||
|
||||
/* contains mapped values for engine RPM sounds but we'll just play the whole thing */
|
||||
|
@ -30,11 +21,11 @@ VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset) {
|
|||
/* 0x14: RPM ??? table size */
|
||||
/* always LE even on X360/PS3 */
|
||||
|
||||
num_samples = read_32bitLE(offset + 0x18, streamFile);
|
||||
sample_rate = read_32bitLE(offset + 0x1c, streamFile);
|
||||
start_offset = offset + 0x20 +
|
||||
(read_32bitLE(offset + 0x10, streamFile) + 1) * 0x04 +
|
||||
(read_32bitLE(offset + 0x14, streamFile) + 1) * 0x04;
|
||||
num_samples = read_32bitLE(0x18, streamFile);
|
||||
sample_rate = read_32bitLE(0x1c, streamFile);
|
||||
start_offset = 0x20 +
|
||||
(read_32bitLE(0x10, streamFile) + 1) * 0x04 +
|
||||
(read_32bitLE(0x14, streamFile) + 1) * 0x04;
|
||||
channel_count = 1;
|
||||
loop_flag = 0;
|
||||
|
||||
|
|
|
@ -376,6 +376,12 @@ static const hcakey_info hcakey_list[] = {
|
|||
/* D4DJ Groovy Mix (Android) [base files] */
|
||||
{393410674916959300}, // 0575ACECA945A444
|
||||
|
||||
/* Toji no Miko: Kizamishi Issen no Tomoshibi (Android) */
|
||||
{62057514034227932}, // 00DC78FAEFA76ADC
|
||||
|
||||
/* Readyyy! (Android) */
|
||||
{1234567890987654321}, // 112210F4B16C1CB1
|
||||
|
||||
/* Dragalia Lost (iOS/Android) */
|
||||
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ VGMSTREAM * init_vgmstream_mus_acm(STREAMFILE *streamFile) {
|
|||
goto fail;
|
||||
|
||||
|
||||
channel_count = data->segments[0]->channels;
|
||||
channel_count = data->output_channels;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
|
|
@ -3,45 +3,54 @@
|
|||
#include "../util.h"
|
||||
|
||||
/* DTK - headerless Nintendo GC DTK file [Harvest Moon: Another Wonderful Life (GC), XGRA (GC)] */
|
||||
VGMSTREAM * init_vgmstream_ngc_adpdtk(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM* init_vgmstream_ngc_adpdtk(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channel_count, loop_flag;
|
||||
int channels, loop_flag;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .dtk: standard [XGRA (GC)], .adp: standard [Harvest Moon AWL (GC)], .wav/lwav: Alien Hominid (GC) */
|
||||
if ( !check_extensions(streamFile,"dtk,adp,wav,lwav"))
|
||||
/* .dtk: standard [XGRA (GC)]
|
||||
* .adp: standard [Harvest Moon AWL (GC)]
|
||||
* .wav/lwav: Alien Hominid (GC) */
|
||||
if (!check_extensions(sf,"dtk,adp,wav,lwav"))
|
||||
goto fail;
|
||||
|
||||
/* check valid frames as files have no header, and .adp/wav are common */
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) { /* try a bunch of frames */
|
||||
if (read_8bit(0x00 + i*0x20,streamFile) != read_8bit(0x02 + i*0x20,streamFile) ||
|
||||
read_8bit(0x01 + i*0x20,streamFile) != read_8bit(0x03 + i*0x20,streamFile))
|
||||
goto fail;
|
||||
/* header 0x00/01 are repeated in 0x02/03 (for error correction?),
|
||||
* could also test header values (upper nibble should be 0..3, and lower nibble 0..C) */
|
||||
if (read_8bit(0x00 + i*0x20,sf) != read_8bit(0x02 + i*0x20,sf) ||
|
||||
read_8bit(0x01 + i*0x20,sf) != read_8bit(0x03 + i*0x20,sf))
|
||||
goto fail;
|
||||
|
||||
/* frame headers for silent frames are 0x0C, never null */
|
||||
if (read_8bit(0x00 + i*0x20,sf) == 0x00)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* always stereo, no loop (since it's hardware-decoded and streamed) */
|
||||
channel_count = 2;
|
||||
/* DTK (Disc Track) are DVD hardware-decoded streams, always stereo and no loop.
|
||||
* Some games fake looping by calling DVD commands to set position with certain timing.
|
||||
* Though headerless, format is HW-wired to those specs. */
|
||||
channels = 2;
|
||||
loop_flag = 0;
|
||||
start_offset = 0x00;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_samples = get_streamfile_size(streamFile) / 0x20 * 28;
|
||||
vgmstream->num_samples = get_streamfile_size(sf) / 0x20 * 28;
|
||||
vgmstream->sample_rate = 48000; /* due to a GC hardware defect this may be closer to 48043 */
|
||||
vgmstream->coding_type = coding_NGC_DTK;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_NGC_ADPDTK;
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
if ( !vgmstream_open_stream(vgmstream, sf, start_offset) )
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
|
|
@ -347,8 +347,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
|||
* .at9: standard ATRAC9
|
||||
* .saf: Whacked! (Xbox)
|
||||
* .mwv: Level-5 games [Dragon Quest VIII (PS2), Rogue Galaxy (PS2)]
|
||||
* .ima: Baja: Edge of Control (PS3/X360)
|
||||
*/
|
||||
if ( check_extensions(sf, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,saf") ) {
|
||||
if ( check_extensions(sf, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,saf,ima") ) {
|
||||
;
|
||||
}
|
||||
else if ( check_extensions(sf, "mwv") ) {
|
||||
|
|
|
@ -1,59 +1,92 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* sadl - from DS games with Procyon Studio audio driver [Professor Layton (DS), Soma Bringer (DS)] */
|
||||
VGMSTREAM* init_vgmstream_sadl(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int channel_count, loop_flag;
|
||||
int channels, loop_flag;
|
||||
off_t start_offset;
|
||||
uint8_t flags;
|
||||
uint32_t loop_start, data_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "sad"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,sf) != 0x7361646c) /* "sadl" */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x40,sf) != get_streamfile_size(sf))
|
||||
if (read_u32be(0x00,sf) != 0x7361646c) /* "sadl" */
|
||||
goto fail;
|
||||
/* 04: null */
|
||||
/* 08: data size, or null in later files */
|
||||
/* 0c: version? (x0410=Luminous Arc, 0x0411=Layton, 0x0415=rest) */
|
||||
/* 0e: file id (for .sad packed in .spd) */
|
||||
/* 14: name related? */
|
||||
/* 20: short filename (may be null or nor match full filename) */
|
||||
|
||||
/* 30: flags? (0/1/2) */
|
||||
loop_flag = read_u8(0x31,sf);
|
||||
channels = read_u8(0x32,sf);
|
||||
flags = read_u8(0x33,sf);
|
||||
/* 34: flags? */
|
||||
/* 38: flags? */
|
||||
/* 3c: null? */
|
||||
data_size = read_u32le(0x40,sf); //?
|
||||
start_offset = read_u32le(0x48,sf); /* usually 0x100, 0xc0 in LA */
|
||||
/* 4c: start offset again or 0x40 in LA */
|
||||
/* 50: size or samples? */
|
||||
loop_start = read_u32le(0x54,sf); //?
|
||||
/* others: sizes/samples/flags? */
|
||||
|
||||
data_size -= start_offset;
|
||||
loop_start -= start_offset;
|
||||
|
||||
|
||||
loop_flag = read_8bit(0x31,sf);
|
||||
channel_count = read_8bit(0x32,sf);
|
||||
start_offset = 0x100;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
switch (read_8bit(0x33,sf) & 6) {
|
||||
vgmstream->meta_type = meta_SADL;
|
||||
|
||||
switch (flags & 6) { /* possibly > 1? (0/1/2) */
|
||||
case 4:
|
||||
vgmstream->sample_rate = 32728;
|
||||
break;
|
||||
case 2:
|
||||
case 2: /* Layton */
|
||||
case 0: /* Luminous Arc (DS) */
|
||||
vgmstream->sample_rate = 16364;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_SADL;
|
||||
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
|
||||
switch(read_8bit(0x33,sf) & 0xf0) {
|
||||
switch(flags & 0xf0) { /* possibly >> 6? (0/1/2) */
|
||||
case 0x00: /* Luminous Arc (DS) (non-int IMA? all files are mono though) */
|
||||
case 0x70: /* Ni no Kuni (DS), Professor Layton and the Curious Village (DS), Soma Bringer (DS) */
|
||||
vgmstream->coding_type = coding_IMA_int;
|
||||
|
||||
vgmstream->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count*2;
|
||||
vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count*2;
|
||||
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
|
||||
vgmstream->loop_start_sample = ima_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < channels; i++) {
|
||||
vgmstream->ch[i].adpcm_history1_32 = read_s16le(0x80 + i*0x04 + 0x00, sf);
|
||||
vgmstream->ch[i].adpcm_step_index = read_s16le(0x80 + i*0x04 + 0x02, sf);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
//TODO: Luminous Arc 2 uses a variation of this, but value 0x70
|
||||
case 0xb0: /* Soma Bringer (DS), Rekishi Taisen Gettenka (DS) */
|
||||
vgmstream->coding_type = coding_NDS_PROCYON;
|
||||
|
||||
vgmstream->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count/16*30;
|
||||
vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count/16*30;
|
||||
vgmstream->num_samples = data_size / channels / 16 * 30;
|
||||
vgmstream->loop_start_sample = loop_start / channels / 16 *30;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
break;
|
||||
|
||||
|
|
|
@ -1,114 +1,129 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
|
||||
|
||||
/* .str - 3DO format with CTRL/SNDS/SHDR blocks [Icebreaker (3DO), Battle Pinball (3DO)] */
|
||||
VGMSTREAM * init_vgmstream_str_snds(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, shdr_offset = -1;
|
||||
int loop_flag, channel_count, found_shdr = 0;
|
||||
size_t file_size, ctrl_size = -1;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "str"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4354524c && /* "CTRL" */
|
||||
read_32bitBE(0x00,streamFile) != 0x534e4453 && /* "SNDS" */
|
||||
read_32bitBE(0x00,streamFile) != 0x53484452) /* "SHDR" */
|
||||
goto fail;
|
||||
|
||||
file_size = get_streamfile_size(streamFile);
|
||||
start_offset = 0x00;
|
||||
|
||||
/* scan chunks until we find a SNDS containing a SHDR */
|
||||
{
|
||||
off_t current_chunk = 0;
|
||||
|
||||
while (!found_shdr && current_chunk < file_size) {
|
||||
if (current_chunk < 0) goto fail;
|
||||
|
||||
if (current_chunk+read_32bitBE(current_chunk+0x04,streamFile) >= file_size)
|
||||
goto fail;
|
||||
|
||||
switch (read_32bitBE(current_chunk,streamFile)) {
|
||||
case 0x4354524C: /* "CTRL" */
|
||||
ctrl_size = read_32bitBE(current_chunk+4,streamFile);
|
||||
break;
|
||||
|
||||
case 0x534e4453: /* "SNDS" */
|
||||
switch (read_32bitBE(current_chunk+16,streamFile)) {
|
||||
case 0x53484452: /* SHDR */
|
||||
found_shdr = 1;
|
||||
shdr_offset = current_chunk+16;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x53484452: /* "SHDR" */
|
||||
switch (read_32bitBE(current_chunk+0x7C, streamFile)) {
|
||||
case 0x4354524C: /* "CTRL" */
|
||||
/* to distinguish between styles */
|
||||
ctrl_size = read_32bitBE(current_chunk + 0x80, streamFile);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* ignore others for now */
|
||||
break;
|
||||
}
|
||||
|
||||
current_chunk += read_32bitBE(current_chunk+0x04,streamFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_shdr) goto fail;
|
||||
|
||||
channel_count = read_32bitBE(shdr_offset+0x20,streamFile);
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_STR_SNDS;
|
||||
vgmstream->sample_rate = read_32bitBE(shdr_offset+0x1c,streamFile);
|
||||
|
||||
if (ctrl_size == 0x1C || ctrl_size == 0x0B || ctrl_size == -1) {
|
||||
vgmstream->num_samples = read_32bitBE(shdr_offset+0x2c,streamFile) - 1; /* sample count? */
|
||||
}
|
||||
else {
|
||||
vgmstream->num_samples = read_32bitBE(shdr_offset+0x2c,streamFile) * 0x10; /* frame count? */
|
||||
}
|
||||
vgmstream->num_samples /= vgmstream->channels;
|
||||
|
||||
switch (read_32bitBE(shdr_offset+0x24,streamFile)) {
|
||||
case 0x53445832: /* "SDX2" */
|
||||
if (channel_count > 1) {
|
||||
vgmstream->coding_type = coding_SDX2_int;
|
||||
vgmstream->interleave_block_size = 1;
|
||||
} else
|
||||
vgmstream->coding_type = coding_SDX2;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
vgmstream->layout_type = layout_blocked_str_snds;
|
||||
|
||||
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"
|
||||
#include "../layout/layout.h"
|
||||
|
||||
|
||||
/* .str - 3DO format with CTRL/SNDS/SHDR blocks [Icebreaker (3DO), Battle Pinball (3DO)] */
|
||||
VGMSTREAM* init_vgmstream_str_snds(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset, shdr_offset = -1;
|
||||
int loop_flag, channels, found_shdr = 0;
|
||||
size_t file_size, ctrl_size = -1;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .str: standard
|
||||
* .stream: Battle Tryst (Arcade) movies
|
||||
* .3do: Aqua World - Umimi Monogatari (3DO) movies */
|
||||
if (!check_extensions(sf, "str,stream,3do"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00,sf) != 0x4354524c && /* "CTRL" */
|
||||
read_u32be(0x00,sf) != 0x534e4453 && /* "SNDS" */
|
||||
read_u32be(0x00,sf) != 0x53484452) /* "SHDR" */
|
||||
goto fail;
|
||||
|
||||
file_size = get_streamfile_size(sf);
|
||||
start_offset = 0x00;
|
||||
|
||||
/* scan chunks until we find a SNDS containing a SHDR */
|
||||
{
|
||||
off_t offset = 0;
|
||||
uint32_t size;
|
||||
|
||||
while (!found_shdr && offset < file_size) {
|
||||
if (offset < 0) goto fail;
|
||||
|
||||
size = read_u32be(offset + 0x04,sf);
|
||||
if (offset + size >= file_size)
|
||||
goto fail;
|
||||
|
||||
switch (read_u32be(offset + 0x00,sf)) {
|
||||
case 0x4354524C: /* "CTRL" */
|
||||
ctrl_size = read_u32be(offset + 0x04,sf);
|
||||
break;
|
||||
|
||||
case 0x534e4453: /* "SNDS" */
|
||||
switch (read_u32be(offset + 0x10,sf)) {
|
||||
case 0x53484452: /* SHDR */
|
||||
found_shdr = 1;
|
||||
shdr_offset = offset + 0x10;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x53484452: /* "SHDR" */
|
||||
switch (read_u32be(offset + 0x7C, sf)) {
|
||||
case 0x4354524C: /* "CTRL" */
|
||||
/* to distinguish between styles */
|
||||
ctrl_size = read_u32be(offset + 0x80, sf);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: /* ignore others */
|
||||
break;
|
||||
}
|
||||
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_shdr)
|
||||
goto fail;
|
||||
|
||||
channels = read_u32be(shdr_offset+0x20,sf);
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_STR_SNDS;
|
||||
vgmstream->sample_rate = read_u32be(shdr_offset+0x1c,sf);
|
||||
|
||||
if (ctrl_size == 0x1C || ctrl_size == 0x0B || ctrl_size == -1) {
|
||||
vgmstream->num_samples = read_u32be(shdr_offset+0x2c,sf) - 1; /* sample count? */
|
||||
}
|
||||
else {
|
||||
vgmstream->num_samples = read_u32be(shdr_offset+0x2c,sf) * 0x10; /* frame count? */
|
||||
}
|
||||
vgmstream->num_samples /= vgmstream->channels;
|
||||
|
||||
switch (read_u32be(shdr_offset + 0x24,sf)) {
|
||||
case 0x53445832: /* "SDX2" (common) */
|
||||
if (channels > 1) {
|
||||
vgmstream->coding_type = coding_SDX2_int;
|
||||
vgmstream->interleave_block_size = 0x01;
|
||||
} else {
|
||||
vgmstream->coding_type = coding_SDX2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x43424432: /* "CBD2" (rare, Battle Tryst) */
|
||||
if (channels > 1) {
|
||||
vgmstream->coding_type = coding_CBD2_int;
|
||||
vgmstream->interleave_block_size = 0x01;
|
||||
} else {
|
||||
vgmstream->coding_type = coding_CBD2; /* assumed */
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
vgmstream->layout_type = layout_blocked_str_snds;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ typedef enum {
|
|||
TGC = 29, /* Tiger Game.com 4-bit ADPCM */
|
||||
ASF = 30, /* Argonaut ASF 4-bit ADPCM */
|
||||
EAXA = 31, /* Electronic Arts EA-XA 4-bit ADPCM v1 */
|
||||
OKI4S = 32, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */
|
||||
} txth_type;
|
||||
|
||||
typedef enum { DEFAULT, NEGATIVE, POSITIVE, INVERTED } txth_loop_t;
|
||||
|
@ -226,6 +227,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
|
|||
case PCM4: coding = coding_PCM4; break;
|
||||
case PCM4_U: coding = coding_PCM4_U; break;
|
||||
case OKI16: coding = coding_OKI16; break;
|
||||
case OKI4S: coding = coding_OKI4S; break;
|
||||
case TGC: coding = coding_TGC; break;
|
||||
case ASF: coding = coding_ASF; break;
|
||||
case EAXA: coding = coding_EA_XA; break;
|
||||
|
@ -337,6 +339,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
|
|||
break;
|
||||
|
||||
case coding_OKI16:
|
||||
case coding_OKI4S:
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
|
||||
|
@ -872,6 +875,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
|||
else if (is_string(val,"PCM4")) txth->codec = PCM4;
|
||||
else if (is_string(val,"PCM4_U")) txth->codec = PCM4_U;
|
||||
else if (is_string(val,"OKI16")) txth->codec = OKI16;
|
||||
else if (is_string(val,"OKI4S")) txth->codec = OKI4S;
|
||||
else if (is_string(val,"AAC")) txth->codec = AAC;
|
||||
else if (is_string(val,"TGC")) txth->codec = TGC;
|
||||
else if (is_string(val,"GCOM_ADPCM")) txth->codec = TGC;
|
||||
|
@ -1524,8 +1528,8 @@ static int parse_name_table(txth_header* txth, char * name_list) {
|
|||
|
||||
//;VGM_LOG("TXTH: compare name '%s'\n", key);
|
||||
/* parse values if key (name) matches default ("") or filename with/without extension */
|
||||
if (key[0]=='\0'
|
||||
|| is_string_match(filename, key)
|
||||
if (key[0]=='\0'
|
||||
|| is_string_match(filename, key)
|
||||
|| is_string_match(basename, key)
|
||||
|| is_string_match(fullname, key)) {
|
||||
int n;
|
||||
|
@ -1790,6 +1794,7 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
|
|||
return yamaha_bytes_to_samples(bytes, txth->channels);
|
||||
case PCFX:
|
||||
case OKI16:
|
||||
case OKI4S:
|
||||
return oki_bytes_to_samples(bytes, txth->channels);
|
||||
|
||||
/* untested */
|
||||
|
|
|
@ -1,161 +1,150 @@
|
|||
#ifndef _TXTH_STREAMFILE_H_
|
||||
#define _TXTH_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t chunk_start;
|
||||
size_t chunk_size;
|
||||
size_t chunk_header_size;
|
||||
size_t chunk_data_size;
|
||||
int chunk_count;
|
||||
int chunk_number;
|
||||
} txth_io_config_data;
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
txth_io_config_data cfg;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} txth_io_data;
|
||||
|
||||
|
||||
static size_t txth_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, txth_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->cfg.chunk_start;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->cfg.chunk_start + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
/* base sizes */
|
||||
data->block_size = data->cfg.chunk_size * data->cfg.chunk_count;
|
||||
data->skip_size = data->cfg.chunk_size * data->cfg.chunk_number;
|
||||
data->data_size = data->cfg.chunk_size;
|
||||
|
||||
/* chunk size modifiers */
|
||||
if (data->cfg.chunk_header_size) {
|
||||
data->skip_size += data->cfg.chunk_header_size;
|
||||
data->data_size -= data->cfg.chunk_header_size;
|
||||
}
|
||||
if (data->cfg.chunk_data_size) {
|
||||
data->data_size = data->cfg.chunk_data_size;
|
||||
}
|
||||
|
||||
/* clamp for games where last block is smaller */ //todo not correct for all cases
|
||||
if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) {
|
||||
data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset;
|
||||
data->skip_size = (data->block_size / data->cfg.chunk_count) * data->cfg.chunk_number;
|
||||
}
|
||||
if (data->physical_offset + data->data_size > data->cfg.chunk_start + data->stream_size) {
|
||||
data->data_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t txth_io_size(STREAMFILE *streamfile, txth_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
txth_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of generic chunked streams */
|
||||
static STREAMFILE* setup_txth_streamfile(STREAMFILE *streamFile, txth_io_config_data cfg, int is_opened_streamfile) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
txth_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(txth_io_data);
|
||||
|
||||
io_data.cfg = cfg; /* memcpy */
|
||||
io_data.stream_size = (get_streamfile_size(streamFile) - cfg.chunk_start);
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
//io_data.logical_size = io_data.stream_size / cfg.chunk_count; //todo would help with performance but not ok if data_size is set
|
||||
|
||||
|
||||
new_streamFile = streamFile;
|
||||
|
||||
/* setup subfile */
|
||||
if (!is_opened_streamfile) {
|
||||
/* if streamFile was opened by txth code we MUST close it once done (as it's now "fused"),,
|
||||
* otherwise it was external to txth and must be wrapped to avoid closing it */
|
||||
new_streamFile = open_wrap_streamfile(new_streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
}
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, txth_io_read,txth_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
//new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
//if (!new_streamFile) goto fail;
|
||||
//temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _TXTH_STREAMFILE_H_ */
|
||||
#ifndef _TXTH_STREAMFILE_H_
|
||||
#define _TXTH_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t chunk_start;
|
||||
size_t chunk_size;
|
||||
size_t chunk_header_size;
|
||||
size_t chunk_data_size;
|
||||
int chunk_count;
|
||||
int chunk_number;
|
||||
} txth_io_config_data;
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
txth_io_config_data cfg;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} txth_io_data;
|
||||
|
||||
|
||||
static size_t txth_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, txth_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->cfg.chunk_start;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->cfg.chunk_start + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
/* base sizes */
|
||||
data->block_size = data->cfg.chunk_size * data->cfg.chunk_count;
|
||||
data->skip_size = data->cfg.chunk_size * data->cfg.chunk_number;
|
||||
data->data_size = data->cfg.chunk_size;
|
||||
|
||||
/* chunk size modifiers */
|
||||
if (data->cfg.chunk_header_size) {
|
||||
data->skip_size += data->cfg.chunk_header_size;
|
||||
data->data_size -= data->cfg.chunk_header_size;
|
||||
}
|
||||
if (data->cfg.chunk_data_size) {
|
||||
data->data_size = data->cfg.chunk_data_size;
|
||||
}
|
||||
|
||||
/* clamp for games where last block is smaller */ //todo not correct for all cases
|
||||
if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) {
|
||||
data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset;
|
||||
data->skip_size = (data->block_size / data->cfg.chunk_count) * data->cfg.chunk_number;
|
||||
}
|
||||
if (data->physical_offset + data->data_size > data->cfg.chunk_start + data->stream_size) {
|
||||
data->data_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t txth_io_size(STREAMFILE* sf, txth_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
txth_io_read(sf, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
//todo use deblock streamfile
|
||||
/* Handles deinterleaving of generic chunked streams */
|
||||
static STREAMFILE* setup_txth_streamfile(STREAMFILE* sf, txth_io_config_data cfg, int is_opened_streamfile) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
txth_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(txth_io_data);
|
||||
|
||||
io_data.cfg = cfg; /* memcpy */
|
||||
io_data.stream_size = (get_streamfile_size(sf) - cfg.chunk_start);
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
//io_data.logical_size = io_data.stream_size / cfg.chunk_count; //todo would help with performance but not ok if data_size is set
|
||||
|
||||
/* setup subfile */
|
||||
if (!is_opened_streamfile) {
|
||||
/* if sf was opened by txth code we MUST close it once done (as it's now "fused"),
|
||||
* otherwise it was external to txth and must be wrapped to avoid closing it */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
}
|
||||
else {
|
||||
new_sf = sf; /* can be closed */
|
||||
}
|
||||
|
||||
new_sf = open_io_streamfile(new_sf, &io_data,io_data_size, txth_io_read,txth_io_size);
|
||||
new_sf = open_buffer_streamfile_f(new_sf, 0); /* big speedup when used with interleaved codecs */
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _TXTH_STREAMFILE_H_ */
|
||||
|
|
|
@ -107,7 +107,7 @@ typedef struct {
|
|||
char repeat;
|
||||
int selected;
|
||||
|
||||
txtp_entry group_settings;
|
||||
txtp_entry entry;
|
||||
|
||||
} txtp_group;
|
||||
|
||||
|
@ -184,7 +184,6 @@ VGMSTREAM* init_vgmstream_txtp(STREAMFILE* sf) {
|
|||
|
||||
clean_txtp(txtp, 0);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
clean_txtp(txtp, 1);
|
||||
return NULL;
|
||||
|
@ -334,10 +333,11 @@ static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_
|
|||
//;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count);
|
||||
|
||||
for (i = position, j = 0; i < position + count; i++, j++) {
|
||||
if (txtp->entry[i].loop_anchor_start) {
|
||||
loop_start = j + 1; /* logic elsewhere also uses +1 */
|
||||
/* catch first time anchors appear only, also logic elsewhere also uses +1 */
|
||||
if (txtp->entry[i].loop_anchor_start && !loop_start) {
|
||||
loop_start = j + 1;
|
||||
}
|
||||
if (txtp->entry[i].loop_anchor_end) {
|
||||
if (txtp->entry[i].loop_anchor_end && !loop_end) {
|
||||
loop_end = j + 1;
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +354,8 @@ static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int make_group_segment(txtp_header* txtp, int is_group, int position, int count) {
|
||||
|
||||
static int make_group_segment(txtp_header* txtp, txtp_group* grp, int position, int count) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
segmented_layout_data *data_s = NULL;
|
||||
int i, loop_flag = 0;
|
||||
|
@ -362,7 +363,7 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
|
|||
|
||||
|
||||
/* allowed for actual groups (not final "mode"), otherwise skip to optimize */
|
||||
if (!is_group && count == 1) {
|
||||
if (!grp && count == 1) {
|
||||
//;VGM_LOG("TXTP: ignored single group\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -440,6 +441,13 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
|
|||
/* set new vgmstream and reorder positions */
|
||||
update_vgmstream_list(vgmstream, txtp, position, count);
|
||||
|
||||
|
||||
/* special "whole loop" settings */
|
||||
if (grp && grp->entry.loop_anchor_start == 1) {
|
||||
grp->entry.config.config_set = 1;
|
||||
grp->entry.config.really_force_loop = 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
|
@ -448,14 +456,14 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int make_group_layer(txtp_header* txtp, int is_group, int position, int count) {
|
||||
static int make_group_layer(txtp_header* txtp, txtp_group* grp, int position, int count) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
layered_layout_data* data_l = NULL;
|
||||
int i;
|
||||
|
||||
|
||||
/* allowed for actual groups (not final mode), otherwise skip to optimize */
|
||||
if (!is_group && count == 1) {
|
||||
if (!grp && count == 1) {
|
||||
//;VGM_LOG("TXTP: ignored single group\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -492,16 +500,17 @@ static int make_group_layer(txtp_header* txtp, int is_group, int position, int c
|
|||
}
|
||||
}
|
||||
|
||||
/* loop settings only make sense if this group becomes final vgmstream */
|
||||
if (position == 0 && txtp->vgmstream_count == count) {
|
||||
if (txtp->is_loop_auto && !vgmstream->loop_flag) {
|
||||
vgmstream_force_loop(vgmstream, 1, 0, vgmstream->num_samples);
|
||||
}
|
||||
}
|
||||
|
||||
/* set new vgmstream and reorder positions */
|
||||
update_vgmstream_list(vgmstream, txtp, position, count);
|
||||
|
||||
|
||||
/* special "whole loop" settings (also loop if this group becomes final vgmstream) */
|
||||
if (grp && (grp->entry.loop_anchor_start == 1
|
||||
|| (position == 0 && txtp->vgmstream_count == count && txtp->is_loop_auto))) {
|
||||
grp->entry.config.config_set = 1;
|
||||
grp->entry.config.really_force_loop = 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
|
@ -510,12 +519,12 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int make_group_random(txtp_header* txtp, int is_group, int position, int count, int selected) {
|
||||
static int make_group_random(txtp_header* txtp, txtp_group* grp, int position, int count, int selected) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int i;
|
||||
|
||||
/* allowed for actual groups (not final mode), otherwise skip to optimize */
|
||||
if (!is_group && count == 1) {
|
||||
if (!grp && count == 1) {
|
||||
//;VGM_LOG("TXTP: ignored single group\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -525,11 +534,6 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* special case meaning "play all", basically for quick testing */
|
||||
if (selected == count) {
|
||||
return make_group_segment(txtp, is_group, position, count);
|
||||
}
|
||||
|
||||
/* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime
|
||||
* (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */
|
||||
if (selected < 0) {
|
||||
|
@ -539,19 +543,44 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int
|
|||
//;VGM_LOG("TXTP: autoselected random %i\n", selected);
|
||||
}
|
||||
|
||||
if (selected < 0 || selected >= count) {
|
||||
if (selected < 0 || selected > count) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* get selected and remove non-selected */
|
||||
vgmstream = txtp->vgmstream[position + selected];
|
||||
txtp->vgmstream[position + selected] = NULL;
|
||||
for (i = 0; i < count; i++) {
|
||||
close_vgmstream(txtp->vgmstream[i + position]);
|
||||
if (selected == count) {
|
||||
/* special case meaning "select all", basically for quick testing and clearer Wwise */
|
||||
if (!make_group_segment(txtp, grp, position, count))
|
||||
goto fail;
|
||||
vgmstream = txtp->vgmstream[position];
|
||||
}
|
||||
else {
|
||||
/* get selected and remove non-selected */
|
||||
vgmstream = txtp->vgmstream[position + selected];
|
||||
txtp->vgmstream[position + selected] = NULL;
|
||||
for (i = 0; i < count; i++) {
|
||||
close_vgmstream(txtp->vgmstream[i + position]);
|
||||
}
|
||||
|
||||
/* set new vgmstream and reorder positions */
|
||||
update_vgmstream_list(vgmstream, txtp, position, count);
|
||||
}
|
||||
|
||||
/* set new vgmstream and reorder positions */
|
||||
update_vgmstream_list(vgmstream, txtp, position, count);
|
||||
|
||||
/* special "whole loop" settings */
|
||||
if (grp && grp->entry.loop_anchor_start == 1) {
|
||||
grp->entry.config.config_set = 1;
|
||||
grp->entry.config.really_force_loop = 1;
|
||||
}
|
||||
|
||||
/* force selected vgmstream to be a segment when not a group already, and
|
||||
* group + vgmstream has config (AKA must loop/modify over the result) */
|
||||
//todo could optimize to not generate segment in some cases?
|
||||
if (grp &&
|
||||
!(vgmstream->layout_type == layout_layered || vgmstream->layout_type == layout_segmented) &&
|
||||
(grp->entry.config.config_set && vgmstream->config.config_set) ) {
|
||||
if (!make_group_segment(txtp, grp, position, 1))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
|
@ -595,15 +624,15 @@ static int parse_groups(txtp_header* txtp) {
|
|||
//;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups);
|
||||
switch(grp->type) {
|
||||
case TXTP_GROUP_MODE_LAYERED:
|
||||
if (!make_group_layer(txtp, 1, pos, grp->count))
|
||||
if (!make_group_layer(txtp, grp, pos, grp->count))
|
||||
goto fail;
|
||||
break;
|
||||
case TXTP_GROUP_MODE_SEGMENTED:
|
||||
if (!make_group_segment(txtp, 1, pos, grp->count))
|
||||
if (!make_group_segment(txtp, grp, pos, grp->count))
|
||||
goto fail;
|
||||
break;
|
||||
case TXTP_GROUP_MODE_RANDOM:
|
||||
if (!make_group_random(txtp, 1, pos, grp->count, grp->selected))
|
||||
if (!make_group_random(txtp, grp, pos, grp->count, grp->selected))
|
||||
goto fail;
|
||||
break;
|
||||
default:
|
||||
|
@ -611,24 +640,28 @@ static int parse_groups(txtp_header* txtp) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* group may also have settings (like downmixing) */
|
||||
apply_settings(txtp->vgmstream[grp->position], &grp->group_settings);
|
||||
txtp->entry[grp->position] = grp->group_settings; /* memcpy old settings for subgroups */
|
||||
apply_settings(txtp->vgmstream[grp->position], &grp->entry);
|
||||
txtp->entry[grp->position] = grp->entry; /* memcpy old settings for subgroups */
|
||||
}
|
||||
|
||||
/* final tweaks (should be integrated with the above?) */
|
||||
if (txtp->is_layered) {
|
||||
if (!make_group_layer(txtp, 0, 0, txtp->vgmstream_count))
|
||||
if (!make_group_layer(txtp, NULL, 0, txtp->vgmstream_count))
|
||||
goto fail;
|
||||
}
|
||||
if (txtp->is_segmented) {
|
||||
if (!make_group_segment(txtp, 0, 0, txtp->vgmstream_count))
|
||||
if (!make_group_segment(txtp, NULL, 0, txtp->vgmstream_count))
|
||||
goto fail;
|
||||
}
|
||||
if (txtp->is_single) {
|
||||
/* special case of setting start_segment to force/overwrite looping
|
||||
* (better to use #E but left for compatibility with older TXTPs) */
|
||||
if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) {
|
||||
//todo try look settings
|
||||
//txtp->default_entry.config.config_set = 1;
|
||||
//txtp->default_entry.config.really_force_loop = 1;
|
||||
vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples);
|
||||
}
|
||||
}
|
||||
|
@ -1426,6 +1459,15 @@ static void parse_params(txtp_entry* entry, char* params) {
|
|||
params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set);
|
||||
tcfg->config_set = 1;
|
||||
}
|
||||
else if (strcmp(command,"B") == 0) {
|
||||
params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set);
|
||||
tcfg->config_set = 1;
|
||||
/* similar to 'b' but implies no fades */
|
||||
tcfg->fade_time_set = 1;
|
||||
tcfg->fade_time = 0;
|
||||
tcfg->fade_delay_set = 1;
|
||||
tcfg->fade_delay = 0;
|
||||
}
|
||||
|
||||
/* other settings */
|
||||
else if (strcmp(command,"h") == 0) {
|
||||
|
@ -1458,7 +1500,7 @@ static void parse_params(txtp_entry* entry, char* params) {
|
|||
entry->loop_anchor_start = 1;
|
||||
//;VGM_LOG("TXTP: anchor start set\n");
|
||||
}
|
||||
else if (is_match(command,"A") || is_match(command,"@LOOP")) {
|
||||
else if (is_match(command,"A") || is_match(command,"@loop-end")) {
|
||||
entry->loop_anchor_end = 1;
|
||||
//;VGM_LOG("TXTP: anchor end set\n");
|
||||
}
|
||||
|
@ -1587,18 +1629,24 @@ static int add_group(txtp_header* txtp, char* line) {
|
|||
|
||||
m = sscanf(line, " >%c%n", &c, &n);
|
||||
if (m == 1 && c == TXTP_GROUP_RANDOM_ALL) {
|
||||
cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>- but allows L/S>- */
|
||||
cfg.selected = cfg.count; /* special meaning */
|
||||
line += n;
|
||||
}
|
||||
else {
|
||||
m = sscanf(line, " >%d%n", &cfg.selected, &n);
|
||||
if (m == 1) {
|
||||
cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>1 but allows L/S>1 */
|
||||
cfg.selected--; /* externally 1=first but internally 0=first */
|
||||
line += n;
|
||||
}
|
||||
else if (cfg.type == TXTP_GROUP_MODE_RANDOM) {
|
||||
/* was a random but didn't select anything, just select all */
|
||||
cfg.selected = cfg.count;
|
||||
}
|
||||
}
|
||||
|
||||
parse_params(&cfg.group_settings, line);
|
||||
parse_params(&cfg.entry, line);
|
||||
|
||||
/* Groups can use "auto" position of last N files, so we need a counter that changes like this:
|
||||
* #layer of 2 (pos = 0)
|
||||
|
|
|
@ -570,7 +570,7 @@ static VGMSTREAM* init_vgmstream_ubi_bao_sequence(ubi_bao_header* bao, STREAMFIL
|
|||
|
||||
|
||||
/* build the base VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single);
|
||||
vgmstream = allocate_vgmstream(data->output_channels, !bao->sequence_single);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_UBI_BAO;
|
||||
|
|
|
@ -724,7 +724,7 @@ static VGMSTREAM *init_vgmstream_ubi_dat_main(ubi_sb_header *sb, STREAMFILE *sf_
|
|||
}
|
||||
case 0x04: { /* standard WAV */
|
||||
if (!sb->is_external) {
|
||||
VGM_LOG("Ubi DAT: Found RAM stream_type 0x04\n");
|
||||
VGM_LOG("UBI DAT: Found RAM stream_type 0x04\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -1157,7 +1157,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_base(ubi_sb_header* sb, STREAMFILE* sf_h
|
|||
size_t bytes, chunk_size;
|
||||
off_t header_offset;
|
||||
|
||||
VGM_ASSERT(sb->is_streamed, "Ubi SB: Raw XMA used for streamed sound\n");
|
||||
VGM_ASSERT(sb->is_streamed, "UBI SB: Raw XMA used for streamed sound\n");
|
||||
|
||||
/* get XMA header from extra section */
|
||||
chunk_size = 0x20;
|
||||
|
@ -1477,7 +1477,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_sequence(ubi_sb_header* sb, STREAMFILE*
|
|||
goto fail;
|
||||
|
||||
/* build the base VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(data->segments[0]->channels, !sb->sequence_single);
|
||||
vgmstream = allocate_vgmstream(data->output_channels, !sb->sequence_single);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_UBI_SB;
|
||||
|
@ -1741,7 +1741,7 @@ fail:
|
|||
}
|
||||
|
||||
static uint32_t ubi_ps2_pitch_to_freq(uint32_t pitch) {
|
||||
/* old PS2 games store sample rate in a weird range of 0-65536 remapped from 0-48000 */
|
||||
/* old PS2 games store sample rate in a weird range of 0-0x10000 remapped from 0-48000 */
|
||||
/* strangely, audio res type does have sample rate value but it's unused */
|
||||
double sample_rate = (((double)pitch / 65536) * 48000);
|
||||
return (uint32_t)ceil(sample_rate);
|
||||
|
@ -1803,7 +1803,7 @@ static int parse_type_layer_ps2_old(ubi_sb_header* sb, off_t offset, STREAMFILE*
|
|||
}
|
||||
|
||||
if (sb->layer_count > SB_MAX_LAYER_COUNT) {
|
||||
VGM_LOG("Ubi SB: incorrect layer count\n");
|
||||
VGM_LOG("UBI SB: incorrect layer count\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -1924,7 +1924,7 @@ static int parse_type_sequence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf)
|
|||
/* sequence chain */
|
||||
sb->type = UBI_SEQUENCE;
|
||||
if (sb->cfg.sequence_sequence_count == 0) {
|
||||
VGM_LOG("Ubi SB: sequence not configured at %x\n", (uint32_t)offset);
|
||||
VGM_LOG("UBI SB: sequence not configured at %x\n", (uint32_t)offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -1934,7 +1934,7 @@ static int parse_type_sequence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf)
|
|||
sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, sf);
|
||||
|
||||
if (sb->sequence_count > SB_MAX_CHAIN_COUNT) {
|
||||
VGM_LOG("Ubi SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT);
|
||||
VGM_LOG("UBI SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -1986,7 +1986,7 @@ static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
|
|||
/* layer header */
|
||||
sb->type = UBI_LAYER;
|
||||
if (sb->cfg.layer_layer_count == 0) {
|
||||
VGM_LOG("Ubi SB: layers not configured at %x\n", (uint32_t)offset);
|
||||
VGM_LOG("UBI SB: layers not configured at %x\n", (uint32_t)offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -2007,7 +2007,7 @@ static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
|
|||
}
|
||||
|
||||
if (sb->layer_count > SB_MAX_LAYER_COUNT) {
|
||||
VGM_LOG("Ubi SB: incorrect layer count\n");
|
||||
VGM_LOG("UBI SB: incorrect layer count\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -2031,7 +2031,7 @@ static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
|
|||
int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, sf);
|
||||
|
||||
if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) {
|
||||
VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset);
|
||||
VGM_LOG("UBI SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset);
|
||||
if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */
|
||||
goto fail;
|
||||
}
|
||||
|
@ -2070,7 +2070,7 @@ static int parse_type_silence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
|
|||
/* silence header */
|
||||
sb->type = UBI_SILENCE;
|
||||
if (sb->cfg.silence_duration_int == 0 && sb->cfg.silence_duration_float == 0) {
|
||||
VGM_LOG("Ubi SB: silence duration not configured at %x\n", (uint32_t)offset);
|
||||
VGM_LOG("UBI SB: silence duration not configured at %x\n", (uint32_t)offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -2096,7 +2096,7 @@ static int parse_type_random(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
|
|||
|
||||
/* sequence chain */
|
||||
if (sb->cfg.random_entry_size == 0) {
|
||||
VGM_LOG("Ubi SB: random entry size not configured at %x\n", (uint32_t)offset);
|
||||
VGM_LOG("UBI SB: random entry size not configured at %x\n", (uint32_t)offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -2689,8 +2689,7 @@ static void config_sb_sequence(ubi_sb_header* sb, off_t sequence_count, off_t en
|
|||
if (sb->is_bnm || sb->is_dat || sb->is_ps2_bnm) {
|
||||
sb->cfg.sequence_sequence_loop = sequence_count - 0x0c;
|
||||
sb->cfg.sequence_sequence_single= sequence_count - 0x08;
|
||||
}
|
||||
if (sb->is_blk) {
|
||||
} else if (sb->is_blk) {
|
||||
sb->cfg.sequence_sequence_loop = sequence_count - 0x14;
|
||||
sb->cfg.sequence_sequence_single= sequence_count - 0x0c;
|
||||
}
|
||||
|
@ -3314,7 +3313,7 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) {
|
|||
/* Prince of Persia: The Sands of Time (2003)(PS2)-bank 0x000A0004 / 0x000A0002 (POP1 port/Demo) */
|
||||
/* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */
|
||||
/* Tom Clancy's Ghost Recon 2 (2004)(PS2)-bank 0x000A0007 */
|
||||
/* Splinter Cell: Pandora Tomorrow (2006)(PS2)-bank 0x000A0008 (separate banks from main map) */
|
||||
/* Splinter Cell: Pandora Tomorrow (2004)(PS2)-bank 0x000A0008 (separate banks from main map) */
|
||||
/* Prince of Persia: Warrior Within (Demo)(2004)(PS2)-bank 0x00100000 */
|
||||
/* Prince of Persia: Warrior Within (2004)(PS2)-bank 0x00120009 */
|
||||
if ((sb->version == 0x000A0002 && sb->platform == UBI_PS2) ||
|
||||
|
@ -3334,7 +3333,6 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) {
|
|||
config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10);
|
||||
|
||||
config_sb_silence_i(sb, 0x18);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ typedef struct {
|
|||
size_t smpl_size;
|
||||
off_t seek_offset;
|
||||
size_t seek_size;
|
||||
|
||||
off_t meta_offset;
|
||||
size_t meta_size;
|
||||
|
||||
/* standard fmt stuff */
|
||||
wwise_codec codec;
|
||||
|
@ -40,6 +41,7 @@ typedef struct {
|
|||
int block_align;
|
||||
int average_bps;
|
||||
int bits_per_sample;
|
||||
uint8_t channel_type;
|
||||
uint32_t channel_layout;
|
||||
size_t extra_size;
|
||||
|
||||
|
@ -65,7 +67,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
|||
|
||||
/* checks */
|
||||
/* .wem: newer "Wwise Encoded Media" used after the 2011.2 SDK (~july 2011)
|
||||
* .wav: older ADPCM files [Punch Out!! (Wii)]
|
||||
* .wav: older PCM/ADPCM files [Spider-Man: Web of Shadows (PC), Punch Out!! (Wii)]
|
||||
* .xma: older XMA files [Too Human (X360), Tron Evolution (X360)]
|
||||
* .ogg: older Vorbis files [The King of Fighters XII (X360)]
|
||||
* .bnk: Wwise banks for memory .wem detection */
|
||||
|
@ -254,6 +256,12 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
|||
cfg.blocksize_0_exp = read_u8(extra_offset + block_offsets + 0x01, sf); /* big */
|
||||
ww.data_size -= audio_offset;
|
||||
|
||||
/* mutant .wem with metadata (voice strings/etc) between seek table and vorbis setup [Gears of War 4 (PC)] */
|
||||
if (ww.meta_offset) {
|
||||
/* 0x00: original setup_offset */
|
||||
setup_offset += read_u32(ww.meta_offset + 0x04, sf); /* metadata size */
|
||||
}
|
||||
|
||||
/* detect normal packets */
|
||||
if (ww.extra_size == 0x30) {
|
||||
/* almost all blocksizes are 0x08+0x0B except some with 0x09+0x09 [Oddworld New 'n' Tasty! (PSV)] */
|
||||
|
@ -462,7 +470,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
|||
break;
|
||||
}
|
||||
|
||||
case OPUS: { /* alt to Vorbis [Girl Cafe Gun (Mobile)] */
|
||||
case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile)] */
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
|
||||
/* extra: size 0x12 */
|
||||
|
@ -484,19 +492,27 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
|||
break;
|
||||
}
|
||||
|
||||
#if 0 // disabled until more files/tests
|
||||
case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */
|
||||
int skip, table_count;
|
||||
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
if (!ww.seek_offset)) goto fail;
|
||||
case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */
|
||||
int mapping;
|
||||
opus_config cfg = {0};
|
||||
|
||||
/* extra: size 0x10 */
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
if (!ww.seek_offset) goto fail;
|
||||
if (ww.channels > 8) goto fail; /* mapping not defined */
|
||||
|
||||
cfg.channels = ww.channels;
|
||||
cfg.table_offset = ww.seek_offset;
|
||||
|
||||
/* extra: size 0x10 (though last 2 fields are beyond, AK plz) */
|
||||
/* 0x12: samples per frame */
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
|
||||
table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
|
||||
skip = read_u16(ww.fmt_offset + 0x20, sf);
|
||||
/* 0x22: 1? (though extra size is declared as 0x10 so this is outsize, AK plz */
|
||||
cfg.table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
|
||||
cfg.skip = read_u16(ww.fmt_offset + 0x20, sf);
|
||||
/* 0x22: codec version */
|
||||
mapping = read_u8(ww.fmt_offset + 0x23, sf);
|
||||
|
||||
if (read_u8(ww.fmt_offset + 0x22, sf) != 1)
|
||||
goto fail;
|
||||
|
||||
/* OPUS is VBR so this is very approximate percent, meh */
|
||||
if (ww.truncated) {
|
||||
|
@ -505,14 +521,47 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
|||
ww.data_size = ww.file_size - start_offset;
|
||||
}
|
||||
|
||||
/* AK does some wonky implicit config for multichannel */
|
||||
if (mapping == 1 && ww.channel_type == 1) { /* only allowed values ATM, set when >2ch */
|
||||
static const int8_t mapping_matrix[8][8] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, },
|
||||
{ 0, 1, 0, 0, 0, 0, 0, 0, },
|
||||
{ 0, 2, 1, 0, 0, 0, 0, 0, },
|
||||
{ 0, 1, 2, 3, 0, 0, 0, 0, },
|
||||
{ 0, 4, 1, 2, 3, 0, 0, 0, },
|
||||
{ 0, 4, 1, 2, 3, 5, 0, 0, },
|
||||
{ 0, 6, 1, 2, 3, 4, 5, 0, },
|
||||
{ 0, 6, 1, 2, 3, 4, 5, 7, },
|
||||
};
|
||||
int i;
|
||||
|
||||
/* find coupled OPUS streams (internal streams using 2ch) */
|
||||
switch(ww.channel_layout) {
|
||||
case mapping_7POINT1_surround: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
|
||||
case mapping_5POINT1_surround: /* 2ch+2ch+1ch+1ch, 4 streams */
|
||||
case mapping_QUAD_side: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
|
||||
case mapping_2POINT1_xiph: /* 2ch+1ch, 2 streams */
|
||||
case mapping_STEREO: cfg.coupled_count = 1; break; /* 2ch, 1 stream */
|
||||
default: cfg.coupled_count = 0; break; /* 1ch, 1 stream */
|
||||
//TODO: AK OPUS doesn't seem to handles others mappings, though AK's .h imply they exist (uses 0 coupleds?)
|
||||
}
|
||||
|
||||
/* total number internal OPUS streams (should be >0) */
|
||||
cfg.stream_count = ww.channels - cfg.coupled_count;
|
||||
|
||||
/* channel assignments */
|
||||
for (i = 0; i < ww.channels; i++) {
|
||||
cfg.channel_mapping[i] = mapping_matrix[ww.channels - 1][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Wwise Opus saves all frame sizes in the seek table */
|
||||
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.seek_offset, table_count, ww.data_offset, ww.data_size, ww.channels, skip);
|
||||
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.data_offset, ww.data_size, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
case HEVAG: /* PSV */
|
||||
|
@ -650,6 +699,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
|||
* - HEVAG: very off
|
||||
* - XMA2: exact file size
|
||||
* - some RIFX have LE size
|
||||
* Value is ignored by AK's parser (set to -1).
|
||||
* (later we'll validate "data" which fortunately is correct)
|
||||
*/
|
||||
if (read_u32(0x04,sf) + 0x04 + 0x04 != ww->file_size) {
|
||||
|
@ -695,6 +745,10 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
|||
ww->smpl_offset = offset;
|
||||
ww->smpl_size = size;
|
||||
break;
|
||||
case 0x6D657461: /* "meta" */
|
||||
ww->meta_offset = offset;
|
||||
ww->meta_size = size;
|
||||
break;
|
||||
|
||||
case 0x66616374: /* "fact" */
|
||||
/* Wwise shouldn't use fact, but if somehow some file does uncomment the following: */
|
||||
|
@ -745,6 +799,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
|||
* - 4b: eConfigType (0=none, 1=standard, 2=ambisonic)
|
||||
* - 19b: uChannelMask */
|
||||
if ((ww->channel_layout & 0xFF) == ww->channels) {
|
||||
ww->channel_type = (ww->channel_layout >> 8) & 0x0F;
|
||||
ww->channel_layout = (ww->channel_layout >> 12);
|
||||
}
|
||||
}
|
||||
|
@ -773,7 +828,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
|||
/* format to codec */
|
||||
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 0x0002: ww->codec = IMA; break; /* newer Wwise (variable, probably means "platform's ADPCM") */
|
||||
case 0x0069: ww->codec = IMA; break; /* older Wwise [Spiderman Web of Shadows (X360), LotR Conquest (PC)] */
|
||||
case 0x0161: ww->codec = XWMA; break; /* WMAv2 */
|
||||
case 0x0162: ww->codec = XWMA; break; /* WMAPro */
|
||||
|
@ -781,13 +836,13 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
|||
case 0x0166: ww->codec = XMA2; break; /* fmt-chunk XMA */
|
||||
case 0xAAC0: ww->codec = AAC; break;
|
||||
case 0xFFF0: ww->codec = DSP; break;
|
||||
case 0xFFFB: ww->codec = HEVAG; break;
|
||||
case 0xFFFB: ww->codec = HEVAG; break; /* "VAG" */
|
||||
case 0xFFFC: ww->codec = ATRAC9; break;
|
||||
case 0xFFFE: ww->codec = PCM; break; /* "PCM for Wwise Authoring" */
|
||||
case 0xFFFF: ww->codec = VORBIS; break;
|
||||
case 0x3039: ww->codec = OPUSNX; break; /* renamed from "OPUS" on Wwise 2018.1 */
|
||||
case 0x3040: ww->codec = OPUS; break;
|
||||
case 0x3041: ww->codec = OPUSWW; break; /* added on Wwise 2019.2.3, presumably replaces OPUS */
|
||||
case 0x3041: ww->codec = OPUSWW; break; /* "OPUS_WEM", added on Wwise 2019.2.3, replaces OPUS */
|
||||
case 0x8311: ww->codec = PTADPCM; break; /* added on Wwise 2019.1, replaces IMA */
|
||||
default:
|
||||
goto fail;
|
||||
|
@ -839,61 +894,54 @@ fail:
|
|||
/*
|
||||
- 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
|
||||
0x00 (4): dwTotalPCMFrames
|
||||
0x04 (4): skip samples?
|
||||
0x08 (4): ? (small if loop, 0 otherwise)
|
||||
0x0c (4): data start offset after seek table+setup, or loop start when "smpl" is present
|
||||
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 (2): biggest packet size (not including header)?
|
||||
0x22 (2): ? (small, N..~0x100) uLastGranuleExtra?
|
||||
0x24 (4): ? (mid, 0~0x5000) dwDecodeAllocSize?
|
||||
0x28 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize?
|
||||
0x2c (4): parent bank/event id? uHashCodebook? (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)
|
||||
0x08 (4): LoopInfo.uLoopBeginExtra? (present if loop)
|
||||
0x0c (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present)
|
||||
0x10 (4): LoopInfo.uLoopEndExtra? (0..~0x400)
|
||||
0x14 (4): LoopInfo.dwLoopEndPacketOffset?
|
||||
0x18 (4): dwSeekTableSize (0 = no seek table)
|
||||
0x1c (4): dwVorbisDataOffset (offset within data)
|
||||
0x20 (2): uMaxPacketSize (not including header)
|
||||
0x22 (2): uLastGranuleExtra (0..~0x100)
|
||||
0x24 (4): dwDecodeAllocSize (0~0x5000)
|
||||
0x28 (4): dwDecodeX64AllocSize (mid, 0~0x5000)
|
||||
0x2c (4): uHashCodebook? (shared by several .wem a game, but not all need to share it)
|
||||
0x30 (1): uBlockSizes[0] (blocksize_1_exp, small)
|
||||
0x31 (1): uBlockSizes[1] (blocksize_0_exp, large)
|
||||
0x32 (2): empty
|
||||
|
||||
"vorb" size 0x28 / 0x2c / 0x2a
|
||||
0x00 (4): num_samples
|
||||
0x04 (4): data start offset after seek table+setup, or loop start when "smpl" is present
|
||||
0x08 (4): data end offset after seek table (setup+packets), or loop end when "smpl" is present
|
||||
0x00 (4): dwTotalPCMFrames
|
||||
0x04 (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present)
|
||||
0x08 (4): LoopInfo.dwLoopEndPacketOffset (data end, or loop end when "smpl" is present)
|
||||
0x0c (2): ? (small, 0..~0x400) [(4) when size is 0x2C]
|
||||
0x10 (4): setup_offset within data (0 = no seek table)
|
||||
0x14 (4): audio_offset within data
|
||||
0x18 (2): biggest packet size (not including header)?
|
||||
0x1a (2): ? (small, N..~0x100) uLastGranuleExtra? [(4) when size is 0x2C]
|
||||
0x1c (4): ? (mid, 0~0x5000) dwDecodeAllocSize?
|
||||
0x20 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize?
|
||||
0x24 (4): parent bank/event id? uHashCodebook? (shared by several .wem a game, but not all need to share it)
|
||||
0x28 (1): blocksize_1_exp (small) [removed when size is 0x28]
|
||||
0x29 (1): blocksize_0_exp (large) [removed when size is 0x28]
|
||||
0x10 (4): dwSeekTableSize (0 = no seek table)
|
||||
0x14 (4): dwVorbisDataOffset (offset within data)
|
||||
0x18 (2): uMaxPacketSize (not including header)
|
||||
0x1a (2): uLastGranuleExtra (0..~0x100) [(4) when size is 0x2C]
|
||||
0x1c (4): dwDecodeAllocSize (0~0x5000)
|
||||
0x20 (4): dwDecodeX64AllocSize (0~0x5000)
|
||||
0x24 (4): uHashCodebook? (shared by several .wem a game, but not all need to share it)
|
||||
0x28 (1): uBlockSizes[0] (blocksize_1_exp, small) [removed when size is 0x28]
|
||||
0x29 (1): uBlockSizes[1] (blocksize_0_exp, large) [removed when size is 0x28]
|
||||
|
||||
- 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): data start offset after seek table+setup, or loop start when "smpl" is present
|
||||
0x20 (4): data end offset after seek table (setup+packets), or loop end when "smpl" is present
|
||||
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): (small, 0..~0x100) uLastGranuleExtra?
|
||||
0x34 (4): ? (mid, 0~0x5000) dwDecodeAllocSize?
|
||||
0x38 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize?
|
||||
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
|
||||
0x18 (4): dwTotalPCMFrames
|
||||
0x1c (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present)
|
||||
0x20 (4): LoopInfo.dwLoopEndPacketOffset (data end, or loop end when "smpl" is present)
|
||||
0x24 (2): LoopInfo.uLoopBeginExtra (small, 0..~0x400)
|
||||
0x26 (2): LoopInfo.uLoopEndExtra (extra samples after seek?)
|
||||
0x28 (4): dwSeekTableSize (0 = no seek table)
|
||||
0x2c (4): dwVorbisDataOffset (offset within data)
|
||||
0x30 (2): uMaxPacketSize (not including header)
|
||||
0x32 (2): uLastGranuleExtra (small, 0..~0x100)
|
||||
0x34 (4): dwDecodeAllocSize (mid, 0~0x5000)
|
||||
0x38 (4): dwDecodeX64AllocSize (mid, 0~0x5000)
|
||||
0x40 (1): uBlockSizes[0] (blocksize_1_exp, small)
|
||||
0x41 (1): uBlockSizes[1] (blocksize_0_exp, large)
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,9 @@
|
|||
#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */
|
||||
#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */
|
||||
#define XSB_XACT1_2_MAX 11 /* other Xbox games */
|
||||
#define XSB_XACT2_MAX 41 /* other PC/X360 games */
|
||||
#define XSB_XACT2_0_MAX 34 /* Table Tennis (v34) */
|
||||
//#define XSB_XACT2_1_MAX 38 /* Prey (v38) */ // v39 too?
|
||||
#define XSB_XACT2_2_MAX 41 /* other PC/X360 games */
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
@ -44,10 +46,10 @@ typedef struct {
|
|||
static void xsb_check_stream(xsb_header *xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
|
||||
if (xsb->parse_done)
|
||||
return;
|
||||
//;VGM_LOG("XSB old: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset);
|
||||
//;VGM_LOG("XSB: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset);
|
||||
|
||||
if (stream_index < 0 || stream_index > 0xFFF || wavebank_index < 0 || wavebank_index > xsb->wavebanks_count) {
|
||||
VGM_LOG("XSB old: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
VGM_LOG("XSB: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -277,7 +279,7 @@ static int parse_xsb_old_cues(xsb_header *xsb, STREAMFILE *sf) {
|
|||
/* 0x0c: some low value or flag? */
|
||||
/* 0x0e: some index? */
|
||||
/* 0x10: 4 fields? (-1 or 7) */
|
||||
//;VGM_LOG("XSB old index %i at %lx: flags=%x, entry=%i, name_offset=%lx\n", i, offset, flags, cue_entry, name_offset);
|
||||
//;VGM_LOG("XSB old index %i at %lx: entry=%i, name_offset=%lx\n", i, offset, cue_entry, name_offset);
|
||||
|
||||
if (cue_entry < 0) {
|
||||
jump_offset = read_s32(offset + 0x08, sf);
|
||||
|
@ -315,12 +317,12 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
|||
|
||||
uint32_t flags;
|
||||
int stream_index, wavebank_index;
|
||||
int i, t, track_count, event_count;
|
||||
int i, t, track_count, event_count, size;
|
||||
|
||||
|
||||
event_count = read_s8(offset + 0x00, sf);
|
||||
|
||||
//;VGM_LOG("XSB clip at %lx\n", offset);
|
||||
//;VGM_LOG("XSB clip at %lx, events=%i\n", offset, event_count);
|
||||
offset += 0x01;
|
||||
|
||||
for (i = 0; i < event_count; i++) {
|
||||
|
@ -333,16 +335,26 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
|||
switch (flags & 0x1F) { /* event ID */
|
||||
|
||||
case 0x01: /* playwave event */
|
||||
/* 00(1): unknown */
|
||||
/* 01(1): flags */
|
||||
stream_index = read_s16(offset + 0x02, sf);
|
||||
wavebank_index = read_s8 (offset + 0x04, sf);
|
||||
/* 05(1): loop count */
|
||||
/* 06(2): pan angle */
|
||||
/* 08(2): pan arc */
|
||||
if (xsb->version <= XSB_XACT2_0_MAX) { /* v34 (Table Tennis) */
|
||||
/* 00(1): unknown */
|
||||
stream_index = read_s16(offset + 0x01, sf);
|
||||
wavebank_index = read_s8 (offset + 0x03, sf);
|
||||
/* 04(1): loop count? */
|
||||
size = 0x05;
|
||||
}
|
||||
else { /* v40 (Blue Dragon) */
|
||||
/* 00(1): unknown */
|
||||
/* 01(1): flags */
|
||||
stream_index = read_s16(offset + 0x02, sf);
|
||||
wavebank_index = read_s8 (offset + 0x04, sf);
|
||||
/* 05(1): loop count */
|
||||
/* 06(2): pan angle */
|
||||
/* 08(2): pan arc */
|
||||
size = 0x0a;
|
||||
}
|
||||
|
||||
//;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x0a;
|
||||
offset += size;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
if (xsb->parse_done) return 1;
|
||||
|
@ -354,11 +366,11 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
|||
/* 02(1): loop count */
|
||||
/* 03(2): pan angle */
|
||||
/* 05(2): pan arc */
|
||||
track_count = read_s16(offset + 0x07, sf);
|
||||
/* 09(1): flags? */
|
||||
/* 0a(5): unknown */
|
||||
/* 07(2): flags? */
|
||||
track_count = read_s16(offset + 0x09, sf); /* MonoGame reads at 0x07, but this looks correct [LocoCycle (X360)-v46] */
|
||||
/* 0b(4): unknown */
|
||||
|
||||
//;VGM_LOG("XSB clip event 3 at %lx\n", offset);
|
||||
//;VGM_LOG("XSB clip event 3 at %lx, tracks=%i\n", offset, track_count);
|
||||
offset += 0x0F;
|
||||
|
||||
for (t = 0; t < track_count; t++) {
|
||||
|
@ -389,13 +401,13 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
|||
/* 0f(1): max volume */
|
||||
/* 10(4): min frequency */
|
||||
/* 14(4): max frequency */
|
||||
/* 18(1): min Q */
|
||||
/* 19(1): max Q */
|
||||
/* 1a(1): unknown */
|
||||
/* 1b(1): variation flags */
|
||||
/* 18(4): min Q */
|
||||
/* 1c(4): max Q */
|
||||
/* 20(1): unknown */
|
||||
/* 21(1): variation flags */
|
||||
|
||||
//;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x1c;
|
||||
offset += 0x22;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
if (xsb->parse_done) return 1;
|
||||
|
@ -413,16 +425,17 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
|||
/* 0c(1): max volume */
|
||||
/* 0d(4): min frequency */
|
||||
/* 11(4): max frequency */
|
||||
/* 15(1): min Q */
|
||||
/* 16(1): max Q */
|
||||
/* 17(1): unknown */
|
||||
/* 18(1): variation flags */
|
||||
track_count = read_s16(offset + 0x19, sf);
|
||||
/* 1a(1): flags 2 */
|
||||
/* 1b(5): unknown 2 */
|
||||
/* 15(4): min Q */
|
||||
/* 19(4): max Q */
|
||||
/* 1d(1): unknown */
|
||||
/* 1e(1): variation flags? */
|
||||
/* 1f(1): unknown 2 */
|
||||
/* 20(1): variation flags? */
|
||||
track_count = read_s16(offset + 0x21, sf); /* MonoGame reads at 0x1f, but this looks correct [LocoCycle (X360)-v46] */
|
||||
/* 23(4): unknown 3 (-1?) */
|
||||
|
||||
//;VGM_LOG("XSB clip event 6 at %lx\n", offset);
|
||||
offset += 0x20;
|
||||
//;VGM_LOG("XSB clip event 6 at %lx, tracks=%i\n", offset, track_count);
|
||||
offset += 0x27;
|
||||
|
||||
for (t = 0; t < track_count; t++) {
|
||||
stream_index = read_s16(offset + 0x00, sf);
|
||||
|
@ -529,6 +542,7 @@ static int parse_xsb_sound(xsb_header *xsb, off_t offset, off_t name_offset, STR
|
|||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("XSB sound end\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -542,11 +556,15 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
|||
int i, variation_count;
|
||||
|
||||
|
||||
variation_count = read_s16(offset + 0x00, sf);
|
||||
flags = read_u16(offset + 0x02, sf);
|
||||
/* MonoGame reads count first, but this looks correct [LocoCycle (X360)-v46] */
|
||||
flags = read_u16(offset + 0x00, sf);
|
||||
variation_count = read_s16(offset + 0x02, sf);
|
||||
/* 0x04(1): unknown */
|
||||
/* 0x05(2): unknown */
|
||||
/* 0x07(1): unknown */
|
||||
|
||||
//;VGM_LOG("XSB variation at %lx\n", offset);
|
||||
offset += 0x04;
|
||||
//;VGM_LOG("XSB variation at %lx, count=%i\n", offset, variation_count);
|
||||
offset += 0x08;
|
||||
|
||||
for (i = 0; i < variation_count; i++) {
|
||||
off_t sound_offset;
|
||||
|
@ -558,7 +576,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
|||
/* 03(1): weight min */
|
||||
/* 04(1): weight max */
|
||||
|
||||
//;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
//;VGM_LOG("XSB variation: type 0 at %lx with stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x05;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
|
@ -570,7 +588,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
|||
/* 04(1): weight min */
|
||||
/* 05(1): weight max */
|
||||
|
||||
//;VGM_LOG("XSB variation: type 1\n");
|
||||
//;VGM_LOG("XSB variation: type 1 at %lx\n", offset);
|
||||
offset += 0x06;
|
||||
|
||||
parse_xsb_sound(xsb, sound_offset, name_offset, sf);
|
||||
|
@ -583,7 +601,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
|||
/* 08(4): weight max */
|
||||
/* 0c(4): flags */
|
||||
|
||||
//;VGM_LOG("XSB variation: type 3\n");
|
||||
//;VGM_LOG("XSB variation: type 3 at %lx\n", offset);
|
||||
offset += 0x10;
|
||||
|
||||
parse_xsb_sound(xsb, sound_offset, name_offset, sf);
|
||||
|
@ -594,7 +612,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
|||
stream_index = read_s16(offset + 0x00, sf);
|
||||
wavebank_index = read_s8(offset + 0x02, sf);
|
||||
|
||||
//;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
//;VGM_LOG("XSB variation: type 4 at %lx with stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x03;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
|
@ -612,7 +630,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
|||
/* 03(1): unknown */
|
||||
offset += 0x04;
|
||||
|
||||
|
||||
//;VGM_LOG("XSB variation end\n");
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
|
@ -620,7 +638,7 @@ fail:
|
|||
|
||||
|
||||
static int parse_xsb_cues(xsb_header *xsb, STREAMFILE *sf) {
|
||||
int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
|
||||
int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
|
||||
|
||||
uint8_t flags;
|
||||
off_t offset, name_offset, sound_offset;
|
||||
|
@ -767,7 +785,7 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
|||
xsb->index_size = 0x14;
|
||||
xsb->entry_size = 0x14;
|
||||
}
|
||||
else if (xsb->version <= XSB_XACT2_MAX) {
|
||||
else if (xsb->version <= XSB_XACT2_2_MAX) {
|
||||
/* 06(2): crc */
|
||||
/* 08(1): platform? (3=X360) */
|
||||
xsb->simple_cues_count = read_s16(0x09, sf);
|
||||
|
@ -821,12 +839,9 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
|||
}
|
||||
|
||||
//;VGM_LOG("XSB header: version=%i\n", xsb->version);
|
||||
//;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n",
|
||||
// xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
|
||||
//;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n",
|
||||
// xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
|
||||
//;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n",
|
||||
// xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
|
||||
//;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
|
||||
//;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
|
||||
//;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
|
||||
|
||||
if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) {
|
||||
VGM_LOG("XSB: no names found\n");
|
||||
|
@ -845,7 +860,7 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
|||
offset = xsb->wavebanks_offset;
|
||||
for (i = 0; i < xsb->wavebanks_count; i++) {
|
||||
read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset, sf);
|
||||
//;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name);
|
||||
//;VGM_LOG("XSB wavebanks: bank %i\n", i); //, wavebank_name
|
||||
if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) {
|
||||
//;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name);
|
||||
xsb->selected_wavebank = i;
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
typedef enum {
|
||||
MIX_SWAP,
|
||||
MIX_ADD,
|
||||
MIX_ADD_COPY,
|
||||
MIX_VOLUME,
|
||||
MIX_LIMIT,
|
||||
MIX_UPMIX,
|
||||
|
@ -89,28 +90,41 @@ typedef struct {
|
|||
size_t mixing_size; /* mixing max */
|
||||
mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
|
||||
float* mixbuf; /* internal mixing buffer */
|
||||
|
||||
/* fades only apply at some points, other mixes are active */
|
||||
int has_non_fade;
|
||||
int has_fade;
|
||||
} mixing_data;
|
||||
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) {
|
||||
static int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) {
|
||||
int i;
|
||||
int32_t fade_start, fade_end;
|
||||
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mix_command_data *mix = &data->mixing_chain[i];
|
||||
int32_t fade_start, fade_end;
|
||||
float vol_start = mix->vol_start;
|
||||
|
||||
if (mix->command != MIX_FADE)
|
||||
return 1; /* has non-fades = active */
|
||||
continue;
|
||||
|
||||
/* check is current range falls within a fade
|
||||
* (assuming fades were already optimized on add) */
|
||||
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
|
||||
if (mix->time_pre < 0 && vol_start == 1.0) {
|
||||
fade_start = mix->time_start; /* ignore unused */
|
||||
}
|
||||
else {
|
||||
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
|
||||
}
|
||||
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
|
||||
|
||||
if (current_start < fade_end && current_end > fade_start)
|
||||
|
||||
//;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end);
|
||||
if (current_start < fade_end && current_end > fade_start) {
|
||||
//;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -249,7 +263,7 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
|||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch, s, m, ok;
|
||||
|
||||
int32_t current_pos, current_subpos;
|
||||
int32_t current_subpos = 0;
|
||||
float temp_f, temp_min, temp_max, cur_vol = 0.0f;
|
||||
float *temp_mixbuf;
|
||||
sample_t *temp_outbuf;
|
||||
|
@ -261,18 +275,21 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
|||
if (!data || !data->mixing_on || data->mixing_count == 0)
|
||||
return;
|
||||
|
||||
/* try to skip if no ops apply (for example if fade set but does nothing yet) */
|
||||
current_pos = get_current_pos(vgmstream, sample_count);
|
||||
if (!is_active(data, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades */
|
||||
if (data->has_fade) {
|
||||
int32_t current_pos = get_current_pos(vgmstream, sample_count);
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, is_fade_active(data, current_pos, current_pos + sample_count));
|
||||
if (!data->has_non_fade && !is_fade_active(data, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
||||
current_subpos = current_pos;
|
||||
}
|
||||
|
||||
|
||||
/* use advancing buffer pointers to simplify logic */
|
||||
temp_mixbuf = data->mixbuf;
|
||||
temp_outbuf = outbuf;
|
||||
|
||||
current_subpos = current_pos;
|
||||
|
||||
/* apply mixes in order per channel */
|
||||
for (s = 0; s < sample_count; s++) {
|
||||
/* reset after new sample 'step'*/
|
||||
|
@ -307,6 +324,10 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
|||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol;
|
||||
break;
|
||||
|
||||
case MIX_ADD_COPY:
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src];
|
||||
break;
|
||||
|
||||
case MIX_VOLUME:
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
|
@ -384,7 +405,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
|||
temp_outbuf += vgmstream->channels;
|
||||
}
|
||||
|
||||
/* copy resulting mix to output */
|
||||
/* copy resulting mix to output
|
||||
* (you'd think using a int32 temp buf would be faster but somehow it's slower?) */
|
||||
for (s = 0; s < sample_count * data->output_channels; s++) {
|
||||
/* when casting float to int, value is simply truncated:
|
||||
* - (int)1.7 = 1, (int)-1.7 = -1
|
||||
|
@ -457,6 +479,14 @@ static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
|
|||
data->mixing_chain[data->mixing_count] = *mix; /* memcpy */
|
||||
data->mixing_count++;
|
||||
|
||||
|
||||
if (mix->command == MIX_FADE) {
|
||||
data->has_fade = 1;
|
||||
}
|
||||
else {
|
||||
data->has_non_fade = 1;
|
||||
}
|
||||
|
||||
//;VGM_LOG("MIX: total %i\n", data->mixing_count);
|
||||
return 1;
|
||||
}
|
||||
|
@ -485,7 +515,7 @@ void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume
|
|||
if (ch_dst < 0 || ch_src < 0) return;
|
||||
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
|
||||
|
||||
mix.command = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */
|
||||
mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.ch_src = ch_src;
|
||||
mix.vol = volume;
|
||||
|
@ -679,6 +709,10 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
|
|||
|
||||
/* ******************************************************************* */
|
||||
|
||||
#define MIX_MACRO_VOCALS 'v'
|
||||
#define MIX_MACRO_EQUAL 'e'
|
||||
#define MIX_MACRO_BGM 'b'
|
||||
|
||||
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch;
|
||||
|
@ -741,6 +775,153 @@ static int get_layered_max_channels(VGMSTREAM* vgmstream) {
|
|||
return max;
|
||||
}
|
||||
|
||||
static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
int i;
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
layered_layout_data* l_data;
|
||||
|
||||
|
||||
if (vgmstream->layout_type != layout_layered)
|
||||
return 0;
|
||||
|
||||
/* no channels set and only vocals for now */
|
||||
if (max > 0 || mode != MIX_MACRO_VOCALS)
|
||||
return 0;
|
||||
|
||||
/* no channel down/upmixing (cannot guess output) */
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mix_command_t mix = data->mixing_chain[i].command;
|
||||
if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* only previsible cases */
|
||||
l_data = vgmstream->layout_data;
|
||||
for (i = 0; i < l_data->layer_count; i++) {
|
||||
int output_channels = 0;
|
||||
|
||||
mixing_info(l_data->layers[i], NULL, &output_channels);
|
||||
|
||||
if (output_channels > 8)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */
|
||||
static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
layered_layout_data* ldata = vgmstream->layout_data;
|
||||
int i, ch;
|
||||
int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0;
|
||||
int ch_num;
|
||||
|
||||
/* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch.
|
||||
* We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */
|
||||
|
||||
/* find target "main" channels (will be first most of the time) */
|
||||
ch_num = 0;
|
||||
ch_max = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
if (ch_max < layer_chs || (ch_max == layer_chs && target_silence)) {
|
||||
target_ch = ch_num;
|
||||
target_chs = layer_chs;
|
||||
target_layer = i;
|
||||
ch_max = layer_chs;
|
||||
/* avoid using silence as main if possible for minor optimization */
|
||||
target_silence = (ldata->layers[i]->coding_type == coding_SILENCE);
|
||||
}
|
||||
|
||||
ch_num += layer_chs;
|
||||
}
|
||||
|
||||
/* all silences? */
|
||||
if (!target_chs) {
|
||||
target_ch = 0;
|
||||
target_chs = 0;
|
||||
target_layer = 0;
|
||||
mixing_info(ldata->layers[0], NULL, &target_chs);
|
||||
}
|
||||
|
||||
/* add other channels to target (assumes standard channel mapping to simplify)
|
||||
* most of the time all layers will have same number of channels though */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
if (target_layer == i) {
|
||||
ch_num += target_chs;
|
||||
continue;
|
||||
}
|
||||
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
if (ldata->layers[i]->coding_type == coding_SILENCE) {
|
||||
ch_num += layer_chs;
|
||||
continue; /* unlikely but sometimes in Wwise */
|
||||
}
|
||||
|
||||
if (layer_chs == target_chs) {
|
||||
/* 1:1 mapping */
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const double vol_sqrt = 1 / sqrt(2);
|
||||
|
||||
/* extra mixing for better sound in some cases (assumes layer_chs is lower than target_chs) */
|
||||
switch(layer_chs) {
|
||||
case 1:
|
||||
mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, vol_sqrt);
|
||||
mixing_push_add(vgmstream, target_ch + 1, ch_num + 0, vol_sqrt);
|
||||
break;
|
||||
case 2:
|
||||
mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, 1.0);
|
||||
mixing_push_add(vgmstream, target_ch + 1, ch_num + 1, 1.0);
|
||||
break;
|
||||
default: /* less common */
|
||||
//TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph)
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ch_num += layer_chs;
|
||||
}
|
||||
|
||||
/* drop non-target channels */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
|
||||
if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */
|
||||
int layer_chs = 0;
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_downmix(vgmstream, ch_num); //+ ch
|
||||
}
|
||||
|
||||
//ch_num += layer_chs; /* dropped channels change this */
|
||||
}
|
||||
else if (i == target_layer) {
|
||||
ch_num += target_chs;
|
||||
}
|
||||
else { /* most common, hopefully (faster) */
|
||||
mixing_push_killmix(vgmstream, ch_num);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int current, ch, output_channels, selected_channels;
|
||||
|
@ -748,6 +929,13 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
|||
if (!data)
|
||||
return;
|
||||
|
||||
if (is_layered_auto(vgmstream, max, mode)) {
|
||||
//;VGM_LOG("MIX: auto layer mode\n");
|
||||
mixing_macro_layer_auto(vgmstream, max, mode);
|
||||
return;
|
||||
}
|
||||
//;VGM_LOG("MIX: regular layer mode\n");
|
||||
|
||||
if (max == 0) /* auto calculate */
|
||||
max = get_layered_max_channels(vgmstream);
|
||||
|
||||
|
@ -781,10 +969,10 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
|||
if (!((mask >> ch) & 1))
|
||||
continue;
|
||||
|
||||
/* mode 'v': same volume for all layers (for layered vocals) */
|
||||
/* mode 'b': volume adjusted depending on layers (for layered bgm) */
|
||||
/* mode 'e': volume adjusted equally for all layers (for generic downmixing) */
|
||||
if (mode == 'b' && ch < max) {
|
||||
/* MIX_MACRO_VOCALS: same volume for all layers (for layered vocals) */
|
||||
/* MIX_MACRO_EQUAL: volume adjusted equally for all layers (for generic downmixing) */
|
||||
/* MIX_MACRO_BGM: volume adjusted depending on layers (for layered bgm) */
|
||||
if (mode == MIX_MACRO_BGM && ch < max) {
|
||||
/* reduce a bit main channels (see below) */
|
||||
int channel_mixes = selected_channels / max;
|
||||
if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */
|
||||
|
@ -795,7 +983,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
|||
|
||||
volume = 1 / sqrt(channel_mixes);
|
||||
}
|
||||
if ((mode == 'b' && ch >= max) || (mode == 'e')) {
|
||||
if ((mode == MIX_MACRO_BGM && ch >= max) || (mode == MIX_MACRO_EQUAL)) {
|
||||
/* find how many will be mixed in current channel (earlier channels receive more
|
||||
* mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */
|
||||
int channel_mixes = selected_channels / max;
|
||||
|
@ -909,13 +1097,13 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
|||
vgmstream->config.config_set = 1;
|
||||
}
|
||||
|
||||
/* mode 'v': constant volume
|
||||
* mode 'e': sets fades to successively lower/equalize volume per loop for each layer
|
||||
/* MIX_MACRO_VOCALS: constant volume
|
||||
* MIX_MACRO_EQUAL: sets fades to successively lower/equalize volume per loop for each layer
|
||||
* (to keep final volume constant-ish), ex. 3 layers/loops, 2 max:
|
||||
* - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
|
||||
* - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
|
||||
* - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]--
|
||||
* mode 'b': similar but 1st layer (main) has higher/delayed volume:
|
||||
* MIX_MACRO_BGM: similar but 1st layer (main) has higher/delayed volume:
|
||||
* - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]--
|
||||
*/
|
||||
for (loop = 1; loop < layer_num; loop++) {
|
||||
|
@ -927,7 +1115,7 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
|||
change_pos = loop_pre + loop_samples * loop;
|
||||
change_time = 10.0 * vgmstream->sample_rate; /* in secs */
|
||||
|
||||
if (mode == 'e') {
|
||||
if (mode == MIX_MACRO_EQUAL) {
|
||||
volume1 = 1 / sqrt(loop + 0);
|
||||
volume2 = 1 / sqrt(loop + 1);
|
||||
}
|
||||
|
@ -936,7 +1124,7 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
|||
for (layer = 0; layer < layer_num; layer++) {
|
||||
char type;
|
||||
|
||||
if (mode == 'b') {
|
||||
if (mode == MIX_MACRO_BGM) {
|
||||
if (layer == 0) {
|
||||
volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1);
|
||||
volume2 = 1 / sqrt(loop + 0);
|
||||
|
|
|
@ -361,7 +361,7 @@ static int render_pad_begin(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_
|
|||
return to_do;
|
||||
}
|
||||
|
||||
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
//play_config_t* pc = &vgmstream->config;
|
||||
|
||||
|
@ -376,7 +376,7 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
|||
int32_t to_do = ps->fade_left;
|
||||
|
||||
if (ps->play_position < ps->fade_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->fade_start);
|
||||
start = samples_left - (ps->play_position + samples_left - ps->fade_start);
|
||||
fade_pos = 0;
|
||||
}
|
||||
else {
|
||||
|
@ -384,8 +384,8 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
|||
fade_pos = ps->play_position - ps->fade_start;
|
||||
}
|
||||
|
||||
if (to_do > samples_done - start)
|
||||
to_do = samples_done - start;
|
||||
if (to_do > samples_left - start)
|
||||
to_do = samples_left - start;
|
||||
|
||||
//TODO: use delta fadedness to improve performance?
|
||||
for (s = start; s < start + to_do; s++, fade_pos++) {
|
||||
|
@ -398,27 +398,33 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
|||
ps->fade_left -= to_do;
|
||||
|
||||
/* next samples after fade end would be pad end/silence, so we can just memset */
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels);
|
||||
|
||||
return samples_done;
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_left - to_do - start) * sizeof(sample_t) * channels);
|
||||
return start + to_do;
|
||||
}
|
||||
}
|
||||
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
int channels = vgmstream->pstate.output_channels;
|
||||
int start = 0;
|
||||
int skip = 0;
|
||||
int32_t to_do;
|
||||
|
||||
/* since anything beyond pad end is silence no need to check end */
|
||||
/* pad end works like fades, where part of buf samples and part padding (silent),
|
||||
* calc exact totals (beyond pad end normally is silence, except with segmented layout) */
|
||||
if (ps->play_position < ps->pad_end_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->pad_end_start);
|
||||
skip = ps->pad_end_start - ps->play_position;
|
||||
to_do = ps->pad_end_duration;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
skip = 0;
|
||||
to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position;
|
||||
}
|
||||
|
||||
memset(buf + (start * channels), 0, (samples_done - start) * channels * sizeof(sample_t));
|
||||
return samples_done;
|
||||
if (to_do > samples_left - skip)
|
||||
to_do = samples_left - skip;
|
||||
|
||||
memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels);
|
||||
return skip + to_do;
|
||||
}
|
||||
|
||||
|
||||
|
@ -453,9 +459,9 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
|||
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
|
||||
}
|
||||
|
||||
/* end padding (done before to avoid decoding if possible, samples_to_do becomes 0) */
|
||||
if (!vgmstream->config.play_forever /* && ps->pad_end_left */
|
||||
&& ps->play_position + samples_done >= ps->pad_end_start
|
||||
/* end padding (before to avoid decoding if possible, but must be inside pad region) */
|
||||
if (!vgmstream->config.play_forever
|
||||
&& ps->play_position /*+ samples_to_do*/ >= ps->pad_end_start
|
||||
&& samples_to_do) {
|
||||
done = render_pad_end(vgmstream, tmpbuf, samples_to_do);
|
||||
samples_done += done;
|
||||
|
@ -542,6 +548,33 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
|||
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
|
||||
|
||||
|
||||
/* cleanup */
|
||||
if (seek_sample < 0)
|
||||
seek_sample = 0;
|
||||
/* play forever can seek past max */
|
||||
if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever)
|
||||
seek_sample = ps->play_duration;
|
||||
|
||||
#if 0 //todo move below, needs to clamp in decode part
|
||||
/* optimize as layouts can seek faster internally */
|
||||
if (vgmstream->layout_type == layout_segmented) {
|
||||
seek_layout_segmented(vgmstream, seek_sample);
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
vgmstream->pstate.play_position = seek_sample;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_layered) {
|
||||
seek_layout_layered(vgmstream, seek_sample);
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
vgmstream->pstate.play_position = seek_sample;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* will decode and loop until seek sample, but slower */
|
||||
//todo apply same loop logic as below, or pretend we have play_forever + settings?
|
||||
if (!vgmstream->config_enabled) {
|
||||
|
@ -574,12 +607,6 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
|||
* | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond)
|
||||
* 0 5s (-3s) 25s 95s 165s 235s 245s Ns
|
||||
*/
|
||||
|
||||
if (seek_sample < 0)
|
||||
seek_sample = 0;
|
||||
if (seek_sample > ps->play_duration && !play_forever) /* play forever can seek to any loop */
|
||||
seek_sample = ps->play_duration;
|
||||
|
||||
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
|
||||
|
||||
/* start/pad-begin: consume pad samples */
|
||||
|
|
|
@ -877,12 +877,20 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) {
|
|||
/* MISC */
|
||||
/*******************************************************************************/
|
||||
|
||||
static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) {
|
||||
double seconds = (double)samples / sample_rate;
|
||||
*p_time_mm = (int)(seconds / 60.0);
|
||||
*p_time_ss = seconds - *p_time_mm * 60.0f;
|
||||
if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */
|
||||
*p_time_ss = 59.999;
|
||||
}
|
||||
|
||||
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
|
||||
* Will always be null-terminated if length > 0 */
|
||||
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
||||
#define TEMPSIZE (256+32)
|
||||
char temp[TEMPSIZE];
|
||||
double time_mm, time_ss, seconds;
|
||||
double time_mm, time_ss;
|
||||
|
||||
if (!vgmstream) {
|
||||
snprintf(temp,TEMPSIZE, "NULL VGMSTREAM");
|
||||
|
@ -935,27 +943,22 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
|||
concatn(length,desc,"\n");
|
||||
}
|
||||
|
||||
/* times mod sounds avoid round up to 60.0 */
|
||||
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
|
||||
if (!vgmstream->loop_flag) {
|
||||
concatn(length,desc,"looping: disabled\n");
|
||||
}
|
||||
|
||||
seconds = (double)vgmstream->loop_start_sample / vgmstream->sample_rate;
|
||||
time_mm = (int)(seconds / 60.0);
|
||||
time_ss = seconds - time_mm * 60.0f;
|
||||
describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
seconds = (double)vgmstream->loop_end_sample / vgmstream->sample_rate;
|
||||
time_mm = (int)(seconds / 60.0);
|
||||
time_ss = seconds - time_mm * 60.0f;
|
||||
describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
seconds = (double)vgmstream->num_samples / vgmstream->sample_rate;
|
||||
time_mm = (int)(seconds / 60.0);
|
||||
time_ss = seconds - time_mm * 60.0;
|
||||
describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
|
@ -1012,7 +1015,7 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
|||
concatn(length,desc,temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000); //todo \n?
|
||||
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
/* only interesting if more than one */
|
||||
|
@ -1032,13 +1035,9 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
|||
}
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
double time_mm, time_ss, seconds;
|
||||
int32_t samples = vgmstream->pstate.play_duration;
|
||||
|
||||
seconds = (double)samples / vgmstream->sample_rate;
|
||||
time_mm = (int)(seconds / 60.0);
|
||||
time_ss = seconds - time_mm * 60.0f;
|
||||
|
||||
describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
|
|
@ -772,16 +772,19 @@ typedef enum {
|
|||
} speaker_t;
|
||||
|
||||
/* typical mappings that metas may use to set channel_layout (but plugin must actually use it)
|
||||
* (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR) */
|
||||
* (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR)
|
||||
* not too sure about names but no clear standards */
|
||||
typedef enum {
|
||||
mapping_MONO = speaker_FC,
|
||||
mapping_STEREO = speaker_FL | speaker_FR,
|
||||
mapping_2POINT1 = speaker_FL | speaker_FR | speaker_LFE,
|
||||
mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC,
|
||||
mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC, /* aka 3STEREO? */
|
||||
mapping_QUAD = speaker_FL | speaker_FR | speaker_BL | speaker_BR,
|
||||
mapping_QUAD_surround = speaker_FL | speaker_FR | speaker_FC | speaker_BC,
|
||||
mapping_QUAD_side = speaker_FL | speaker_FR | speaker_SL | speaker_SR,
|
||||
mapping_5POINT0 = speaker_FL | speaker_FR | speaker_LFE | speaker_BL | speaker_BR,
|
||||
mapping_5POINT0_xiph = speaker_FL | speaker_FR | speaker_FC | speaker_BL | speaker_BR,
|
||||
mapping_5POINT0_surround = speaker_FL | speaker_FR | speaker_FC | speaker_SL | speaker_SR,
|
||||
mapping_5POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR,
|
||||
mapping_5POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_SL | speaker_SR,
|
||||
mapping_7POINT0 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BC | speaker_FLC | speaker_FRC,
|
||||
|
@ -1003,6 +1006,7 @@ typedef struct {
|
|||
sample_t* buffer;
|
||||
int input_channels; /* internal buffer channels */
|
||||
int output_channels; /* resulting channels (after mixing, if applied) */
|
||||
int mixed_channels; /* segments have different number of channels */
|
||||
} segmented_layout_data;
|
||||
|
||||
/* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */
|
||||
|
|
Loading…
Reference in New Issue