Merge pull request #371 from bnnm/bnm-ntav-txth

bnm ntav txth
This commit is contained in:
Christopher Snowhill 2019-03-17 17:04:33 -07:00 committed by GitHub
commit 68bdc65583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2200 additions and 141 deletions

View File

@ -21,7 +21,7 @@
#define VERSION "(unknown version)"
#endif
#define BUFFER_SAMPLES 0x8000
#define SAMPLE_BUFFER_SIZE 0x8000
/* getopt globals (the horror...) */
extern char * optarg;
@ -384,9 +384,17 @@ int main(int argc, char ** argv) {
}
/* modify the VGMSTREAM if needed */
/* modify the VGMSTREAM if needed (before printing file info) */
apply_config(vgmstream, &cfg);
channels = vgmstream->channels;
input_channels = vgmstream->channels;
#ifdef VGMSTREAM_MIXING
/* enable after config but before outbuf */
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels);
#endif
if (cfg.play_forever && (!vgmstream->loop_flag || vgmstream->loop_target > 0)) {
fprintf(stderr,"I could play a nonlooped track forever, but it wouldn't end well.");
goto fail;
@ -462,15 +470,7 @@ int main(int argc, char ** argv) {
/* last init */
channels = vgmstream->channels;
input_channels = vgmstream->channels;
#ifdef VGMSTREAM_MIXING
/* enable after all config but before outbuf */
vgmstream_mixing_enable(vgmstream, BUFFER_SAMPLES, &input_channels, &channels);
#endif
buf = malloc(BUFFER_SAMPLES * sizeof(sample_t) * input_channels);
buf = malloc(SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels);
if (!buf) {
fprintf(stderr,"failed allocating output buffer\n");
goto fail;
@ -492,7 +492,7 @@ int main(int argc, char ** argv) {
/* decode forever */
while (cfg.play_forever) {
int to_get = BUFFER_SAMPLES;
int to_get = SAMPLE_BUFFER_SIZE;
render_vgmstream(buf, to_get, vgmstream);
@ -508,9 +508,9 @@ int main(int argc, char ** argv) {
/* decode */
for (i = 0; i < len_samples; i += BUFFER_SAMPLES) {
int to_get = BUFFER_SAMPLES;
if (i + BUFFER_SAMPLES > len_samples)
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
int to_get = SAMPLE_BUFFER_SIZE;
if (i + SAMPLE_BUFFER_SIZE > len_samples)
to_get = len_samples - i;
render_vgmstream(buf, to_get, vgmstream);
@ -563,9 +563,9 @@ int main(int argc, char ** argv) {
}
/* decode */
for (i = 0; i < len_samples; i += BUFFER_SAMPLES) {
int to_get = BUFFER_SAMPLES;
if (i + BUFFER_SAMPLES > len_samples)
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
int to_get = SAMPLE_BUFFER_SIZE;
if (i + SAMPLE_BUFFER_SIZE > len_samples)
to_get = len_samples - i;
render_vgmstream(buf, to_get, vgmstream);

View File

@ -260,3 +260,13 @@ To compile we'll use autotools with GCC preprocessor renaming:
```
- take the .dlls from celt-x.x.x/libcelt/.libs, and rename libcelt.dll to libcelt-0061.dll and libcelt-0110.dll respectively.
- Finally the includes. libcelt gives "celt.h" "celt_types.h" "celt_header.h", but since we renamed a few functions we have a simpler custom .h with minimal renamed symbols.
### maiatrac3plus
This lib was used as an alternate for ATRAC3PLUS decoding. Now this is handled by FFmpeg, though some code remains for now.
It was a straight-up decompilation from Sony's libs, without any clean-up or actual reverse engineering, thus legally and morally dubious.
It doesn't do encoder delay properly, but on the other hand decoding is 100% accurate unlike FFmpeg (probably inaudible though).
So, don't use it unless you have a very good reason.

View File

@ -193,15 +193,25 @@ channels = (number)|(offset)|(field)
# MUSIC FREQUENCY [REQUIRED]
sample_rate = (number)|(offset)|(field)
# DATA START [OPTIONAL, default to 0]
# DATA START [OPTIONAL, defaults to 0]
start_offset = (number)|(offset)|(field)
# DATA SIZE [OPTIONAL]
# Special variable that can be used in sample values.
# Defaults to (file_size - start_offset), re-calculated when start_offset
# is set (won't recalculate if data_size is set then start_offset changes).
# is set. With multiple subsongs or block_size are this it's recalculated as well.
# When padding is set it's also adjusted.
# If data_size is manually set it stays constant and won't be auto changed.
data_size = (number)|(offset)|(field)
# DATA PADDING [OPTIONAL, defaults to 0]
# Some aligned files have some extra padding that is meant to be skipped.
# This adjusts the padding in data_size, manually or auto-calculated.
# Special values (for PS-ADPCM):
# - auto: discards null frames
# - auto-empty: discards null and 'empty' frames (for games with weird padding)
padding_size = (number)|(offset)|(field)|auto|auto-empty
# SAMPLE MEANINGS [OPTIONAL, defaults to samples]
# Modifies the meaning of sample fields when set *before* them.
# Accepted values:
@ -209,10 +219,10 @@ data_size = (number)|(offset)|(field)
# - bytes: automatically converts bytes/offset to samples (applies after */+- modifiers)
# - blocks: same as bytes, but value is given in blocks/frames
# Value is internally converted from blocks to bytes first: bytes = (value * interleave*channels)
# Some codecs can't convert bytes-to-samples at the moment: MPEG/FFMPEG
# Some codecs can't convert bytes-to-samples at the moment: FFMPEG
# For XMA1/2 bytes does special parsing, with loop values being bit offsets within data.
sample_type = samples|bytes|blocks
# SAMPLE VALUES [REQUIRED (num_samples) / OPTIONAL (rest)]
# Special values:
# - data_size: automatically converts bytes-to-samples
@ -311,7 +321,11 @@ subfile_extension = (string)
## Usages
### Temporary values
Most commands are evaluated and calculated immediatedly, every time they are found. This is by design, as it can be used to adjust and trick for certain calculations.
Most commands are evaluated and calculated immediatedly, every time they are found.
This is by design, as it can be used to adjust and trick for certain calculations.
It makes TXTHs a bit harder to follow, as they are order dependant, but otherwise it's hard to accomplish some things or others become ambiguous.
For example, normally you are given a data_size in bytes, that can be used to calculate num_samples for all channels.
```
@ -327,6 +341,29 @@ sample_type = bytes
num_samples = @0x10 #calculated from channel_size
channels = 2 #change once calculations are done
```
You can also use:
```
channels = 2
sample_type = bytes
num_samples = @0x10 * channels # resulting bytes is transformed to samples
```
Do note when using special values/strings like `data_size` in `num_samples` and `loop_end_samples` they must be alone to trigger.
```
data_size = @0x100
num_samples = data_size * 2 # doesn't tranform bytes-to-samples (do it before? after?)
```
```
data_size = @0x100 * 2
num_samples = data_size # ok
```
Also beware of order:
```
start_offset = 0x200 # recalculated data_size
num_samples = data_size # transforms bytes-to-samples
data_size = @0x100 # useless as num_samples is already transformed
```
### Redefining values
Some commands alter the function of all next commands and can be redefined as needed:

View File

@ -9,7 +9,7 @@ struct atrac9_codec_data {
uint8_t *data_buffer;
size_t data_buffer_size;
sample *sample_buffer;
sample_t *sample_buffer;
size_t samples_filled; /* number of samples in the buffer */
size_t samples_used; /* number of samples extracted from the buffer */
@ -39,7 +39,7 @@ atrac9_codec_data *init_atrac9(atrac9_config *cfg) {
status = Atrac9GetCodecInfo(data->handle, &data->info);
if (status < 0) goto fail;
//;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, info.superframeSize, info.framesInSuperframe, info.frameSamples);
//;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples);
if (cfg->channels && cfg->channels != data->info.channels) {
VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels);
@ -50,7 +50,7 @@ atrac9_codec_data *init_atrac9(atrac9_config *cfg) {
/* must hold at least one superframe and its samples */
data->data_buffer_size = data->info.superframeSize;
data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size);
data->sample_buffer = calloc(sizeof(sample), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
data->samples_to_discard = cfg->encoder_delay;
@ -63,7 +63,7 @@ fail:
return NULL;
}
void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) {
void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) {
VGMSTREAMCHANNEL *stream = &vgmstream->ch[0];
atrac9_codec_data * data = vgmstream->codec_data;
int samples_done = 0;
@ -220,20 +220,20 @@ void free_atrac9(atrac9_codec_data *data) {
}
size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) {
return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe);
}
#if 0 //not needed (for now)
int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size) {
static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) {
static const int sample_rate_table[16] = {
11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
44100, 48000, 64000, 88200, 96000,128000,176400,192000
};
static const int samples_power_table[16] = {
6, 6, 7, 7, 7, 8, 8, 8,
6, 6, 7, 7, 7, 8, 8, 8
};
static const int channel_table[8] = {
1, 2, 2, 6, 8, 4, 0, 0
};
int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe;
uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */
uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */
uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */
@ -242,6 +242,11 @@ int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_c
size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */
/* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */
superframe_size = ((frame_size+1) << superframe_index);
frames_per_superframe = (1 << superframe_index);
samples_per_frame = 1 << samples_power_table[sample_rate_index];
samples_per_superframe = samples_per_frame * frames_per_superframe;
if (sync != 0xFE)
goto fail;
if (out_sample_rate)
@ -249,11 +254,23 @@ int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_c
if (out_channels)
*out_channels = channel_table[channels_index];
if (out_frame_size)
*out_frame_size = (frame_size+1) * (1 << superframe_index);
*out_frame_size = superframe_size;
if (out_samples_per_frame)
*out_samples_per_frame = samples_per_superframe;
return 1;
fail:
return 0;
}
#endif
size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) {
return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe);
}
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) {
size_t frame_size, samples_per_frame;
if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame))
return 0;
return bytes / frame_size * samples_per_frame;
}
#endif

View File

@ -84,6 +84,7 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing
void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size);
int ps_find_loop_offsets(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * out_loop_start, int32_t * out_loop_end);
int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * out_loop_start, int32_t * out_loop_end);
size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty);
size_t ps_bytes_to_samples(size_t bytes, int channels);
size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels);
@ -260,12 +261,12 @@ void free_at3plus(maiatrac3plus_codec_data *data);
#ifdef VGM_USE_ATRAC9
/* atrac9_decoder */
atrac9_codec_data *init_atrac9(atrac9_config *cfg);
void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels);
void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels);
void reset_atrac9(VGMSTREAM *vgmstream);
void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample);
void free_atrac9(atrac9_codec_data *data);
size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data);
//int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size);
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config);
#endif
#ifdef VGM_USE_CELT

View File

@ -176,6 +176,9 @@ static int ps_find_loop_offsets_internal(STREAMFILE *streamFile, off_t start_off
int detect_full_loops = config & 1;
if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0))
return 0;
while (offset < max_offset) {
uint8_t flag = (uint8_t)read_8bit(offset+0x01,streamFile) & 0x0F; /* lower nibble only (for HEVAG) */
@ -268,6 +271,63 @@ int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t
return ps_find_loop_offsets_internal(streamFile, start_offset, data_size, channels, interleave, out_loop_start, out_loop_end, 1);
}
size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty) {
off_t min_offset, offset;
size_t frame_size = 0x10;
size_t padding_size = 0;
size_t interleave_consumed = 0;
if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0))
return 0;
offset = start_offset + data_size;
min_offset = 0; //offset - interleave; /* some files have padding spanning multiple interleave blocks */
while (offset > min_offset) {
uint32_t f1,f2,f3,f4;
uint8_t flag;
int is_empty = 0;
offset -= frame_size;
f1 = read_32bitBE(offset+0x00,streamFile);
f2 = read_32bitBE(offset+0x04,streamFile);
f3 = read_32bitBE(offset+0x08,streamFile);
f4 = read_32bitBE(offset+0x0c,streamFile);
flag = (f1 >> 16) & 0xFF;
if (f1 == 0 && f2 == 0 && f3 == 0 && f4 == 0)
is_empty = 1;
if (!is_empty && discard_empty) {
if (flag == 0x07 || flag == 0x77)
is_empty = 1; /* 'discard frame' flag */
else if ((f1 & 0xFF00FFFF) == 0 && f2 == 0 && f3 == 0 && f4 == 0)
is_empty = 1; /* silent with flags (typical for looping files) */
else if ((f1 & 0xFF00FFFF) == 0x0C000000 && f2 == 0 && f3 == 0 && f4 == 0)
is_empty = 1; /* silent (maybe shouldn't ignore flag 0x03?) */
else if ((f1 & 0x0000FFFF) == 0x00007777 && f2 == 0x77777777 && f3 ==0x77777777 && f4 == 0x77777777)
is_empty = 1; /* silent-ish */
}
if (!is_empty)
break;
padding_size += frame_size * channels;
/* skip other channels */
interleave_consumed += 0x10;
if (interleave_consumed == interleave) {
interleave_consumed = 0;
offset -= interleave*(channels - 1);
}
}
return padding_size;
}
size_t ps_bytes_to_samples(size_t bytes, int channels) {
if (channels <= 0) return 0;
return bytes / channels / 0x10 * 28;

View File

@ -18,6 +18,7 @@ static const char* extension_list[] = {
"2dx9",
"2pfs",
"800",
"9tav",
//"aac", //common
"aa3", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA)
@ -90,6 +91,7 @@ static const char* extension_list[] = {
"bmdx",
"bms",
"bnk",
"bnm",
"bns",
"bnsf",
"bo2",
@ -1178,6 +1180,7 @@ static const meta_info meta_info_list[] = {
{meta_STRM_ABYLIGHT, "Abylight STRM header"},
{meta_MSF_KONAMI, "Konami MSF header"},
{meta_XWMA_KONAMI, "Konami XWMA header"},
{meta_9TAV, "Konami 9TAV header"},
};

View File

@ -1,45 +1,54 @@
#include "layout.h"
#include "../vgmstream.h"
#ifdef VGMSTREAM_MIXING
#include "../mixing.h"
#endif
/* NOTE: if loop settings change the layered vgmstreams must be notified (preferably using vgmstream_force_loop) */
#define LAYER_BUF_SIZE 512
#define LAYER_MAX_CHANNELS 6 /* at least 2, but let's be generous */
#define VGMSTREAM_MAX_LAYERS 255
#define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192
/* Decodes samples for layered streams.
* Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each
* with custom codecs and different number of channels, creating a single super-vgmstream.
* Usually combined with custom streamfiles to handle data interleaved in weird ways. */
void render_vgmstream_layered(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
void render_vgmstream_layered(sample_t * outbuf, int32_t sample_count, VGMSTREAM * vgmstream) {
int samples_written = 0;
layered_layout_data *data = vgmstream->layout_data;
sample_t interleave_buf[LAYER_BUF_SIZE*LAYER_MAX_CHANNELS];
while (samples_written < sample_count) {
int samples_to_do = LAYER_BUF_SIZE;
int samples_to_do = VGMSTREAM_LAYER_SAMPLE_BUFFER;
int layer, ch = 0;
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
for (layer = 0; layer < data->layer_count; layer++) {
int s, layer_ch;
int layer_channels = data->layers[layer]->channels;
int s, layer_ch, layer_channels;
/* each layer will handle its own looping internally */
/* each layer will handle its own looping/mixing internally */
render_vgmstream(interleave_buf, samples_to_do, data->layers[layer]);
#ifdef VGMSTREAM_MIXING
/* layers may have its own number of channels */
mixing_info(data->layers[layer], NULL, &layer_channels);
#else
layer_channels = data->layers[layer]->channels;
#endif
render_vgmstream(
data->buffer,
samples_to_do,
data->layers[layer]);
/* mix layer samples to main samples */
for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) {
for (s = 0; s < samples_to_do; s++) {
size_t layer_sample = s*layer_channels + layer_ch;
size_t buffer_sample = (samples_written+s)*vgmstream->channels + ch;
size_t buffer_sample = (samples_written+s)*data->output_channels + ch;
buffer[buffer_sample] = interleave_buf[layer_sample];
outbuf[buffer_sample] = data->buffer[layer_sample];
}
ch++;
}
@ -73,18 +82,29 @@ fail:
}
int setup_layout_layered(layered_layout_data* data) {
int i;
int i, max_input_channels = 0, max_output_channels = 0;
sample_t *outbuf_re = NULL;
/* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */
for (i = 0; i < data->layer_count; i++) {
int layer_input_channels, layer_output_channels;
if (!data->layers[i])
goto fail;
if (data->layers[i]->num_samples <= 0)
goto fail;
if (data->layers[i]->channels > LAYER_MAX_CHANNELS)
goto fail;
#ifdef VGMSTREAM_MIXING
/* different layers may have different input/output channels */
mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels);
#else
layer_input_channels = layer_output_channels = data->layers[i]->channels;
#endif
max_output_channels += layer_output_channels;
if (max_input_channels < layer_input_channels)
max_input_channels = layer_input_channels;
if (i > 0) {
/* a bit weird, but no matter */
@ -100,9 +120,24 @@ int setup_layout_layered(layered_layout_data* data) {
/* loops and other values could be mismatched but hopefully not */
setup_vgmstream(data->layers[i]); /* final setup in case the VGMSTREAM was created manually */
#ifdef VGMSTREAM_MIXING
mixing_setup(data->layers[i], VGMSTREAM_LAYER_SAMPLE_BUFFER); /* init mixing */
#endif
}
if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS)
goto fail;
/* create internal buffer big enough for mixing */
outbuf_re = realloc(data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t));
if (!outbuf_re) goto fail;
data->buffer = outbuf_re;
data->input_channels = max_input_channels;
data->output_channels = max_output_channels;
return 1;
fail:
return 0; /* caller is expected to free */
@ -136,15 +171,13 @@ void reset_layout_layered(layered_layout_data *data) {
/* helper for easier creation of layers */
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data) {
VGMSTREAM *vgmstream;
VGMSTREAM *vgmstream = NULL;
int i, channels, loop_flag;
/* get data */
channels = 0;
channels = data->output_channels;
loop_flag = 1;
for (i = 0; i < data->layer_count; i++) {
channels += data->layers[i]->channels;
if (loop_flag && !data->layers[i]->loop_flag)
loop_flag = 0;
}

View File

@ -1,22 +1,31 @@
#include "layout.h"
#include "../vgmstream.h"
#ifdef VGMSTREAM_MIXING
#include "../mixing.h"
#endif
#define VGMSTREAM_MAX_SEGMENTS 255
#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192
/* Decodes samples for segmented streams.
* Chains together sequential vgmstreams, for data divided into separate sections or files
* (like one part for intro and other for loop segments, which may even use different codecs). */
void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
void render_vgmstream_segmented(sample_t * outbuf, int32_t sample_count, VGMSTREAM * vgmstream) {
int samples_written = 0, loop_samples_skip = 0;
segmented_layout_data *data = vgmstream->layout_data;
int use_internal_buffer = 0;
/* normally uses outbuf directly (faster) but could need internal buffer if downmixing */
if (vgmstream->channels != data->input_channels) {
use_internal_buffer = 1;
}
while (samples_written < sample_count) {
int samples_to_do;
int samples_this_block = data->segments[data->current_segment]->num_samples;
int samples_this_segment = data->segments[data->current_segment]->num_samples;
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
int segment, loop_segment, total_samples;
@ -51,9 +60,11 @@ void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTRE
continue;
}
samples_to_do = vgmstream_samples_to_do(samples_this_block, sample_count, vgmstream);
samples_to_do = vgmstream_samples_to_do(samples_this_segment, sample_count, vgmstream);
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
/* segment looping: discard until actual start */
if (loop_samples_skip > 0) {
@ -69,18 +80,29 @@ void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTRE
continue;
}
render_vgmstream(&buffer[samples_written*data->segments[data->current_segment]->channels],
samples_to_do,data->segments[data->current_segment]);
render_vgmstream(
use_internal_buffer ?
data->buffer :
&outbuf[samples_written * data->output_channels],
samples_to_do,
data->segments[data->current_segment]);
if (loop_samples_skip > 0) {
loop_samples_skip -= samples_to_do;
vgmstream->samples_into_block += samples_to_do;
continue;
}
else {
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
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];
}
}
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
}
}
@ -107,24 +129,47 @@ fail:
}
int setup_layout_segmented(segmented_layout_data* data) {
int i;
int i, max_input_channels = 0, max_output_channels = 0;
sample_t *outbuf_re = NULL;
/* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */
for (i = 0; i < data->segment_count; i++) {
int segment_input_channels, segment_output_channels;
if (!data->segments[i])
goto fail;
if (data->segments[i]->num_samples <= 0)
goto fail;
/* shouldn't happen */
/* disable so that looping is controlled by render_vgmstream_segmented */
if (data->segments[i]->loop_flag != 0) {
VGM_LOG("segmented layout: segment %i is looped\n", i);
data->segments[i]->loop_flag = 0;
}
#ifdef VGMSTREAM_MIXING
/* 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) */
mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels);
#else
segment_input_channels = segment_output_channels = data->segments[i]->channels;
#endif
if (max_input_channels < segment_input_channels)
max_input_channels = segment_input_channels;
if (max_output_channels < segment_output_channels)
max_output_channels = segment_output_channels;
if (i > 0) {
if (data->segments[i]->channels != data->segments[i-1]->channels)
int prev_output_channels;
#ifdef VGMSTREAM_MIXING
mixing_info(data->segments[i-1], NULL, &prev_output_channels);
#else
prev_output_channels = data->segments[i-1]->channels;
#endif
if (segment_output_channels != prev_output_channels)
goto fail;
/* a bit weird, but no matter */
@ -132,14 +177,28 @@ int setup_layout_segmented(segmented_layout_data* data) {
VGM_LOG("segmented layout: segment %i has different sample rate\n", i);
}
/* perfectly acceptable */
//if (data->segments[i]->coding_type != data->segments[i-1]->coding_type)
// goto fail; /* perfectly acceptable */
// goto fail;
}
setup_vgmstream(data->segments[i]); /* final setup in case the VGMSTREAM was created manually */
#ifdef VGMSTREAM_MIXING
mixing_setup(data->segments[i], VGMSTREAM_SEGMENT_SAMPLE_BUFFER); /* init mixing */
#endif
}
if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS)
goto fail;
/* create internal buffer big enough for mixing */
outbuf_re = realloc(data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t));
if (!outbuf_re) goto fail;
data->buffer = outbuf_re;
data->input_channels = max_input_channels;
data->output_channels = max_output_channels;
return 1;
fail:
@ -175,7 +234,7 @@ void reset_layout_segmented(segmented_layout_data *data) {
/* helper for easier creation of segments */
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment) {
VGMSTREAM *vgmstream;
VGMSTREAM *vgmstream = NULL;
int channel_layout;
int i, num_samples, loop_start, loop_end;
@ -201,7 +260,7 @@ VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_fl
/* respect loop_flag even when no loop_end found as it's possible file loops are set outside */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(data->segments[0]->channels, loop_flag);
vgmstream = allocate_vgmstream(data->output_channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = data->segments[0]->meta_type;

View File

@ -147,6 +147,10 @@
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath=".\mixing.h"
>
</File>
<File
RelativePath=".\plugins.h"
>
@ -177,6 +181,10 @@
RelativePath=".\formats.c"
>
</File>
<File
RelativePath=".\mixing.c"
>
</File>
<File
RelativePath=".\plugins.c"
>
@ -224,6 +232,10 @@
RelativePath=".\meta\aax_utf.h"
>
</File>
<File
RelativePath=".\meta\9tav_streamfile.h"
>
</File>
<File
RelativePath=".\meta\aix_streamfile.h"
>
@ -312,6 +324,10 @@
RelativePath=".\meta\208.c"
>
</File>
<File
RelativePath=".\meta\9tav.c"
>
</File>
<File
RelativePath=".\meta\2dx9.c"
>

View File

@ -92,6 +92,7 @@
<ClInclude Include="meta\xvag_streamfile.h" />
<ClInclude Include="meta\xwma_konami_streamfile.h" />
<ClInclude Include="meta\zsnd_streamfile.h" />
<ClInclude Include="mixing.h" />
<ClInclude Include="plugins.h" />
<ClInclude Include="streamfile.h" />
<ClInclude Include="streamtypes.h" />
@ -99,6 +100,7 @@
<ClInclude Include="vgmstream.h" />
<ClInclude Include="meta\adx_keys.h" />
<ClInclude Include="meta\aax_utf.h" />
<ClInclude Include="meta\9tav_streamfile.h" />
<ClInclude Include="meta\aix_streamfile.h" />
<ClInclude Include="meta\awc_xma_streamfile.h" />
<ClInclude Include="meta\bar_streamfile.h" />
@ -201,12 +203,14 @@
<ClCompile Include="meta\x360_cxs.c" />
<ClCompile Include="meta\x360_tra.c" />
<ClCompile Include="formats.c" />
<ClCompile Include="mixing.c" />
<ClCompile Include="plugins.c" />
<ClCompile Include="meta\ps2_va3.c" />
<ClCompile Include="streamfile.c" />
<ClCompile Include="util.c" />
<ClCompile Include="vgmstream.c" />
<ClCompile Include="meta\208.c" />
<ClCompile Include="meta\9tav.c" />
<ClCompile Include="meta\2dx9.c" />
<ClCompile Include="meta\a2m.c" />
<ClCompile Include="meta\ahv.c" />

View File

@ -47,6 +47,9 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="mixing.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="plugins.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -68,6 +71,9 @@
<ClInclude Include="meta\aax_utf.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\9tav_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\aix_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
@ -181,6 +187,9 @@
<ClCompile Include="formats.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mixing.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="plugins.c">
<Filter>Source Files</Filter>
</ClCompile>
@ -196,6 +205,9 @@
<ClCompile Include="meta\208.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\9tav.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\2dx9.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

118
src/meta/9tav.c Normal file
View File

@ -0,0 +1,118 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "9tav_streamfile.h"
/* 9TAV - from Metal Gear Solid 2/3 HD (Vita) */
VGMSTREAM * init_vgmstream_9tav(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag, channel_count, sample_rate, track_count;
int32_t num_samples, loop_start, loop_end;
size_t track_size;
uint32_t config_data;
int i, is_padded;
layered_layout_data * data = NULL;
STREAMFILE* temp_streamFile = NULL;
/* checks */
/* .9tav: header id */
if (!check_extensions(streamFile, "9tav"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x39544156) /* "9TAV" */
goto fail;
/* 0x04: always 0x09 */
channel_count = read_16bitLE(0x08,streamFile);
track_count = read_16bitLE(0x0a,streamFile); /* MGS3 uses multitracks */
sample_rate = read_32bitLE(0x0c,streamFile);
track_size = read_32bitLE(0x10,streamFile); /* without padding */
//data_size = read_32bitLE(0x14,streamFile); /* without padding */
num_samples = read_32bitLE(0x18,streamFile);
config_data = read_32bitBE(0x1c,streamFile);
if (read_32bitBE(0x20,streamFile) == 0x4D544146) { /* "MTAF" */
/* MGS3 has a MTAF header (data size and stuff don't match, probably for track info) */
loop_start = read_32bitLE(0x78, streamFile);
loop_end = read_32bitLE(0x7c, streamFile);
loop_flag = read_32bitLE(0x90, streamFile) & 1;
is_padded = 1; /* data also has padding and other oddities */
start_offset = 0x00;
}
else {
/* MGS2 doesn't */
loop_start = 0;
loop_end = 0;
loop_flag = 0;
is_padded = 0;
start_offset = 0x20;
}
/* init layout */
data = init_layout_layered(track_count);
if (!data) goto fail;
/* open each layer subfile */
for (i = 0; i < data->layer_count; i++) {
data->layers[i] = allocate_vgmstream(channel_count, loop_flag);
if (!data->layers[i]) goto fail;
data->layers[i]->meta_type = meta_9TAV;
data->layers[i]->sample_rate = sample_rate;
data->layers[i]->num_samples = num_samples;
data->layers[i]->loop_start_sample = loop_start;
data->layers[i]->loop_end_sample = loop_end;
#ifdef VGM_USE_ATRAC9
{
atrac9_config cfg = {0};
cfg.channels = channel_count;
cfg.config_data = config_data;
cfg.encoder_delay = atrac9_bytes_to_samples_cfg(track_size, cfg.config_data) - num_samples; /* seems ok */
if (cfg.encoder_delay > 4096) /* doesn't seem too normal */
cfg.encoder_delay = 0;
data->layers[i]->codec_data = init_atrac9(&cfg);
if (!data->layers[i]->codec_data) goto fail;
data->layers[i]->coding_type = coding_ATRAC9;
data->layers[i]->layout_type = layout_none;
}
#else
goto fail;
#endif
if (is_padded) {
temp_streamFile = setup_9tav_streamfile(streamFile, 0xFE4, track_size, i, track_count);
if (!temp_streamFile) goto fail;
}
if (!vgmstream_open_stream(data->layers[i],temp_streamFile == NULL ? streamFile : temp_streamFile,start_offset))
goto fail;
close_streamfile(temp_streamFile);
temp_streamFile = NULL;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
/* build the layered VGMSTREAM */
vgmstream = allocate_layered_vgmstream(data);
if (!vgmstream) goto fail;
return vgmstream;
fail:
close_streamfile(temp_streamFile);
close_vgmstream(vgmstream);
if (!vgmstream)
free_layout_layered(data);
return NULL;
}

182
src/meta/9tav_streamfile.h Normal file
View File

@ -0,0 +1,182 @@
#ifndef _9TAV_STREAMFILE_H_
#define _9TAV_STREAMFILE_H_
#include "../streamfile.h"
typedef struct {
/* config */
off_t stream_offset;
size_t stream_size;
size_t track_size;
int track_number;
int track_count;
int skip_count;
int read_count;
size_t frame_size;
size_t interleave_count;
size_t interleave_last_count;
/* 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;
} ntav_io_data;
static size_t ntav_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ntav_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->stream_offset;
data->logical_offset = 0x00;
data->data_size = 0;
data->skip_size = 0;
data->read_count = 0;
data->skip_count = data->interleave_count * data->track_number;
//VGM_LOG("0 o=%lx, sc=%i\n", data->physical_offset, data->skip_count);
}
/* read blocks */
while (length > 0) {
//VGM_LOG("1 of=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset);
/* ignore EOF */
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
//VGM_LOG("9 o=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset);
//VGM_LOG("eof\n");
break;
}
/* process new block */
if (data->data_size == 0) {
/* not very exact compared to real blocks but ok enough */
if (read_32bitLE(data->physical_offset, streamfile) == 0x00) {
data->block_size = 0x10;
//VGM_LOG("1 o=%lx, lo=%lx skip\n", data->physical_offset, data->logical_offset);
}
else {
data->block_size = data->frame_size;
//VGM_LOG("2 o=%lx, lo=%lx, skip=%i, read=%i\n", data->physical_offset, data->logical_offset, data->skip_count, data->read_count);
/* each track interleaves NTAV_INTERLEAVE frames, but can contain padding in between,
* so must read one by one up to max */
if (data->skip_count == 0 && data->read_count == 0) {
data->read_count = data->interleave_count;
}
if (data->skip_count) {
data->skip_count--;
}
if (data->read_count) {
data->data_size = data->block_size;
data->read_count--;
if (data->read_count == 0) {
if (data->logical_offset + data->interleave_count * data->frame_size > data->track_size)
data->skip_count = data->interleave_last_count * (data->track_count - 1);
else
data->skip_count = data->interleave_count * (data->track_count - 1);
}
}
}
}
/* 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 ntav_io_size(STREAMFILE *streamfile, ntav_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) */
ntav_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
data->logical_size = data->logical_offset;
return data->logical_size;
}
/* Handles deinterleaving of 9TAV blocked streams. Unlike other games using .sdt,
* KCEJ blocks have a data_size field and rest is padding. Even after that all blocks start
* with 0 (skipped) and there are padding blocks that start with LE 0xDEADBEEF.
* This streamfile handles 9tav extracted like regular sdt and remove padding manually. */
static STREAMFILE* setup_9tav_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t track_size, int track_number, int track_count) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
ntav_io_data io_data = {0};
size_t io_data_size = sizeof(ntav_io_data);
size_t last_size;
io_data.stream_offset = stream_offset;
io_data.stream_size = get_streamfile_size(streamFile) - stream_offset;
io_data.track_size = track_size;
io_data.track_number = track_number;
io_data.track_count = track_count;
io_data.frame_size = 0x40;
io_data.interleave_count = 256;
last_size = track_size % (io_data.interleave_count * io_data.frame_size);
if (last_size)
io_data.interleave_last_count = last_size / io_data.frame_size;
io_data.logical_offset = -1; /* force state reset */
/* setup subfile */
new_streamFile = open_wrap_streamfile(streamFile);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ntav_io_read,ntav_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 /* _9TAV_STREAMFILE_H_ */

View File

@ -673,6 +673,7 @@ VGMSTREAM * init_vgmstream_naac(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ezw(STREAMFILE * streamFile);
@ -846,4 +847,6 @@ VGMSTREAM * init_vgmstream_msf_konami(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_9tav(STREAMFILE* streamFile);
#endif /*_META_H*/

View File

@ -32,7 +32,7 @@ VGMSTREAM * init_vgmstream_mtaf(STREAMFILE *streamFile) {
loop_start = read_32bitLE(0x58, streamFile);
loop_end = read_32bitLE(0x5c, streamFile);
loop_flag = (loop_start != loop_end);
loop_flag = read_32bitLE(0x70, streamFile) & 1;
/* check loop points vs frame counts */
if (loop_start/0x100 != read_32bitLE(0x64, streamFile) ||

View File

@ -57,6 +57,7 @@ typedef struct {
uint32_t data_size;
int data_size_set;
uint32_t start_offset;
uint32_t padding_size;
int sample_type;
uint32_t num_samples;
@ -616,6 +617,7 @@ static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const c
static int is_string(const char * val, const char * cmp);
static int is_substring(const char * val, const char * cmp);
static int get_bytes_to_samples(txth_header * txth, uint32_t bytes);
static int get_padding_size(txth_header * txth, int discard_empty);
/* Simple text parser of "key = value" lines.
* The code is meh and error handling not exactly the best. */
@ -776,9 +778,42 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
}
else if (is_string(key,"start_offset")) {
if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail;
/* apply */
if (!txth->data_size_set) {
txth->data_size = !txth->streamBody ? 0 :
get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */
uint32_t body_size = !txth->streamBody ? 0 : get_streamfile_size(txth->streamBody);
/* with subsongs we want to clamp body_size from this subsong start to next subsong start */
if (txth->subsong_count > 1 && txth->target_subsong < txth->subsong_count) {
uint32_t next_offset;
/* temp move to next start_offset and move back*/
txth->target_subsong++;
parse_num(txth->streamHead,txth,val, &next_offset);
txth->target_subsong--;
if (next_offset > txth->start_offset) {
body_size = next_offset;
}
}
if (body_size && body_size > txth->start_offset)
txth->data_size = body_size - txth->start_offset; /* re-evaluate */
}
}
else if (is_string(key,"padding_size")) {
if (is_string(val,"auto")) {
txth->padding_size = get_padding_size(txth, 0);
}
else if (is_string(val,"auto-empty")) {
txth->padding_size = get_padding_size(txth, 1);
}
else {
if (!parse_num(txth->streamHead,txth,val, &txth->padding_size)) goto fail;
}
/* apply */
if (!txth->data_size_set) {
if (txth->padding_size < txth->data_size)
txth->data_size -= txth->padding_size;
}
}
else if (is_string(key,"data_size")) {
@ -1248,3 +1283,15 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) {
return 0;
}
}
static int get_padding_size(txth_header * txth, int discard_empty) {
if (txth->data_size == 0 || txth->channels == 0)
return 0;
switch(txth->codec) {
case PSX:
return ps_find_padding(txth->streamBody, txth->start_offset, txth->data_size, txth->channels, txth->interleave, discard_empty);
default:
return 0;
}
}

View File

@ -151,7 +151,7 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
data_l = init_layout_layered(txtp->entry_count);
if (!data_l) goto fail;
/* open each segment subfile */
/* open each layer subfile */
for (i = 0; i < data_l->layer_count; i++) {
STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename);
if (!temp_streamFile) goto fail;
@ -192,6 +192,8 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
for (i = 0; i < data_s->segment_count; i++) {
STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename);
if (!temp_streamFile) goto fail;
/* subsongs ranges also work for files without subsongs (as to repeat the same file), not sure if bug or feature */
temp_streamFile->stream_index = txtp->entry[i].subsong;
data_s->segments[i] = init_vgmstream_from_STREAMFILE(temp_streamFile);
@ -253,8 +255,10 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
fail:
clean_txtp(txtp);
close_vgmstream(vgmstream);
free_layout_segmented(data_s);
free_layout_layered(data_l);
if (!vgmstream) {
free_layout_segmented(data_s);
free_layout_layered(data_l);
}
return NULL;
}

View File

@ -5,7 +5,7 @@
#include "ubi_sb_streamfile.h"
static STREAMFILE* setup_ubi_bao_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) {
return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian);
return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian, 0);
}
#endif /* _UBI_BAO_STREAMFILE_H_ */

View File

@ -5,9 +5,9 @@
#define SB_MAX_LAYER_COUNT 16 /* arbitrary max */
#define SB_MAX_CHAIN_COUNT 64 /* arbitrary max */
#define SB_MAX_CHAIN_COUNT 256 /* +150 exist in Tonic Trouble */
typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV } ubi_sb_codec;
typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV, FMT_APM } ubi_sb_codec;
typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform;
typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_sb_type;
@ -57,10 +57,16 @@ typedef struct {
off_t layer_stream_type;
off_t layer_num_samples;
size_t layer_entry_size;
size_t layer_hijack;
off_t silence_duration_int;
off_t silence_duration_float;
off_t random_extra_offset;
off_t random_sequence_count;
size_t random_entry_size;
int random_percent_int;
int is_padded_section1_offset;
int is_padded_section2_offset;
int is_padded_section3_offset;
@ -93,7 +99,9 @@ typedef struct {
uint32_t map_unknown;
/* SB info (some values are derived depending if it's standard sbX or map sbX) */
int is_bank;
int is_map;
int is_bnm;
uint32_t version; /* 16b+16b major+minor version */
uint32_t version_empty; /* map sbX versions are empty */
/* events (often share header_id/type with some descriptors,
@ -140,6 +148,8 @@ typedef struct {
int layer_channels[SB_MAX_LAYER_COUNT];
int sequence_count; /* number of segments in a sequence type */
int sequence_chain[SB_MAX_CHAIN_COUNT]; /* sequence of entry numbers */
int sequence_banks[SB_MAX_CHAIN_COUNT]; /* sequence of bnk bank numbers */
int sequence_multibank; /* info flag */
int sequence_loop; /* chain index to loop */
int sequence_single; /* if que sequence plays once (loops by default) */
@ -153,6 +163,7 @@ typedef struct {
int allowed_types[16];
} ubi_sb_header;
static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile);
static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index);
static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong);
static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile);
@ -192,7 +203,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) {
/* SB HEADER */
/* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */
sb.is_map = 0;
sb.is_bank = 1;
sb.version = read_32bit(0x00, streamFile);
sb.section1_num = read_32bit(0x04, streamFile);
sb.section2_num = read_32bit(0x08, streamFile);
@ -341,18 +352,85 @@ fail:
return NULL;
}
#if 0
/* .BNM - proto-sbX with map style format [Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */
VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) {
/* v0x00000000, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields
* are fixed/reserved (unused?). Header entry sizes and config looks the same as early games.
* Main codecs are 01=RAW_PCM and 04=FMT_APM (.apm if external, possibly Ubi IMA v0 but has a full header).
* The sound engine doesn't seem to be named DARE yet.
*/
/* .BNM - proto-sbX with map style format [Rayman 2 (PC), Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */
VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *streamTest = NULL;
ubi_sb_header sb = {0};
int target_subsong = streamFile->stream_index;
if (target_subsong <= 0) target_subsong = 1;
/* checks */
if (!check_extensions(streamFile, "bnm"))
goto fail;
/* v0, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields are
* fixed/reserved (unused?). Header entry sizes and config works the same, and type numbers are
* slightly different, but otherwise pretty much the same engine (not named DARE yet). Curiously
* it may stream RIFF .wav (stream_offset pointing to "data"), and also .raw (PCM) or .apm IMA. */
/* use smaller header buffer for performance */
streamTest = reopen_streamfile(streamFile, 0x100);
if (!streamTest) goto fail;
if (!parse_bnm_header(&sb, streamTest))
goto fail;
if (!parse_sb(&sb, streamTest, target_subsong))
goto fail;
/* CREATE VGMSTREAM */
vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile);
close_streamfile(streamTest);
return vgmstream;
fail:
close_streamfile(streamTest);
return NULL;
}
#endif
static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile) {
int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL;
/* PLATFORM DETECTION */
sb->platform = UBI_PC;
read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE;
/* SB HEADER */
/* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */
sb->is_bnm = 1;
sb->version = read_32bit(0x00, streamFile);
sb->section1_offset = read_32bit(0x04, streamFile);
sb->section1_num = read_32bit(0x08, streamFile);
sb->section2_offset = read_32bit(0x0c, streamFile);
sb->section2_num = read_32bit(0x10, streamFile);
/* next are data start offset x3 + data size offset x3 */
sb->section3_offset = read_32bit(0x14, streamFile);
sb->section3_num = 0;
if (!config_sb_version(sb, streamFile))
goto fail;
sb->sectionX_offset = sb->section2_offset + sb->section2_num * sb->cfg.section2_entry_size;
sb->sectionX_size = sb->section3_offset - sb->sectionX_offset;
return 1;
fail:
return 0;
}
static int is_bnm_other_bank(STREAMFILE *streamFile, int bank_number) {
char current_name[PATH_LIMIT];
char bank_name[255];
get_streamfile_filename(streamFile, current_name, PATH_LIMIT);
sprintf(bank_name, "Bnk_%i.bnm", bank_number);
return strcmp(current_name, bank_name) != 0;
}
#if 0
/* .BLK - maps in separate .blk chunks [Donald Duck: Goin' Quackers (PS2), The Jungle Book Rhythm N'Groove (PS2)] */
@ -397,15 +475,15 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str
break;
case UBI_ADPCM:
/* todo custom Ubi 4/6-bit ADPCM (4b for music/stereo and 6b for voices?)
* - ~0x30: block-like header (some versions may not have it).
* Seems to contain frame size, channels, sometimes sample rate/samples/etc.
* - 0x34 per channel: channel header, starts with 0x02
* - 0x600: frame data (unknown interleave)
* - 0x02: ADPCM hist?
*
* frame data looks decoded in two passes (first may expand nibble somehow, other
* seems to calculate diffs)
/* todo custom Ubi 4/6-bit ADPCM (4b for music/stereo and 6b for voices)
* - ~0x30: stream header (varies with versions).
* contains frame sizes (normal/last), channels, sometimes sample rate/samples/etc.
* - 0x34 per channel: channel header, starts with 0x02, contains ADPCM info
* - 0x602/902: frame data divided into 2 chunks (with a padding byte)
* for each chunk frame data is read as LE int32, then divided into codes
* (some 6b span two int32). stereo interleaves 4 codes per channel.
* data is decoded using 3 tables and ops to adjust sample and calculate next step
* (6b decoding is much simpler than 4b, that applies more extra ops).
*
* used in:
* - Batman: Vengeance (PC)
@ -421,6 +499,12 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str
vgmstream->coding_type = coding_PCM16LE; /* always LE */
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
if (vgmstream->num_samples == 0) { /* happens in .bnm */
//todo with external wav streams stream_size may be off?
vgmstream->num_samples = pcm_bytes_to_samples(sb->stream_size, sb->channels, 16);
vgmstream->loop_end_sample = vgmstream->num_samples;
}
break;
case RAW_PSX:
@ -429,6 +513,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str
vgmstream->interleave_block_size = (sb->cfg.audio_interleave) ?
sb->cfg.audio_interleave :
sb->stream_size / sb->channels;
if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for internal streams */
vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels);
vgmstream->loop_end_sample = vgmstream->num_samples;
@ -490,7 +575,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str
block_size = 0x98 * sb->channels;
joint_stereo = 0;
encoder_delay = 0x00; /* TODO: this is incorrect */
encoder_delay = 0x00; /* TODO: this is may be incorrect */
bytes = ffmpeg_make_riff_atrac3(buf, 0x100, sb->num_samples, sb->stream_size, sb->channels, sb->sample_rate, block_size, joint_stereo, encoder_delay);
ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size);
@ -555,6 +640,9 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str
/* get XMA header from extra section */
chunk_size = 0x20;
header_offset = sb->xma_header_offset;
if (header_offset == 0)
header_offset = sb->extra_offset;
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamHead, 1);
ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size);
@ -588,6 +676,53 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str
start_offset += 0xe0; /* skip CWAV header */
break;
case FMT_APM:
/* APM is a full format though most fields are repeated from .bnm
* (info from https://github.com/Synthesis/ray2get)
* 0x00(2): format tag (0x2000 for Ubisoft ADPCM)
* 0x02(2): channels
* 0x04(4): sample rate
* 0x08(4): byte rate? PCM samples?
* 0x0C(2): block align
* 0x0E(2): bits per sample
* 0x10(4): header size
* 0x14(4): "vs12"
* 0x18(4): file size
* 0x1C(4): nibble size
* 0x20(4): -1?
* 0x24(4): 0?
* 0x28(4): high/low nibble flag (when loaded in memory)
* 0x2C(N): ADPCM info per channel, last to first
* - 0x00(4): ADPCM hist
* - 0x04(4): ADPCM step index
* - 0x08(4): copy of ADPCM data (after interleave, ex. R from data + 0x01)
* 0x60(4): "DATA"
* 0x64(N): ADPCM data
*/
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x01;
/* read initial hist (last to first) */
{
int i;
for (i = 0; i < sb->channels; i++) {
vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x00, streamData);
vgmstream->ch[i].adpcm_step_index = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x04, streamData);
}
}
//todo supposedly APM IMA removes lower 3b after assigning step, but wave looks a bit off (Rayman 2 only?):
// ...; step = adpcm_table[step_index]; delta = (step >> 3); step &= (~7); ...
start_offset += 0x64; /* skip APM header (may be internal or external) */
if (vgmstream->num_samples == 0) {
vgmstream->num_samples = ima_bytes_to_samples(sb->stream_size - 0x64, sb->channels);
vgmstream->loop_end_sample = vgmstream->num_samples;
}
break;
default:
VGM_LOG("UBI SB: unknown codec\n");
goto fail;
@ -661,7 +796,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st
/* open all layers and mix */
for (i = 0; i < sb->layer_count; i++) {
/* prepare streamfile from a single layer section */
temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian);
temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian, sb->cfg.layer_hijack);
if (!temp_streamFile) goto fail;
sb->stream_size = get_streamfile_size(temp_streamFile);
@ -714,6 +849,8 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE
VGMSTREAM * vgmstream = NULL;
segmented_layout_data* data = NULL;
int i;
STREAMFILE *streamBank = streamTest;
//todo optimization: open streamData once / only if new name (doesn't change 99% of the time)
@ -726,32 +863,60 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE
/* open all segments and mix */
for (i = 0; i < sb->sequence_count; i++) {
ubi_sb_header temp_sb = *sb; /* memcpy'ed */
ubi_sb_header temp_sb = {0};
off_t entry_offset;
int entry_index = sb->sequence_chain[i];
off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_index;
/* bnm sequences may use to entries from other banks, do some voodoo */
if (sb->is_bnm) {
/* see if *current* bank has changed (may use a different bank N times) */
if (is_bnm_other_bank(streamBank, sb->sequence_banks[i])) {
char bank_name[255];
sprintf(bank_name, "Bnk_%i.bnm", sb->sequence_banks[i]);
if (streamBank != streamTest)
close_streamfile(streamBank);
streamBank = open_streamfile_by_filename(streamFile, bank_name);
if (!streamBank) goto fail;
}
/* re-parse the thing */
if (!parse_bnm_header(&temp_sb, streamBank))
goto fail;
temp_sb.total_subsongs = 1; /* eh... just to keep parse_header happy */
}
else {
temp_sb = *sb; /* memcpy'ed */
}
/* parse expected entry */
if (!parse_header(&temp_sb, streamTest, entry_offset, entry_index))
entry_offset = temp_sb.section2_offset + temp_sb.cfg.section2_entry_size * entry_index;
if (!parse_header(&temp_sb, streamBank, entry_offset, entry_index))
goto fail;
if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) {
VGM_LOG("UBI SB: unexpected sequence entry type at %x\n", (uint32_t)entry_offset);
goto fail; /* technically ok but too much recursiveness? */
VGM_LOG("UBI SB: unexpected sequence %i entry type at %x\n", i, (uint32_t)entry_offset);
goto fail; /* not seen, technically ok but too much recursiveness? */
}
/* build the layer VGMSTREAM (current sb entry config) */
data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamTest, streamFile);
data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamBank, streamFile);
if (!data->segments[i]) goto fail;
if (i == sb->sequence_loop)
sb->loop_start = sb->num_samples;
sb->num_samples += data->segments[i]->num_samples;
/* save current (silences don't have values, so this unsures they know when memcpy'ed) */
/* save current (silences don't have values, so this ensures they know later, when memcpy'ed) */
sb->channels = temp_sb.channels;
sb->sample_rate = temp_sb.sample_rate;
}
if (streamBank != streamTest)
close_streamfile(streamBank);
if (!setup_layout_segmented(data))
goto fail;
@ -778,6 +943,8 @@ fail:
close_vgmstream(vgmstream);
else
free_layout_segmented(data);
if (streamBank != streamTest)
close_streamfile(streamBank);
return NULL;
}
@ -908,10 +1075,18 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb)
int index;
/* config */
if (sb->is_map)
if (sb->is_map) {
grp_name = sb->map_name;
else
}
else if (sb->is_bnm) {
if (sb->sequence_multibank)
grp_name = "bnm-multi";
else
grp_name = "bnm";
}
else {
grp_name = "bank";
}
id = sb->header_id;
type = sb->header_type;
if (sb->is_map)
@ -1052,7 +1227,7 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str
sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, streamFile);
if (sb->sequence_count > SB_MAX_CHAIN_COUNT) {
VGM_LOG("Ubi SB: incorrect layer count\n");
VGM_LOG("Ubi SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT);
goto fail;
}
@ -1061,15 +1236,30 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str
for (i = 0; i < sb->sequence_count; i++) {
uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfg.sequence_entry_number, streamFile);
/* bnm sequences may refer to entries from different banks, whee */
if (sb->is_bnm) {
int16_t bank_number = (entry_number >> 16) & 0xFFFF;
entry_number = (entry_number >> 00) & 0xFFFF;
//;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number);
sb->sequence_banks[i] = bank_number;
/* info flag, does bank number point to another file? */
if (!sb->sequence_multibank) {
sb->sequence_multibank = is_bnm_other_bank(streamFile, bank_number);
}
}
else {
entry_number = entry_number & 0x3FFFFFFF;
if (entry_number > sb->section2_num) {
VGM_LOG("UBI SB: chain with wrong entry %i vs %i at %x\n", entry_number, sb->section2_num, (uint32_t)sb->extra_offset);
goto fail;
}
}
/* some sequences have an upper bit (2 bits in Donald Duck voices) for some reason */
//;VGM_ASSERT_ONCE(entry_number & 0xC0000000, "UBI SB: sequence bit entry found at %x\n", (uint32_t)sb->extra_offset);
entry_number = entry_number & 0x3FFFFFFF;
if (entry_number > sb->section2_num) {
VGM_LOG("UBI SB: chain with wrong entry %i vs %i at %x\n", entry_number, sb->section2_num, (uint32_t)sb->extra_offset);
goto fail;
}
sb->sequence_chain[i] = entry_number;
table_offset += sb->cfg.sequence_entry_size;
@ -1191,6 +1381,57 @@ fail:
return 0;
}
// todo improve, only used in bnm sequences as sequence end (and may point to another bnm)
static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE;
off_t sb_extra_offset, table_offset;
int i, sb_sequence_count;
/* sequence chain */
if (sb->cfg.random_entry_size == 0) {
VGM_LOG("Ubi SB: random entry size not configured at %x\n", (uint32_t)offset);
goto fail;
}
sb_extra_offset = read_32bit(offset + sb->cfg.random_extra_offset, streamFile) + sb->sectionX_offset;
sb_sequence_count = read_32bit(offset + sb->cfg.random_sequence_count, streamFile);
/* get chain in extra table */
table_offset = sb_extra_offset;
for (i = 0; i < sb_sequence_count; i++) {
uint32_t entry_number = (uint32_t)read_32bit(table_offset+0x00, streamFile);
//uint32_t entry_chance = (uint32_t)read_32bit(table_offset+0x04, streamFile);
if (sb->is_bnm) {
int16_t bank_number = (entry_number >> 16) & 0xFFFF;
entry_number = (entry_number >> 00) & 0xFFFF;
;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number);
//sb->sequence_banks[i] = bank_number;
/* not seen */
if (is_bnm_other_bank(streamFile, bank_number)) {
VGM_LOG("UBI SB: random in other bank\n");
goto fail;
}
}
//todo make rand or stuff (old chance: int from 0 to 0x10000, new: float from 0.0 to 1.0)
{ //if (entry_chance == ...)
off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_number;
return parse_type_audio(sb, entry_offset, streamFile);
}
table_offset += sb->cfg.random_entry_size;
}
return 1;
fail:
return 0;
}
/* find actual codec from type (as different games' stream_type can overlap) */
static int parse_stream_codec(ubi_sb_header * sb) {
@ -1282,6 +1523,10 @@ static int parse_stream_codec(ubi_sb_header * sb) {
case 0x04:
switch (sb->version) {
case 0x00000000: /* Rayman 2, Tonic Trouble */
sb->codec = FMT_APM;
break;
case 0x00000007: /* Splinter Cell, Splinter Cell: Pandora Tomorrow */
sb->codec = UBI_IMA;
break;
@ -1457,6 +1702,7 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset
goto fail;
break;
case 0x05:
case 0x0b:
case 0x0c:
if (!parse_type_sequence(sb, offset, streamFile))
goto fail;
@ -1471,8 +1717,12 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset
if (!parse_type_silence(sb, offset, streamFile))
goto fail;
break;
case 0x0a:
if (!parse_type_random(sb, offset, streamFile))
goto fail;
break;
default:
VGM_LOG("UBI SB: unknown header type at %x\n", (uint32_t)offset);
VGM_LOG("UBI SB: unknown header type %x at %x\n", sb->header_type, (uint32_t)offset);
goto fail;
}
@ -1505,7 +1755,7 @@ static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subso
/*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */
header_type = read_32bit(offset + 0x04, streamFile);
if (header_type <= 0x00 || header_type >= 0x10 || header_type == 0x09 || header_type == 0x0b) {
if (header_type <= 0x00 || header_type >= 0x10 || header_type == 0x09) {
VGM_LOG("UBI SB: unknown type %x at %x\n", header_type, (uint32_t)offset);
goto fail;
}
@ -1654,6 +1904,10 @@ static void config_sb_sequence(ubi_sb_header * sb, off_t sequence_count, off_t e
sb->cfg.sequence_sequence_count = sequence_count;
sb->cfg.sequence_entry_size = entry_size;
sb->cfg.sequence_entry_number = 0x00;
if (sb->is_bnm) {
sb->cfg.sequence_sequence_loop = sequence_count - 0x0c;
sb->cfg.sequence_sequence_single= sequence_count - 0x08;
}
}
static void config_sb_layer_hs(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t stream_name) {
/* layer headers with stream name */
@ -1686,9 +1940,17 @@ static void config_sb_silence_f(ubi_sb_header * sb, off_t duration) {
sb->cfg.silence_duration_float = duration;
}
static void config_sb_random_old(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) {
sb->cfg.random_sequence_count = sequence_count;
sb->cfg.random_entry_size = entry_size;
sb->cfg.random_percent_int = 1;
}
static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
int is_dino_pc = 0;
int is_bia_ps2 = 0, is_biadd_psp = 0;
int is_sc2_ps2_gc = 0;
int is_sc4_pc_online = 0;
/* Most of the format varies with almost every game + platform (struct serialization?).
* Support is configured case-by-case as offsets/order/fields only change slightly,
@ -1757,24 +2019,25 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
/* debug strings reference:
* - TYPE_SAMPLE: should be 0x01 (also "sound resource")
* - TYPE_MULTITRACK: should be 0x06/0x0d (also "multilayer resource")
* - TYPE_MULTITRACK: should be 0x06/0x0d/0x0b (also "multilayer resource")
* - TYPE_SILENCE: should be 0x08
* sequences may be "theme resource"
* "class descryptor" is referenced too.
* "class descriptor" is referenced too.
*
* Possible type names from .bnm (.sb's predecessor):
* Type names from .bnm (.sb's predecessor):
* 0: TYPE_INVALID
* 1: TYPE_SAMPLE
* 2: TYPE_MIDI
* 3: TYPE_CDAUDIO
* 4: TYPE_SEQUENCE
* 4: TYPE_SEQUENCE (sfx chain?)
* 5: TYPE_SWITCH_OLD
* 6: TYPE_SPLIT
* 7: TYPE_THEME_OLD
* 8: TYPE_SWITCH
* 9: TYPE_THEME_OLD2
* A: TYPE_RANDOM
* B: TYPE_THEME0
* B: TYPE_THEME0 (sequence)
* (only 1, 4, A and B are known)
*/
/* All types may contain memory garbage, making it harder to identify fields (platforms
@ -1799,7 +2062,22 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34;
if (sb->version <= 0x00000007) {
if (sb->version <= 0x00000200) {
sb->cfg.audio_stream_size = 0x0c;
sb->cfg.audio_stream_offset = 0x10;
//sb->cfg.audio_extra_offset = 0x10;
//sb->cfg.audio_extra_size = 0x0c;
sb->cfg.sequence_extra_offset = 0x10;
//sb->cfg.sequence_extra_size = 0x0c;
//sb->cfg.layer_extra_offset = 0x10;
//sb->cfg.layer_extra_size = 0x0c;
sb->cfg.random_extra_offset = 0x10;
//sb->cfg.random_extra_size = 0x0c;
}
else if (sb->version <= 0x00000007) {
sb->cfg.audio_stream_size = 0x0c;
sb->cfg.audio_extra_offset = 0x10;
sb->cfg.audio_stream_offset = 0x14;
@ -1825,6 +2103,10 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
sb->allowed_types[0x0d] = 1;
//sb->allowed_types[0x08] = 1; /* only needed inside sequences */
//sb->allowed_types[0x0f] = 1;
if (sb->is_bnm) {
//sb->allowed_types[0x0a] = 1; /* only needed inside sequences */
sb->allowed_types[0x0b] = 1;
}
#if 0
{
@ -1840,6 +2122,43 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
}
#endif
/* two configs with same id; use SND file as identifier */
if (sb->version == 0x00000000 && sb->platform == UBI_PC) {
STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Dino.lcb");
if (streamTest) {
is_dino_pc = 1;
close_streamfile(streamTest);
}
}
/* some files in Dinosaur */
if (sb->version == 0x00000200 && sb->platform == UBI_PC) {
sb->version = 0x00000000;
is_dino_pc = 1;
}
/* Rayman 2: The Great Escape (1999)(PC)-bnm */
/* Tonic Trouble (1999)(PC)-bnm */
/* Donald Duck: Goin' Quackers (2000)(PC)-bnm */
/* Disney's Dinosaur (2000)(PC)-bnm */
if (sb->version == 0x00000000 && sb->platform == UBI_PC) {
config_sb_entry(sb, 0x20, 0x5c);
config_sb_audio_fs(sb, 0x2c, 0x2c, 0x30); /* no group id */
config_sb_audio_hs(sb, 0x42, 0x3c, 0x34, 0x34, 0x48, 0x44);
sb->cfg.audio_has_internal_names = 1;
config_sb_sequence(sb, 0x24, 0x18);
config_sb_random_old(sb, 0x18, 0x0c); /* Rayman 2 needs it for rare sequence ends (ex. Bnk_31.bnm) */
/* no layers */
if (is_dino_pc)
config_sb_entry(sb, 0x20, 0x60);
return 1;
}
/* Batman: Vengeance (2001)(PC)-map */
if (sb->version == 0x00000003 && sb->platform == UBI_PC) {
config_sb_entry(sb, 0x40, 0x68);
@ -2347,6 +2666,22 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
return 1;
}
/* Tom Clancy's Ghost Recon Advanced Warfighter (2006)(X360)-bank */
if (sb->version == 0x00170001 && sb->platform == UBI_X360) {
config_sb_entry(sb, 0x68, 0x70);
config_sb_audio_fs(sb, 0x2c, 0x30, 0x34);
config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60);
sb->cfg.audio_xma_offset = 0; /* header is in the extra table */
config_sb_sequence(sb, 0x2c, 0x14);
config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x48);
config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14);
sb->cfg.layer_hijack = 1; /* WTF!!! layer format different from other layers using same id!!! */
return 1;
}
/* Open Season (2006)(PC)-map 0x00180003 */
if (sb->version == 0x00180003 && sb->platform == UBI_PC) {
config_sb_entry(sb, 0x68, 0x78);
@ -2395,8 +2730,18 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
return 1;
}
/* Splinter Cell: Double Agent (2006)(PC)-map */
/* two configs with same id; use project file as identifier */
if (sb->version == 0x00180006 && sb->platform == UBI_PC) {
STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Sc4_online_SoundProject.SP0");
if (streamTest) {
is_sc4_pc_online = 1;
close_streamfile(streamTest);
}
}
/* Splinter Cell: Double Agent (2006)(PC)-map (offline) */
if (sb->version == 0x00180006 && sb->platform == UBI_PC && !is_sc4_pc_online) {
config_sb_entry(sb, 0x68, 0x7c);
config_sb_audio_fs(sb, 0x2c, 0x34, 0x30);
@ -2407,6 +2752,18 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
return 1;
}
/* Splinter Cell: Double Agent (2006)(PC)-map (online) */
if (sb->version == 0x00180006 && sb->platform == UBI_PC && is_sc4_pc_online) {
config_sb_entry(sb, 0x68, 0x78);
config_sb_audio_fs(sb, 0x2c, 0x34, 0x30);
config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60);
config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44);
config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14);
return 1;
}
/* Splinter Cell: Double Agent (2006)(X360)-map */
if (sb->version == 0x00180006 && sb->platform == UBI_X360) {
config_sb_entry(sb, 0x68, 0x78);

View File

@ -11,6 +11,7 @@ typedef struct {
int layer_count;
int layer_max;
int big_endian;
int layer_hijack;
/* internal config */
off_t header_next_start; /* offset to header field */
@ -148,6 +149,11 @@ static int ubi_sb_io_init(STREAMFILE *streamfile, ubi_sb_io_data* data) {
/* Layers have a main header, then headered blocks with data.
* We configure stuff to unify parsing of all variations. */
version = (uint32_t)read_32bit(offset+0x00, streamfile);
/* it was bound to happen... orz */
if (data->layer_hijack == 1 && version == 0x000B0008)
version = 0xFFFF0007;
switch(version) {
case 0x00000002: /* Splinter Cell */
/* - layer header
@ -228,6 +234,35 @@ static int ubi_sb_io_init(STREAMFILE *streamfile, ubi_sb_io_data* data) {
data->block_data_start = 0x0c + data->layer_max*0x04;
break;
case 0xFFFF0007: /* Ghost Recon Advanced Warfighter (X360) */
/* - layer header
* 0x04: config?
* 0x08: layer count
* 0x0c: stream size
* 0x10: block count
* 0x14: block header size
* 0x18: block size (fixed)
* 0x1c+(04*11): min layer data? for 11 layers (-1 after layer count)
* 0x48: size of header sizes
* 0x4c+(04*N): header size per layer
* 0xNN: header data per layer
* - block header
* 0x00: block number
* 0x04: block offset
* 0x08: always 0x03
* 0x0c+(04*N): layer size per layer
* 0xNN: layer data per layer */
data->layer_max = read_32bit(offset+0x08, streamfile);
data->header_next_start = 0x18;
data->header_sizes_start = 0x4c;
data->header_data_start = 0x4c + data->layer_max*0x04;
data->block_next_start = 0;
data->block_sizes_start = 0x0c;
data->block_data_start = 0x0c + data->layer_max*0x04;
break;
case 0x00040008: /* Assassin's Creed */
case 0x000B0008: /* Open Season, Surf's Up, TMNT, Splinter Cell HD */
case 0x000C0008: /* Splinter Cell: Double Agent */
@ -326,7 +361,7 @@ fail:
/* Handles deinterleaving of Ubisoft's headered+blocked 'multitrack' streams */
static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) {
static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian, int layer_hijack) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
ubi_sb_io_data io_data = {0};
size_t io_data_size = sizeof(ubi_sb_io_data);
@ -336,6 +371,7 @@ static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_
io_data.layer_number = layer_number;
io_data.layer_count = layer_count;
io_data.big_endian = big_endian;
io_data.layer_hijack = layer_hijack;
if (!ubi_sb_io_init(streamFile, &io_data))
goto fail;

968
src/mixing.c Normal file
View File

@ -0,0 +1,968 @@
#include "vgmstream.h"
#include "mixing.h"
#include "plugins.h"
#include <math.h>
#ifdef VGMSTREAM_MIXING
/**
* Mixing lets vgmstream modify the resulting sample buffer before final output.
* This can be implemented in a number of ways but it's done like it is considering
* overall simplicity in coding, usage and performance (main complexity is allowing
* down/upmixing). Code is mostly independent with some hooks in the main vgmstream
* code.
*
* It works using two buffers:
* - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count
* - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count
* outbuf starts with decoded samples of vgmstream->channel size. This unsures that
* if no mixing is done (most common case) we can skip copying samples between buffers.
* Resulting outbuf after mixing has samples for ->output_channels (plus garbage).
* - output_channels is the resulting total channels (that may be less/more/equal)
* - input_channels is normally ->channels or ->output_channels when it's higher
*
* First, a meta (ex. TXTP) or plugin may add mixing commands through the API,
* validated so non-sensical mixes are ignored (to ensure mixing code doesn't
* have to recheck every time). Then, before starting to decode mixing must be
* manually activated, because plugins need to be ready for possibly different
* input/output channels. API could be improved but this way we can avoid having
* to update all plugins, while allowing internal setup and layer/segment mixing
* (may change in the future for simpler usage).
*
* Then after decoding normally, vgmstream applies mixing internally:
* - detect if mixing is active and needs to be done at this point (some effects
* like fades only apply after certain time) and skip otherwise.
* - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops
* apply float volumes) and slightly improve performance (avoids doing
* int16-to-float casts per mix, as it's not free)
* - apply all mixes on mixbuf
* - copy mixbuf to outbuf
* segmented/layered layouts handle mixing on their own.
*
* Mixing is tuned for most common case (no mix except fade-out at the end) and is
* fast enough but not super-optimized yet, there is some penalty the more effects
* are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0
* could simply use a clear op), only use mixbuf if necessary (swap can be done without
* mixbuf if it goes first) or add function pointer indexes but isn't too important.
* Operations are applied once per "step" with 1 sample from all channels to simplify code
* (and maybe improve memory cache?), though maybe it should call one function per operation.
*/
#define VGMSTREAM_MAX_MIXING 128
/* mixing info */
typedef enum {
MIX_SWAP,
MIX_ADD,
MIX_VOLUME,
MIX_LIMIT,
MIX_UPMIX,
MIX_DOWNMIX,
MIX_KILLMIX,
MIX_FADE
} mix_command_t;
typedef struct {
mix_command_t command;
/* common */
int ch_dst;
int ch_src;
float vol;
/* fade envelope */
float vol_start; /* volume from pre to start */
float vol_end; /* volume from end to post */
char shape; /* curve type */
int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */
int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */
int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */
int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */
} mix_command_data;
typedef struct {
int mixing_channels; /* max channels needed to mix */
int output_channels; /* resulting channels after mixing */
int mixing_on; /* mixing allowed */
int mixing_count; /* mixing number */
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 */
} mixing_data;
/* ******************************************************************* */
static int is_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];
if (mix.command != MIX_FADE)
return 1; /* has non-fades = active */
/* 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;
fade_end = mix.time_post < 0 ? INT_MAX : mix.time_post;
if (current_start < fade_end && current_end > fade_start)
return 1;
}
return 0;
}
static int32_t get_current_pos(VGMSTREAM* vgmstream) {
int32_t current_pos;
if (vgmstream->loop_flag && vgmstream->current_sample > vgmstream->loop_start_sample) {
int loop_pre = vgmstream->loop_start_sample;
int loop_into = vgmstream->current_sample - vgmstream->loop_start_sample;
int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample;
current_pos = loop_pre + loop_into + loop_samples*vgmstream->loop_count;
}
else {
current_pos = vgmstream->current_sample;
}
return current_pos;
}
static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
//todo optimizations: interleave calcs, maybe use cosf, powf, etc?
float cur_vol = 0.0f;
if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) {
cur_vol = mix->vol_start; /* before */
}
else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) {
cur_vol = mix->vol_end; /* after */
}
else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) {
/* in between */
float range_vol, range_dur, range_idx, index, gain;
if (mix->vol_start < mix->vol_end) { /* fade in */
range_vol = mix->vol_end - mix->vol_start;
range_dur = mix->time_end - mix->time_start;
range_idx = current_subpos - mix->time_start;
index = range_idx / range_dur;
} else { /* fade out */
range_vol = mix->vol_end - mix->vol_start;
range_dur = mix->time_end - mix->time_start;
range_idx = mix->time_end - current_subpos;
index = range_idx / range_dur;
}
/* Fading is done like this:
* - find current position within fade duration
* - get linear % (or rather, index from 0.0 .. 1.0) of duration
* - apply shape to % (from linear fade to curved fade)
* - get final volume for that point
*
* Roughly speaking some curve shapes are better for fades (decay rate is more natural
* sounding in that highest to mid/low happens faster but low to lowest takes more time,
* kinda like a gunshot or bell), and others for crossfades (decay of fade-in + fade-out
* is adjusted so that added volume level stays constant-ish).
*
* As curves can fade in two ways ('normal' and curving 'the other way'), they are adjusted
* to get 'normal' shape on both fades (by reversing index and making 1 - gain), thus some
* curves are complementary (exponential fade-in ~= logarithmic fade-out); the following
* are described taking fade-in = normal.
*/
/* (curve math mostly from SoX/FFmpeg) */
switch(mix->shape) {
/* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast
* (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */
case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */
//gain = pow(0.1f, (1.0f - index) * 2.5f);
gain = exp(-5.75646273248511f * (1.0f - index));
break;
case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */
//gain = 1 - pow(0.1f, (index) * 2.5f);
gain = 1 - exp(-5.75646273248511f * (index));
break;
case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */
gain = (1.0f - cos(index * M_PI )) / 2.0f;
break;
case 'Q': /* quarter of sine wave (for musical fades) */
gain = sin(index * M_PI / 2.0f);
break;
case 'p': /* parabola (maybe for crossfades) */
gain = 1.0f - sqrt(1.0f - index);
break;
case 'P': /* inverted parabola (maybe for fades) */
gain = (1.0f - (1.0f - index) * (1.0f - index));
break;
case 'T': /* triangular/linear (simpler/sharper fades) */
default:
gain = index;
break;
}
if (mix->vol_start < mix->vol_end) { /* fade in */
cur_vol = mix->vol_start + range_vol * gain;
} else { /* fade out */
cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain);
}
}
else {
goto fail;
}
*out_cur_vol = cur_vol;
return 1;
fail:
return 0;
}
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;
float temp_f, temp_min, temp_max, cur_vol;
float *temp_mixbuf;
sample_t *temp_outbuf;
const float limiter_max = 32767.0f;
const float limiter_min = -32768.0f;
/* no support or not need to apply */
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);
if (!is_active(data, current_pos, current_pos + sample_count))
return;
/* use advancing buffer pointers to simplify logic */
temp_mixbuf = data->mixbuf;
temp_outbuf = outbuf;
/* apply mixes in order per channel */
for (s = 0; s < sample_count; s++) {
/* reset after new sample 'step'*/
float *stpbuf = temp_mixbuf;
int step_channels = vgmstream->channels;
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */
}
for (m = 0; m < data->mixing_count; m++) {
mix_command_data mix = data->mixing_chain[m];
/* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change
* total channels, channel number meaning varies as ops move them around, ex:
* - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4
* - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4
* - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2
* - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2
* - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1
* - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2
*/
switch(mix.command) {
case MIX_SWAP:
temp_f = stpbuf[mix.ch_dst];
stpbuf[mix.ch_dst] = stpbuf[mix.ch_src];
stpbuf[mix.ch_src] = temp_f;
break;
case MIX_ADD:
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] + stpbuf[mix.ch_src] * mix.vol;
break;
case MIX_VOLUME:
if (mix.ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch] * mix.vol;
}
}
else {
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * mix.vol;
}
break;
case MIX_LIMIT:
temp_max = limiter_max * mix.vol;
temp_min = limiter_min * mix.vol;
if (mix.ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
if (stpbuf[ch] > temp_max)
stpbuf[ch] = temp_max;
else if (stpbuf[ch] < temp_min)
stpbuf[ch] = temp_min;
}
}
else {
if (stpbuf[mix.ch_dst] > temp_max)
stpbuf[mix.ch_dst] = temp_max;
else if (stpbuf[mix.ch_dst] < temp_min)
stpbuf[mix.ch_dst] = temp_min;
}
break;
case MIX_UPMIX:
step_channels += 1;
for (ch = step_channels - 1; ch > mix.ch_dst; ch--) {
stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */
}
stpbuf[mix.ch_dst] = 0; /* inserted as silent */
break;
case MIX_DOWNMIX:
step_channels -= 1;
for (ch = mix.ch_dst; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */
}
break;
case MIX_KILLMIX:
step_channels = mix.ch_dst; /* clamp channels */
break;
case MIX_FADE:
current_subpos = current_pos + s;
ok = get_fade_gain(&mix, &cur_vol, current_subpos);
if (!ok) {
break;
}
if (mix.ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch] * cur_vol;
}
}
else {
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * cur_vol;
}
break;
default:
break;
}
}
temp_mixbuf += step_channels;
temp_outbuf += vgmstream->channels;
}
/* copy resulting mix to output */
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
* alts for more accurate rounding could be:
* - (int)floor(f)
* - (int)(f < 0 ? f - 0.5f : f + 0.5f)
* - (((int) (f1 + 32768.5)) - 32768)
* - etc
* but since +-1 isn't really audible we'll just cast as it's the fastest
*/
outbuf[s] = clamp16( (int32_t)data->mixbuf[s] );
}
}
/* ******************************************************************* */
void mixing_init(VGMSTREAM* vgmstream) {
mixing_data *data = calloc(1, sizeof(mixing_data));
if (!data) goto fail;
data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */
data->mixing_channels = vgmstream->channels;
data->output_channels = vgmstream->channels;
vgmstream->mixing_data = data;
return;
fail:
free(data);
return;
}
void mixing_close(VGMSTREAM* vgmstream) {
mixing_data *data = NULL;
if (!vgmstream) return;
data = vgmstream->mixing_data;
if (!data) return;
free(data->mixbuf);
free(data);
}
void mixing_update_channel(VGMSTREAM* vgmstream) {
mixing_data *data = vgmstream->mixing_data;
if (!data) return;
/* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */
data->mixing_channels++;
data->output_channels++;
}
/* ******************************************************************* */
static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
mixing_data *data = vgmstream->mixing_data;
if (!data) return 0;
if (data->mixing_on) {
VGM_LOG("MIX: ignoring new mixes when mixing active\n");
return 0; /* to avoid down/upmixing after activation */
}
if (data->mixing_count + 1 > data->mixing_size) {
VGM_LOG("MIX: too many mixes\n");
return 0;
}
data->mixing_chain[data->mixing_count] = *mix; /* memcpy */
data->mixing_count++;
//;VGM_LOG("MIX: total %i\n", data->mixing_count);
return 1;
}
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return;
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
mix.command = MIX_SWAP;
mix.ch_dst = ch_dst;
mix.ch_src = ch_src;
add_mixing(vgmstream, &mix);
}
void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
if (!data) return;
//if (volume < 0.0) return; /* negative volume inverts the waveform */
if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */
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.ch_dst = ch_dst;
mix.ch_src = ch_src;
mix.vol = volume;
add_mixing(vgmstream, &mix);
}
void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
//if (ch_dst < 0) return; /* means all channels */
//if (volume < 0.0) return; /* negative volume inverts the waveform */
if (volume == 1.0) return; /* no change */
if (!data || ch_dst >= data->output_channels) return;
mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */
mix.ch_dst = ch_dst;
mix.vol = volume;
add_mixing(vgmstream, &mix);
}
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
//if (ch_dst < 0) return; /* means all channels */
if (volume < 0.0) return;
if (volume == 1.0) return; /* no actual difference */
if (!data || ch_dst >= data->output_channels) return;
//if (volume == 0.0) return; /* dumb but whatevs */
mix.command = MIX_LIMIT;
mix.ch_dst = ch_dst;
mix.vol = volume;
add_mixing(vgmstream, &mix);
}
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
int ok;
if (ch_dst < 0) return;
if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return;
/* dst can be == output_channels here, since we are inserting */
mix.command = MIX_UPMIX;
mix.ch_dst = ch_dst;
ok = add_mixing(vgmstream, &mix);
if (ok) {
data->output_channels += 1;
if (data->mixing_channels < data->output_channels)
data->mixing_channels = data->output_channels;
}
}
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
int ok;
if (ch_dst < 0) return;
if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return;
mix.command = MIX_DOWNMIX;
mix.ch_dst = ch_dst;
ok = add_mixing(vgmstream, &mix);
if (ok) {
data->output_channels -= 1;
}
}
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
int ok;
if (ch_dst <= 1) return; /* can't kill from 1 */
if (!data || ch_dst >= data->output_channels) return;
mix.command = MIX_KILLMIX;
mix.ch_dst = ch_dst;
ok = add_mixing(vgmstream, &mix);
if (ok) {
data->output_channels = ch_dst; /* clamp channels */
}
}
static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
int i;
for (i = data->mixing_count; i > 0; i--) {
mix_command_data *mix = &data->mixing_chain[i-1];
if (mix->command != MIX_FADE)
continue;
if (mix->ch_dst == target_channel)
return mix;
}
return NULL;
}
void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape,
int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mix_command_data *mix_prev;
//if (ch_dst < 0) return; /* means all channels */
if (!data || ch_dst >= data->output_channels) return;
if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return;
if (time_start < 0 || time_end < 0) return;
//if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */
//if (vol_start == vol_end) /* weird but let in case of being used to cancel others fades... maybe? */
if (shape == '{' || shape == '}')
shape = 'E';
if (shape == '(' || shape == ')')
shape = 'H';
mix.command = MIX_FADE;
mix.ch_dst = ch_dst;
mix.vol_start = vol_start;
mix.vol_end = vol_end;
mix.shape = shape;
mix.time_pre = time_pre;
mix.time_start = time_start;
mix.time_end = time_end;
mix.time_post = time_post;
/* cancel fades and optimize a bit when using negative pre/post:
* - fades work like this:
* <----------|----------|---------->
* pre1 start1 end1 post1
* - when pre and post are set nothing is done (fade is exact and multiple fades may overlap)
* - when previous fade's post or current fade's pre are negative (meaning file end/start)
* they should cancel each other (to allow chaning fade-in + fade-out + fade-in + etc):
* <----------|----------|----------| |----------|----------|---------->
* pre1 start1 end1 post1 pre2 start2 end2 post2
* - other cases (previous fade is actually after/in-between current fade) are ignored
* as they're uncommon and hard to optimize
* fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels'
*/
mix_prev = get_last_fade(data, mix.ch_dst);
if (mix_prev == NULL) {
if (vol_start == 1.0 && time_pre < 0)
time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */
if (vol_end == 1.0 && time_post < 0)
time_post = time_end; /* fade-in helds default volume after fade end can be clamped */
}
else if (mix_prev->time_post < 0 || mix.time_pre < 0) {
int is_prev = 1;
if ((mix_prev->time_end > mix.time_start) ||
(mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) ||
(mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end))
is_prev = 0;
if (is_prev) {
/* change negative values to actual points */
if (mix_prev->time_post < 0 && mix_prev->time_post < 0) {
mix_prev->time_post = mix_prev->time_end;
mix.time_pre = mix_prev->time_post;
}
if (mix_prev->time_post >= 0 && mix.time_pre < 0) {
mix.time_pre = mix_prev->time_post;
}
else if (mix_prev->time_post < 0 && mix.time_pre >= 0) {
mix_prev->time_post = mix.time_pre;
}
/* else: both define start/ends, do nothing */
}
/* should only modify prev if add_mixing but meh */
}
//;VGM_LOG("MIX: fade: %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post);
add_mixing(vgmstream, &mix);
}
/* ******************************************************************* */
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
mixing_data *data = vgmstream->mixing_data;
int ch;
if (!data)
return;
if (mask == 0) {
mixing_push_volume(vgmstream, -1, volume);
return;
}
for (ch = 0; ch < data->output_channels; ch++) {
if (!((mask >> ch) & 1))
continue;
mixing_push_volume(vgmstream, ch, volume);
}
}
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
mixing_data *data = vgmstream->mixing_data;
int ch;
if (!data)
return;
if (mask == 0) {
return;
}
/* reverse remove all channels (easier this way as when removing channels numbers change) */
for (ch = data->output_channels - 1; ch >= 0; ch--) {
if ((mask >> ch) & 1)
continue;
mixing_push_downmix(vgmstream, ch);
}
}
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overlap) {
mixing_data *data = vgmstream->mixing_data;
int current, ch, selected_channels;
if (!data)
return;
if (max <= 0 || data->output_channels <= max)
return;
/* set all channels (non-existant channels will be ignored) */
if (mask == 0) {
mask = ~mask;
}
/* count possibly set channels */
selected_channels = 0;
for (ch = 0; ch < data->output_channels; ch++) {
selected_channels += (mask >> ch) & 1;
}
/* make N fake channels at the beginning for easier calcs */
for (ch = 0; ch < max; ch++) {
mixing_push_upmix(vgmstream, 0);
}
/* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */
current = 0;
for (ch = 0; ch < data->output_channels; ch++) {
double volume = 1.0;
if (!((mask >> ch) & 1))
continue;
/* adjust volume (layer = lower, for layered bgm, overlap = same, for layered vocals) */
if (!overlap) {
/* 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;
if (channel_mixes <= 0) /* ??? */
channel_mixes = 1;
if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */
channel_mixes += 1;
volume = 1 / sqrt(channel_mixes); /* "power" add, good results most of the time */
}
mixing_push_add(vgmstream, current, max + ch, volume); /* ch adjusted considering upmixed channels */
current++;
if (current >= max)
current = 0;
}
/* remove all mixed channels */
mixing_push_killmix(vgmstream, max);
}
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
mixing_data *data = vgmstream->mixing_data;
int current, ch, track, track_ch, track_num, output_channels;
int32_t change_pos, change_next, change_time;
if (!data)
return;
if (max <= 0 || data->output_channels <= max)
return;
if (!vgmstream->loop_flag) /* maybe force loop? */
return;
/* this probably only makes sense for even channels so upmix before if needed) */
output_channels = data->output_channels;
if (output_channels % 2) {
mixing_push_upmix(vgmstream, output_channels);
output_channels += 1;
}
/* set loops to hear all track changes */
track_num = output_channels / max;
if (vgmstream->config_loop_count < track_num)
vgmstream->config_loop_count = track_num;
ch = 0;
for (track = 0; track < track_num; track++) {
double volume = 1.0; /* won't play at the same time, no volume change needed */
int loop_pre = vgmstream->loop_start_sample;
int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample;
change_pos = loop_pre + loop_samples * track;
change_next = loop_pre + loop_samples * (track + 1);
change_time = 15.0 * vgmstream->sample_rate; /* in secs */
for (track_ch = 0; track_ch < max; track_ch++) {
if (track > 0) { /* fade-in when prev track fades-out */
mixing_push_fade(vgmstream, ch + track_ch, 0.0, volume, '(', -1, change_pos, change_pos + change_time, -1);
}
if (track + 1 < track_num) { /* fade-out when next track fades-in */
mixing_push_fade(vgmstream, ch + track_ch, volume, 0.0, ')', -1, change_next, change_next + change_time, -1);
}
}
ch += max;
}
/* mix all tracks into first */
current = 0;
for (ch = max; ch < output_channels; ch++) {
mixing_push_add(vgmstream, current, ch, 1.0); /* won't play at the same time, no volume change needed */
current++;
if (current >= max)
current = 0;
}
/* remove unneeded channels */
mixing_push_killmix(vgmstream, max);
}
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max) {
mixing_data *data = vgmstream->mixing_data;
int current, ch, layer, layer_ch, layer_num, loop, output_channels;
int32_t change_pos, change_time;
if (!data)
return;
if (max <= 0 || data->output_channels <= max)
return;
if (!vgmstream->loop_flag) /* maybe force loop? */
return;
/* this probably only makes sense for even channels so upmix before if needed) */
output_channels = data->output_channels;
if (output_channels % 2) {
mixing_push_upmix(vgmstream, output_channels);
output_channels += 1;
}
/* set loops to hear all track changes */
layer_num = output_channels / max;
if (vgmstream->config_loop_count < layer_num)
vgmstream->config_loop_count = layer_num;
/* must set fades to successively lower 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]--
*/
for (loop = 1; loop < layer_num; loop++) {
double volume1 = 1 / sqrt(loop + 0);
double volume2 = 1 / sqrt(loop + 1);
int loop_pre = vgmstream->loop_start_sample;
int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample;
change_pos = loop_pre + loop_samples * loop;
change_time = 10.0 * vgmstream->sample_rate; /* in secs */
ch = 0;
for (layer = 0; layer < layer_num; layer++) {
char type;
if (layer > loop) { /* not playing yet (volume is implicitly 0.0 from first fade in) */
continue;
} else if (layer == loop) { /* fades in for the first time */
volume1 = 0.0;
type = '(';
} else { /* otherwise fades out to match other layers's volume */
type = ')';
}
for (layer_ch = 0; layer_ch < max; layer_ch++) {
mixing_push_fade(vgmstream, ch + layer_ch, volume1, volume2, type, -1, change_pos, change_pos + change_time, -1);
}
ch += max;
}
}
/* mix all tracks into first */
current = 0;
for (ch = max; ch < output_channels; ch++) {
mixing_push_add(vgmstream, current, ch, 1.0);
current++;
if (current >= max)
current = 0;
}
/* remove unneeded channels */
mixing_push_killmix(vgmstream, max);
}
/* ******************************************************************* */
void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) {
mixing_data *data = vgmstream->mixing_data;
float *mixbuf_re = NULL;
if (!data) goto fail;
/* create or alter internal buffer */
mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float));
if (!mixbuf_re) goto fail;
data->mixbuf = mixbuf_re;
data->mixing_on = 1;
/* a bit wonky but eh... */
if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) {
vgmstream->channel_layout = 0;
((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = 0;
}
/* since data exists on its own memory and pointer is already set
* there is no need to propagate to start_vgmstream */
/* segments/layers are independant from external buffers and may always mix */
return;
fail:
return;
}
void mixing_info(VGMSTREAM * vgmstream, int *out_input_channels, int *out_output_channels) {
mixing_data *data = vgmstream->mixing_data;
int input_channels, output_channels;
if (!data) goto fail;
output_channels = data->output_channels;
if (data->output_channels > vgmstream->channels)
input_channels = data->output_channels;
else
input_channels = vgmstream->channels;
if (out_input_channels) *out_input_channels = input_channels;
if (out_output_channels) *out_output_channels = output_channels;
//;VGM_LOG("MIX: channels %i, in=%i, out=%i, mix=%i\n", vgmstream->channels, input_channels, output_channels, data->mixing_channels);
return;
fail:
return;
}
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
mixing_setup(vgmstream, max_sample_count);
mixing_info(vgmstream, input_channels, output_channels);
}
/* ********************************************************* */
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
mixing_data *data = vgmstream->mixing_data;
if (!data) goto fail;
if (max_channels <= 0)
return;
/* guess mixing the best we can */
//todo: could use standard downmixing for known max_channels <> vgmstream->channels combos:
// https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables#tbl_mono
// https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations
mixing_macro_layer(vgmstream, max_channels, 0, 0);
return;
fail:
return;
}
#endif

41
src/mixing.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef _MIXING_H_
#define _MIXING_H_
#include "vgmstream.h"
/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and
* outbuf must big enough to hold output_channels*samples_to_do */
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream);
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
* If init somehow fails next calls are ignored. */
void mixing_init(VGMSTREAM* vgmstream);
void mixing_close(VGMSTREAM* vgmstream);
void mixing_update_channel(VGMSTREAM* vgmstream);
/* Call to let vgmstream apply mixing, which must handle input/output_channels.
* Once mixing is active any new mixes are ignored (to avoid the possibility
* of down/upmixing without querying input/output_channels). */
void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count);
/* gets current mixing info */
void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels);
/* adds mixes filtering and optimizing if needed */
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src);
void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume);
void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume);
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume);
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst);
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst);
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst);
void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post);
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask);
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask);
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, int overlap);
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max);
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max);
#endif /* _MIXING_H_ */

View File

@ -86,11 +86,11 @@ void vgmstream_tags_close(VGMSTREAM_TAGS* tags);
* Needs to be enabled last after adding effects. */
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels);
/* sets automatic downmixing if vgmstream's channels are higher than max_channels */
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels);
/* sets a fadeout */
//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds);
/* sets downmixing if needed */
//void vgmstream_mixing_downmix(VGMSTREAM *vgmstream, int max_channels)
#endif
#endif /* _PLUGINS_H_ */

View File

@ -1127,12 +1127,17 @@ void dump_streamfile(STREAMFILE *streamFile, int num) {
if (!f) return;
}
VGM_LOG("dump streamfile, size: %x\n", get_streamfile_size(streamFile));
VGM_LOG("dump streamfile: size %x\n", get_streamfile_size(streamFile));
while (offset < get_streamfile_size(streamFile)) {
uint8_t buffer[0x8000];
size_t read;
read = read_streamfile(buffer,offset,0x8000,streamFile);
if(!read) {
VGM_LOG("dump streamfile: can't read at %lx\n", offset);
break;
}
if (f)
fwrite(buffer,sizeof(uint8_t),read, f);
else

View File

@ -374,6 +374,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_naac,
init_vgmstream_ubi_sb,
init_vgmstream_ubi_sm,
init_vgmstream_ubi_bnm,
init_vgmstream_ezw,
init_vgmstream_vxn,
init_vgmstream_ea_snr_sns,
@ -477,6 +478,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_ea_schl_video,
init_vgmstream_msf_konami,
init_vgmstream_xwma_konami,
init_vgmstream_9tav,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
@ -2313,6 +2315,18 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
snprintf(temp,TEMPSIZE, "channels: %d\n", vgmstream->channels);
concatn(length,desc,temp);
#ifdef VGMSTREAM_MIXING
{
int output_channels = 0;
mixing_info(vgmstream, NULL, &output_channels);
if (output_channels != vgmstream->channels) {
snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels);
concatn(length,desc,temp);
}
}
#endif
if (vgmstream->channel_layout) {
int cl = vgmstream->channel_layout;
@ -2348,20 +2362,20 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
seconds = (double)vgmstream->loop_start_sample / vgmstream->sample_rate;
time_mm = (int)(seconds / 60.0);
time_ss = seconds - time_mm * 60.0f;
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%2.3f seconds)\n", vgmstream->loop_start_sample, 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;
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%2.3f seconds)\n", vgmstream->loop_end_sample, 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;
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%2.3f seconds)\n", vgmstream->num_samples, 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);
snprintf(temp,TEMPSIZE, "encoding: ");

View File

@ -729,6 +729,7 @@ typedef enum {
meta_STRM_ABYLIGHT,
meta_MSF_KONAMI,
meta_XWMA_KONAMI,
meta_9TAV,
} meta_t;
@ -1146,12 +1147,18 @@ typedef struct {
int segment_count;
VGMSTREAM **segments;
int current_segment;
sample_t *buffer;
int input_channels; /* internal buffer channels */
int output_channels; /* resulting channels (after mixing, if applied) */
} segmented_layout_data;
/* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */
typedef struct {
int layer_count;
VGMSTREAM **layers;
sample_t *buffer;
int input_channels; /* internal buffer channels */
int output_channels; /* resulting channels (after mixing, if applied) */
} layered_layout_data;
/* for compressed NWA */

View File

@ -79,10 +79,13 @@ winamp_settings settings;
winamp_song_config config;
/* Winamp needs at least 576 16-bit samples, stereo, doubled in case DSP effects are active */
#define SAMPLE_BUFFER_SIZE 576
short sample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS]; //todo maybe should be dynamic
/* plugin state */
VGMSTREAM * vgmstream = NULL;
HANDLE decode_thread_handle = INVALID_HANDLE_VALUE;
short sample_buffer[(576*2) * 2]; /* at least 576 16-bit samples, stereo, doubled in case Winamp's DSP is active */
int paused = 0;
int decode_abort = 0;
@ -93,7 +96,7 @@ int stream_length_samples = 0;
int fade_samples = 0;
int output_channels = 0;
const char* tagfile_name = "!tags.m3u"; //todo make configurable
const char* tagfile_name = "!tags.m3u";
in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
@ -1051,9 +1054,15 @@ int winamp_Play(const in_char *fn) {
set_config_defaults(&config);
apply_config(vgmstream, &config);
#ifdef VGMSTREAM_MIXING
/* enable after all config but before outbuf (though ATM outbuf is not dynamic so no need to read input_channels) */
vgmstream_mixing_autodownmix(vgmstream, settings.downmix_channels);
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, NULL /*&input_channels*/, &output_channels);
#else
output_channels = vgmstream->channels;
if (settings.downmix_channels > 0 && settings.downmix_channels < vgmstream->channels)
output_channels = settings.downmix_channels;
#endif
/* save original name */
@ -1191,6 +1200,10 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
set_config_defaults(&infoconfig);
apply_config(infostream, &infoconfig);
#ifdef VGMSTREAM_MIXING
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
//vgmstream_mixing_enable(infostream, SAMPLE_BUFFER_SIZE, NULL, NULL);
#endif
describe_vgmstream(infostream,description,description_size);
@ -1242,6 +1255,10 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
set_config_defaults(&infoconfig);
apply_config(infostream, &infoconfig);
#ifdef VGMSTREAM_MIXING
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
//vgmstream_mixing_enable(infostream, SAMPLE_BUFFER_SIZE, NULL, NULL);
#endif
if (title) {
get_title(title,GETFILEINFO_TITLE_LENGTH, fn, infostream);
@ -1267,7 +1284,7 @@ void winamp_EQSet(int on, char data[10], int preamp) {
/* the decode thread */
DWORD WINAPI __stdcall decode(void *arg) {
const int max_buffer_samples = sizeof(sample_buffer) / sizeof(sample_buffer[0]) / 2 / vgmstream->channels;
const int max_buffer_samples = SAMPLE_BUFFER_SIZE;
const int max_samples = stream_length_samples;
while (!decode_abort) {
@ -1335,21 +1352,28 @@ DWORD WINAPI __stdcall decode(void *arg) {
/* fade near the end */
if (vgmstream->loop_flag && fade_samples > 0 && !settings.loop_forever) {
int fade_channels;
int samples_into_fade = decode_pos_samples - (stream_length_samples - fade_samples);
#ifdef VGMSTREAM_MIXING
fade_channels = output_channels;
#else
fade_channels = vgmstream->channels;
#endif
if (samples_into_fade + samples_to_do > 0) {
int j, k;
for (j = 0; j < samples_to_do; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
const double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples;
for (k = 0; k < vgmstream->channels; k++) {
sample_buffer[j*vgmstream->channels+k] =
(short)(sample_buffer[j*vgmstream->channels+k]*fadedness);
for (k = 0; k < fade_channels; k++) {
sample_buffer[j*fade_channels+k] =
(short)(sample_buffer[j*fade_channels+k]*fadedness);
}
}
}
}
}
#ifndef VGMSTREAM_MIXING
/* downmix enabled (useful when the stream's channels are too much for Winamp's output) */
if (settings.downmix_channels > 0 && settings.downmix_channels < vgmstream->channels) {
short temp_buffer[(576*2) * 2];
@ -1374,6 +1398,7 @@ DWORD WINAPI __stdcall decode(void *arg) {
/* copy back to global buffer... in case of multithreading stuff? */
memcpy(sample_buffer,temp_buffer, samples_to_do*settings.downmix_channels*sizeof(short));
}
#endif
/* output samples */
input_module.SAAddPCMData((char*)sample_buffer,output_channels,16,decode_pos_ms);