mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 09:40:51 +01:00
commit
68bdc65583
@ -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);
|
||||
|
10
doc/BUILD.md
10
doc/BUILD.md
@ -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.
|
||||
|
47
doc/TXTH.md
47
doc/TXTH.md
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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"},
|
||||
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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" />
|
||||
|
@ -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
118
src/meta/9tav.c
Normal 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
182
src/meta/9tav_streamfile.h
Normal 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_ */
|
@ -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*/
|
||||
|
@ -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) ||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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_ */
|
||||
|
@ -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);
|
||||
|
@ -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
968
src/mixing.c
Normal 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
41
src/mixing.h
Normal 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_ */
|
@ -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_ */
|
||||
|
@ -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
|
||||
|
@ -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: ");
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user