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 @@ - +