diff --git a/cli/vgmstream_cli.vcproj b/cli/vgmstream_cli.vcproj
index 2f916d66..4a17791f 100644
--- a/cli/vgmstream_cli.vcproj
+++ b/cli/vgmstream_cli.vcproj
@@ -1,204 +1,204 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cli/vgmstream_cli.vcxproj b/cli/vgmstream_cli.vcxproj
index 2be97937..69872184 100644
--- a/cli/vgmstream_cli.vcxproj
+++ b/cli/vgmstream_cli.vcxproj
@@ -1,148 +1,148 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Template
- Win32
-
-
-
- {AF7D88A0-3CB1-4CD8-BAD1-0305EB996D69}
- test
- Win32Proj
-
-
-
-
-
-
-
-
- 8.1
- test
-
-
-
- Application
- Unicode
- true
- v141_xp
-
-
- Application
- Unicode
- v141_xp
-
-
- v141_xp
-
-
-
-
-
-
-
-
-
-
-
- ../dependencies
-
-
- <_ProjectFileVersion>10.0.30319.1
- AllRules.ruleset
-
-
- AllRules.ruleset
-
-
- AllRules.ruleset
-
-
-
-
-
- Disabled
- ..;../ext_libs/Getopt;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
- WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;_DEBUG;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions)
- EnableFastChecks
- MultiThreadedDebug
-
-
- Level3
- EditAndContinue
-
-
- ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
- %(AdditionalLibraryDirectories)
- Console
- true
- MachineX86
-
-
- "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
-
-
- Generating version.h
-
-
-
-
- ..;../ext_libs/Getopt;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
- WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;NDEBUG;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions)
- MultiThreaded
-
-
- Level3
- ProgramDatabase
- Fast
-
-
- ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
- %(AdditionalLibraryDirectories)
- %(IgnoreSpecificDefaultLibraries)
- Console
- true
- true
- true
- MachineX86
-
-
- "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
-
-
- Generating version.h
-
-
-
-
-
-
-
- {308e2ad5-be31-4770-9441-a8d50f56895c}
-
-
- {86a064e2-c81b-4eee-8be0-a39a2e7c7c76}
-
-
- {10e6bfc6-1e5b-46e4-ba42-f04dfbd0abff}
-
-
- {330b53ae-4fae-46da-8785-9016db4e3e23}
-
-
- {54a6ad11-5369-4895-a06f-e255abb99b11}
-
-
-
-
-
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Template
+ Win32
+
+
+
+ {AF7D88A0-3CB1-4CD8-BAD1-0305EB996D69}
+ test
+ Win32Proj
+
+
+
+
+
+
+
+
+ 8.1
+ test
+
+
+
+ Application
+ Unicode
+ true
+ v141_xp
+
+
+ Application
+ Unicode
+ v141_xp
+
+
+ v141_xp
+
+
+
+
+
+
+
+
+
+
+
+ ../dependencies
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ AllRules.ruleset
+
+
+ AllRules.ruleset
+
+
+ AllRules.ruleset
+
+
+
+
+
+ Disabled
+ ..;../ext_libs/Getopt;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
+ WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;_DEBUG;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions)
+ EnableFastChecks
+ MultiThreadedDebug
+
+
+ Level3
+ EditAndContinue
+
+
+ ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
+ %(AdditionalLibraryDirectories)
+ Console
+ true
+ MachineX86
+
+
+ "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
+
+
+ Generating version.h
+
+
+
+
+ ..;../ext_libs/Getopt;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
+ WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;NDEBUG;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreaded
+
+
+ Level3
+ ProgramDatabase
+ Fast
+
+
+ ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
+ %(AdditionalLibraryDirectories)
+ %(IgnoreSpecificDefaultLibraries)
+ Console
+ true
+ true
+ true
+ MachineX86
+
+
+ "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
+
+
+ Generating version.h
+
+
+
+
+
+
+
+ {308e2ad5-be31-4770-9441-a8d50f56895c}
+
+
+ {86a064e2-c81b-4eee-8be0-a39a2e7c7c76}
+
+
+ {10e6bfc6-1e5b-46e4-ba42-f04dfbd0abff}
+
+
+ {330b53ae-4fae-46da-8785-9016db4e3e23}
+
+
+ {54a6ad11-5369-4895-a06f-e255abb99b11}
+
+
+
+
+
\ No newline at end of file
diff --git a/cli/vgmstream_cli.vcxproj.filters b/cli/vgmstream_cli.vcxproj.filters
index 57e5973a..7ffb42b7 100644
--- a/cli/vgmstream_cli.vcxproj.filters
+++ b/cli/vgmstream_cli.vcxproj.filters
@@ -1,22 +1,22 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hpp;hxx;hm;inl;inc;xsd
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav
-
-
-
-
- Source Files
-
-
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav
+
+
+
+
+ Source Files
+
+
\ No newline at end of file
diff --git a/fb2k/foo_input_vgmstream.vcxproj b/fb2k/foo_input_vgmstream.vcxproj
index 4e6f7397..2fd9ab35 100644
--- a/fb2k/foo_input_vgmstream.vcxproj
+++ b/fb2k/foo_input_vgmstream.vcxproj
@@ -1,4 +1,4 @@
-
+
diff --git a/fb2k/foo_input_vgmstream.vcxproj.filters b/fb2k/foo_input_vgmstream.vcxproj.filters
index e95e0f46..e32e1f7c 100644
--- a/fb2k/foo_input_vgmstream.vcxproj.filters
+++ b/fb2k/foo_input_vgmstream.vcxproj.filters
@@ -1,47 +1,47 @@
-
-
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hpp;hxx;hm;inl;inc;xsd
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
-
-
- Resource Files
-
-
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
+
+
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
\ No newline at end of file
diff --git a/src/coding/atrac9_decoder.c b/src/coding/atrac9_decoder.c
index 5141d192..9f983cad 100644
--- a/src/coding/atrac9_decoder.c
+++ b/src/coding/atrac9_decoder.c
@@ -1,278 +1,278 @@
-#include "coding.h"
-
-#ifdef VGM_USE_ATRAC9
-#include "libatrac9.h"
-
-
-/* opaque struct */
-struct atrac9_codec_data {
- uint8_t *data_buffer;
- size_t data_buffer_size;
-
- 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 */
-
- int samples_to_discard;
-
- atrac9_config config;
-
- void *handle; /* decoder handle */
- Atrac9CodecInfo info; /* decoder info */
-};
-
-
-atrac9_codec_data *init_atrac9(atrac9_config *cfg) {
- int status;
- uint8_t config_data[4];
- atrac9_codec_data *data = NULL;
-
- data = calloc(1, sizeof(atrac9_codec_data));
- if (!data) goto fail;
-
- data->handle = Atrac9GetHandle();
- if (!data->handle) goto fail;
-
- put_32bitBE(config_data, cfg->config_data);
- status = Atrac9InitDecoder(data->handle, config_data);
- if (status < 0) goto fail;
-
- 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, 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);
- goto fail; /* unknown multichannel layout */
- }
-
-
- /* must hold at least one superframe and its samples */
- data->data_buffer_size = data->info.superframeSize;
- /* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */
- data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10);
- /* while ATRAC9 uses float internally, Sony's API only return PCM16 */
- data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
-
- data->samples_to_discard = cfg->encoder_delay;
-
- memcpy(&data->config, cfg, sizeof(atrac9_config));
-
- return data;
-
-fail:
- free_atrac9(data);
- return NULL;
-}
-
-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;
-
-
- while (samples_done < samples_to_do) {
-
- if (data->samples_filled) { /* consume samples */
- int samples_to_get = data->samples_filled;
-
- if (data->samples_to_discard) {
- /* discard samples for looping */
- if (samples_to_get > data->samples_to_discard)
- samples_to_get = data->samples_to_discard;
- data->samples_to_discard -= samples_to_get;
- }
- else {
- /* get max samples and copy */
- if (samples_to_get > samples_to_do - samples_done)
- samples_to_get = samples_to_do - samples_done;
-
- memcpy(outbuf + samples_done*channels,
- data->sample_buffer + data->samples_used*channels,
- samples_to_get*channels * sizeof(sample));
-
- samples_done += samples_to_get;
- }
-
- /* mark consumed samples */
- data->samples_used += samples_to_get;
- data->samples_filled -= samples_to_get;
- }
- else { /* decode data */
- int iframe, status;
- int bytes_used = 0;
- uint8_t *buffer = data->data_buffer;
- size_t bytes;
-
- data->samples_used = 0;
-
- /* ATRAC9 is made of decodable superframes with several sub-frames. AT9 config data gives
- * superframe size, number of frames and samples (~100-200 bytes and ~256/1024 samples). */
-
- /* read one raw block (superframe) and advance offsets */
- bytes = read_streamfile(data->data_buffer,stream->offset, data->info.superframeSize,stream->streamfile);
- if (bytes != data->data_buffer_size) goto decode_fail;
-
- stream->offset += bytes;
-
- /* decode all frames in the superframe block */
- for (iframe = 0; iframe < data->info.framesInSuperframe; iframe++) {
- status = Atrac9Decode(data->handle, buffer, data->sample_buffer + data->samples_filled*channels, &bytes_used);
- if (status < 0) goto decode_fail;
-
- buffer += bytes_used;
- data->samples_filled += data->info.frameSamples;
- }
- }
- }
-
- return;
-
-decode_fail:
- /* on error just put some 0 samples */
- VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
- memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
-}
-
-void reset_atrac9(VGMSTREAM *vgmstream) {
- atrac9_codec_data *data = vgmstream->codec_data;
- if (!data) return;
-
- if (!data->handle)
- goto fail;
-
-#if 0
- /* reopen/flush, not needed as superframes decode separatedly and there is no carried state */
- {
- int status;
- uint8_t config_data[4];
-
- Atrac9ReleaseHandle(data->handle);
- data->handle = Atrac9GetHandle();
- if (!data->handle) goto fail;
-
- put_32bitBE(config_data, data->config.config_data);
- status = Atrac9InitDecoder(data->handle, config_data);
- if (status < 0) goto fail;
- }
-#endif
-
- data->samples_used = 0;
- data->samples_filled = 0;
- data->samples_to_discard = data->config.encoder_delay;
-
- return;
-
-fail:
- return; /* decode calls should fail... */
-}
-
-void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample) {
- atrac9_codec_data *data = vgmstream->codec_data;
- if (!data) return;
-
- reset_atrac9(vgmstream);
-
- /* find closest offset to desired sample, and samples to discard after that offset to reach loop */
- {
- int32_t seek_sample = data->config.encoder_delay + num_sample;
- off_t seek_offset;
- int32_t seek_discard;
- int32_t superframe_samples = data->info.frameSamples * data->info.framesInSuperframe;
- size_t superframe_number, superframe_back;
-
- superframe_number = (seek_sample / superframe_samples); /* closest */
-
- /* decoded frames affect each other slightly, so move offset back to make PCM stable
- * and equivalent to a full discard loop */
- superframe_back = 1; /* 1 seems enough (even when only 1 subframe in superframe) */
- if (superframe_back > superframe_number)
- superframe_back = superframe_number;
-
- seek_discard = (seek_sample % superframe_samples) + (superframe_back * superframe_samples);
- seek_offset = (superframe_number - superframe_back) * data->info.superframeSize;
-
- data->samples_to_discard = seek_discard; /* already includes encoder delay */
-
- if (vgmstream->loop_ch)
- vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + seek_offset;
- }
-
-#if 0
- //old full discard loop
- {
- data->samples_to_discard = num_sample;
- data->samples_to_discard += data->config.encoder_delay;
-
- /* loop offsets are set during decode; force them to stream start so discard works */
- if (vgmstream->loop_ch)
- vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
- }
-#endif
-
-}
-
-void free_atrac9(atrac9_codec_data *data) {
- if (!data) return;
-
- if (data->handle) Atrac9ReleaseHandle(data->handle);
- free(data->data_buffer);
- free(data->sample_buffer);
- free(data);
-}
-
-
-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 */
- /* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */
- size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */
- 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)
- *out_sample_rate = sample_rate_table[sample_rate_index];
- if (out_channels)
- *out_channels = channel_table[channels_index];
- if (out_frame_size)
- *out_frame_size = superframe_size;
- if (out_samples_per_frame)
- *out_samples_per_frame = samples_per_superframe;
-
- return 1;
-fail:
- return 0;
-}
-
-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
+#include "coding.h"
+
+#ifdef VGM_USE_ATRAC9
+#include "libatrac9.h"
+
+
+/* opaque struct */
+struct atrac9_codec_data {
+ uint8_t *data_buffer;
+ size_t data_buffer_size;
+
+ 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 */
+
+ int samples_to_discard;
+
+ atrac9_config config;
+
+ void *handle; /* decoder handle */
+ Atrac9CodecInfo info; /* decoder info */
+};
+
+
+atrac9_codec_data *init_atrac9(atrac9_config *cfg) {
+ int status;
+ uint8_t config_data[4];
+ atrac9_codec_data *data = NULL;
+
+ data = calloc(1, sizeof(atrac9_codec_data));
+ if (!data) goto fail;
+
+ data->handle = Atrac9GetHandle();
+ if (!data->handle) goto fail;
+
+ put_32bitBE(config_data, cfg->config_data);
+ status = Atrac9InitDecoder(data->handle, config_data);
+ if (status < 0) goto fail;
+
+ 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, 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);
+ goto fail; /* unknown multichannel layout */
+ }
+
+
+ /* must hold at least one superframe and its samples */
+ data->data_buffer_size = data->info.superframeSize;
+ /* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */
+ data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10);
+ /* while ATRAC9 uses float internally, Sony's API only return PCM16 */
+ data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
+
+ data->samples_to_discard = cfg->encoder_delay;
+
+ memcpy(&data->config, cfg, sizeof(atrac9_config));
+
+ return data;
+
+fail:
+ free_atrac9(data);
+ return NULL;
+}
+
+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;
+
+
+ while (samples_done < samples_to_do) {
+
+ if (data->samples_filled) { /* consume samples */
+ int samples_to_get = data->samples_filled;
+
+ if (data->samples_to_discard) {
+ /* discard samples for looping */
+ if (samples_to_get > data->samples_to_discard)
+ samples_to_get = data->samples_to_discard;
+ data->samples_to_discard -= samples_to_get;
+ }
+ else {
+ /* get max samples and copy */
+ if (samples_to_get > samples_to_do - samples_done)
+ samples_to_get = samples_to_do - samples_done;
+
+ memcpy(outbuf + samples_done*channels,
+ data->sample_buffer + data->samples_used*channels,
+ samples_to_get*channels * sizeof(sample));
+
+ samples_done += samples_to_get;
+ }
+
+ /* mark consumed samples */
+ data->samples_used += samples_to_get;
+ data->samples_filled -= samples_to_get;
+ }
+ else { /* decode data */
+ int iframe, status;
+ int bytes_used = 0;
+ uint8_t *buffer = data->data_buffer;
+ size_t bytes;
+
+ data->samples_used = 0;
+
+ /* ATRAC9 is made of decodable superframes with several sub-frames. AT9 config data gives
+ * superframe size, number of frames and samples (~100-200 bytes and ~256/1024 samples). */
+
+ /* read one raw block (superframe) and advance offsets */
+ bytes = read_streamfile(data->data_buffer,stream->offset, data->info.superframeSize,stream->streamfile);
+ if (bytes != data->data_buffer_size) goto decode_fail;
+
+ stream->offset += bytes;
+
+ /* decode all frames in the superframe block */
+ for (iframe = 0; iframe < data->info.framesInSuperframe; iframe++) {
+ status = Atrac9Decode(data->handle, buffer, data->sample_buffer + data->samples_filled*channels, &bytes_used);
+ if (status < 0) goto decode_fail;
+
+ buffer += bytes_used;
+ data->samples_filled += data->info.frameSamples;
+ }
+ }
+ }
+
+ return;
+
+decode_fail:
+ /* on error just put some 0 samples */
+ VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
+ memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
+}
+
+void reset_atrac9(VGMSTREAM *vgmstream) {
+ atrac9_codec_data *data = vgmstream->codec_data;
+ if (!data) return;
+
+ if (!data->handle)
+ goto fail;
+
+#if 0
+ /* reopen/flush, not needed as superframes decode separatedly and there is no carried state */
+ {
+ int status;
+ uint8_t config_data[4];
+
+ Atrac9ReleaseHandle(data->handle);
+ data->handle = Atrac9GetHandle();
+ if (!data->handle) goto fail;
+
+ put_32bitBE(config_data, data->config.config_data);
+ status = Atrac9InitDecoder(data->handle, config_data);
+ if (status < 0) goto fail;
+ }
+#endif
+
+ data->samples_used = 0;
+ data->samples_filled = 0;
+ data->samples_to_discard = data->config.encoder_delay;
+
+ return;
+
+fail:
+ return; /* decode calls should fail... */
+}
+
+void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample) {
+ atrac9_codec_data *data = vgmstream->codec_data;
+ if (!data) return;
+
+ reset_atrac9(vgmstream);
+
+ /* find closest offset to desired sample, and samples to discard after that offset to reach loop */
+ {
+ int32_t seek_sample = data->config.encoder_delay + num_sample;
+ off_t seek_offset;
+ int32_t seek_discard;
+ int32_t superframe_samples = data->info.frameSamples * data->info.framesInSuperframe;
+ size_t superframe_number, superframe_back;
+
+ superframe_number = (seek_sample / superframe_samples); /* closest */
+
+ /* decoded frames affect each other slightly, so move offset back to make PCM stable
+ * and equivalent to a full discard loop */
+ superframe_back = 1; /* 1 seems enough (even when only 1 subframe in superframe) */
+ if (superframe_back > superframe_number)
+ superframe_back = superframe_number;
+
+ seek_discard = (seek_sample % superframe_samples) + (superframe_back * superframe_samples);
+ seek_offset = (superframe_number - superframe_back) * data->info.superframeSize;
+
+ data->samples_to_discard = seek_discard; /* already includes encoder delay */
+
+ if (vgmstream->loop_ch)
+ vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + seek_offset;
+ }
+
+#if 0
+ //old full discard loop
+ {
+ data->samples_to_discard = num_sample;
+ data->samples_to_discard += data->config.encoder_delay;
+
+ /* loop offsets are set during decode; force them to stream start so discard works */
+ if (vgmstream->loop_ch)
+ vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
+ }
+#endif
+
+}
+
+void free_atrac9(atrac9_codec_data *data) {
+ if (!data) return;
+
+ if (data->handle) Atrac9ReleaseHandle(data->handle);
+ free(data->data_buffer);
+ free(data->sample_buffer);
+ free(data);
+}
+
+
+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 */
+ /* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */
+ size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */
+ 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)
+ *out_sample_rate = sample_rate_table[sample_rate_index];
+ if (out_channels)
+ *out_channels = channel_table[channels_index];
+ if (out_frame_size)
+ *out_frame_size = superframe_size;
+ if (out_samples_per_frame)
+ *out_samples_per_frame = samples_per_superframe;
+
+ return 1;
+fail:
+ return 0;
+}
+
+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
diff --git a/src/coding/ffmpeg_decoder_custom_opus.c b/src/coding/ffmpeg_decoder_custom_opus.c
index 62405577..93eb023d 100644
--- a/src/coding/ffmpeg_decoder_custom_opus.c
+++ b/src/coding/ffmpeg_decoder_custom_opus.c
@@ -1,700 +1,700 @@
-#include "coding.h"
-#include "../streamfile.h"
-#include
-
-#ifdef VGM_USE_FFMPEG
-
-/**
- * Transmogrifies custom Opus (no Ogg layer and custom packet headers) into is Xiph Opus, creating
- * valid Ogg pages with single Opus packets.
- * Uses an intermediate buffer to make full Ogg pages, since checksums are calculated with the whole page.
- *
- * Mostly as an experiment/demonstration, until some details are sorted out before adding actual libopus.
- *
- * Info, CRC and stuff:
- * https://www.opus-codec.org/docs/
- * https://tools.ietf.org/html/rfc7845.html
- * https://github.com/hcs64/ww2ogg
- */
-
-typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X } opus_type_t;
-
-static size_t make_oggs_first(uint8_t *buf, int buf_size, opus_config *cfg);
-static size_t make_oggs_page(uint8_t *buf, int buf_size, size_t data_size, int page_sequence, int granule);
-static size_t opus_get_packet_samples(const uint8_t *buf, int len);
-static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset);
-static size_t get_xopus_packet_size(int packet, STREAMFILE *streamfile);
-static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset);
-
-typedef struct {
- /* config */
- opus_type_t type;
- off_t stream_offset;
- size_t stream_size;
-
- /* state */
- off_t logical_offset; /* offset that corresponds to physical_offset */
- off_t physical_offset; /* actual file offset */
-
- size_t block_size; /* current block size */
- size_t page_size; /* current OggS page size */
- uint8_t page_buffer[0x2000]; /* OggS page (observed max is ~0xc00) */
- size_t sequence; /* OggS sequence */
- size_t samples_done; /* OggS granule */
-
- uint8_t head_buffer[0x100]; /* OggS head page */
- size_t head_size; /* OggS head page size */
-
- size_t logical_size;
-} opus_io_data;
-
-
-/* Convers custom Opus packets to Ogg Opus, so the resulting data is larger than physical data. */
-static size_t opus_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, opus_io_data* data) {
- size_t total_read = 0;
-
- /* ignore bad reads */
- if (offset < 0 || offset > data->logical_size) {
- return total_read;
- }
-
- /* previous offset: re-start as we can't map logical<>physical offsets */
- if (offset < data->logical_offset) {
- data->physical_offset = data->stream_offset;
- data->logical_offset = 0x00;
- data->page_size = 0;
- data->samples_done = 0;
- data->sequence = 2; /* appended header is 0/1 */
-
- if (offset >= data->head_size)
- data->logical_offset = data->head_size;
- }
-
- /* insert fake header */
- if (offset < data->head_size) {
- size_t bytes_consumed, to_read;
-
- bytes_consumed = offset - data->logical_offset;
- to_read = data->head_size - bytes_consumed;
- if (to_read > length)
- to_read = length;
- memcpy(dest, data->head_buffer + bytes_consumed, to_read);
-
- total_read += to_read;
- dest += to_read;
- offset += to_read;
- length -= to_read;
- data->logical_offset += to_read;
- }
-
- /* read blocks, one at a time */
- while (length > 0) {
-
- /* ignore EOF */
- if (data->logical_offset >= data->logical_size) {
- break;
- }
-
- /* process new block */
- if (data->page_size == 0) {
- size_t data_size, skip_size, oggs_size, packet_samples = 0;
-
- switch(data->type) {
- case OPUS_SWITCH: /* format seem to come from opus_test and not Nintendo-specific */
- data_size = read_u32be(data->physical_offset, streamfile);
- skip_size = 0x08; /* size + Opus state(?) */
- break;
- case OPUS_UE4_v1:
- data_size = read_u16le(data->physical_offset, streamfile);
- skip_size = 0x02;
- break;
- case OPUS_UE4_v2:
- data_size = read_u16le(data->physical_offset + 0x00, streamfile);
- packet_samples = read_u16le(data->physical_offset + 0x02, streamfile);
- skip_size = 0x02 + 0x02;
- break;
- case OPUS_EA:
- data_size = read_u16be(data->physical_offset, streamfile);
- skip_size = 0x02;
- break;
- case OPUS_X:
- data_size = get_xopus_packet_size(data->sequence - 2, streamfile);
- skip_size = 0;
- break;
- default:
- return 0;
- }
-
- oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */
-
- data->block_size = data_size + skip_size;
- data->page_size = oggs_size + data_size;
-
- if (data->page_size > sizeof(data->page_buffer)) { /* happens on bad reads/EOF too */
- VGM_LOG("OPUS: buffer can't hold OggS at %x\n", (uint32_t)data->physical_offset);
- data->page_size = 0;
- break;
- }
-
- /* create fake OggS page (full page for checksums) */
- read_streamfile(data->page_buffer+oggs_size, data->physical_offset + skip_size, data_size, streamfile); /* store page data */
- if (packet_samples == 0)
- packet_samples = opus_get_packet_samples(data->page_buffer + oggs_size, data_size);
- data->samples_done += packet_samples;
- make_oggs_page(data->page_buffer, sizeof(data->page_buffer), data_size, data->sequence, data->samples_done);
- data->sequence++;
- }
-
- /* move to next block */
- if (offset >= data->logical_offset + data->page_size) {
- data->physical_offset += data->block_size;
- data->logical_offset += data->page_size;
- data->page_size = 0;
- continue;
- }
-
- /* read data */
- {
- size_t bytes_consumed, to_read;
-
- bytes_consumed = offset - data->logical_offset;
- to_read = data->page_size - bytes_consumed;
- if (to_read > length)
- to_read = length;
- memcpy(dest, data->page_buffer + bytes_consumed, to_read);
-
- total_read += to_read;
- dest += to_read;
- offset += to_read;
- length -= to_read;
-
- if (to_read == 0) {
- break; /* error/EOF */
- }
- }
- }
-
- return total_read;
-}
-
-
-static size_t opus_io_size(STREAMFILE *streamfile, opus_io_data* data) {
- off_t physical_offset, max_physical_offset;
- size_t logical_size = 0;
- int packet = 0;
-
- if (data->logical_size)
- return data->logical_size;
-
- if (data->stream_offset + data->stream_size > get_streamfile_size(streamfile)) {
- VGM_LOG("OPUS: wrong streamsize %x + %x vs %x\n", (uint32_t)data->stream_offset, data->stream_size, get_streamfile_size(streamfile));
- return 0;
- }
-
- physical_offset = data->stream_offset;
- max_physical_offset = data->stream_offset + data->stream_size;
- logical_size = data->head_size;
-
- /* get size of the logical stream */
- while (physical_offset < max_physical_offset) {
- size_t data_size, skip_size, oggs_size;
-
- switch(data->type) {
- case OPUS_SWITCH:
- data_size = read_u32be(physical_offset, streamfile);
- skip_size = 0x08;
- break;
- case OPUS_UE4_v1:
- data_size = read_u16le(physical_offset, streamfile);
- skip_size = 0x02;
- break;
- case OPUS_UE4_v2:
- data_size = read_u16le(physical_offset, streamfile);
- skip_size = 0x02 + 0x02;
- break;
- case OPUS_EA:
- data_size = read_u16be(physical_offset, streamfile);
- skip_size = 0x02;
- break;
- case OPUS_X:
- data_size = get_xopus_packet_size(packet, streamfile);
- skip_size = 0x00;
- break;
- default:
- return 0;
- }
-
- if (data_size == 0 ) {
- VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)physical_offset);
- return 0; /* bad rip? or could 'break' and truck along */
- }
-
- oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */
-
- physical_offset += data_size + skip_size;
- logical_size += oggs_size + data_size;
- packet++;
- }
-
- /* logical size can be bigger though */
- if (physical_offset > get_streamfile_size(streamfile)) {
- VGM_LOG("OPUS: wrong size\n");
- return 0;
- }
-
- data->logical_size = logical_size;
- return data->logical_size;
-}
-
-
-/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */
-static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
- STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
- opus_io_data io_data = {0};
- size_t io_data_size = sizeof(opus_io_data);
-
- io_data.type = type;
- io_data.stream_offset = stream_offset;
- io_data.stream_size = stream_size;
- io_data.physical_offset = stream_offset;
- io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg);
- if (!io_data.head_size) goto fail;
- io_data.sequence = 2;
- io_data.logical_size = opus_io_size(streamFile, &io_data); /* force init */
-
- /* setup subfile */
- new_streamFile = open_wrap_streamfile(streamFile);
- if (!new_streamFile) goto fail;
- temp_streamFile = new_streamFile;
-
- new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, opus_io_read,opus_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;
-}
-
-/* ******************************** */
-
-/* from ww2ogg - from Tremor (lowmem) */
-static uint32_t crc_lookup[256]={
- 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005,
- 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd,
- 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75,
- 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd,
- 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5,
- 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d,
- 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95,
- 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d,
- 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072,
- 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca,
- 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02,
- 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba,
- 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692,
- 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a,
- 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2,
- 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a,
- 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb,
- 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53,
- 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b,
- 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623,
- 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b,
- 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3,
- 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b,
- 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3,
- 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c,
- 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24,
- 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec,
- 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654,
- 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c,
- 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4,
- 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c,
- 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4
-};
-
-/* from ww2ogg */
-static uint32_t get_oggs_checksum(uint8_t * data, int bytes) {
- uint32_t crc_reg=0;
- int i;
-
- for(i=0;i> 24)&0xff)^data[i]];
-
- return crc_reg;
-}
-
-/* from opus_decoder.c's opus_packet_get_samples_per_frame */
-static uint32_t opus_packet_get_samples_per_frame(const uint8_t * data, int Fs) {
- int audiosize;
- if (data[0]&0x80)
- {
- audiosize = ((data[0]>>3)&0x3);
- audiosize = (Fs<>3)&0x3);
- if (audiosize == 3)
- audiosize = Fs*60/1000;
- else
- audiosize = (Fs< buf_size) {
- VGM_LOG("OPUS: buffer can't hold OggS page\n");
- goto fail;
- }
-
- segment_count = (int)(data_size / 0xFF + 1);
- put_32bitBE(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */
- put_8bit (buf+0x04, 0); /* stream structure version, fixed */
- put_8bit (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */
- put_32bitLE(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */
- put_32bitLE(buf+0x0A, (uint32_t)(absolute_granule >> 32 & 0xFFFFFFFF)); /* upper */
- put_32bitLE(buf+0x0E, stream_serial_number); /* for interleaved multi-streams */
- put_32bitLE(buf+0x12, page_sequence);
- put_32bitLE(buf+0x16, checksum); /* 0 for now, until all data is written */
- put_8bit (buf+0x1A, segment_count); /* count of all lacing values */
-
- /* segment table: size N in "lacing values" (ex. 0x20E=0xFF+FF+10; 0xFF=0xFF+00) */
- page_done = 0x1B;
- while (lacing_done < data_size) {
- int bytes = data_size - lacing_done;
- if (bytes > 0xFF)
- bytes = 0xFF;
-
- put_8bit(buf+page_done, bytes);
- page_done++;
- lacing_done += bytes;
-
- if (lacing_done == data_size && bytes == 0xFF) {
- put_8bit(buf+page_done, 0x00);
- page_done++;
- }
- }
-
- /* data */
- //memcpy(buf+page_done, data_buf, data_size); /* data must be copied before this call */
- page_done += data_size;
-
- /* final checksum */
- checksum = get_oggs_checksum(buf, page_done);
- put_32bitLE(buf+0x16, checksum);
-
- return page_done;
-fail:
- return 0;
-}
-
-static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) {
- size_t header_size = 0x13;
- int mapping_family = 0;
-
- /* special multichannel config */
- if (cfg->channels > 2) {
- /* channel config: 0=standard (single stream mono/stereo), 1=vorbis, 255: not defined */
- mapping_family = 1;
- header_size += 0x01+0x01+cfg->channels;
- }
-
- if (header_size > buf_size) {
- VGM_LOG("OPUS: buffer can't hold header\n");
- goto fail;
- }
-
- put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
- put_32bitBE(buf+0x04, 0x48656164); /* "Head" header magic */
- put_8bit (buf+0x08, 1); /* version */
- put_8bit (buf+0x09, cfg->channels);
- put_16bitLE(buf+0x0A, cfg->skip);
- put_32bitLE(buf+0x0c, cfg->sample_rate);
- put_16bitLE(buf+0x10, 0); /* output gain */
- put_8bit (buf+0x12, mapping_family);
-
- if (mapping_family > 0) {
- int i;
-
- /* internal mono/stereo streams (N mono/stereo streams form M channels) */
- put_8bit(buf+0x13, cfg->stream_count);
- /* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
- put_8bit(buf+0x14, cfg->coupled_count);
- /* mapping bits per channel? */
- for (i = 0; i < cfg->channels; i++) {
- put_8bit(buf+0x15+i, cfg->channel_mapping[i]);
- }
- }
-
- return header_size;
-fail:
- return 0;
-}
-
-static size_t make_opus_comment(uint8_t * buf, int buf_size) {
- const char * vendor_string = "vgmstream";
- const char * user_comment_0_string = "vgmstream Opus converter";
- size_t comment_size;
- int vendor_string_length, user_comment_0_length;
-
- vendor_string_length = strlen(vendor_string);
- user_comment_0_length = strlen(user_comment_0_string);
- comment_size = 0x14 + vendor_string_length + user_comment_0_length;
-
- if (comment_size > buf_size) {
- VGM_LOG("OPUS: buffer can't hold comment\n");
- goto fail;
- }
-
- put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
- put_32bitBE(buf+0x04, 0x54616773); /* "Tags" header magic */
- put_32bitLE(buf+0x08, vendor_string_length);
- memcpy (buf+0x0c, vendor_string, vendor_string_length);
- put_32bitLE(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */
- put_32bitLE(buf+0x0c + vendor_string_length+0x04, user_comment_0_length);
- memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length);
-
- return comment_size;
-fail:
- return 0;
-}
-
-static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg) {
- int buf_done = 0;
- size_t bytes;
-
- if (buf_size < 0x100) /* approx */
- goto fail;
-
- /* make header */
- bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg);
- make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0);
- buf_done += 0x1c + bytes;
-
- /* make comment */
- bytes = make_opus_comment(buf+buf_done + 0x1c,buf_size);
- make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 1, 0);
- buf_done += 0x1c + bytes;
-
- return buf_done;
-fail:
- return 0;
-}
-
-static size_t opus_get_packet_samples(const uint8_t * buf, int len) {
- return opus_packet_get_nb_frames(buf, len) * opus_packet_get_samples_per_frame(buf, 48000);
-}
-static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset) {
- uint8_t buf[0x04]; /* at least 0x02 */
- read_streamfile(buf, offset, sizeof(buf), sf);
- return opus_get_packet_samples(buf, sizeof(buf));
-}
-
-/************************** */
-
-static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) {
- /* XOPUS has a packet size table at the beginning, get size from there.
- * Maybe should copy the table during setup to avoid IO, but all XOPUS are
- * quite small so it isn't very noticeable. */
- return read_u16le(0x20 + packet*0x02, streamfile);
-}
-
-
-static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *sf, opus_type_t type) {
- size_t num_samples = 0;
- off_t end_offset = offset + stream_size;
- int packet = 0;
-
- if (end_offset > get_streamfile_size(sf)) {
- VGM_LOG("OPUS: wrong end offset found\n");
- end_offset = get_streamfile_size(sf);
- }
-
- /* count by reading all frames */
- while (offset < end_offset) {
- size_t data_size, skip_size, packet_samples = 0;
-
- switch(type) {
- case OPUS_SWITCH:
- data_size = read_u32be(offset, sf);
- skip_size = 0x08;
- break;
- case OPUS_UE4_v1:
- data_size = read_u16le(offset, sf);
- skip_size = 0x02;
- break;
- case OPUS_UE4_v2:
- data_size = read_u16le(offset + 0x00, sf);
- packet_samples = read_u16le(offset + 0x02, sf);
- skip_size = 0x02 + 0x02;
- break;
- case OPUS_EA:
- data_size = read_u16be(offset, sf);
- skip_size = 0x02;
- break;
- case OPUS_X:
- data_size = get_xopus_packet_size(packet, sf);
- skip_size = 0x00;
- break;
- default:
- return 0;
- }
-
- if (packet_samples == 0)
- packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size);
- num_samples += packet_samples;
-
- offset += skip_size + data_size;
- packet++;
- }
-
- return num_samples;
-}
-
-size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile) {
- return custom_opus_get_samples(offset, stream_size, streamFile, OPUS_SWITCH);
-}
-
-
-static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE *sf, opus_type_t type) {
- size_t skip_size, packet_samples = 0;
-
- switch(type) {
- case OPUS_SWITCH:
- skip_size = 0x08;
- break;
- case OPUS_UE4_v1:
- skip_size = 0x02;
- break;
- case OPUS_UE4_v2:
- packet_samples = read_u16le(offset + 0x02, sf);
- skip_size = 0x02 + 0x02;
- break;
- case OPUS_EA:
- skip_size = 0x02;
- break;
- case OPUS_X:
- skip_size = 0x00;
- break;
- default:
- return 0;
- }
-
- if (packet_samples == 0)
- packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size);
- /* encoder delay seems fixed to 1/8 of samples per frame, but may need more testing */
- return packet_samples / 8;
-}
-size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
- return custom_opus_get_encoder_delay(offset, streamFile, OPUS_SWITCH);
-}
-size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
- return custom_opus_get_encoder_delay(offset, streamFile, get_ue4opus_version(streamFile, offset));
-}
-size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
- return custom_opus_get_encoder_delay(offset, streamFile, OPUS_EA);
-}
-
-
-/* ******************************************************* */
-
-/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile) */
-//#ifdef VGM_USE_FFMPEG
-
-static ffmpeg_codec_data * init_ffmpeg_custom_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config *cfg, opus_type_t type) {
- ffmpeg_codec_data * ffmpeg_data = NULL;
- STREAMFILE *temp_streamFile = NULL;
-
- temp_streamFile = setup_opus_streamfile(streamFile, cfg, start_offset, data_size, type);
- if (!temp_streamFile) goto fail;
-
- ffmpeg_data = init_ffmpeg_offset(temp_streamFile, 0x00,get_streamfile_size(temp_streamFile));
- if (!ffmpeg_data) goto fail;
-
- /* FFmpeg + libopus: skips samples, notifies skip in codecCtx->delay (not in stream->skip_samples)
- * FFmpeg + opus: *doesn't* skip, also notifies skip in codecCtx->delay, hurray (possibly fixed in recent versions)
- * FFmpeg + opus is audibly buggy with some low bitrate SSB Ultimate files too */
- //if (ffmpeg_data->skipSamples <= 0) {
- // ffmpeg_set_skip_samples(ffmpeg_data, skip);
- //}
-
- close_streamfile(temp_streamFile);
- return ffmpeg_data;
-
-fail:
- close_streamfile(temp_streamFile);
- return NULL;
-}
-static ffmpeg_codec_data * init_ffmpeg_custom_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) {
- opus_config cfg = {0};
- cfg.channels = channels;
- cfg.skip = skip;
- cfg.sample_rate = sample_rate;
-
- return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, &cfg, type);
-}
-
-ffmpeg_codec_data * init_ffmpeg_switch_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config* cfg) {
- return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, cfg, OPUS_SWITCH);
-}
-ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
- return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_SWITCH);
-}
-ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
- return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, get_ue4opus_version(streamFile, start_offset));
-}
-ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
- return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_EA);
-}
-ffmpeg_codec_data * init_ffmpeg_x_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
- return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_X);
-}
-
-static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset) {
- int read_samples, calc_samples;
-
- /* UE4OPUS v2 has packet samples right after packet size, check if data follows this */
- read_samples = read_u16le(offset + 0x02, sf);
- calc_samples = opus_get_packet_samples_sf(sf, offset + 0x04);
- if (read_samples > 0 && read_samples == calc_samples)
- return OPUS_UE4_v2;
- else
- return OPUS_UE4_v1;
-}
-
-#endif
+#include "coding.h"
+#include "../streamfile.h"
+#include
+
+#ifdef VGM_USE_FFMPEG
+
+/**
+ * Transmogrifies custom Opus (no Ogg layer and custom packet headers) into is Xiph Opus, creating
+ * valid Ogg pages with single Opus packets.
+ * Uses an intermediate buffer to make full Ogg pages, since checksums are calculated with the whole page.
+ *
+ * Mostly as an experiment/demonstration, until some details are sorted out before adding actual libopus.
+ *
+ * Info, CRC and stuff:
+ * https://www.opus-codec.org/docs/
+ * https://tools.ietf.org/html/rfc7845.html
+ * https://github.com/hcs64/ww2ogg
+ */
+
+typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X } opus_type_t;
+
+static size_t make_oggs_first(uint8_t *buf, int buf_size, opus_config *cfg);
+static size_t make_oggs_page(uint8_t *buf, int buf_size, size_t data_size, int page_sequence, int granule);
+static size_t opus_get_packet_samples(const uint8_t *buf, int len);
+static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset);
+static size_t get_xopus_packet_size(int packet, STREAMFILE *streamfile);
+static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset);
+
+typedef struct {
+ /* config */
+ opus_type_t type;
+ off_t stream_offset;
+ size_t stream_size;
+
+ /* state */
+ off_t logical_offset; /* offset that corresponds to physical_offset */
+ off_t physical_offset; /* actual file offset */
+
+ size_t block_size; /* current block size */
+ size_t page_size; /* current OggS page size */
+ uint8_t page_buffer[0x2000]; /* OggS page (observed max is ~0xc00) */
+ size_t sequence; /* OggS sequence */
+ size_t samples_done; /* OggS granule */
+
+ uint8_t head_buffer[0x100]; /* OggS head page */
+ size_t head_size; /* OggS head page size */
+
+ size_t logical_size;
+} opus_io_data;
+
+
+/* Convers custom Opus packets to Ogg Opus, so the resulting data is larger than physical data. */
+static size_t opus_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, opus_io_data* data) {
+ size_t total_read = 0;
+
+ /* ignore bad reads */
+ if (offset < 0 || offset > data->logical_size) {
+ return total_read;
+ }
+
+ /* previous offset: re-start as we can't map logical<>physical offsets */
+ if (offset < data->logical_offset) {
+ data->physical_offset = data->stream_offset;
+ data->logical_offset = 0x00;
+ data->page_size = 0;
+ data->samples_done = 0;
+ data->sequence = 2; /* appended header is 0/1 */
+
+ if (offset >= data->head_size)
+ data->logical_offset = data->head_size;
+ }
+
+ /* insert fake header */
+ if (offset < data->head_size) {
+ size_t bytes_consumed, to_read;
+
+ bytes_consumed = offset - data->logical_offset;
+ to_read = data->head_size - bytes_consumed;
+ if (to_read > length)
+ to_read = length;
+ memcpy(dest, data->head_buffer + bytes_consumed, to_read);
+
+ total_read += to_read;
+ dest += to_read;
+ offset += to_read;
+ length -= to_read;
+ data->logical_offset += to_read;
+ }
+
+ /* read blocks, one at a time */
+ while (length > 0) {
+
+ /* ignore EOF */
+ if (data->logical_offset >= data->logical_size) {
+ break;
+ }
+
+ /* process new block */
+ if (data->page_size == 0) {
+ size_t data_size, skip_size, oggs_size, packet_samples = 0;
+
+ switch(data->type) {
+ case OPUS_SWITCH: /* format seem to come from opus_test and not Nintendo-specific */
+ data_size = read_u32be(data->physical_offset, streamfile);
+ skip_size = 0x08; /* size + Opus state(?) */
+ break;
+ case OPUS_UE4_v1:
+ data_size = read_u16le(data->physical_offset, streamfile);
+ skip_size = 0x02;
+ break;
+ case OPUS_UE4_v2:
+ data_size = read_u16le(data->physical_offset + 0x00, streamfile);
+ packet_samples = read_u16le(data->physical_offset + 0x02, streamfile);
+ skip_size = 0x02 + 0x02;
+ break;
+ case OPUS_EA:
+ data_size = read_u16be(data->physical_offset, streamfile);
+ skip_size = 0x02;
+ break;
+ case OPUS_X:
+ data_size = get_xopus_packet_size(data->sequence - 2, streamfile);
+ skip_size = 0;
+ break;
+ default:
+ return 0;
+ }
+
+ oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */
+
+ data->block_size = data_size + skip_size;
+ data->page_size = oggs_size + data_size;
+
+ if (data->page_size > sizeof(data->page_buffer)) { /* happens on bad reads/EOF too */
+ VGM_LOG("OPUS: buffer can't hold OggS at %x\n", (uint32_t)data->physical_offset);
+ data->page_size = 0;
+ break;
+ }
+
+ /* create fake OggS page (full page for checksums) */
+ read_streamfile(data->page_buffer+oggs_size, data->physical_offset + skip_size, data_size, streamfile); /* store page data */
+ if (packet_samples == 0)
+ packet_samples = opus_get_packet_samples(data->page_buffer + oggs_size, data_size);
+ data->samples_done += packet_samples;
+ make_oggs_page(data->page_buffer, sizeof(data->page_buffer), data_size, data->sequence, data->samples_done);
+ data->sequence++;
+ }
+
+ /* move to next block */
+ if (offset >= data->logical_offset + data->page_size) {
+ data->physical_offset += data->block_size;
+ data->logical_offset += data->page_size;
+ data->page_size = 0;
+ continue;
+ }
+
+ /* read data */
+ {
+ size_t bytes_consumed, to_read;
+
+ bytes_consumed = offset - data->logical_offset;
+ to_read = data->page_size - bytes_consumed;
+ if (to_read > length)
+ to_read = length;
+ memcpy(dest, data->page_buffer + bytes_consumed, to_read);
+
+ total_read += to_read;
+ dest += to_read;
+ offset += to_read;
+ length -= to_read;
+
+ if (to_read == 0) {
+ break; /* error/EOF */
+ }
+ }
+ }
+
+ return total_read;
+}
+
+
+static size_t opus_io_size(STREAMFILE *streamfile, opus_io_data* data) {
+ off_t physical_offset, max_physical_offset;
+ size_t logical_size = 0;
+ int packet = 0;
+
+ if (data->logical_size)
+ return data->logical_size;
+
+ if (data->stream_offset + data->stream_size > get_streamfile_size(streamfile)) {
+ VGM_LOG("OPUS: wrong streamsize %x + %x vs %x\n", (uint32_t)data->stream_offset, data->stream_size, get_streamfile_size(streamfile));
+ return 0;
+ }
+
+ physical_offset = data->stream_offset;
+ max_physical_offset = data->stream_offset + data->stream_size;
+ logical_size = data->head_size;
+
+ /* get size of the logical stream */
+ while (physical_offset < max_physical_offset) {
+ size_t data_size, skip_size, oggs_size;
+
+ switch(data->type) {
+ case OPUS_SWITCH:
+ data_size = read_u32be(physical_offset, streamfile);
+ skip_size = 0x08;
+ break;
+ case OPUS_UE4_v1:
+ data_size = read_u16le(physical_offset, streamfile);
+ skip_size = 0x02;
+ break;
+ case OPUS_UE4_v2:
+ data_size = read_u16le(physical_offset, streamfile);
+ skip_size = 0x02 + 0x02;
+ break;
+ case OPUS_EA:
+ data_size = read_u16be(physical_offset, streamfile);
+ skip_size = 0x02;
+ break;
+ case OPUS_X:
+ data_size = get_xopus_packet_size(packet, streamfile);
+ skip_size = 0x00;
+ break;
+ default:
+ return 0;
+ }
+
+ if (data_size == 0 ) {
+ VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)physical_offset);
+ return 0; /* bad rip? or could 'break' and truck along */
+ }
+
+ oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */
+
+ physical_offset += data_size + skip_size;
+ logical_size += oggs_size + data_size;
+ packet++;
+ }
+
+ /* logical size can be bigger though */
+ if (physical_offset > get_streamfile_size(streamfile)) {
+ VGM_LOG("OPUS: wrong size\n");
+ return 0;
+ }
+
+ data->logical_size = logical_size;
+ return data->logical_size;
+}
+
+
+/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */
+static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
+ STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
+ opus_io_data io_data = {0};
+ size_t io_data_size = sizeof(opus_io_data);
+
+ io_data.type = type;
+ io_data.stream_offset = stream_offset;
+ io_data.stream_size = stream_size;
+ io_data.physical_offset = stream_offset;
+ io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg);
+ if (!io_data.head_size) goto fail;
+ io_data.sequence = 2;
+ io_data.logical_size = opus_io_size(streamFile, &io_data); /* force init */
+
+ /* setup subfile */
+ new_streamFile = open_wrap_streamfile(streamFile);
+ if (!new_streamFile) goto fail;
+ temp_streamFile = new_streamFile;
+
+ new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, opus_io_read,opus_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;
+}
+
+/* ******************************** */
+
+/* from ww2ogg - from Tremor (lowmem) */
+static uint32_t crc_lookup[256]={
+ 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005,
+ 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd,
+ 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75,
+ 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd,
+ 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5,
+ 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d,
+ 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95,
+ 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d,
+ 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072,
+ 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca,
+ 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02,
+ 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba,
+ 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692,
+ 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a,
+ 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2,
+ 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a,
+ 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb,
+ 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53,
+ 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b,
+ 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623,
+ 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b,
+ 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3,
+ 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b,
+ 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3,
+ 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c,
+ 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24,
+ 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec,
+ 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654,
+ 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c,
+ 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4,
+ 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c,
+ 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4
+};
+
+/* from ww2ogg */
+static uint32_t get_oggs_checksum(uint8_t * data, int bytes) {
+ uint32_t crc_reg=0;
+ int i;
+
+ for(i=0;i> 24)&0xff)^data[i]];
+
+ return crc_reg;
+}
+
+/* from opus_decoder.c's opus_packet_get_samples_per_frame */
+static uint32_t opus_packet_get_samples_per_frame(const uint8_t * data, int Fs) {
+ int audiosize;
+ if (data[0]&0x80)
+ {
+ audiosize = ((data[0]>>3)&0x3);
+ audiosize = (Fs<>3)&0x3);
+ if (audiosize == 3)
+ audiosize = Fs*60/1000;
+ else
+ audiosize = (Fs< buf_size) {
+ VGM_LOG("OPUS: buffer can't hold OggS page\n");
+ goto fail;
+ }
+
+ segment_count = (int)(data_size / 0xFF + 1);
+ put_32bitBE(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */
+ put_8bit (buf+0x04, 0); /* stream structure version, fixed */
+ put_8bit (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */
+ put_32bitLE(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */
+ put_32bitLE(buf+0x0A, (uint32_t)(absolute_granule >> 32 & 0xFFFFFFFF)); /* upper */
+ put_32bitLE(buf+0x0E, stream_serial_number); /* for interleaved multi-streams */
+ put_32bitLE(buf+0x12, page_sequence);
+ put_32bitLE(buf+0x16, checksum); /* 0 for now, until all data is written */
+ put_8bit (buf+0x1A, segment_count); /* count of all lacing values */
+
+ /* segment table: size N in "lacing values" (ex. 0x20E=0xFF+FF+10; 0xFF=0xFF+00) */
+ page_done = 0x1B;
+ while (lacing_done < data_size) {
+ int bytes = data_size - lacing_done;
+ if (bytes > 0xFF)
+ bytes = 0xFF;
+
+ put_8bit(buf+page_done, bytes);
+ page_done++;
+ lacing_done += bytes;
+
+ if (lacing_done == data_size && bytes == 0xFF) {
+ put_8bit(buf+page_done, 0x00);
+ page_done++;
+ }
+ }
+
+ /* data */
+ //memcpy(buf+page_done, data_buf, data_size); /* data must be copied before this call */
+ page_done += data_size;
+
+ /* final checksum */
+ checksum = get_oggs_checksum(buf, page_done);
+ put_32bitLE(buf+0x16, checksum);
+
+ return page_done;
+fail:
+ return 0;
+}
+
+static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) {
+ size_t header_size = 0x13;
+ int mapping_family = 0;
+
+ /* special multichannel config */
+ if (cfg->channels > 2) {
+ /* channel config: 0=standard (single stream mono/stereo), 1=vorbis, 255: not defined */
+ mapping_family = 1;
+ header_size += 0x01+0x01+cfg->channels;
+ }
+
+ if (header_size > buf_size) {
+ VGM_LOG("OPUS: buffer can't hold header\n");
+ goto fail;
+ }
+
+ put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
+ put_32bitBE(buf+0x04, 0x48656164); /* "Head" header magic */
+ put_8bit (buf+0x08, 1); /* version */
+ put_8bit (buf+0x09, cfg->channels);
+ put_16bitLE(buf+0x0A, cfg->skip);
+ put_32bitLE(buf+0x0c, cfg->sample_rate);
+ put_16bitLE(buf+0x10, 0); /* output gain */
+ put_8bit (buf+0x12, mapping_family);
+
+ if (mapping_family > 0) {
+ int i;
+
+ /* internal mono/stereo streams (N mono/stereo streams form M channels) */
+ put_8bit(buf+0x13, cfg->stream_count);
+ /* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
+ put_8bit(buf+0x14, cfg->coupled_count);
+ /* mapping bits per channel? */
+ for (i = 0; i < cfg->channels; i++) {
+ put_8bit(buf+0x15+i, cfg->channel_mapping[i]);
+ }
+ }
+
+ return header_size;
+fail:
+ return 0;
+}
+
+static size_t make_opus_comment(uint8_t * buf, int buf_size) {
+ const char * vendor_string = "vgmstream";
+ const char * user_comment_0_string = "vgmstream Opus converter";
+ size_t comment_size;
+ int vendor_string_length, user_comment_0_length;
+
+ vendor_string_length = strlen(vendor_string);
+ user_comment_0_length = strlen(user_comment_0_string);
+ comment_size = 0x14 + vendor_string_length + user_comment_0_length;
+
+ if (comment_size > buf_size) {
+ VGM_LOG("OPUS: buffer can't hold comment\n");
+ goto fail;
+ }
+
+ put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
+ put_32bitBE(buf+0x04, 0x54616773); /* "Tags" header magic */
+ put_32bitLE(buf+0x08, vendor_string_length);
+ memcpy (buf+0x0c, vendor_string, vendor_string_length);
+ put_32bitLE(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */
+ put_32bitLE(buf+0x0c + vendor_string_length+0x04, user_comment_0_length);
+ memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length);
+
+ return comment_size;
+fail:
+ return 0;
+}
+
+static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg) {
+ int buf_done = 0;
+ size_t bytes;
+
+ if (buf_size < 0x100) /* approx */
+ goto fail;
+
+ /* make header */
+ bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg);
+ make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0);
+ buf_done += 0x1c + bytes;
+
+ /* make comment */
+ bytes = make_opus_comment(buf+buf_done + 0x1c,buf_size);
+ make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 1, 0);
+ buf_done += 0x1c + bytes;
+
+ return buf_done;
+fail:
+ return 0;
+}
+
+static size_t opus_get_packet_samples(const uint8_t * buf, int len) {
+ return opus_packet_get_nb_frames(buf, len) * opus_packet_get_samples_per_frame(buf, 48000);
+}
+static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset) {
+ uint8_t buf[0x04]; /* at least 0x02 */
+ read_streamfile(buf, offset, sizeof(buf), sf);
+ return opus_get_packet_samples(buf, sizeof(buf));
+}
+
+/************************** */
+
+static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) {
+ /* XOPUS has a packet size table at the beginning, get size from there.
+ * Maybe should copy the table during setup to avoid IO, but all XOPUS are
+ * quite small so it isn't very noticeable. */
+ return read_u16le(0x20 + packet*0x02, streamfile);
+}
+
+
+static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *sf, opus_type_t type) {
+ size_t num_samples = 0;
+ off_t end_offset = offset + stream_size;
+ int packet = 0;
+
+ if (end_offset > get_streamfile_size(sf)) {
+ VGM_LOG("OPUS: wrong end offset found\n");
+ end_offset = get_streamfile_size(sf);
+ }
+
+ /* count by reading all frames */
+ while (offset < end_offset) {
+ size_t data_size, skip_size, packet_samples = 0;
+
+ switch(type) {
+ case OPUS_SWITCH:
+ data_size = read_u32be(offset, sf);
+ skip_size = 0x08;
+ break;
+ case OPUS_UE4_v1:
+ data_size = read_u16le(offset, sf);
+ skip_size = 0x02;
+ break;
+ case OPUS_UE4_v2:
+ data_size = read_u16le(offset + 0x00, sf);
+ packet_samples = read_u16le(offset + 0x02, sf);
+ skip_size = 0x02 + 0x02;
+ break;
+ case OPUS_EA:
+ data_size = read_u16be(offset, sf);
+ skip_size = 0x02;
+ break;
+ case OPUS_X:
+ data_size = get_xopus_packet_size(packet, sf);
+ skip_size = 0x00;
+ break;
+ default:
+ return 0;
+ }
+
+ if (packet_samples == 0)
+ packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size);
+ num_samples += packet_samples;
+
+ offset += skip_size + data_size;
+ packet++;
+ }
+
+ return num_samples;
+}
+
+size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile) {
+ return custom_opus_get_samples(offset, stream_size, streamFile, OPUS_SWITCH);
+}
+
+
+static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE *sf, opus_type_t type) {
+ size_t skip_size, packet_samples = 0;
+
+ switch(type) {
+ case OPUS_SWITCH:
+ skip_size = 0x08;
+ break;
+ case OPUS_UE4_v1:
+ skip_size = 0x02;
+ break;
+ case OPUS_UE4_v2:
+ packet_samples = read_u16le(offset + 0x02, sf);
+ skip_size = 0x02 + 0x02;
+ break;
+ case OPUS_EA:
+ skip_size = 0x02;
+ break;
+ case OPUS_X:
+ skip_size = 0x00;
+ break;
+ default:
+ return 0;
+ }
+
+ if (packet_samples == 0)
+ packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size);
+ /* encoder delay seems fixed to 1/8 of samples per frame, but may need more testing */
+ return packet_samples / 8;
+}
+size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
+ return custom_opus_get_encoder_delay(offset, streamFile, OPUS_SWITCH);
+}
+size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
+ return custom_opus_get_encoder_delay(offset, streamFile, get_ue4opus_version(streamFile, offset));
+}
+size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) {
+ return custom_opus_get_encoder_delay(offset, streamFile, OPUS_EA);
+}
+
+
+/* ******************************************************* */
+
+/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile) */
+//#ifdef VGM_USE_FFMPEG
+
+static ffmpeg_codec_data * init_ffmpeg_custom_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config *cfg, opus_type_t type) {
+ ffmpeg_codec_data * ffmpeg_data = NULL;
+ STREAMFILE *temp_streamFile = NULL;
+
+ temp_streamFile = setup_opus_streamfile(streamFile, cfg, start_offset, data_size, type);
+ if (!temp_streamFile) goto fail;
+
+ ffmpeg_data = init_ffmpeg_offset(temp_streamFile, 0x00,get_streamfile_size(temp_streamFile));
+ if (!ffmpeg_data) goto fail;
+
+ /* FFmpeg + libopus: skips samples, notifies skip in codecCtx->delay (not in stream->skip_samples)
+ * FFmpeg + opus: *doesn't* skip, also notifies skip in codecCtx->delay, hurray (possibly fixed in recent versions)
+ * FFmpeg + opus is audibly buggy with some low bitrate SSB Ultimate files too */
+ //if (ffmpeg_data->skipSamples <= 0) {
+ // ffmpeg_set_skip_samples(ffmpeg_data, skip);
+ //}
+
+ close_streamfile(temp_streamFile);
+ return ffmpeg_data;
+
+fail:
+ close_streamfile(temp_streamFile);
+ return NULL;
+}
+static ffmpeg_codec_data * init_ffmpeg_custom_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) {
+ opus_config cfg = {0};
+ cfg.channels = channels;
+ cfg.skip = skip;
+ cfg.sample_rate = sample_rate;
+
+ return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, &cfg, type);
+}
+
+ffmpeg_codec_data * init_ffmpeg_switch_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config* cfg) {
+ return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, cfg, OPUS_SWITCH);
+}
+ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
+ return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_SWITCH);
+}
+ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
+ return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, get_ue4opus_version(streamFile, start_offset));
+}
+ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
+ return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_EA);
+}
+ffmpeg_codec_data * init_ffmpeg_x_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
+ return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_X);
+}
+
+static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset) {
+ int read_samples, calc_samples;
+
+ /* UE4OPUS v2 has packet samples right after packet size, check if data follows this */
+ read_samples = read_u16le(offset + 0x02, sf);
+ calc_samples = opus_get_packet_samples_sf(sf, offset + 0x04);
+ if (read_samples > 0 && read_samples == calc_samples)
+ return OPUS_UE4_v2;
+ else
+ return OPUS_UE4_v1;
+}
+
+#endif
diff --git a/src/coding/mpeg_custom_utils_ahx.c b/src/coding/mpeg_custom_utils_ahx.c
index 8b3f76d6..1398707d 100644
--- a/src/coding/mpeg_custom_utils_ahx.c
+++ b/src/coding/mpeg_custom_utils_ahx.c
@@ -1,114 +1,114 @@
-#include "mpeg_decoder.h"
-
-#ifdef VGM_USE_MPEG
-#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414
-
-static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config);
-
-/* writes data to the buffer and moves offsets, transforming AHX frames as needed */
-int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
- mpeg_custom_stream *ms = data->streams[num_stream];
- size_t current_data_size = 0;
- size_t file_size = get_streamfile_size(stream->streamfile);
-
- /* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */
-
- /* read supposed frame size first (to minimize reads) */
- ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile);
-
- /* find actual frame size by looking for the next frame header */
- {
- uint32_t current_header = get_u32be(ms->buffer);
- int next_pos = 0x04;
-
- while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) {
- uint32_t next_header = get_u32be(ms->buffer + next_pos);
-
- if (current_header == next_header) {
- current_data_size = next_pos;
- break;
- }
-
- /* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */
- if (stream->offset + next_pos + 0x0c >= file_size) {
- current_data_size = next_pos;
- break;
- }
-
- next_pos++;
- }
- }
-
- if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) {
- VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size);
- goto fail;
- }
-
- /* 0-fill up to expected size to keep mpg123 happy */
- memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size);
- ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE;
-
-
- /* decrypt if needed */
- switch(data->config.encryption) {
- case 0x00: break;
- case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break;
- default:
- VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption);
- break; /* garbled frame */
- }
-
- /* update offsets */
- stream->offset += current_data_size;
- if (stream->offset + 0x0c >= file_size)
- stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */
-
- return 1;
-fail:
- return 0;
-}
-
-/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */
-static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) {
- int i, index, encrypted_bits;
- uint32_t value;
- uint16_t current_key;
-
- /* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */
-
- /* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */
- /* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */
-
- /* read 2b from a bitstream offset to decrypt, and use it as an index to get the key.
- * AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */
- value = get_u32be(buffer + 0x0d);
- index = (value >> (32-3-2)) & 0x03;
- switch(index) {
- case 0: current_key = 0; break;
- case 1: current_key = config->cri_key1; break;
- case 2: current_key = config->cri_key2; break;
- case 3: current_key = config->cri_key3; break;
- default: goto fail;
- }
-
- /* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */
- encrypted_bits = config->cri_type == 0x10 ? 16 : 6;
-
- /* decrypt next bitstream 2b pairs, up to 16b (max key size):
- * - read 2b from bitstream (from higher to lower)
- * - read 2b from key (from lower to higher)
- * - XOR them to decrypt */
- for (i = 0; i < encrypted_bits; i+=2) {
- uint32_t xor_2b = (current_key >> i) & 0x03;
- value ^= ((xor_2b << (32-3-2-2)) >> i);
- }
-
- /* write output */
- put_32bitBE(buffer + 0x0d, value);
-
- return 1;
-fail:
- return 0;
-}
-
-#endif
+#include "mpeg_decoder.h"
+
+#ifdef VGM_USE_MPEG
+#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414
+
+static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config);
+
+/* writes data to the buffer and moves offsets, transforming AHX frames as needed */
+int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
+ mpeg_custom_stream *ms = data->streams[num_stream];
+ size_t current_data_size = 0;
+ size_t file_size = get_streamfile_size(stream->streamfile);
+
+ /* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */
+
+ /* read supposed frame size first (to minimize reads) */
+ ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile);
+
+ /* find actual frame size by looking for the next frame header */
+ {
+ uint32_t current_header = get_u32be(ms->buffer);
+ int next_pos = 0x04;
+
+ while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) {
+ uint32_t next_header = get_u32be(ms->buffer + next_pos);
+
+ if (current_header == next_header) {
+ current_data_size = next_pos;
+ break;
+ }
+
+ /* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */
+ if (stream->offset + next_pos + 0x0c >= file_size) {
+ current_data_size = next_pos;
+ break;
+ }
+
+ next_pos++;
+ }
+ }
+
+ if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) {
+ VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size);
+ goto fail;
+ }
+
+ /* 0-fill up to expected size to keep mpg123 happy */
+ memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size);
+ ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE;
+
+
+ /* decrypt if needed */
+ switch(data->config.encryption) {
+ case 0x00: break;
+ case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break;
+ default:
+ VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption);
+ break; /* garbled frame */
+ }
+
+ /* update offsets */
+ stream->offset += current_data_size;
+ if (stream->offset + 0x0c >= file_size)
+ stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */
+
+ return 1;
+fail:
+ return 0;
+}
+
+/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */
+static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) {
+ int i, index, encrypted_bits;
+ uint32_t value;
+ uint16_t current_key;
+
+ /* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */
+
+ /* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */
+ /* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */
+
+ /* read 2b from a bitstream offset to decrypt, and use it as an index to get the key.
+ * AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */
+ value = get_u32be(buffer + 0x0d);
+ index = (value >> (32-3-2)) & 0x03;
+ switch(index) {
+ case 0: current_key = 0; break;
+ case 1: current_key = config->cri_key1; break;
+ case 2: current_key = config->cri_key2; break;
+ case 3: current_key = config->cri_key3; break;
+ default: goto fail;
+ }
+
+ /* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */
+ encrypted_bits = config->cri_type == 0x10 ? 16 : 6;
+
+ /* decrypt next bitstream 2b pairs, up to 16b (max key size):
+ * - read 2b from bitstream (from higher to lower)
+ * - read 2b from key (from lower to higher)
+ * - XOR them to decrypt */
+ for (i = 0; i < encrypted_bits; i+=2) {
+ uint32_t xor_2b = (current_key >> i) & 0x03;
+ value ^= ((xor_2b << (32-3-2-2)) >> i);
+ }
+
+ /* write output */
+ put_32bitBE(buffer + 0x0d, value);
+
+ return 1;
+fail:
+ return 0;
+}
+
+#endif
diff --git a/src/coding/mpeg_custom_utils_eamp3.c b/src/coding/mpeg_custom_utils_eamp3.c
index e1080fc7..5a3b8728 100644
--- a/src/coding/mpeg_custom_utils_eamp3.c
+++ b/src/coding/mpeg_custom_utils_eamp3.c
@@ -1,176 +1,176 @@
-#include "mpeg_decoder.h"
-
-#ifdef VGM_USE_MPEG
-
-/* parsed info from a single EAMP3 frame */
-typedef struct {
- uint32_t extended_flag;
- uint32_t stereo_flag; /* assumed */
- uint32_t unknown_flag; /* unused? */
- uint32_t frame_size; /* full size including headers and pcm block */
- uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */
-
- uint32_t pre_size; /* size of the header part */
- uint32_t mpeg_size; /* size of the MPEG part */
- uint32_t pcm_size; /* size of the PCM block */
-} eamp3_frame_info;
-
-static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf);
-static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf);
-static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
-
-/* init config and validate */
-int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
- mpeg_frame_info info;
- uint16_t frame_header;
- size_t header_size;
-
-
- /* test unknown stuff */
- frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile);
- if (frame_header & 0x2000) {
- VGM_LOG("EAMP3: found unknown bit 13\n");
- goto fail;
- }
- if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) {
- VGM_LOG("EAMP3: found big PCM block\n");
- goto fail;
- }
-
- /* get frame info at offset */
- header_size = (frame_header & 0x8000) ? 0x06 : 0x02;
- if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info))
- goto fail;
- switch(info.layer) {
- case 1: *coding_type = coding_MPEG_layer1; break;
- case 2: *coding_type = coding_MPEG_layer2; break;
- case 3: *coding_type = coding_MPEG_layer3; break;
- default: goto fail;
- }
- data->channels_per_frame = info.channels;
- data->samples_per_frame = info.frame_samples;
- data->bitrate_per_frame = info.bit_rate;
- data->sample_rate_per_frame = info.sample_rate;
-
-
- return 1;
-fail:
- return 0;
-}
-
-/* reads custom frame header + MPEG data + (optional) PCM block */
-int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
- mpeg_custom_stream *ms = data->streams[num_stream];
- eamp3_frame_info eaf;
- int ok;
-
-
- if (!eamp3_skip_data(stream, data, num_stream, 1))
- goto fail;
-
- ok = eamp3_parse_frame(stream, data, &eaf);
- if (!ok) goto fail;
-
- ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
-
- ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf);
- if (!ok) goto fail;
-
- stream->offset += eaf.frame_size;
-
- if (!eamp3_skip_data(stream, data, num_stream, 0))
- goto fail;
-
- return 1;
-fail:
- return 0;
-}
-
-
-static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) {
- uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile);
-
- eaf->extended_flag = (current_header & 0x8000);
- eaf->stereo_flag = (current_header & 0x4000);
- eaf->unknown_flag = (current_header & 0x2000);
- eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */
- eaf->pcm_number = 0;
- if (eaf->extended_flag > 0) {
- eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile);
- eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame;
- eaf->pre_size = 0x06;
- eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size;
- if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) {
- VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset);
- goto fail;
- }
- }
- else {
- eaf->pcm_size = 0;
- eaf->pre_size = 0x02;
- eaf->mpeg_size = eaf->frame_size - eaf->pre_size;
- }
-
- return 1;
-fail:
- return 0;
-}
-
-/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */
-static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) {
- mpeg_custom_stream *ms = data->streams[num_stream];
- size_t bytes_filled;
- int i;
-
-
- bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame;
- if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
- VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
- goto fail;
- }
-
-
- if (eaf->pcm_number) {
-
- /* read + write PCM block samples (always LE) */
- for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
- off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
- int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
- put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
- }
- ms->samples_filled += eaf->pcm_number;
-
- /* modify decoded samples */
- {
- size_t decode_to_discard = eaf->pcm_number; //todo guessed
- ms->decode_to_discard += decode_to_discard;
- }
- }
-
- return 1;
-fail:
- return 0;
-}
-
-/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */
-static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
- int ok, i;
- eamp3_frame_info eaf;
- int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
-
-
- for (i = 0; i < skips; i++) {
- ok = eamp3_parse_frame(stream, data, &eaf);
- if (!ok) goto fail;
-
- //;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset);
- stream->offset += eaf.frame_size;
- }
- //;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset);
-
- return 1;
-fail:
- return 0;
-}
-
-#endif
+#include "mpeg_decoder.h"
+
+#ifdef VGM_USE_MPEG
+
+/* parsed info from a single EAMP3 frame */
+typedef struct {
+ uint32_t extended_flag;
+ uint32_t stereo_flag; /* assumed */
+ uint32_t unknown_flag; /* unused? */
+ uint32_t frame_size; /* full size including headers and pcm block */
+ uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */
+
+ uint32_t pre_size; /* size of the header part */
+ uint32_t mpeg_size; /* size of the MPEG part */
+ uint32_t pcm_size; /* size of the PCM block */
+} eamp3_frame_info;
+
+static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf);
+static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf);
+static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
+
+/* init config and validate */
+int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
+ mpeg_frame_info info;
+ uint16_t frame_header;
+ size_t header_size;
+
+
+ /* test unknown stuff */
+ frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile);
+ if (frame_header & 0x2000) {
+ VGM_LOG("EAMP3: found unknown bit 13\n");
+ goto fail;
+ }
+ if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) {
+ VGM_LOG("EAMP3: found big PCM block\n");
+ goto fail;
+ }
+
+ /* get frame info at offset */
+ header_size = (frame_header & 0x8000) ? 0x06 : 0x02;
+ if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info))
+ goto fail;
+ switch(info.layer) {
+ case 1: *coding_type = coding_MPEG_layer1; break;
+ case 2: *coding_type = coding_MPEG_layer2; break;
+ case 3: *coding_type = coding_MPEG_layer3; break;
+ default: goto fail;
+ }
+ data->channels_per_frame = info.channels;
+ data->samples_per_frame = info.frame_samples;
+ data->bitrate_per_frame = info.bit_rate;
+ data->sample_rate_per_frame = info.sample_rate;
+
+
+ return 1;
+fail:
+ return 0;
+}
+
+/* reads custom frame header + MPEG data + (optional) PCM block */
+int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
+ mpeg_custom_stream *ms = data->streams[num_stream];
+ eamp3_frame_info eaf;
+ int ok;
+
+
+ if (!eamp3_skip_data(stream, data, num_stream, 1))
+ goto fail;
+
+ ok = eamp3_parse_frame(stream, data, &eaf);
+ if (!ok) goto fail;
+
+ ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
+
+ ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf);
+ if (!ok) goto fail;
+
+ stream->offset += eaf.frame_size;
+
+ if (!eamp3_skip_data(stream, data, num_stream, 0))
+ goto fail;
+
+ return 1;
+fail:
+ return 0;
+}
+
+
+static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) {
+ uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile);
+
+ eaf->extended_flag = (current_header & 0x8000);
+ eaf->stereo_flag = (current_header & 0x4000);
+ eaf->unknown_flag = (current_header & 0x2000);
+ eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */
+ eaf->pcm_number = 0;
+ if (eaf->extended_flag > 0) {
+ eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile);
+ eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame;
+ eaf->pre_size = 0x06;
+ eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size;
+ if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) {
+ VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset);
+ goto fail;
+ }
+ }
+ else {
+ eaf->pcm_size = 0;
+ eaf->pre_size = 0x02;
+ eaf->mpeg_size = eaf->frame_size - eaf->pre_size;
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */
+static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) {
+ mpeg_custom_stream *ms = data->streams[num_stream];
+ size_t bytes_filled;
+ int i;
+
+
+ bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame;
+ if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
+ VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
+ goto fail;
+ }
+
+
+ if (eaf->pcm_number) {
+
+ /* read + write PCM block samples (always LE) */
+ for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
+ off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
+ int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
+ put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
+ }
+ ms->samples_filled += eaf->pcm_number;
+
+ /* modify decoded samples */
+ {
+ size_t decode_to_discard = eaf->pcm_number; //todo guessed
+ ms->decode_to_discard += decode_to_discard;
+ }
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */
+static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
+ int ok, i;
+ eamp3_frame_info eaf;
+ int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
+
+
+ for (i = 0; i < skips; i++) {
+ ok = eamp3_parse_frame(stream, data, &eaf);
+ if (!ok) goto fail;
+
+ //;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset);
+ stream->offset += eaf.frame_size;
+ }
+ //;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset);
+
+ return 1;
+fail:
+ return 0;
+}
+
+#endif
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 4ccb9527..7288b942 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -1,1649 +1,1649 @@
-
-
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hpp;hxx;hm;inl;inc;xsd
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {07c49d19-cbf2-4e9f-b45d-d8a087f7926b}
-
-
- {4d311953-abb4-4e92-adcb-bcad28c13312}
-
-
- {f3158382-6a38-463f-aaba-1a1618a18868}
-
-
- {4367a8fa-d09f-489d-86b3-ab8fa707e5b2}
-
-
- {8c0f574c-7490-47c3-aa37-ba7d30ad8e72}
-
-
- {e273dc50-87c5-4d9b-8c71-773a369f00c5}
-
-
- {54878363-4abe-444e-aa77-4757e09edb4d}
-
-
- {f05d3f7e-37a3-4373-b1bf-b952af37854c}
-
-
- {e729b7b3-e13c-4cf9-9ded-428c209b6f62}
-
-
- {48DB0DEF-3694-40E0-9FF6-8A736E6C3A62}
-
-
- {78A32BD9-0DB4-4164-A7E6-41506B78392E}
-
-
- {20824073-8817-41CF-8A21-D54294A56050}
-
-
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- layout\Header Files
-
-
- ext_libs\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- coding\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
- meta\Header Files
-
-
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- layout\Source Files
-
-
- coding\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- layout\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- coding\Source Files
-
-
- meta\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- Source Files
-
-
- ext_libs\Source Files
-
-
- meta\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- coding\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
- meta\Source Files
-
-
+
+
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {07c49d19-cbf2-4e9f-b45d-d8a087f7926b}
+
+
+ {4d311953-abb4-4e92-adcb-bcad28c13312}
+
+
+ {f3158382-6a38-463f-aaba-1a1618a18868}
+
+
+ {4367a8fa-d09f-489d-86b3-ab8fa707e5b2}
+
+
+ {8c0f574c-7490-47c3-aa37-ba7d30ad8e72}
+
+
+ {e273dc50-87c5-4d9b-8c71-773a369f00c5}
+
+
+ {54878363-4abe-444e-aa77-4757e09edb4d}
+
+
+ {f05d3f7e-37a3-4373-b1bf-b952af37854c}
+
+
+ {e729b7b3-e13c-4cf9-9ded-428c209b6f62}
+
+
+ {48DB0DEF-3694-40E0-9FF6-8A736E6C3A62}
+
+
+ {78A32BD9-0DB4-4164-A7E6-41506B78392E}
+
+
+ {20824073-8817-41CF-8A21-D54294A56050}
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ layout\Header Files
+
+
+ ext_libs\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ coding\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+ meta\Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ layout\Source Files
+
+
+ coding\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ layout\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ coding\Source Files
+
+
+ meta\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ Source Files
+
+
+ ext_libs\Source Files
+
+
+ meta\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ coding\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
\ No newline at end of file
diff --git a/src/meta/xwb_xsb.h b/src/meta/xwb_xsb.h
index b254a7fe..6965bea6 100644
--- a/src/meta/xwb_xsb.h
+++ b/src/meta/xwb_xsb.h
@@ -1,843 +1,843 @@
-#ifndef _XWB_XSB_H_
-#define _XWB_XSB_H_
-#include "meta.h"
-
-#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */
-#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */
-#define XSB_XACT1_2_MAX 11 /* other Xbox games */
-#define XSB_XACT2_MAX 41 /* other PC/X360 games */
-
-
-typedef struct {
- /* config */
- int selected_stream;
- int selected_wavebank;
-
- /* state */
- int big_endian;
- int version;
-
- int simple_cues_count;
- off_t simple_cues_offset;
- int complex_cues_count;
- off_t complex_cues_offset;
- int sounds_count;
- off_t sounds_offset;
- int wavebanks_count;
- off_t wavebanks_offset;
- int wavebanks_name_size;
- off_t nameoffsets_offset;
- int cue_names_size;
- off_t cue_names_offset;
-
- /* output */
- int parse_done;
- char name[STREAM_NAME_SIZE];
- int name_len;
-
-} xsb_header;
-
-
-static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
- if (xsb->parse_done)
- return;
-
- /* multiple names may correspond to a stream (ex. Blue Dragon), so we concat all */
- if (xsb->selected_stream == stream_index &&
- (xsb->selected_wavebank == wavebank_index || wavebank_index == -1 || wavebank_index == 255)) {
- char name[STREAM_NAME_SIZE];
- size_t name_size;
-
- name_size = read_string(name,sizeof(name), name_offset,sf); /* null-terminated */
-
- if (xsb->name_len) {
- const char *cat = "; ";
- int cat_len = 2;
-
- if (xsb->name_len + cat_len + name_size + 1 < STREAM_NAME_SIZE) {
- strcat(xsb->name + xsb->name_len, cat);
- strcat(xsb->name + xsb->name_len, name);
- }
- }
- else {
- strcpy(xsb->name, name);
- }
- xsb->name_len += name_size;
- //xsb->parse_done = 1; /* uncomment this to stop reading after first name */
- //;VGM_LOG("XSB: parse found stream=%i, wavebank=%i, name_offset=%lx\n", stream_index, wavebank_index, name_offset);
- }
-}
-
-/* old XACT1 is a bit different and much of it is unknown but this seems to work.
- * - after header is the simple(?) cues table then complex(?) cues table
- * - simple cues point to complex cues by index
- * - complex cues may have stream/wavebank or point again to a sound(?) with the stream/wavebank
- */
-static int parse_xsb_cues_old(xsb_header * xsb, STREAMFILE *sf) {
- int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
- int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
-
- uint8_t flags, subflags;
- int cue_index, stream_index, wavebank_index = 0;
- off_t offset, name_offset, cue_offset, sound_offset;
- int i;
- size_t simple_entry, complex_entry;
-
-
- if (xsb->version <= XSB_XACT1_1_MAX) {
- simple_entry = 0x10;
- complex_entry = 0x14;
- }
- else if (xsb->version <= XSB_XACT1_2_MAX) {
- simple_entry = 0x14;
- complex_entry = 0x14;
- }
- else {
- VGM_LOG("XSB: unknown old format for version %x\n", xsb->version);
- goto fail;
- }
-
-
- offset = xsb->sounds_offset;
- for (i = 0; i < xsb->simple_cues_count; i++) {
-
- /* *** simple sound *** */
- /* 00(2): flags? */
- cue_index = read_s16(offset + 0x02,sf);
- name_offset = read_s32(offset + 0x04,sf);
- /* 06-14: unknown */
-
- //;VGM_LOG("XSB old simple at %lx: cue=%i, name_offset=%lx\n", offset, cue_index, name_offset);
- offset += simple_entry;
-
- /* when cue_index is -1 @0x08 points to some offset (random sound type?) [ex. ATV 3 Lawless (Xbox)] */
- if (cue_index < 0 && cue_index > xsb->complex_cues_count) {
- VGM_LOG("XSB old: ignored cue index %i\n", cue_index);
- continue;
- }
-
-
- /* *** complex sound *** */
- cue_offset = xsb->sounds_offset + xsb->simple_cues_count*simple_entry + cue_index*complex_entry;
-
- /* most fields looks like more flags and optional offsets depending of flags */
- flags = read_u8(cue_offset + 0x0b,sf);
-
- if (flags & 8) { /* simple */
- stream_index = read_s16(cue_offset + 0x00,sf);
- wavebank_index = read_s16(cue_offset + 0x02,sf);
- }
- //else if (flags & 4) { /* unsure */
- // VGM_LOG("XSB old complex at %lx: unknown flags=%x\n", cue_offset, flags);
- // continue;
- //}
- else { /* complex (flags none/1/2) */
- sound_offset = read_s32(cue_offset + 0x00,sf);
-
- /* *** jump entry *** */
- /* 00(1): flags? */
- sound_offset = read_s32(sound_offset + 0x01,sf) & 0x00FFFFFF; /* 24b */
-
- /* *** sound entry *** */
- subflags = read_u8(sound_offset + 0x00,sf);
- if (subflags == 0x00) { /* 0x0c entry */
- stream_index = read_s16(sound_offset + 0x08,sf);
- wavebank_index = read_s16(sound_offset + 0x0a,sf);
- }
- else if (subflags == 0x0a) { /* 0x20 entry */
- stream_index = read_s16(sound_offset + 0x1c,sf);
- wavebank_index = read_s16(sound_offset + 0x1e,sf);
- }
- else {
- VGM_LOG("XSB old sound at %lx: unknown subflags=%x\n", sound_offset, subflags);
- continue;
- }
- }
-
- //;VGM_LOG("XSB old complex at %lx: flags=%x, stream=%i, wavebank=%i, name_offset=%lx\n", cue_offset, flags, stream_index, wavebank_index, name_offset);
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- }
-
- return 1;
-fail:
- return 0;
-}
-
-static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
- uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le;
- int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
-
- uint32_t flags;
- int stream_index, wavebank_index;
- int i, t, track_count, event_count;
-
-
- event_count = read_s8(offset + 0x00,sf);
-
- //;VGM_LOG("XSB clip at %lx\n", offset);
- offset += 0x01;
-
- for (i = 0; i < event_count; i++) {
- flags = read_u32(offset + 0x00,sf);
- /* 04(2): random offset */
-
- //;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset);
- offset += 0x06;
-
- switch (flags & 0x1F) { /* event ID */
-
- case 0x01: /* playwave event */
- /* 00(1): unknown */
- /* 01(1): flags */
- stream_index = read_s16(offset + 0x02,sf);
- wavebank_index = read_s8 (offset + 0x04,sf);
- /* 05(1): loop count */
- /* 06(2): pan angle */
- /* 08(2): pan arc */
-
- //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
- offset += 0x0a;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- break;
-
- case 0x03: /* playwave event */
- /* 00(1): unknown */
- /* 01(1): flags */
- /* 02(1): loop count */
- /* 03(2): pan angle */
- /* 05(2): pan arc */
- track_count = read_s16(offset + 0x07,sf);
- /* 09(1): flags? */
- /* 0a(5): unknown */
-
- //;VGM_LOG("XSB clip event 3 at %lx\n", offset);
- offset += 0x0F;
-
- for (t = 0; t < track_count; t++) {
- stream_index = read_s16(offset + 0x00,sf);
- wavebank_index = read_s8 (offset + 0x02,sf);
- /* 03(1): min weight */
- /* 04(1): min weight */
-
- //;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index);
- offset += 0x05;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- }
- break;
-
- case 0x04: /* playwave event */
- /* 00(1): unknown */
- /* 01(1): flags */
- stream_index = read_s16(offset + 0x02,sf);
- wavebank_index = read_s8 (offset + 0x04,sf);
- /* 05(1): loop count */
- /* 06(2): pan angle */
- /* 08(2): pan arc */
- /* 0a(2): min pitch */
- /* 0c(2): max pitch */
- /* 0e(1): min volume */
- /* 0f(1): max volume */
- /* 10(4): min frequency */
- /* 14(4): max frequency */
- /* 18(1): min Q */
- /* 19(1): max Q */
- /* 1a(1): unknown */
- /* 1b(1): variation flags */
-
- //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
- offset += 0x1c;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- break;
-
- case 0x06: /* playwave event */
- /* 00(1): unknown */
- /* 01(1): flags */
- /* 02(1): loop count */
- /* 03(2): pan angle */
- /* 05(2): pan arc */
- /* 07(2): min pitch */
- /* 09(2): max pitch */
- /* 0b(1): min volume */
- /* 0c(1): max volume */
- /* 0d(4): min frequency */
- /* 11(4): max frequency */
- /* 15(1): min Q */
- /* 16(1): max Q */
- /* 17(1): unknown */
- /* 18(1): variation flags */
- track_count = read_s16(offset + 0x19,sf);
- /* 1a(1): flags 2 */
- /* 1b(5): unknown 2 */
-
- //;VGM_LOG("XSB clip event 6 at %lx\n", offset);
- offset += 0x20;
-
- for (t = 0; t < track_count; t++) {
- stream_index = read_s16(offset + 0x00,sf);
- wavebank_index = read_s8 (offset + 0x02,sf);
- /* 03(1): min weight */
- /* 04(1): min weight */
-
- //;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset);
- offset += 0x05;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- }
- break;
-
- case 0x08: /* volume event */
- /* 00(2): unknown */
- /* 02(1): flags */
- /* 03(4): decibels */
- /* 07(9): unknown */
-
- //;VGM_LOG("XSB clip event 8 at %lx\n", offset);
- offset += 0x10;
- break;
-
- case 0x00: /* stop event */
- case 0x07: /* pitch event */
- case 0x09: /* marker event */
- case 0x11: /* volume repeat event */
- default:
- VGM_LOG("XSB event: unknown type %x at %lx\n", flags, offset);
- goto fail;
- }
- }
-
- return 1;
-fail:
- return 0;
-}
-
-static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
- int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
- int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
-
- uint8_t flags;
- int stream_index = 0, wavebank_index = 0;
- int i, clip_count = 0;
-
-
- flags = read_u8 (offset + 0x00,sf);
- /* 0x01(2): category */
- /* 0x03(1): decibels */
- /* 0x04(2): pitch */
- /* 0x06(1): priority */
- /* 0x07(2): entry size? "filter stuff"? */
-
- //;VGM_LOG("XSB sound at %lx\n", offset);
- offset += 0x09;
-
- if (flags & 0x01) { /* complex sound */
- clip_count = read_u8 (offset + 0x00,sf);
-
- //;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count);
- offset += 0x01;
- }
- else {
- stream_index = read_s16(offset + 0x00,sf);
- wavebank_index = read_s8(offset + 0x02,sf);
-
- //;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
- offset += 0x03;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- }
-
- if (flags & 0x0E) { /* has RPCs */
- size_t rpc_size = read_s16(offset + 0x00,sf);
- /* 0x02(2): preset count */
- /* 0x04(4*count): RPC indexes */
- /* (presets per flag 2/4/8 flag) */
- offset += rpc_size;
- }
-
- if (flags & 0x10) { /* has DSPs */
- size_t dsp_size = read_s16(offset + 0x00,sf);
- /* follows RPC format? */
- offset += dsp_size;
- }
-
- if (flags & 0x01) { /* complex sound clips */
- off_t clip_offset;
- for (i = 0; i < clip_count; i++) {
- /* 00(1): decibels */
- clip_offset = read_s32(offset + 0x01,sf);
- /* 05(2): filter config */
- /* 07(2): filter frequency */
-
- //;VGM_LOG("XSB sound clip %i at %lx\n", i, offset);
- offset += 0x09;
-
- parse_xsb_clip(xsb, clip_offset, name_offset,sf);
- if (xsb->parse_done) return 1;
- }
- }
-
- return 0;
-}
-
-static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
- int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
- uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le;
- int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
-
- uint16_t flags;
- int stream_index, wavebank_index;
- int i, variation_count;
-
-
- variation_count = read_s16(offset + 0x00,sf);
- flags = read_u16(offset + 0x02,sf);
-
- //;VGM_LOG("XSB variation at %lx\n", offset);
- offset += 0x04;
-
- for (i = 0; i < variation_count; i++) {
- off_t sound_offset;
-
- switch ((flags >> 3) & 0x7) {
- case 0: /* wave */
- stream_index = read_s16(offset + 0x00,sf);
- wavebank_index = read_s8(offset + 0x02,sf);
- /* 03(1): weight min */
- /* 04(1): weight max */
-
- //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
- offset += 0x05;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- break;
-
- case 1: /* sound */
- sound_offset = read_s32(offset + 0x00,sf);
- /* 04(1): weight min */
- /* 05(1): weight max */
-
- //;VGM_LOG("XSB variation: type 1\n");
- offset += 0x06;
-
- parse_xsb_sound(xsb, sound_offset, name_offset,sf);
- if (xsb->parse_done) return 1;
- break;
-
- case 3: /* sound */
- sound_offset = read_s32(offset + 0x00,sf);
- /* 04(4): weight min */
- /* 08(4): weight max */
- /* 0c(4): flags */
-
- //;VGM_LOG("XSB variation: type 3\n");
- offset += 0x10;
-
- parse_xsb_sound(xsb, sound_offset, name_offset,sf);
- if (xsb->parse_done) return 1;
- break;
-
- case 4: /* compact wave */
- stream_index = read_s16(offset + 0x00,sf);
- wavebank_index = read_s8(offset + 0x02,sf);
-
- //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
- offset += 0x03;
-
- xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
- if (xsb->parse_done) return 1;
- break;
-
- default:
- VGM_LOG("XSB variation: unknown type %x at %lx\n", flags, offset);
- goto fail;
- }
- }
-
- /* 00(1): unknown */
- /* 01(2): unknown */
- /* 03(1): unknown */
- offset += 0x04;
-
-
- return 1;
-fail:
- return 0;
-}
-
-
-static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
- int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
-
- uint8_t flags;
- off_t offset, name_offset, sound_offset;
- off_t names_offset = xsb->nameoffsets_offset;
- int i;
-
-
- offset = xsb->simple_cues_offset;
- for (i = 0; i < xsb->simple_cues_count; i++) {
- /* 00(1): flags */
- sound_offset = read_s32(offset + 0x01,sf);
-
- //;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset);
- offset += 0x05;
-
- name_offset = read_s32(names_offset + 0x00,sf);
- /* 04(2): unknown (-1) */
- names_offset += 0x06;
-
- parse_xsb_sound(xsb, sound_offset, name_offset,sf);
- if (xsb->parse_done) break;
- }
-
- offset = xsb->complex_cues_offset;
- for (i = 0; i < xsb->complex_cues_count; i++) {
- flags = read_u8(offset + 0x00,sf);
- sound_offset = read_s32(offset + 0x01,sf);
- /* 05(4): unknown (sound) / transition table offset (variation) */
- /* 09(1): instance limit */
- /* 0a(2): fade in sec */
- /* 0c(2): fade out sec */
- /* 0e(1): instance flags */
-
- //;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset);
- offset += 0x0f;
-
- name_offset = read_s32(names_offset + 0x00,sf);
- /* 04(2): unknown (-1) */
- names_offset += 0x06;
-
- if (flags & (1<<2))
- parse_xsb_sound(xsb, sound_offset, name_offset,sf);
- else
- parse_xsb_variation(xsb, sound_offset, name_offset,sf);
- if (xsb->parse_done) break;
- }
-
- return 1;
-}
-
-/**
- * XWB "wave bank" has streams (channels, loops, etc), while XSB "sound bank" has cues/sounds
- * (volume, pitch, name, etc). Each XSB cue/sound has a variable size and somewhere inside may
- * be the stream/wavebank index (some cues are just commands, though).
- *
- * We want to find a cue pointing to our current wave to get the name. Cues may point to
- * multiple streams out of order, and a stream can be used by multiple cues:
- * - name 1: simple cue 1 > simple sound 2 > xwb stream 3
- * - name 2: simple cue 2 > complex sound 1 > clip 1/2/3 > xwb streams 4/5/5
- * - name 3: complex cue 1 > simple sound 3 > xwb stream 0
- * - name 4: complex cue 2 > variation > xwb stream 1
- * - name 5: complex cue 3 > variation > simple sound 4/5 > xwb streams 0/1
- * - etc
- * Names are optional (almost always included though), and some cues don't have a name
- * even if others do. Some offsets are optional, usually signaled by -1/wrong values.
- *
- * More info:
- * - https://wiki.multimedia.cx/index.php/XACT
- * - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/
- * - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract
- */
-static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
- int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
- int16_t (*read_s16)(off_t,STREAMFILE*) = NULL;
-
-
- /* check header */
- if ((read_u32be(0x00,sf) != 0x5344424B) && /* "SDBK" (LE) */
- (read_u32be(0x00,sf) != 0x4B424453)) /* "KBDS" (BE) */
- goto fail;
-
- xsb->big_endian = (read_u32be(0x00,sf) == 0x4B424453); /* "KBDS" */
- read_s32 = xsb->big_endian ? read_s32be : read_s32le;
- read_s16 = xsb->big_endian ? read_s16be : read_s16le;
-
-
- /* parse sound bank header */
- xsb->version = read_s16(0x04,sf); /* tool version */
- if (xsb->version <= XSB_XACT1_0_MAX) {
- /* 06(2): crc */
- xsb->wavebanks_offset = read_s32(0x08,sf);
- /* 0c(4): unknown1 offset (entry: 0x04) */
- /* 10(4): unknown2 offset */
- /* 14(2): element count? */
- /* 16(2): empty? */
- /* 18(2): empty? */
- xsb->complex_cues_count = read_s16(0x1a,sf);
- xsb->simple_cues_count = read_s16(0x1c,sf);
- xsb->wavebanks_count = read_s16(0x1e,sf);
- /* 20(10): xsb name */
-
- xsb->sounds_offset = 0x30;
- xsb->wavebanks_name_size = 0x10;
- }
- else if (xsb->version <= XSB_XACT1_1_MAX) {
- /* 06(2): crc */
- xsb->wavebanks_offset = read_s32(0x08,sf);
- /* 0c(4): unknown1 offset (entry: 0x04) */
- /* 10(4): unknown2 offset */
- /* 14(4): unknown3 offset */
- /* 18(2): empty? */
- /* 1a(2): element count? */
- xsb->complex_cues_count = read_s16(0x1c,sf);
- xsb->simple_cues_count = read_s16(0x1e,sf);
- /* 20(2): unknown count? (related to unknown2?) */
- xsb->wavebanks_count = read_s16(0x22,sf);
- /* 24(10): xsb name */
-
- xsb->sounds_offset = 0x34;
- xsb->wavebanks_name_size = 0x10;
- }
- else if (xsb->version <= XSB_XACT1_2_MAX) {
- /* 06(2): crc */
- xsb->wavebanks_offset = read_s32(0x08,sf);
- /* 0c(4): unknown1 offset (entry: 0x14) */
- /* 10(4): unknown2 offset (entry: variable) */
- /* 14(4): unknown3 offset */
- /* 18(2): empty? */
- /* 1a(2): element count? */
- xsb->complex_cues_count = read_s16(0x1c,sf);
- xsb->simple_cues_count = read_s16(0x1e,sf);
- /* 20(2): unknown count? (related to unknown2?) */
- xsb->wavebanks_count = read_s16(0x22,sf);
- /* 24(4): null? */
- /* 28(10): xsb name */
-
- xsb->sounds_offset = 0x38;
- xsb->wavebanks_name_size = 0x10;
- }
- else if (xsb->version <= XSB_XACT2_MAX) {
- /* 06(2): crc */
- /* 08(1): platform? (3=X360) */
- xsb->simple_cues_count = read_s16(0x09,sf);
- xsb->complex_cues_count = read_s16(0x0B,sf);
- xsb->wavebanks_count = read_s8 (0x11,sf);
- xsb->sounds_count = read_s16(0x12,sf);
- /* 14(2): unknown */
- xsb->cue_names_size = read_s32(0x16,sf);
- xsb->simple_cues_offset = read_s32(0x1a,sf);
- xsb->complex_cues_offset = read_s32(0x1e,sf);
- xsb->cue_names_offset = read_s32(0x22,sf);
- /* 26(4): unknown */
- /* 2a(4): unknown */
- /* 2e(4): unknown */
- xsb->wavebanks_offset = read_s32(0x32,sf);
- /* 36(4): cue name hash table offset? */
- xsb->nameoffsets_offset = read_s32(0x3a,sf);
- xsb->sounds_offset = read_s32(0x3e,sf);
- /* 42(4): unknown */
- /* 46(4): unknown */
- /* 4a(64): xsb name */
-
- xsb->wavebanks_name_size = 0x40;
- }
- else {
- /* 06(2): format version */
- /* 08(2): crc (fcs16 checksum of all following data) */
- /* 0a(4): last modified low */
- /* 0e(4): last modified high */
- /* 12(1): platform? (1=PC, 3=X360) */
- xsb->simple_cues_count = read_s16(0x13,sf);
- xsb->complex_cues_count = read_s16(0x15,sf);
- /* 17(2): unknown count? */
- /* 19(2): element count? (often simple+complex cues, but may be more) */
- xsb->wavebanks_count = read_s8 (0x1b,sf);
- xsb->sounds_count = read_s16(0x1c,sf);
- xsb->cue_names_size = read_s32(0x1e,sf);
- xsb->simple_cues_offset = read_s32(0x22,sf);
- xsb->complex_cues_offset = read_s32(0x26,sf);
- xsb->cue_names_offset = read_s32(0x2a,sf);
- /* 0x2E(4): unknown offset */
- /* 0x32(4): variation tables offset */
- /* 0x36(4): unknown offset */
- xsb->wavebanks_offset = read_s32(0x3a,sf);
- /* 0x3E(4): cue name hash table offset (16b each) */
- xsb->nameoffsets_offset = read_s32(0x42,sf);
- xsb->sounds_offset = read_s32(0x46,sf);
- /* 4a(64): xsb name */
-
- xsb->wavebanks_name_size = 0x40;
- }
-
- //;VGM_LOG("XSB header: version=%i\n", xsb->version);
- //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n",
- // xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
- //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n",
- // xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
- //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n",
- // xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
-
- if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) {
- VGM_LOG("XSB: no names found\n");
- return 1;
- }
-
-
- /* find target wavebank */
- if (xsb->wavebanks_count) {
- char xsb_wavebank_name[64+1];
- int i;
- off_t offset;
-
- xsb->selected_wavebank = -1;
-
- offset = xsb->wavebanks_offset;
- for (i = 0; i < xsb->wavebanks_count; i++) {
- read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset,sf);
- //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name);
- if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) {
- //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name);
- xsb->selected_wavebank = i;
- }
-
- offset += xsb->wavebanks_name_size;
- }
-
- //;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank);
- if (xsb->selected_wavebank == -1) {
- VGM_LOG("XSB: current wavebank not found, selecting first\n");
- xsb->selected_wavebank = 0; //todo goto fail?
- }
- }
-
-
- /* find cue pointing to stream */
- if (xsb->version <= XSB_XACT1_2_MAX) {
- parse_xsb_cues_old(xsb, sf);
- }
- else {
- parse_xsb_cues_new(xsb, sf);
- }
-
- return 1;
-fail:
- return 0;
-}
-
-static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) {
- STREAMFILE *streamXsb = NULL;
- /* .xwb to .xsb name conversion, since often they don't match */
- static const char *const filename_pairs[][2] = {
- {"MUSIC.xwb","Everything.xsb"}, /* Unreal Championship (Xbox) */
- {"Music.xwb","Sound Bank.xsb"}, /* Stardew Valley (Vita) */
- {"Ambiences_intro.xwb","Ambiences.xsb"}, /* Arx Fatalis (Xbox) */
- {"Wave*.xwb","Sound*.xsb"}, /* XNA/MonoGame games? */
- {"*MusicBank.xwb","*SoundBank.xsb"}, /* NFL Fever 2004 (Xbox) */
- {"*_xwb","*_xsb"}, /* Ikaruga (PC) */
- {"WB_*","SB_*"}, /* Ikaruga (X360) */
- {"*StreamBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */
- {"*WaveBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */
- {"StreamBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */
- {"WaveBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */
- {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */
- {"*.xwb","*.xsb"}, /* default */
- };
- int i;
- int pair_count = (sizeof(filename_pairs) / sizeof(filename_pairs[0]));
- char target_filename[PATH_LIMIT];
- char temp_filename[PATH_LIMIT];
- int target_len;
-
- /* try names in external .xsb, using a bunch of possible name pairs */
- get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT);
- target_len = strlen(target_filename);
-
- for (i = 0; i < pair_count; i++) {
- const char *xwb_match = filename_pairs[i][0];
- const char *xsb_match = filename_pairs[i][1];
- size_t xwb_len = strlen(xwb_match);
- size_t xsb_len = strlen(xsb_match);
- int match_pos1 = -1, match_pos2 = -1, xwb_pos = -1 , xsb_pos = -1, new_len = 0;
- const char * teststr;
-
-
- //;VGM_LOG("XSB: pair1 '%s'='%s' << '%s' \n", xwb_match, xsb_match, target_filename);
- if (target_len < xwb_len)
- continue;
-
- /* ghetto string wildcard replace, ex:
- * - target filename = "start1_wildcard_end1", xwb_match = "start1_*_end1", xsb_match = "start2_*_end2"
- * > check xwb's "start_" starts in target_filename (from 0..xwb_pos), set match_pos1
- * > check xwb's "_end" ends in target_filename (from xwb_pos+1..end), set match_pos2
- * > copy xsb's "start2_" (from 0..xsb_pos)
- * > copy target "wildcard" (from 0..xsb_pos)
- * > copy xsb's "end" (from xsb_pos+1..end)
- * > final target_filename is "start2_wildcard_end2"
- * (skips start/end if wildcard is at start/end)
- */
-
- teststr = strchr(xwb_match, '*');
- if (teststr)
- xwb_pos = (intptr_t)teststr - (intptr_t)xwb_match;
- teststr = strchr(xsb_match, '*');
- if (teststr)
- xsb_pos = (intptr_t)teststr - (intptr_t)xsb_match;
-
- match_pos1 = 0;
- match_pos2 = target_len;
- temp_filename[0] = '\0';
-
- if (xwb_pos < 0) { /* no wildcard, check exact match */
- if (target_len != xwb_len || strncmp(target_filename, xwb_match, xwb_len))
- continue;
- strcpy(target_filename, xsb_match);
- }
-
- if (xwb_pos > 0) { /* wildcard after start, check starts_with */
- int starts_len = xwb_pos;
- if (strncmp(target_filename + 0, xwb_match + 0, xwb_pos) != 0)
- continue;
- match_pos1 = 0 + starts_len;
- }
-
- if (xwb_pos >= 0 && xwb_pos + 1 < xwb_len) { /* wildcard before end, check ends_with */
- int ends_len = xwb_len - (xwb_pos+1);
- if (strncmp(target_filename + target_len - ends_len, xwb_match + xwb_len - ends_len, ends_len) != 0)
- continue;
- match_pos2 = target_len - ends_len;
- }
-
- if (match_pos1 >= 0 && match_pos2 > match_pos1) { /* save match */
- int match_len = match_pos2 - match_pos1;
- strncpy(temp_filename, target_filename + match_pos1, match_len);
- temp_filename[match_len] = '\0';
- }
-
- if (xsb_pos > 0) { /* copy xsb start */
- strncpy(target_filename + 0, xsb_match, (xsb_pos));
- new_len += (xsb_pos);
- target_filename[new_len] = '\0';
- }
-
- if (xsb_pos >= 0){ /* copy xsb match */
- strncpy(target_filename + new_len, temp_filename, (match_pos2 - match_pos1));
- new_len += (match_pos2 - match_pos1);
- target_filename[new_len] = '\0';
- }
-
- if (xsb_pos >= 0 && xsb_pos + 1 < xsb_len) { /* copy xsb end */
- strncpy(target_filename + new_len, xsb_match + (xsb_pos+1), (xsb_len - (xsb_pos+1)));
- new_len += (xsb_len - (xsb_pos+1));
- target_filename[new_len] = '\0';
- }
-
- //;VGM_LOG("XSB: pair2 '%s'='%s' >> '%s'\n", xwb_match, xsb_match, target_filename);
- streamXsb = open_streamfile_by_filename(streamXwb, target_filename);
- if (streamXsb) return streamXsb;
-
- get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); /* reset for next loop */
- }
-
- return NULL;
-}
-
-#endif /* _XWB_XSB_H_ */
+#ifndef _XWB_XSB_H_
+#define _XWB_XSB_H_
+#include "meta.h"
+
+#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */
+#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */
+#define XSB_XACT1_2_MAX 11 /* other Xbox games */
+#define XSB_XACT2_MAX 41 /* other PC/X360 games */
+
+
+typedef struct {
+ /* config */
+ int selected_stream;
+ int selected_wavebank;
+
+ /* state */
+ int big_endian;
+ int version;
+
+ int simple_cues_count;
+ off_t simple_cues_offset;
+ int complex_cues_count;
+ off_t complex_cues_offset;
+ int sounds_count;
+ off_t sounds_offset;
+ int wavebanks_count;
+ off_t wavebanks_offset;
+ int wavebanks_name_size;
+ off_t nameoffsets_offset;
+ int cue_names_size;
+ off_t cue_names_offset;
+
+ /* output */
+ int parse_done;
+ char name[STREAM_NAME_SIZE];
+ int name_len;
+
+} xsb_header;
+
+
+static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
+ if (xsb->parse_done)
+ return;
+
+ /* multiple names may correspond to a stream (ex. Blue Dragon), so we concat all */
+ if (xsb->selected_stream == stream_index &&
+ (xsb->selected_wavebank == wavebank_index || wavebank_index == -1 || wavebank_index == 255)) {
+ char name[STREAM_NAME_SIZE];
+ size_t name_size;
+
+ name_size = read_string(name,sizeof(name), name_offset,sf); /* null-terminated */
+
+ if (xsb->name_len) {
+ const char *cat = "; ";
+ int cat_len = 2;
+
+ if (xsb->name_len + cat_len + name_size + 1 < STREAM_NAME_SIZE) {
+ strcat(xsb->name + xsb->name_len, cat);
+ strcat(xsb->name + xsb->name_len, name);
+ }
+ }
+ else {
+ strcpy(xsb->name, name);
+ }
+ xsb->name_len += name_size;
+ //xsb->parse_done = 1; /* uncomment this to stop reading after first name */
+ //;VGM_LOG("XSB: parse found stream=%i, wavebank=%i, name_offset=%lx\n", stream_index, wavebank_index, name_offset);
+ }
+}
+
+/* old XACT1 is a bit different and much of it is unknown but this seems to work.
+ * - after header is the simple(?) cues table then complex(?) cues table
+ * - simple cues point to complex cues by index
+ * - complex cues may have stream/wavebank or point again to a sound(?) with the stream/wavebank
+ */
+static int parse_xsb_cues_old(xsb_header * xsb, STREAMFILE *sf) {
+ int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
+ int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
+
+ uint8_t flags, subflags;
+ int cue_index, stream_index, wavebank_index = 0;
+ off_t offset, name_offset, cue_offset, sound_offset;
+ int i;
+ size_t simple_entry, complex_entry;
+
+
+ if (xsb->version <= XSB_XACT1_1_MAX) {
+ simple_entry = 0x10;
+ complex_entry = 0x14;
+ }
+ else if (xsb->version <= XSB_XACT1_2_MAX) {
+ simple_entry = 0x14;
+ complex_entry = 0x14;
+ }
+ else {
+ VGM_LOG("XSB: unknown old format for version %x\n", xsb->version);
+ goto fail;
+ }
+
+
+ offset = xsb->sounds_offset;
+ for (i = 0; i < xsb->simple_cues_count; i++) {
+
+ /* *** simple sound *** */
+ /* 00(2): flags? */
+ cue_index = read_s16(offset + 0x02,sf);
+ name_offset = read_s32(offset + 0x04,sf);
+ /* 06-14: unknown */
+
+ //;VGM_LOG("XSB old simple at %lx: cue=%i, name_offset=%lx\n", offset, cue_index, name_offset);
+ offset += simple_entry;
+
+ /* when cue_index is -1 @0x08 points to some offset (random sound type?) [ex. ATV 3 Lawless (Xbox)] */
+ if (cue_index < 0 && cue_index > xsb->complex_cues_count) {
+ VGM_LOG("XSB old: ignored cue index %i\n", cue_index);
+ continue;
+ }
+
+
+ /* *** complex sound *** */
+ cue_offset = xsb->sounds_offset + xsb->simple_cues_count*simple_entry + cue_index*complex_entry;
+
+ /* most fields looks like more flags and optional offsets depending of flags */
+ flags = read_u8(cue_offset + 0x0b,sf);
+
+ if (flags & 8) { /* simple */
+ stream_index = read_s16(cue_offset + 0x00,sf);
+ wavebank_index = read_s16(cue_offset + 0x02,sf);
+ }
+ //else if (flags & 4) { /* unsure */
+ // VGM_LOG("XSB old complex at %lx: unknown flags=%x\n", cue_offset, flags);
+ // continue;
+ //}
+ else { /* complex (flags none/1/2) */
+ sound_offset = read_s32(cue_offset + 0x00,sf);
+
+ /* *** jump entry *** */
+ /* 00(1): flags? */
+ sound_offset = read_s32(sound_offset + 0x01,sf) & 0x00FFFFFF; /* 24b */
+
+ /* *** sound entry *** */
+ subflags = read_u8(sound_offset + 0x00,sf);
+ if (subflags == 0x00) { /* 0x0c entry */
+ stream_index = read_s16(sound_offset + 0x08,sf);
+ wavebank_index = read_s16(sound_offset + 0x0a,sf);
+ }
+ else if (subflags == 0x0a) { /* 0x20 entry */
+ stream_index = read_s16(sound_offset + 0x1c,sf);
+ wavebank_index = read_s16(sound_offset + 0x1e,sf);
+ }
+ else {
+ VGM_LOG("XSB old sound at %lx: unknown subflags=%x\n", sound_offset, subflags);
+ continue;
+ }
+ }
+
+ //;VGM_LOG("XSB old complex at %lx: flags=%x, stream=%i, wavebank=%i, name_offset=%lx\n", cue_offset, flags, stream_index, wavebank_index, name_offset);
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
+ uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le;
+ int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
+
+ uint32_t flags;
+ int stream_index, wavebank_index;
+ int i, t, track_count, event_count;
+
+
+ event_count = read_s8(offset + 0x00,sf);
+
+ //;VGM_LOG("XSB clip at %lx\n", offset);
+ offset += 0x01;
+
+ for (i = 0; i < event_count; i++) {
+ flags = read_u32(offset + 0x00,sf);
+ /* 04(2): random offset */
+
+ //;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset);
+ offset += 0x06;
+
+ switch (flags & 0x1F) { /* event ID */
+
+ case 0x01: /* playwave event */
+ /* 00(1): unknown */
+ /* 01(1): flags */
+ stream_index = read_s16(offset + 0x02,sf);
+ wavebank_index = read_s8 (offset + 0x04,sf);
+ /* 05(1): loop count */
+ /* 06(2): pan angle */
+ /* 08(2): pan arc */
+
+ //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
+ offset += 0x0a;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ break;
+
+ case 0x03: /* playwave event */
+ /* 00(1): unknown */
+ /* 01(1): flags */
+ /* 02(1): loop count */
+ /* 03(2): pan angle */
+ /* 05(2): pan arc */
+ track_count = read_s16(offset + 0x07,sf);
+ /* 09(1): flags? */
+ /* 0a(5): unknown */
+
+ //;VGM_LOG("XSB clip event 3 at %lx\n", offset);
+ offset += 0x0F;
+
+ for (t = 0; t < track_count; t++) {
+ stream_index = read_s16(offset + 0x00,sf);
+ wavebank_index = read_s8 (offset + 0x02,sf);
+ /* 03(1): min weight */
+ /* 04(1): min weight */
+
+ //;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index);
+ offset += 0x05;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ }
+ break;
+
+ case 0x04: /* playwave event */
+ /* 00(1): unknown */
+ /* 01(1): flags */
+ stream_index = read_s16(offset + 0x02,sf);
+ wavebank_index = read_s8 (offset + 0x04,sf);
+ /* 05(1): loop count */
+ /* 06(2): pan angle */
+ /* 08(2): pan arc */
+ /* 0a(2): min pitch */
+ /* 0c(2): max pitch */
+ /* 0e(1): min volume */
+ /* 0f(1): max volume */
+ /* 10(4): min frequency */
+ /* 14(4): max frequency */
+ /* 18(1): min Q */
+ /* 19(1): max Q */
+ /* 1a(1): unknown */
+ /* 1b(1): variation flags */
+
+ //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
+ offset += 0x1c;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ break;
+
+ case 0x06: /* playwave event */
+ /* 00(1): unknown */
+ /* 01(1): flags */
+ /* 02(1): loop count */
+ /* 03(2): pan angle */
+ /* 05(2): pan arc */
+ /* 07(2): min pitch */
+ /* 09(2): max pitch */
+ /* 0b(1): min volume */
+ /* 0c(1): max volume */
+ /* 0d(4): min frequency */
+ /* 11(4): max frequency */
+ /* 15(1): min Q */
+ /* 16(1): max Q */
+ /* 17(1): unknown */
+ /* 18(1): variation flags */
+ track_count = read_s16(offset + 0x19,sf);
+ /* 1a(1): flags 2 */
+ /* 1b(5): unknown 2 */
+
+ //;VGM_LOG("XSB clip event 6 at %lx\n", offset);
+ offset += 0x20;
+
+ for (t = 0; t < track_count; t++) {
+ stream_index = read_s16(offset + 0x00,sf);
+ wavebank_index = read_s8 (offset + 0x02,sf);
+ /* 03(1): min weight */
+ /* 04(1): min weight */
+
+ //;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset);
+ offset += 0x05;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ }
+ break;
+
+ case 0x08: /* volume event */
+ /* 00(2): unknown */
+ /* 02(1): flags */
+ /* 03(4): decibels */
+ /* 07(9): unknown */
+
+ //;VGM_LOG("XSB clip event 8 at %lx\n", offset);
+ offset += 0x10;
+ break;
+
+ case 0x00: /* stop event */
+ case 0x07: /* pitch event */
+ case 0x09: /* marker event */
+ case 0x11: /* volume repeat event */
+ default:
+ VGM_LOG("XSB event: unknown type %x at %lx\n", flags, offset);
+ goto fail;
+ }
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
+ int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
+ int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
+
+ uint8_t flags;
+ int stream_index = 0, wavebank_index = 0;
+ int i, clip_count = 0;
+
+
+ flags = read_u8 (offset + 0x00,sf);
+ /* 0x01(2): category */
+ /* 0x03(1): decibels */
+ /* 0x04(2): pitch */
+ /* 0x06(1): priority */
+ /* 0x07(2): entry size? "filter stuff"? */
+
+ //;VGM_LOG("XSB sound at %lx\n", offset);
+ offset += 0x09;
+
+ if (flags & 0x01) { /* complex sound */
+ clip_count = read_u8 (offset + 0x00,sf);
+
+ //;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count);
+ offset += 0x01;
+ }
+ else {
+ stream_index = read_s16(offset + 0x00,sf);
+ wavebank_index = read_s8(offset + 0x02,sf);
+
+ //;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
+ offset += 0x03;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ }
+
+ if (flags & 0x0E) { /* has RPCs */
+ size_t rpc_size = read_s16(offset + 0x00,sf);
+ /* 0x02(2): preset count */
+ /* 0x04(4*count): RPC indexes */
+ /* (presets per flag 2/4/8 flag) */
+ offset += rpc_size;
+ }
+
+ if (flags & 0x10) { /* has DSPs */
+ size_t dsp_size = read_s16(offset + 0x00,sf);
+ /* follows RPC format? */
+ offset += dsp_size;
+ }
+
+ if (flags & 0x01) { /* complex sound clips */
+ off_t clip_offset;
+ for (i = 0; i < clip_count; i++) {
+ /* 00(1): decibels */
+ clip_offset = read_s32(offset + 0x01,sf);
+ /* 05(2): filter config */
+ /* 07(2): filter frequency */
+
+ //;VGM_LOG("XSB sound clip %i at %lx\n", i, offset);
+ offset += 0x09;
+
+ parse_xsb_clip(xsb, clip_offset, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
+ int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
+ uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le;
+ int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
+
+ uint16_t flags;
+ int stream_index, wavebank_index;
+ int i, variation_count;
+
+
+ variation_count = read_s16(offset + 0x00,sf);
+ flags = read_u16(offset + 0x02,sf);
+
+ //;VGM_LOG("XSB variation at %lx\n", offset);
+ offset += 0x04;
+
+ for (i = 0; i < variation_count; i++) {
+ off_t sound_offset;
+
+ switch ((flags >> 3) & 0x7) {
+ case 0: /* wave */
+ stream_index = read_s16(offset + 0x00,sf);
+ wavebank_index = read_s8(offset + 0x02,sf);
+ /* 03(1): weight min */
+ /* 04(1): weight max */
+
+ //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
+ offset += 0x05;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ break;
+
+ case 1: /* sound */
+ sound_offset = read_s32(offset + 0x00,sf);
+ /* 04(1): weight min */
+ /* 05(1): weight max */
+
+ //;VGM_LOG("XSB variation: type 1\n");
+ offset += 0x06;
+
+ parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ break;
+
+ case 3: /* sound */
+ sound_offset = read_s32(offset + 0x00,sf);
+ /* 04(4): weight min */
+ /* 08(4): weight max */
+ /* 0c(4): flags */
+
+ //;VGM_LOG("XSB variation: type 3\n");
+ offset += 0x10;
+
+ parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ break;
+
+ case 4: /* compact wave */
+ stream_index = read_s16(offset + 0x00,sf);
+ wavebank_index = read_s8(offset + 0x02,sf);
+
+ //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
+ offset += 0x03;
+
+ xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+ if (xsb->parse_done) return 1;
+ break;
+
+ default:
+ VGM_LOG("XSB variation: unknown type %x at %lx\n", flags, offset);
+ goto fail;
+ }
+ }
+
+ /* 00(1): unknown */
+ /* 01(2): unknown */
+ /* 03(1): unknown */
+ offset += 0x04;
+
+
+ return 1;
+fail:
+ return 0;
+}
+
+
+static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
+ int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
+
+ uint8_t flags;
+ off_t offset, name_offset, sound_offset;
+ off_t names_offset = xsb->nameoffsets_offset;
+ int i;
+
+
+ offset = xsb->simple_cues_offset;
+ for (i = 0; i < xsb->simple_cues_count; i++) {
+ /* 00(1): flags */
+ sound_offset = read_s32(offset + 0x01,sf);
+
+ //;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset);
+ offset += 0x05;
+
+ name_offset = read_s32(names_offset + 0x00,sf);
+ /* 04(2): unknown (-1) */
+ names_offset += 0x06;
+
+ parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+ if (xsb->parse_done) break;
+ }
+
+ offset = xsb->complex_cues_offset;
+ for (i = 0; i < xsb->complex_cues_count; i++) {
+ flags = read_u8(offset + 0x00,sf);
+ sound_offset = read_s32(offset + 0x01,sf);
+ /* 05(4): unknown (sound) / transition table offset (variation) */
+ /* 09(1): instance limit */
+ /* 0a(2): fade in sec */
+ /* 0c(2): fade out sec */
+ /* 0e(1): instance flags */
+
+ //;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset);
+ offset += 0x0f;
+
+ name_offset = read_s32(names_offset + 0x00,sf);
+ /* 04(2): unknown (-1) */
+ names_offset += 0x06;
+
+ if (flags & (1<<2))
+ parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+ else
+ parse_xsb_variation(xsb, sound_offset, name_offset,sf);
+ if (xsb->parse_done) break;
+ }
+
+ return 1;
+}
+
+/**
+ * XWB "wave bank" has streams (channels, loops, etc), while XSB "sound bank" has cues/sounds
+ * (volume, pitch, name, etc). Each XSB cue/sound has a variable size and somewhere inside may
+ * be the stream/wavebank index (some cues are just commands, though).
+ *
+ * We want to find a cue pointing to our current wave to get the name. Cues may point to
+ * multiple streams out of order, and a stream can be used by multiple cues:
+ * - name 1: simple cue 1 > simple sound 2 > xwb stream 3
+ * - name 2: simple cue 2 > complex sound 1 > clip 1/2/3 > xwb streams 4/5/5
+ * - name 3: complex cue 1 > simple sound 3 > xwb stream 0
+ * - name 4: complex cue 2 > variation > xwb stream 1
+ * - name 5: complex cue 3 > variation > simple sound 4/5 > xwb streams 0/1
+ * - etc
+ * Names are optional (almost always included though), and some cues don't have a name
+ * even if others do. Some offsets are optional, usually signaled by -1/wrong values.
+ *
+ * More info:
+ * - https://wiki.multimedia.cx/index.php/XACT
+ * - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/
+ * - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract
+ */
+static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
+ int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
+ int16_t (*read_s16)(off_t,STREAMFILE*) = NULL;
+
+
+ /* check header */
+ if ((read_u32be(0x00,sf) != 0x5344424B) && /* "SDBK" (LE) */
+ (read_u32be(0x00,sf) != 0x4B424453)) /* "KBDS" (BE) */
+ goto fail;
+
+ xsb->big_endian = (read_u32be(0x00,sf) == 0x4B424453); /* "KBDS" */
+ read_s32 = xsb->big_endian ? read_s32be : read_s32le;
+ read_s16 = xsb->big_endian ? read_s16be : read_s16le;
+
+
+ /* parse sound bank header */
+ xsb->version = read_s16(0x04,sf); /* tool version */
+ if (xsb->version <= XSB_XACT1_0_MAX) {
+ /* 06(2): crc */
+ xsb->wavebanks_offset = read_s32(0x08,sf);
+ /* 0c(4): unknown1 offset (entry: 0x04) */
+ /* 10(4): unknown2 offset */
+ /* 14(2): element count? */
+ /* 16(2): empty? */
+ /* 18(2): empty? */
+ xsb->complex_cues_count = read_s16(0x1a,sf);
+ xsb->simple_cues_count = read_s16(0x1c,sf);
+ xsb->wavebanks_count = read_s16(0x1e,sf);
+ /* 20(10): xsb name */
+
+ xsb->sounds_offset = 0x30;
+ xsb->wavebanks_name_size = 0x10;
+ }
+ else if (xsb->version <= XSB_XACT1_1_MAX) {
+ /* 06(2): crc */
+ xsb->wavebanks_offset = read_s32(0x08,sf);
+ /* 0c(4): unknown1 offset (entry: 0x04) */
+ /* 10(4): unknown2 offset */
+ /* 14(4): unknown3 offset */
+ /* 18(2): empty? */
+ /* 1a(2): element count? */
+ xsb->complex_cues_count = read_s16(0x1c,sf);
+ xsb->simple_cues_count = read_s16(0x1e,sf);
+ /* 20(2): unknown count? (related to unknown2?) */
+ xsb->wavebanks_count = read_s16(0x22,sf);
+ /* 24(10): xsb name */
+
+ xsb->sounds_offset = 0x34;
+ xsb->wavebanks_name_size = 0x10;
+ }
+ else if (xsb->version <= XSB_XACT1_2_MAX) {
+ /* 06(2): crc */
+ xsb->wavebanks_offset = read_s32(0x08,sf);
+ /* 0c(4): unknown1 offset (entry: 0x14) */
+ /* 10(4): unknown2 offset (entry: variable) */
+ /* 14(4): unknown3 offset */
+ /* 18(2): empty? */
+ /* 1a(2): element count? */
+ xsb->complex_cues_count = read_s16(0x1c,sf);
+ xsb->simple_cues_count = read_s16(0x1e,sf);
+ /* 20(2): unknown count? (related to unknown2?) */
+ xsb->wavebanks_count = read_s16(0x22,sf);
+ /* 24(4): null? */
+ /* 28(10): xsb name */
+
+ xsb->sounds_offset = 0x38;
+ xsb->wavebanks_name_size = 0x10;
+ }
+ else if (xsb->version <= XSB_XACT2_MAX) {
+ /* 06(2): crc */
+ /* 08(1): platform? (3=X360) */
+ xsb->simple_cues_count = read_s16(0x09,sf);
+ xsb->complex_cues_count = read_s16(0x0B,sf);
+ xsb->wavebanks_count = read_s8 (0x11,sf);
+ xsb->sounds_count = read_s16(0x12,sf);
+ /* 14(2): unknown */
+ xsb->cue_names_size = read_s32(0x16,sf);
+ xsb->simple_cues_offset = read_s32(0x1a,sf);
+ xsb->complex_cues_offset = read_s32(0x1e,sf);
+ xsb->cue_names_offset = read_s32(0x22,sf);
+ /* 26(4): unknown */
+ /* 2a(4): unknown */
+ /* 2e(4): unknown */
+ xsb->wavebanks_offset = read_s32(0x32,sf);
+ /* 36(4): cue name hash table offset? */
+ xsb->nameoffsets_offset = read_s32(0x3a,sf);
+ xsb->sounds_offset = read_s32(0x3e,sf);
+ /* 42(4): unknown */
+ /* 46(4): unknown */
+ /* 4a(64): xsb name */
+
+ xsb->wavebanks_name_size = 0x40;
+ }
+ else {
+ /* 06(2): format version */
+ /* 08(2): crc (fcs16 checksum of all following data) */
+ /* 0a(4): last modified low */
+ /* 0e(4): last modified high */
+ /* 12(1): platform? (1=PC, 3=X360) */
+ xsb->simple_cues_count = read_s16(0x13,sf);
+ xsb->complex_cues_count = read_s16(0x15,sf);
+ /* 17(2): unknown count? */
+ /* 19(2): element count? (often simple+complex cues, but may be more) */
+ xsb->wavebanks_count = read_s8 (0x1b,sf);
+ xsb->sounds_count = read_s16(0x1c,sf);
+ xsb->cue_names_size = read_s32(0x1e,sf);
+ xsb->simple_cues_offset = read_s32(0x22,sf);
+ xsb->complex_cues_offset = read_s32(0x26,sf);
+ xsb->cue_names_offset = read_s32(0x2a,sf);
+ /* 0x2E(4): unknown offset */
+ /* 0x32(4): variation tables offset */
+ /* 0x36(4): unknown offset */
+ xsb->wavebanks_offset = read_s32(0x3a,sf);
+ /* 0x3E(4): cue name hash table offset (16b each) */
+ xsb->nameoffsets_offset = read_s32(0x42,sf);
+ xsb->sounds_offset = read_s32(0x46,sf);
+ /* 4a(64): xsb name */
+
+ xsb->wavebanks_name_size = 0x40;
+ }
+
+ //;VGM_LOG("XSB header: version=%i\n", xsb->version);
+ //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n",
+ // xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
+ //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n",
+ // xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
+ //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n",
+ // xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
+
+ if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) {
+ VGM_LOG("XSB: no names found\n");
+ return 1;
+ }
+
+
+ /* find target wavebank */
+ if (xsb->wavebanks_count) {
+ char xsb_wavebank_name[64+1];
+ int i;
+ off_t offset;
+
+ xsb->selected_wavebank = -1;
+
+ offset = xsb->wavebanks_offset;
+ for (i = 0; i < xsb->wavebanks_count; i++) {
+ read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset,sf);
+ //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name);
+ if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) {
+ //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name);
+ xsb->selected_wavebank = i;
+ }
+
+ offset += xsb->wavebanks_name_size;
+ }
+
+ //;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank);
+ if (xsb->selected_wavebank == -1) {
+ VGM_LOG("XSB: current wavebank not found, selecting first\n");
+ xsb->selected_wavebank = 0; //todo goto fail?
+ }
+ }
+
+
+ /* find cue pointing to stream */
+ if (xsb->version <= XSB_XACT1_2_MAX) {
+ parse_xsb_cues_old(xsb, sf);
+ }
+ else {
+ parse_xsb_cues_new(xsb, sf);
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) {
+ STREAMFILE *streamXsb = NULL;
+ /* .xwb to .xsb name conversion, since often they don't match */
+ static const char *const filename_pairs[][2] = {
+ {"MUSIC.xwb","Everything.xsb"}, /* Unreal Championship (Xbox) */
+ {"Music.xwb","Sound Bank.xsb"}, /* Stardew Valley (Vita) */
+ {"Ambiences_intro.xwb","Ambiences.xsb"}, /* Arx Fatalis (Xbox) */
+ {"Wave*.xwb","Sound*.xsb"}, /* XNA/MonoGame games? */
+ {"*MusicBank.xwb","*SoundBank.xsb"}, /* NFL Fever 2004 (Xbox) */
+ {"*_xwb","*_xsb"}, /* Ikaruga (PC) */
+ {"WB_*","SB_*"}, /* Ikaruga (X360) */
+ {"*StreamBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */
+ {"*WaveBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */
+ {"StreamBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */
+ {"WaveBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */
+ {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */
+ {"*.xwb","*.xsb"}, /* default */
+ };
+ int i;
+ int pair_count = (sizeof(filename_pairs) / sizeof(filename_pairs[0]));
+ char target_filename[PATH_LIMIT];
+ char temp_filename[PATH_LIMIT];
+ int target_len;
+
+ /* try names in external .xsb, using a bunch of possible name pairs */
+ get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT);
+ target_len = strlen(target_filename);
+
+ for (i = 0; i < pair_count; i++) {
+ const char *xwb_match = filename_pairs[i][0];
+ const char *xsb_match = filename_pairs[i][1];
+ size_t xwb_len = strlen(xwb_match);
+ size_t xsb_len = strlen(xsb_match);
+ int match_pos1 = -1, match_pos2 = -1, xwb_pos = -1 , xsb_pos = -1, new_len = 0;
+ const char * teststr;
+
+
+ //;VGM_LOG("XSB: pair1 '%s'='%s' << '%s' \n", xwb_match, xsb_match, target_filename);
+ if (target_len < xwb_len)
+ continue;
+
+ /* ghetto string wildcard replace, ex:
+ * - target filename = "start1_wildcard_end1", xwb_match = "start1_*_end1", xsb_match = "start2_*_end2"
+ * > check xwb's "start_" starts in target_filename (from 0..xwb_pos), set match_pos1
+ * > check xwb's "_end" ends in target_filename (from xwb_pos+1..end), set match_pos2
+ * > copy xsb's "start2_" (from 0..xsb_pos)
+ * > copy target "wildcard" (from 0..xsb_pos)
+ * > copy xsb's "end" (from xsb_pos+1..end)
+ * > final target_filename is "start2_wildcard_end2"
+ * (skips start/end if wildcard is at start/end)
+ */
+
+ teststr = strchr(xwb_match, '*');
+ if (teststr)
+ xwb_pos = (intptr_t)teststr - (intptr_t)xwb_match;
+ teststr = strchr(xsb_match, '*');
+ if (teststr)
+ xsb_pos = (intptr_t)teststr - (intptr_t)xsb_match;
+
+ match_pos1 = 0;
+ match_pos2 = target_len;
+ temp_filename[0] = '\0';
+
+ if (xwb_pos < 0) { /* no wildcard, check exact match */
+ if (target_len != xwb_len || strncmp(target_filename, xwb_match, xwb_len))
+ continue;
+ strcpy(target_filename, xsb_match);
+ }
+
+ if (xwb_pos > 0) { /* wildcard after start, check starts_with */
+ int starts_len = xwb_pos;
+ if (strncmp(target_filename + 0, xwb_match + 0, xwb_pos) != 0)
+ continue;
+ match_pos1 = 0 + starts_len;
+ }
+
+ if (xwb_pos >= 0 && xwb_pos + 1 < xwb_len) { /* wildcard before end, check ends_with */
+ int ends_len = xwb_len - (xwb_pos+1);
+ if (strncmp(target_filename + target_len - ends_len, xwb_match + xwb_len - ends_len, ends_len) != 0)
+ continue;
+ match_pos2 = target_len - ends_len;
+ }
+
+ if (match_pos1 >= 0 && match_pos2 > match_pos1) { /* save match */
+ int match_len = match_pos2 - match_pos1;
+ strncpy(temp_filename, target_filename + match_pos1, match_len);
+ temp_filename[match_len] = '\0';
+ }
+
+ if (xsb_pos > 0) { /* copy xsb start */
+ strncpy(target_filename + 0, xsb_match, (xsb_pos));
+ new_len += (xsb_pos);
+ target_filename[new_len] = '\0';
+ }
+
+ if (xsb_pos >= 0){ /* copy xsb match */
+ strncpy(target_filename + new_len, temp_filename, (match_pos2 - match_pos1));
+ new_len += (match_pos2 - match_pos1);
+ target_filename[new_len] = '\0';
+ }
+
+ if (xsb_pos >= 0 && xsb_pos + 1 < xsb_len) { /* copy xsb end */
+ strncpy(target_filename + new_len, xsb_match + (xsb_pos+1), (xsb_len - (xsb_pos+1)));
+ new_len += (xsb_len - (xsb_pos+1));
+ target_filename[new_len] = '\0';
+ }
+
+ //;VGM_LOG("XSB: pair2 '%s'='%s' >> '%s'\n", xwb_match, xsb_match, target_filename);
+ streamXsb = open_streamfile_by_filename(streamXwb, target_filename);
+ if (streamXsb) return streamXsb;
+
+ get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); /* reset for next loop */
+ }
+
+ return NULL;
+}
+
+#endif /* _XWB_XSB_H_ */
diff --git a/src/mixing.c b/src/mixing.c
index ab2b820b..5dd900b4 100644
--- a/src/mixing.c
+++ b/src/mixing.c
@@ -1,1111 +1,1111 @@
-#include "vgmstream.h"
-#include "mixing.h"
-#include "plugins.h"
-#include
-#include
-
-
-/**
- * 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 512
-#define MIXING_PI 3.14159265358979323846f
-
-
-/* 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 sample_count) {
- int32_t current_pos;
-
- if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
- int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
- int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
- int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
-
- current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
- }
- else {
- current_pos = (vgmstream->current_sample - sample_count);
- }
-
- return current_pos;
-}
-
-static float get_fade_gain_curve(char shape, float index) {
- float gain;
-
- /* don't bother doing calcs near 0.0/1.0 */
- if (index <= 0.0001f || index >= 0.9999f) {
- return index;
- }
-
- //todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
-
- /* (curve math mostly from SoX/FFmpeg) */
- switch(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 * MIXING_PI)) / 2.0f;
- break;
-
- case 'Q': /* quarter of sine wave (for musical fades) */
- gain = sin(index * MIXING_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;
- }
-
- return gain;
-}
-
-static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
- 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.
- */
- gain = get_fade_gain_curve(mix->shape, index);
-
- 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 {
- /* fade is outside reach */
- 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 = 0.0f;
- 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, sample_count);
- 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;
-
- current_subpos = current_pos;
-
- /* 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:
- ok = get_fade_gain(mix, &cur_vol, current_subpos);
- if (!ok) {
- break; /* fade doesn't apply right now */
- }
-
- 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;
- }
- }
-
- current_subpos++;
-
- 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;
-
- //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,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;
-
- //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,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 <= 0) return; /* can't kill from first channel */
- if (!data || ch_dst >= data->output_channels) return;
-
- mix.command = MIX_KILLMIX;
- mix.ch_dst = ch_dst;
-
- //;VGM_LOG("MIX: killmix %i\n", 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, char mode) {
- mixing_data *data = vgmstream->mixing_data;
- int current, ch, output_channels, 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;
- }
-
- /* save before adding fake channels */
- output_channels = data->output_channels;
-
- /* count possibly set channels */
- selected_channels = 0;
- for (ch = 0; ch < 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 < output_channels; ch++) {
- double volume = 1.0;
-
- if (!((mask >> ch) & 1))
- continue;
-
- /* mode 'v': same volume for all layers (for layered vocals) */
- /* mode 'b': volume adjusted depending on layers (for layered bgm) */
- /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */
- if (mode == 'b' && ch < max) {
- /* reduce a bit main channels (see below) */
- int channel_mixes = selected_channels / max;
- if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */
- channel_mixes += 1;
- channel_mixes -= 1; /* better formula? */
- if (channel_mixes <= 0) /* ??? */
- channel_mixes = 1;
-
- volume = 1 / sqrt(channel_mixes);
- }
- if ((mode == 'b' && ch >= max) || (mode == 'e')) {
- /* 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 */
- }
- //;VGM_LOG("MIX: layer ch=%i, cur=%i, v=%f\n", ch, current, volume);
-
- 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, char mode) {
- 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;
-
- /* mode 'v': constant volume
- * mode 'e': sets fades to successively lower/equalize volume per loop for each layer
- * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max:
- * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
- * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
- * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]--
- * mode 'b': similar but 1st layer (main) has higher/delayed volume:
- * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]--
- */
- for (loop = 1; loop < layer_num; loop++) {
- double volume1 = 1.0;
- double volume2 = 1.0;
-
- 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 */
-
- if (mode == 'e') {
- volume1 = 1 / sqrt(loop + 0);
- volume2 = 1 / sqrt(loop + 1);
- }
-
- ch = 0;
- for (layer = 0; layer < layer_num; layer++) {
- char type;
-
- if (mode == 'b') {
- if (layer == 0) {
- volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1);
- volume2 = 1 / sqrt(loop + 0);
- }
- else {
- volume1 = 1 / sqrt(loop + 0);
- volume2 = 1 / sqrt(loop + 1);
- }
- }
-
- 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 = ')';
- }
-
- //;VGM_LOG("MIX: loop=%i, layer %i, vol1=%f, vol2=%f\n", loop, layer, volume1, volume2);
-
- 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);
-}
-
-
-typedef enum {
- pos_FL = 0,
- pos_FR = 1,
- pos_FC = 2,
- pos_LFE = 3,
- pos_BL = 4,
- pos_BR = 5,
- pos_FLC = 6,
- pos_FRC = 7,
- pos_BC = 8,
- pos_SL = 9,
- pos_SR = 10,
-} mixing_position_t;
-
-void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) {
- mixing_data *data = vgmstream->mixing_data;
- int ch, output_channels, mp_in, mp_out, ch_in, ch_out;
- mapping_t input_mapping, output_mapping;
- const double vol_max = 1.0;
- const double vol_sqrt = 1 / sqrt(2);
- const double vol_half = 1 / 2;
- double matrix[16][16] = {{0}};
-
-
- if (!data)
- return;
- if (max <= 1 || data->output_channels <= max || max >= 8)
- return;
-
- /* assume WAV defaults if not set */
- input_mapping = vgmstream->channel_layout;
- if (input_mapping == 0) {
- switch(data->output_channels) {
- case 1: input_mapping = mapping_MONO; break;
- case 2: input_mapping = mapping_STEREO; break;
- case 3: input_mapping = mapping_2POINT1; break;
- case 4: input_mapping = mapping_QUAD; break;
- case 5: input_mapping = mapping_5POINT0; break;
- case 6: input_mapping = mapping_5POINT1; break;
- case 7: input_mapping = mapping_7POINT0; break;
- case 8: input_mapping = mapping_7POINT1; break;
- default: return;
- }
- }
-
- /* build mapping matrix[input channel][output channel] = volume,
- * using standard WAV/AC3 downmix formulas
- * - https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables
- * - https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations
- */
- switch(max) {
- case 1:
- output_mapping = mapping_MONO;
- matrix[pos_FL][pos_FC] = vol_sqrt;
- matrix[pos_FR][pos_FC] = vol_sqrt;
- matrix[pos_FC][pos_FC] = vol_max;
- matrix[pos_SL][pos_FC] = vol_half;
- matrix[pos_SR][pos_FC] = vol_half;
- matrix[pos_BL][pos_FC] = vol_half;
- matrix[pos_BR][pos_FC] = vol_half;
- break;
- case 2:
- output_mapping = mapping_STEREO;
- matrix[pos_FL][pos_FL] = vol_max;
- matrix[pos_FR][pos_FR] = vol_max;
- matrix[pos_FC][pos_FL] = vol_sqrt;
- matrix[pos_FC][pos_FR] = vol_sqrt;
- matrix[pos_SL][pos_FL] = vol_sqrt;
- matrix[pos_SR][pos_FR] = vol_sqrt;
- matrix[pos_BL][pos_FL] = vol_sqrt;
- matrix[pos_BR][pos_FR] = vol_sqrt;
- break;
- default:
- /* not sure if +3ch would use FC/LFE, SL/BR and whatnot without passing extra config, so ignore for now */
- return;
- }
-
- /* save and make N fake channels at the beginning for easier calcs */
- output_channels = data->output_channels;
- for (ch = 0; ch < max; ch++) {
- mixing_push_upmix(vgmstream, 0);
- }
-
- /* downmix */
- ch_in = 0;
- for (mp_in = 0; mp_in < 16; mp_in++) {
- /* read input mapping (ex. 5.1) and find channel */
- if (!(input_mapping & (1< max)
- break;
- }
-
- ch_in++;
- if (ch_in >= output_channels)
- break;
- }
-
- /* 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;
-
- /* 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;
- }
-
- /* special value to not actually enable anything (used to query values) */
- if (max_sample_count <= 0)
- 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;
-
- /* 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;
-}
+#include "vgmstream.h"
+#include "mixing.h"
+#include "plugins.h"
+#include
+#include
+
+
+/**
+ * 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 512
+#define MIXING_PI 3.14159265358979323846f
+
+
+/* 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 sample_count) {
+ int32_t current_pos;
+
+ if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
+ int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
+ int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
+ int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
+
+ current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
+ }
+ else {
+ current_pos = (vgmstream->current_sample - sample_count);
+ }
+
+ return current_pos;
+}
+
+static float get_fade_gain_curve(char shape, float index) {
+ float gain;
+
+ /* don't bother doing calcs near 0.0/1.0 */
+ if (index <= 0.0001f || index >= 0.9999f) {
+ return index;
+ }
+
+ //todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
+
+ /* (curve math mostly from SoX/FFmpeg) */
+ switch(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 * MIXING_PI)) / 2.0f;
+ break;
+
+ case 'Q': /* quarter of sine wave (for musical fades) */
+ gain = sin(index * MIXING_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;
+ }
+
+ return gain;
+}
+
+static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
+ 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.
+ */
+ gain = get_fade_gain_curve(mix->shape, index);
+
+ 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 {
+ /* fade is outside reach */
+ 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 = 0.0f;
+ 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, sample_count);
+ 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;
+
+ current_subpos = current_pos;
+
+ /* 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:
+ ok = get_fade_gain(mix, &cur_vol, current_subpos);
+ if (!ok) {
+ break; /* fade doesn't apply right now */
+ }
+
+ 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;
+ }
+ }
+
+ current_subpos++;
+
+ 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;
+
+ //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,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;
+
+ //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,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 <= 0) return; /* can't kill from first channel */
+ if (!data || ch_dst >= data->output_channels) return;
+
+ mix.command = MIX_KILLMIX;
+ mix.ch_dst = ch_dst;
+
+ //;VGM_LOG("MIX: killmix %i\n", 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, char mode) {
+ mixing_data *data = vgmstream->mixing_data;
+ int current, ch, output_channels, 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;
+ }
+
+ /* save before adding fake channels */
+ output_channels = data->output_channels;
+
+ /* count possibly set channels */
+ selected_channels = 0;
+ for (ch = 0; ch < 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 < output_channels; ch++) {
+ double volume = 1.0;
+
+ if (!((mask >> ch) & 1))
+ continue;
+
+ /* mode 'v': same volume for all layers (for layered vocals) */
+ /* mode 'b': volume adjusted depending on layers (for layered bgm) */
+ /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */
+ if (mode == 'b' && ch < max) {
+ /* reduce a bit main channels (see below) */
+ int channel_mixes = selected_channels / max;
+ if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */
+ channel_mixes += 1;
+ channel_mixes -= 1; /* better formula? */
+ if (channel_mixes <= 0) /* ??? */
+ channel_mixes = 1;
+
+ volume = 1 / sqrt(channel_mixes);
+ }
+ if ((mode == 'b' && ch >= max) || (mode == 'e')) {
+ /* 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 */
+ }
+ //;VGM_LOG("MIX: layer ch=%i, cur=%i, v=%f\n", ch, current, volume);
+
+ 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, char mode) {
+ 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;
+
+ /* mode 'v': constant volume
+ * mode 'e': sets fades to successively lower/equalize volume per loop for each layer
+ * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max:
+ * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
+ * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
+ * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]--
+ * mode 'b': similar but 1st layer (main) has higher/delayed volume:
+ * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]--
+ */
+ for (loop = 1; loop < layer_num; loop++) {
+ double volume1 = 1.0;
+ double volume2 = 1.0;
+
+ 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 */
+
+ if (mode == 'e') {
+ volume1 = 1 / sqrt(loop + 0);
+ volume2 = 1 / sqrt(loop + 1);
+ }
+
+ ch = 0;
+ for (layer = 0; layer < layer_num; layer++) {
+ char type;
+
+ if (mode == 'b') {
+ if (layer == 0) {
+ volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1);
+ volume2 = 1 / sqrt(loop + 0);
+ }
+ else {
+ volume1 = 1 / sqrt(loop + 0);
+ volume2 = 1 / sqrt(loop + 1);
+ }
+ }
+
+ 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 = ')';
+ }
+
+ //;VGM_LOG("MIX: loop=%i, layer %i, vol1=%f, vol2=%f\n", loop, layer, volume1, volume2);
+
+ 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);
+}
+
+
+typedef enum {
+ pos_FL = 0,
+ pos_FR = 1,
+ pos_FC = 2,
+ pos_LFE = 3,
+ pos_BL = 4,
+ pos_BR = 5,
+ pos_FLC = 6,
+ pos_FRC = 7,
+ pos_BC = 8,
+ pos_SL = 9,
+ pos_SR = 10,
+} mixing_position_t;
+
+void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) {
+ mixing_data *data = vgmstream->mixing_data;
+ int ch, output_channels, mp_in, mp_out, ch_in, ch_out;
+ mapping_t input_mapping, output_mapping;
+ const double vol_max = 1.0;
+ const double vol_sqrt = 1 / sqrt(2);
+ const double vol_half = 1 / 2;
+ double matrix[16][16] = {{0}};
+
+
+ if (!data)
+ return;
+ if (max <= 1 || data->output_channels <= max || max >= 8)
+ return;
+
+ /* assume WAV defaults if not set */
+ input_mapping = vgmstream->channel_layout;
+ if (input_mapping == 0) {
+ switch(data->output_channels) {
+ case 1: input_mapping = mapping_MONO; break;
+ case 2: input_mapping = mapping_STEREO; break;
+ case 3: input_mapping = mapping_2POINT1; break;
+ case 4: input_mapping = mapping_QUAD; break;
+ case 5: input_mapping = mapping_5POINT0; break;
+ case 6: input_mapping = mapping_5POINT1; break;
+ case 7: input_mapping = mapping_7POINT0; break;
+ case 8: input_mapping = mapping_7POINT1; break;
+ default: return;
+ }
+ }
+
+ /* build mapping matrix[input channel][output channel] = volume,
+ * using standard WAV/AC3 downmix formulas
+ * - https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables
+ * - https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations
+ */
+ switch(max) {
+ case 1:
+ output_mapping = mapping_MONO;
+ matrix[pos_FL][pos_FC] = vol_sqrt;
+ matrix[pos_FR][pos_FC] = vol_sqrt;
+ matrix[pos_FC][pos_FC] = vol_max;
+ matrix[pos_SL][pos_FC] = vol_half;
+ matrix[pos_SR][pos_FC] = vol_half;
+ matrix[pos_BL][pos_FC] = vol_half;
+ matrix[pos_BR][pos_FC] = vol_half;
+ break;
+ case 2:
+ output_mapping = mapping_STEREO;
+ matrix[pos_FL][pos_FL] = vol_max;
+ matrix[pos_FR][pos_FR] = vol_max;
+ matrix[pos_FC][pos_FL] = vol_sqrt;
+ matrix[pos_FC][pos_FR] = vol_sqrt;
+ matrix[pos_SL][pos_FL] = vol_sqrt;
+ matrix[pos_SR][pos_FR] = vol_sqrt;
+ matrix[pos_BL][pos_FL] = vol_sqrt;
+ matrix[pos_BR][pos_FR] = vol_sqrt;
+ break;
+ default:
+ /* not sure if +3ch would use FC/LFE, SL/BR and whatnot without passing extra config, so ignore for now */
+ return;
+ }
+
+ /* save and make N fake channels at the beginning for easier calcs */
+ output_channels = data->output_channels;
+ for (ch = 0; ch < max; ch++) {
+ mixing_push_upmix(vgmstream, 0);
+ }
+
+ /* downmix */
+ ch_in = 0;
+ for (mp_in = 0; mp_in < 16; mp_in++) {
+ /* read input mapping (ex. 5.1) and find channel */
+ if (!(input_mapping & (1< max)
+ break;
+ }
+
+ ch_in++;
+ if (ch_in >= output_channels)
+ break;
+ }
+
+ /* 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;
+
+ /* 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;
+ }
+
+ /* special value to not actually enable anything (used to query values) */
+ if (max_sample_count <= 0)
+ 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;
+
+ /* 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;
+}
diff --git a/src/mixing.h b/src/mixing.h
index 9bb49b97..e563c5b4 100644
--- a/src/mixing.h
+++ b/src/mixing.h
@@ -1,42 +1,42 @@
-#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, char mode);
-void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max);
-void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
-void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
-
-
-#endif /* _MIXING_H_ */
+#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, char mode);
+void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max);
+void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
+void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
+
+
+#endif /* _MIXING_H_ */
diff --git a/src/plugins.c b/src/plugins.c
index 71ed37fb..fc683927 100644
--- a/src/plugins.c
+++ b/src/plugins.c
@@ -1,344 +1,344 @@
-#include "vgmstream.h"
-#include "plugins.h"
-#include "mixing.h"
-
-
-/* ****************************************** */
-/* CONTEXT: simplifies plugin code */
-/* ****************************************** */
-
-int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
- const char ** extension_list;
- size_t extension_list_len;
- const char *extension;
- int i;
-
-
- if (cfg->is_extension) {
- extension = filename;
- } else {
- extension = filename_extension(filename);
- }
-
- /* some metas accept extensionless files */
- if (strlen(extension) <= 0) {
- return !cfg->reject_extensionless;
- }
-
- /* try in default list */
- if (!cfg->skip_standard) {
- extension_list = vgmstream_get_formats(&extension_list_len);
- for (i = 0; i < extension_list_len; i++) {
- if (strcasecmp(extension, extension_list[i]) == 0) {
- return 1;
- }
- }
- }
-
- /* try in common extensions */
- if (cfg->accept_common) {
- extension_list = vgmstream_get_common_formats(&extension_list_len);
- for (i = 0; i < extension_list_len; i++) {
- if (strcasecmp(extension, extension_list[i]) == 0)
- return 1;
- }
- }
-
- /* allow anything not in the normal list but not in common extensions */
- if (cfg->accept_unknown) {
- int is_common = 0;
-
- extension_list = vgmstream_get_common_formats(&extension_list_len);
- for (i = 0; i < extension_list_len; i++) {
- if (strcasecmp(extension, extension_list[i]) == 0) {
- is_common = 1;
- break;
- }
- }
-
- if (!is_common)
- return 1;
- }
-
- return 0;
-}
-
-/* ****************************************** */
-/* TAGS: loads key=val tags from a file */
-/* ****************************************** */
-
-#define VGMSTREAM_TAGS_LINE_MAX 2048
-
-/* opaque tag state */
-struct VGMSTREAM_TAGS {
- /* extracted output */
- char key[VGMSTREAM_TAGS_LINE_MAX];
- char val[VGMSTREAM_TAGS_LINE_MAX];
-
- /* file to find tags for */
- int targetname_len;
- char targetname[VGMSTREAM_TAGS_LINE_MAX];
- /* path of targetname */
- char targetpath[VGMSTREAM_TAGS_LINE_MAX];
-
- /* tag section for filename (see comments below) */
- int section_found;
- off_t section_start;
- off_t section_end;
- off_t offset;
-
- /* commands */
- int autotrack_on;
- int autotrack_written;
- int track_count;
-
- int autoalbum_on;
- int autoalbum_written;
-};
-
-
-static void tags_clean(VGMSTREAM_TAGS* tag) {
- int i;
- int val_len = strlen(tag->val);
-
- /* remove trailing spaces */
- for (i = val_len - 1; i > 0; i--) {
- if (tag->val[i] != ' ')
- break;
- tag->val[i] = '\0';
- }
-}
-
-VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
- VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
- if (!tags) goto fail;
-
- *tag_key = tags->key;
- *tag_val = tags->val;
-
- return tags;
-fail:
- return NULL;
-}
-
-void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
- free(tags);
-}
-
-/* Find next tag and return 1 if found.
- *
- * Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
- * To extract tags we must find either global tags, or the filename's tag "section"
- * where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
- * When a new "other_filename" is found that offset is marked as section_start, and when
- * target_filename is found it's marked as section_end. Then we can begin extracting tags
- * within that section, until all tags are exhausted. Global tags are extracted as found,
- * so they always go first, also meaning any tags after file's section are ignored.
- * Command tags have special meanings and are output after all section tags. */
-int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
- off_t file_size = get_streamfile_size(tagfile);
- char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
- char line[VGMSTREAM_TAGS_LINE_MAX];
- int ok, bytes_read, line_ok, n1,n2;
-
- if (!tags)
- return 0;
-
- /* prepare file start and skip BOM if needed */
- if (tags->offset == 0) {
- if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE ||
- (uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) {
- tags->offset = 0x02;
- if (tags->section_start == 0)
- tags->section_start = 0x02;
- }
- else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) {
- tags->offset = 0x03;
- if (tags->section_start == 0)
- tags->section_start = 0x03;
- }
- }
-
- /* read lines */
- while (tags->offset <= file_size) {
-
- /* after section: no more tags to extract */
- if (tags->section_found && tags->offset >= tags->section_end) {
-
- /* write extra tags after all regular tags */
- if (tags->autotrack_on && !tags->autotrack_written) {
- sprintf(tags->key, "%s", "TRACK");
- sprintf(tags->val, "%i", tags->track_count);
- tags->autotrack_written = 1;
- return 1;
- }
-
- if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
- const char* path;
-
- path = strrchr(tags->targetpath,'\\');
- if (!path) {
- path = strrchr(tags->targetpath,'/');
- }
- if (!path) {
- path = tags->targetpath;
- }
-
- sprintf(tags->key, "%s", "ALBUM");
- sprintf(tags->val, "%s", path+1);
- tags->autoalbum_written = 1;
- return 1;
- }
-
- goto fail;
- }
-
- bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
- if (!line_ok || bytes_read == 0) goto fail;
-
- tags->offset += bytes_read;
-
-
- if (tags->section_found) {
- /* find possible file tag */
- ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val);
- if (ok == 2) {
- tags_clean(tags);
- return 1;
- }
- }
- else {
-
- if (line[0] == '#') {
- /* find possible global command */
- ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val);
- if (ok == 1 || ok == 2) {
- if (strcasecmp(tags->key,"AUTOTRACK") == 0) {
- tags->autotrack_on = 1;
- }
- else if (strcasecmp(tags->key,"AUTOALBUM") == 0) {
- tags->autoalbum_on = 1;
- }
-
- continue; /* not an actual tag */
- }
-
- /* find possible global tag */
- ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val);
- if (ok == 2) {
- tags_clean(tags);
- return 1;
- }
-
- continue; /* next line */
- }
-
- /* find possible filename and section start/end
- * (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
- ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
- if (ok == 1) {
- int currentname_len = n2 - n1;
- int filename_found = 0;
-
- /* we want to find file with the same name (case insensitive), OR a virtual .txtp with
- * the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading
- * tags even if we don't open !tags.m3u with virtual .txtp directly) */
-
- /* strcasecmp works ok even for UTF-8 */
- if (currentname_len >= tags->targetname_len && /* starts with targetname */
- strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) {
-
- if (currentname_len == tags->targetname_len) { /* exact match */
- filename_found = 1;
- }
- else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */
- char c = currentname[tags->targetname_len];
- /* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
- filename_found = (c==' ' || c == '.' || c == '#');
- }
- }
-
- if (filename_found) {
- /* section ok, start would be set before this (or be 0) */
- tags->section_end = tags->offset;
- tags->section_found = 1;
- tags->offset = tags->section_start;
- }
- else {
- /* mark new possible section */
- tags->section_start = tags->offset;
- }
-
- tags->track_count++; /* new track found (target filename or not) */
- continue;
- }
-
- /* empty/bad line, probably */
- }
- }
-
- /* may reach here if read up to file_size but no section was found */
-
-fail:
- tags->key[0] = '\0';
- tags->val[0] = '\0';
- return 0;
-}
-
-
-void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
- char *path;
-
- if (!tags)
- return;
-
- memset(tags, 0, sizeof(VGMSTREAM_TAGS));
-
- //todo validate sizes and copy sensible max
-
- /* get base name */
- strcpy(tags->targetpath, target_filename);
-
- /* Windows CMD accepts both \\ and /, and maybe plugin uses either */
- path = strrchr(tags->targetpath,'\\');
- if (!path) {
- path = strrchr(tags->targetpath,'/');
- }
- if (path != NULL) {
- path[0] = '\0'; /* leave targetpath with path only */
- path = path+1;
- }
-
- if (path) {
- strcpy(tags->targetname, path);
- } else {
- tags->targetpath[0] = '\0';
- strcpy(tags->targetname, target_filename);
- }
- tags->targetname_len = strlen(tags->targetname);
-}
-
-/* ****************************************** */
-/* MIXING: modifies vgmstream output */
-/* ****************************************** */
-
-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) {
- if (max_channels <= 0)
- return;
-
- /* guess mixing the best we can, using standard downmixing if possible
- * (without mapping we can't be sure if format is using a standard layout) */
- if (vgmstream->channel_layout && max_channels <= 2) {
- mixing_macro_downmix(vgmstream, max_channels);
- }
- else {
- mixing_macro_layer(vgmstream, max_channels, 0, 'e');
- }
-
- return;
-}
+#include "vgmstream.h"
+#include "plugins.h"
+#include "mixing.h"
+
+
+/* ****************************************** */
+/* CONTEXT: simplifies plugin code */
+/* ****************************************** */
+
+int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
+ const char ** extension_list;
+ size_t extension_list_len;
+ const char *extension;
+ int i;
+
+
+ if (cfg->is_extension) {
+ extension = filename;
+ } else {
+ extension = filename_extension(filename);
+ }
+
+ /* some metas accept extensionless files */
+ if (strlen(extension) <= 0) {
+ return !cfg->reject_extensionless;
+ }
+
+ /* try in default list */
+ if (!cfg->skip_standard) {
+ extension_list = vgmstream_get_formats(&extension_list_len);
+ for (i = 0; i < extension_list_len; i++) {
+ if (strcasecmp(extension, extension_list[i]) == 0) {
+ return 1;
+ }
+ }
+ }
+
+ /* try in common extensions */
+ if (cfg->accept_common) {
+ extension_list = vgmstream_get_common_formats(&extension_list_len);
+ for (i = 0; i < extension_list_len; i++) {
+ if (strcasecmp(extension, extension_list[i]) == 0)
+ return 1;
+ }
+ }
+
+ /* allow anything not in the normal list but not in common extensions */
+ if (cfg->accept_unknown) {
+ int is_common = 0;
+
+ extension_list = vgmstream_get_common_formats(&extension_list_len);
+ for (i = 0; i < extension_list_len; i++) {
+ if (strcasecmp(extension, extension_list[i]) == 0) {
+ is_common = 1;
+ break;
+ }
+ }
+
+ if (!is_common)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ****************************************** */
+/* TAGS: loads key=val tags from a file */
+/* ****************************************** */
+
+#define VGMSTREAM_TAGS_LINE_MAX 2048
+
+/* opaque tag state */
+struct VGMSTREAM_TAGS {
+ /* extracted output */
+ char key[VGMSTREAM_TAGS_LINE_MAX];
+ char val[VGMSTREAM_TAGS_LINE_MAX];
+
+ /* file to find tags for */
+ int targetname_len;
+ char targetname[VGMSTREAM_TAGS_LINE_MAX];
+ /* path of targetname */
+ char targetpath[VGMSTREAM_TAGS_LINE_MAX];
+
+ /* tag section for filename (see comments below) */
+ int section_found;
+ off_t section_start;
+ off_t section_end;
+ off_t offset;
+
+ /* commands */
+ int autotrack_on;
+ int autotrack_written;
+ int track_count;
+
+ int autoalbum_on;
+ int autoalbum_written;
+};
+
+
+static void tags_clean(VGMSTREAM_TAGS* tag) {
+ int i;
+ int val_len = strlen(tag->val);
+
+ /* remove trailing spaces */
+ for (i = val_len - 1; i > 0; i--) {
+ if (tag->val[i] != ' ')
+ break;
+ tag->val[i] = '\0';
+ }
+}
+
+VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
+ VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
+ if (!tags) goto fail;
+
+ *tag_key = tags->key;
+ *tag_val = tags->val;
+
+ return tags;
+fail:
+ return NULL;
+}
+
+void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
+ free(tags);
+}
+
+/* Find next tag and return 1 if found.
+ *
+ * Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
+ * To extract tags we must find either global tags, or the filename's tag "section"
+ * where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
+ * When a new "other_filename" is found that offset is marked as section_start, and when
+ * target_filename is found it's marked as section_end. Then we can begin extracting tags
+ * within that section, until all tags are exhausted. Global tags are extracted as found,
+ * so they always go first, also meaning any tags after file's section are ignored.
+ * Command tags have special meanings and are output after all section tags. */
+int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
+ off_t file_size = get_streamfile_size(tagfile);
+ char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
+ char line[VGMSTREAM_TAGS_LINE_MAX];
+ int ok, bytes_read, line_ok, n1,n2;
+
+ if (!tags)
+ return 0;
+
+ /* prepare file start and skip BOM if needed */
+ if (tags->offset == 0) {
+ if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE ||
+ (uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) {
+ tags->offset = 0x02;
+ if (tags->section_start == 0)
+ tags->section_start = 0x02;
+ }
+ else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) {
+ tags->offset = 0x03;
+ if (tags->section_start == 0)
+ tags->section_start = 0x03;
+ }
+ }
+
+ /* read lines */
+ while (tags->offset <= file_size) {
+
+ /* after section: no more tags to extract */
+ if (tags->section_found && tags->offset >= tags->section_end) {
+
+ /* write extra tags after all regular tags */
+ if (tags->autotrack_on && !tags->autotrack_written) {
+ sprintf(tags->key, "%s", "TRACK");
+ sprintf(tags->val, "%i", tags->track_count);
+ tags->autotrack_written = 1;
+ return 1;
+ }
+
+ if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
+ const char* path;
+
+ path = strrchr(tags->targetpath,'\\');
+ if (!path) {
+ path = strrchr(tags->targetpath,'/');
+ }
+ if (!path) {
+ path = tags->targetpath;
+ }
+
+ sprintf(tags->key, "%s", "ALBUM");
+ sprintf(tags->val, "%s", path+1);
+ tags->autoalbum_written = 1;
+ return 1;
+ }
+
+ goto fail;
+ }
+
+ bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
+ if (!line_ok || bytes_read == 0) goto fail;
+
+ tags->offset += bytes_read;
+
+
+ if (tags->section_found) {
+ /* find possible file tag */
+ ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val);
+ if (ok == 2) {
+ tags_clean(tags);
+ return 1;
+ }
+ }
+ else {
+
+ if (line[0] == '#') {
+ /* find possible global command */
+ ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val);
+ if (ok == 1 || ok == 2) {
+ if (strcasecmp(tags->key,"AUTOTRACK") == 0) {
+ tags->autotrack_on = 1;
+ }
+ else if (strcasecmp(tags->key,"AUTOALBUM") == 0) {
+ tags->autoalbum_on = 1;
+ }
+
+ continue; /* not an actual tag */
+ }
+
+ /* find possible global tag */
+ ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val);
+ if (ok == 2) {
+ tags_clean(tags);
+ return 1;
+ }
+
+ continue; /* next line */
+ }
+
+ /* find possible filename and section start/end
+ * (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
+ ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
+ if (ok == 1) {
+ int currentname_len = n2 - n1;
+ int filename_found = 0;
+
+ /* we want to find file with the same name (case insensitive), OR a virtual .txtp with
+ * the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading
+ * tags even if we don't open !tags.m3u with virtual .txtp directly) */
+
+ /* strcasecmp works ok even for UTF-8 */
+ if (currentname_len >= tags->targetname_len && /* starts with targetname */
+ strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) {
+
+ if (currentname_len == tags->targetname_len) { /* exact match */
+ filename_found = 1;
+ }
+ else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */
+ char c = currentname[tags->targetname_len];
+ /* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
+ filename_found = (c==' ' || c == '.' || c == '#');
+ }
+ }
+
+ if (filename_found) {
+ /* section ok, start would be set before this (or be 0) */
+ tags->section_end = tags->offset;
+ tags->section_found = 1;
+ tags->offset = tags->section_start;
+ }
+ else {
+ /* mark new possible section */
+ tags->section_start = tags->offset;
+ }
+
+ tags->track_count++; /* new track found (target filename or not) */
+ continue;
+ }
+
+ /* empty/bad line, probably */
+ }
+ }
+
+ /* may reach here if read up to file_size but no section was found */
+
+fail:
+ tags->key[0] = '\0';
+ tags->val[0] = '\0';
+ return 0;
+}
+
+
+void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
+ char *path;
+
+ if (!tags)
+ return;
+
+ memset(tags, 0, sizeof(VGMSTREAM_TAGS));
+
+ //todo validate sizes and copy sensible max
+
+ /* get base name */
+ strcpy(tags->targetpath, target_filename);
+
+ /* Windows CMD accepts both \\ and /, and maybe plugin uses either */
+ path = strrchr(tags->targetpath,'\\');
+ if (!path) {
+ path = strrchr(tags->targetpath,'/');
+ }
+ if (path != NULL) {
+ path[0] = '\0'; /* leave targetpath with path only */
+ path = path+1;
+ }
+
+ if (path) {
+ strcpy(tags->targetname, path);
+ } else {
+ tags->targetpath[0] = '\0';
+ strcpy(tags->targetname, target_filename);
+ }
+ tags->targetname_len = strlen(tags->targetname);
+}
+
+/* ****************************************** */
+/* MIXING: modifies vgmstream output */
+/* ****************************************** */
+
+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) {
+ if (max_channels <= 0)
+ return;
+
+ /* guess mixing the best we can, using standard downmixing if possible
+ * (without mapping we can't be sure if format is using a standard layout) */
+ if (vgmstream->channel_layout && max_channels <= 2) {
+ mixing_macro_downmix(vgmstream, max_channels);
+ }
+ else {
+ mixing_macro_layer(vgmstream, max_channels, 0, 'e');
+ }
+
+ return;
+}
diff --git a/src/plugins.h b/src/plugins.h
index 94b8d69a..9133eae8 100644
--- a/src/plugins.h
+++ b/src/plugins.h
@@ -1,106 +1,106 @@
-/*
- * plugins.h - helper for plugins
- */
-#ifndef _PLUGINS_H_
-#define _PLUGINS_H_
-
-#include "streamfile.h"
-
-/* ****************************************** */
-/* CONTEXT: simplifies plugin code */
-/* ****************************************** */
-
-typedef struct {
- int is_extension; /* set if filename is already an extension */
- int skip_standard; /* set if shouldn't check standard formats */
- int reject_extensionless; /* set if player can't play extensionless files */
- int accept_unknown; /* set to allow any extension (for txth) */
- int accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
-} vgmstream_ctx_valid_cfg;
-
-/* returns if vgmstream can parse file by extension */
-int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg);
-
-#if 0
-
-/* opaque player state */
-typedef struct VGMSTREAM_CTX VGMSTREAM_CTX;
-
-typedef struct {
- //...
-} VGMSTREAM_CTX_INFO;
-
-VGMSTREAM_CTX* vgmstream_ctx_init(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_format_check(...);
-VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...);
-VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_set_file(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_get_config(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_set_config(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_get_info(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_describe(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_get_title(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_play(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_seek(...);
-
-VGMSTREAM_CTX* vgmstream_ctx_close(...);
-
-#endif
-
-
-
-/* ****************************************** */
-/* TAGS: loads key=val tags from a file */
-/* ****************************************** */
-
-/* opaque tag state */
-typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS;
-
-/* Initializes TAGS and returns pointers to extracted strings (always valid but change
- * on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL).
- * ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */
-VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val);
-
-/* Resets tagfile to restart reading from the beginning for a new filename.
- * Must be called first before extracting tags. */
-void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename);
-
-
-/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be
- * called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */
-int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile);
-
-/* Closes tag file */
-void vgmstream_tags_close(VGMSTREAM_TAGS* tags);
-
-
-/* ****************************************** */
-/* MIXING: modifies vgmstream output */
-/* ****************************************** */
-
-/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin
- * must use returned input_channels to create outbuf and output_channels to output audio.
- * max_sample_count may be 0 if you only need to query values and not actually enable it.
- * 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);
-
-#endif /* _PLUGINS_H_ */
+/*
+ * plugins.h - helper for plugins
+ */
+#ifndef _PLUGINS_H_
+#define _PLUGINS_H_
+
+#include "streamfile.h"
+
+/* ****************************************** */
+/* CONTEXT: simplifies plugin code */
+/* ****************************************** */
+
+typedef struct {
+ int is_extension; /* set if filename is already an extension */
+ int skip_standard; /* set if shouldn't check standard formats */
+ int reject_extensionless; /* set if player can't play extensionless files */
+ int accept_unknown; /* set to allow any extension (for txth) */
+ int accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
+} vgmstream_ctx_valid_cfg;
+
+/* returns if vgmstream can parse file by extension */
+int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg);
+
+#if 0
+
+/* opaque player state */
+typedef struct VGMSTREAM_CTX VGMSTREAM_CTX;
+
+typedef struct {
+ //...
+} VGMSTREAM_CTX_INFO;
+
+VGMSTREAM_CTX* vgmstream_ctx_init(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_format_check(...);
+VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...);
+VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_set_file(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_get_config(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_set_config(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_get_info(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_describe(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_get_title(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_play(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_seek(...);
+
+VGMSTREAM_CTX* vgmstream_ctx_close(...);
+
+#endif
+
+
+
+/* ****************************************** */
+/* TAGS: loads key=val tags from a file */
+/* ****************************************** */
+
+/* opaque tag state */
+typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS;
+
+/* Initializes TAGS and returns pointers to extracted strings (always valid but change
+ * on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL).
+ * ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */
+VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val);
+
+/* Resets tagfile to restart reading from the beginning for a new filename.
+ * Must be called first before extracting tags. */
+void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename);
+
+
+/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be
+ * called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */
+int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile);
+
+/* Closes tag file */
+void vgmstream_tags_close(VGMSTREAM_TAGS* tags);
+
+
+/* ****************************************** */
+/* MIXING: modifies vgmstream output */
+/* ****************************************** */
+
+/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin
+ * must use returned input_channels to create outbuf and output_channels to output audio.
+ * max_sample_count may be 0 if you only need to query values and not actually enable it.
+ * 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);
+
+#endif /* _PLUGINS_H_ */
diff --git a/winamp/in_vgmstream.vcxproj b/winamp/in_vgmstream.vcxproj
index 47114fca..1fbb2bef 100644
--- a/winamp/in_vgmstream.vcxproj
+++ b/winamp/in_vgmstream.vcxproj
@@ -1,154 +1,154 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Template
- Win32
-
-
-
- {42D86561-8CE4-40F5-86CE-58C986B77502}
- in_vgmstream
- Win32Proj
-
-
-
-
-
-
-
-
- 7.0
-
-
-
- DynamicLibrary
- MultiByte
- true
- v141_xp
-
-
- DynamicLibrary
- Unicode
- v141_xp
-
-
- v141_xp
-
-
-
-
-
-
-
-
-
-
-
- ../dependencies
-
-
- <_ProjectFileVersion>10.0.30319.1
- AllRules.ruleset
-
-
- AllRules.ruleset
-
-
- AllRules.ruleset
-
-
-
-
-
- Disabled
- ..;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
- WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;_DEBUG;_WINDOWS;_USRDLL;IN_VGMSTREAM_EXPORTS;VGM_WINAMP_UNICODE;%(PreprocessorDefinitions)
- EnableFastChecks
- MultiThreadedDebug
-
-
- Level3
- EditAndContinue
-
-
- ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
- %(AdditionalLibraryDirectories)
- true
- Windows
- false
-
- MachineX86
-
-
- "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
-
-
- Generating version.h
-
-
-
-
- ..;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
- WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;NDEBUG;_WINDOWS;_USRDLL;IN_VGMSTREAM_EXPORTS;VGM_WINAMP_UNICODE;%(PreprocessorDefinitions)
- MultiThreaded
-
-
- Level3
- ProgramDatabase
- Fast
-
-
- ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
- %(AdditionalLibraryDirectories)
- %(IgnoreSpecificDefaultLibraries)
- true
- true
- true
- false
-
- Windows
- MachineX86
-
-
- "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
-
-
- Generating version.h
-
-
-
-
-
-
-
-
-
-
-
-
-
- {308e2ad5-be31-4770-9441-a8d50f56895c}
-
-
- {86a064e2-c81b-4eee-8be0-a39a2e7c7c76}
-
-
- {10e6bfc6-1e5b-46e4-ba42-f04dfbd0abff}
-
-
- {54a6ad11-5369-4895-a06f-e255abb99b11}
-
-
-
-
-
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Template
+ Win32
+
+
+
+ {42D86561-8CE4-40F5-86CE-58C986B77502}
+ in_vgmstream
+ Win32Proj
+
+
+
+
+
+
+
+
+ 7.0
+
+
+
+ DynamicLibrary
+ MultiByte
+ true
+ v141_xp
+
+
+ DynamicLibrary
+ Unicode
+ v141_xp
+
+
+ v141_xp
+
+
+
+
+
+
+
+
+
+
+
+ ../dependencies
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ AllRules.ruleset
+
+
+ AllRules.ruleset
+
+
+ AllRules.ruleset
+
+
+
+
+
+ Disabled
+ ..;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
+ WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;_DEBUG;_WINDOWS;_USRDLL;IN_VGMSTREAM_EXPORTS;VGM_WINAMP_UNICODE;%(PreprocessorDefinitions)
+ EnableFastChecks
+ MultiThreadedDebug
+
+
+ Level3
+ EditAndContinue
+
+
+ ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
+ %(AdditionalLibraryDirectories)
+ true
+ Windows
+ false
+
+ MachineX86
+
+
+ "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
+
+
+ Generating version.h
+
+
+
+
+ ..;../ext_includes;$(DependenciesDir)/qaac/mp4v2/include;$(DependenciesDir)/fdk-aac/libSYS/include;$(DependenciesDir)/fdk-aac/libAACdec/include;%(AdditionalIncludeDirectories)
+ WIN32;VGM_USE_VORBIS;VGM_USE_MPEG;VGM_USE_FFMPEG;VGM_USE_G7221;VGM_USE_MP4V2;VGM_USE_FDKAAC;VGM_USE_ATRAC9;VGM_USE_CELT;NDEBUG;_WINDOWS;_USRDLL;IN_VGMSTREAM_EXPORTS;VGM_WINAMP_UNICODE;%(PreprocessorDefinitions)
+ MultiThreaded
+
+
+ Level3
+ ProgramDatabase
+ Fast
+
+
+ ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg7221_decode.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;%(AdditionalDependencies)
+ %(AdditionalLibraryDirectories)
+ %(IgnoreSpecificDefaultLibraries)
+ true
+ true
+ true
+ false
+
+ Windows
+ MachineX86
+
+
+ "$(ProjectDir)..\version.bat" "$(ProjectDir)..\version.h" VERSION
+
+
+ Generating version.h
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {308e2ad5-be31-4770-9441-a8d50f56895c}
+
+
+ {86a064e2-c81b-4eee-8be0-a39a2e7c7c76}
+
+
+ {10e6bfc6-1e5b-46e4-ba42-f04dfbd0abff}
+
+
+ {54a6ad11-5369-4895-a06f-e255abb99b11}
+
+
+
+
+
\ No newline at end of file
diff --git a/winamp/in_vgmstream.vcxproj.filters b/winamp/in_vgmstream.vcxproj.filters
index 5f282319..75b28ac9 100644
--- a/winamp/in_vgmstream.vcxproj.filters
+++ b/winamp/in_vgmstream.vcxproj.filters
@@ -1,32 +1,32 @@
-
-
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hpp;hxx;hm;inl;inc;xsd
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
-
-
- Header Files
-
-
-
-
- Resource Files
-
-
-
-
- Source Files
-
-
+
+
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+
+
+ Source Files
+
+
\ No newline at end of file
diff --git a/xmplay/xmp-vgmstream.vcxproj b/xmplay/xmp-vgmstream.vcxproj
index 64e09169..736f8361 100644
--- a/xmplay/xmp-vgmstream.vcxproj
+++ b/xmplay/xmp-vgmstream.vcxproj
@@ -1,4 +1,4 @@
-
+