diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 5396a2f7..5711f232 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -6182,7 +6182,43 @@ namespace T6 SA_LOADED = 0x1, SA_STREAMED = 0x2, SA_PRIMED = 0x3, - SA_COUNT = 0x4, + + SA_COUNT + }; + + enum SndLimitType + { + SND_LIMIT_NONE = 0x0, + SND_LIMIT_OLDEST = 0x1, + SND_LIMIT_REJECT = 0x2, + SND_LIMIT_PRIORITY = 0x3, + + SND_LIMIT_COUNT + }; + + enum SndBus + { + SND_BUS_REVERB = 0x0, + SND_BUS_FX = 0x1, + SND_BUS_VOICE = 0x2, + SND_BUS_PFUTZ = 0x3, + SND_BUS_HDRFX = 0x4, + SND_BUS_UI = 0x5, + SND_BUS_MUSIC = 0x6, + SND_BUS_MOVIE = 0x7, + SND_BUS_REFERENCE = 0x8, + + SND_BUS_COUNT + }; + + enum SndRandomizeType + { + SND_RANDOMIZE_INSTANCE = 0x0, + SND_RANDOMIZE_ENTITY_VOLUME = 0x1, + SND_RANDOMIZE_ENTITY_PITCH = 0x2, + SND_RANDOMIZE_ENTITY_VARIANT = 0x4, + + SND_RANDOMIZE_ENTITY_COUNT }; struct SndAliasFlags diff --git a/src/ObjCommon/Game/T6/SoundConstantsT6.h b/src/ObjCommon/Game/T6/SoundConstantsT6.h index bb347d36..878db002 100644 --- a/src/ObjCommon/Game/T6/SoundConstantsT6.h +++ b/src/ObjCommon/Game/T6/SoundConstantsT6.h @@ -1,9 +1,13 @@ #pragma once -#include + +#include "Game/T6/T6.h" + +#include namespace T6 { - inline const std::string SOUND_GROUPS[]{ + // From SndDriverGlobals + inline constexpr const char* SOUND_GROUPS[]{ // clang-format off "grp_reference", "grp_master", @@ -34,7 +38,8 @@ namespace T6 // clang-format on }; - inline const std::string SOUND_CURVES[]{ + // From SndDriverGlobals + inline constexpr const char* SOUND_CURVES[]{ "default", "defaultmin", "allon", @@ -54,7 +59,8 @@ namespace T6 "rev65", }; - inline const std::string SOUND_DUCK_GROUPS[]{ + // From SndDriverGlobals + inline constexpr const char* SOUND_DUCK_GROUPS[]{ "snp_alerts_gameplay", "snp_ambience", "snp_claw", @@ -89,14 +95,16 @@ namespace T6 "snp_x3", }; - inline const std::string SOUND_LIMIT_TYPES[]{ + inline constexpr const char* SOUND_LIMIT_TYPES[]{ "none", "oldest", "reject", "priority", }; + static_assert(std::extent_v == SND_LIMIT_COUNT); - inline const std::string SOUND_MOVE_TYPES[]{ + // From executable + inline constexpr const char* SOUND_MOVE_TYPES[]{ "none", "left_player", "center_player", @@ -107,14 +115,15 @@ namespace T6 "right_shot", }; - inline const std::string SOUND_LOAD_TYPES[]{ + inline constexpr const char* SOUND_LOAD_TYPES[]{ "unknown", "loaded", "streamed", "primed", }; + static_assert(std::extent_v == SA_COUNT); - inline const std::string SOUND_BUS_IDS[]{ + inline constexpr const char* SOUND_BUS_IDS[]{ "bus_reverb", "bus_fx", "bus_voice", @@ -125,8 +134,10 @@ namespace T6 "bus_movie", "bus_reference", }; + static_assert(std::extent_v == SND_BUS_COUNT); - inline const std::string SOUND_RANDOMIZE_TYPES[]{ + // From executable + inline constexpr const char* SOUND_RANDOMIZE_TYPES[]{ "", "volume", "pitch", diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp index 837be1b6..371187ab 100644 --- a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp @@ -72,7 +72,7 @@ bool AssetLoaderSoundBank::CanLoadFromRaw() const return true; } -size_t GetValueIndex(const std::string& value, const std::string* lookupTable, size_t len) +size_t GetValueIndex(const std::string& value, const char* const* lookupTable, const size_t len) { if (value.empty()) return 0; diff --git a/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp b/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp index 2263f6ef..0c60f554 100644 --- a/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp +++ b/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp @@ -5,12 +5,16 @@ #include "Sound/FlacDecoder.h" #include "Sound/WavTypes.h" #include "Utils/FileUtils.h" +#include "Utils/StringUtils.h" #include #include +#include #include #include +namespace fs = std::filesystem; + std::unordered_map INDEX_FOR_FRAMERATE{ {8000, 0}, {12000, 1}, @@ -32,6 +36,30 @@ class SoundBankWriterImpl : public SoundBankWriter inline static const std::string PAD_DATA = std::string(16, '\x00'); + class SoundBankEntryInfo + { + public: + SoundBankEntryInfo() + : m_sound_id(0u), + m_looping(false), + m_streamed(false) + { + } + + SoundBankEntryInfo(std::string filePath, const unsigned int soundId, const bool looping, const bool streamed) + : m_file_path(std::move(filePath)), + m_sound_id(soundId), + m_looping(looping), + m_streamed(streamed) + { + } + + std::string m_file_path; + unsigned int m_sound_id; + bool m_looping; + bool m_streamed; + }; + public: explicit SoundBankWriterImpl(std::string fileName, std::ostream& stream, ISearchPath* assetSearchPath) : m_file_name(std::move(fileName)), @@ -118,6 +146,100 @@ public: Write(&header, sizeof(header)); } + bool LoadWavFile(const SearchPathOpenFile& file, const SoundBankEntryInfo& sound, std::unique_ptr& soundData, size_t& soundSize) + { + WavHeader header{}; + file.m_stream->read(reinterpret_cast(&header), sizeof(WavHeader)); + + soundSize = static_cast(file.m_length - sizeof(WavHeader)); + const auto frameCount = soundSize / (header.formatChunk.nChannels * (header.formatChunk.wBitsPerSample / 8)); + const auto frameRateIndex = INDEX_FOR_FRAMERATE[header.formatChunk.nSamplesPerSec]; + + SoundAssetBankEntry entry{ + sound.m_sound_id, + soundSize, + static_cast(m_current_offset), + frameCount, + frameRateIndex, + static_cast(header.formatChunk.nChannels), + sound.m_looping, + 0, + }; + + m_entries.push_back(entry); + + soundData = std::make_unique(soundSize); + file.m_stream->read(soundData.get(), soundSize); + + return true; + } + + bool LoadFlacFile( + const SearchPathOpenFile& file, const std::string& filePath, const SoundBankEntryInfo& sound, std::unique_ptr& soundData, size_t& soundSize) + { + soundSize = static_cast(file.m_length); + + soundData = std::make_unique(soundSize); + file.m_stream->read(soundData.get(), soundSize); + + flac::FlacMetaData metaData; + if (flac::GetFlacMetaData(soundData.get(), soundSize, metaData)) + { + const auto frameRateIndex = INDEX_FOR_FRAMERATE[metaData.m_sample_rate]; + SoundAssetBankEntry entry{ + sound.m_sound_id, + soundSize, + static_cast(m_current_offset), + static_cast(metaData.m_total_samples), + frameRateIndex, + metaData.m_number_of_channels, + sound.m_looping, + 8, + }; + + m_entries.push_back(entry); + return true; + } + + std::cerr << std::format("Unable to decode .flac file for sound {}\n", filePath); + return false; + } + + bool LoadFileByExtension(const std::string& filePath, const SoundBankEntryInfo& sound, std::unique_ptr& soundData, size_t& soundSize) + { + auto extension = fs::path(filePath).extension().string(); + utils::MakeStringLowerCase(extension); + if (extension.empty()) + return false; + + const auto file = m_asset_search_path->Open(filePath); + if (!file.IsOpen()) + return false; + + if (extension == ".wav") + return LoadWavFile(file, sound, soundData, soundSize); + + if (extension == ".flac") + return LoadFlacFile(file, filePath, sound, soundData, soundSize); + + return false; + } + + bool GuessFilenameAndLoadFile(const std::string& filePath, const SoundBankEntryInfo& sound, std::unique_ptr& soundData, size_t& soundSize) + { + fs::path pathWithExtension = fs::path(filePath).replace_extension(".wav"); + auto file = m_asset_search_path->Open(pathWithExtension.string()); + if (file.IsOpen()) + return LoadWavFile(file, sound, soundData, soundSize); + + pathWithExtension = fs::path(filePath).replace_extension(".flac"); + file = m_asset_search_path->Open(pathWithExtension.string()); + if (file.IsOpen()) + return LoadFlacFile(file, pathWithExtension.string(), sound, soundData, soundSize); + + return false; + } + bool WriteEntries() { GoTo(DATA_OFFSET); @@ -125,85 +247,23 @@ public: for (auto& sound : m_sounds) { const auto& soundFilePath = sound.m_file_path; - const auto soundId = sound.m_sound_id; size_t soundSize; std::unique_ptr soundData; - // try to find a file for the sound path - const auto file = m_asset_search_path->Open(soundFilePath); - if (!file.IsOpen()) + if (!LoadFileByExtension(soundFilePath, sound, soundData, soundSize) && !GuessFilenameAndLoadFile(soundFilePath, sound, soundData, soundSize)) { - std::cerr << "Unable to find a compatible file for sound " << soundFilePath << "\n"; + std::cerr << std::format("Unable to find a compatible file for sound {}\n", soundFilePath); return false; } - // check if sound path ends in .wav - const std::string extension = ".wav"; - if (soundFilePath.size() >= extension.size() && soundFilePath.compare(soundFilePath.size() - extension.size(), extension.size(), extension) == 0) - { - WavHeader header{}; - file.m_stream->read(reinterpret_cast(&header), sizeof(WavHeader)); - - soundSize = static_cast(file.m_length - sizeof(WavHeader)); - const auto frameCount = soundSize / (header.formatChunk.nChannels * (header.formatChunk.wBitsPerSample / 8)); - const auto frameRateIndex = INDEX_FOR_FRAMERATE[header.formatChunk.nSamplesPerSec]; - - SoundAssetBankEntry entry{ - soundId, - soundSize, - static_cast(m_current_offset), - frameCount, - frameRateIndex, - static_cast(header.formatChunk.nChannels), - sound.m_looping, - 0, - }; - - m_entries.push_back(entry); - - soundData = std::make_unique(soundSize); - file.m_stream->read(soundData.get(), soundSize); - } - else - { - soundSize = static_cast(file.m_length); - - soundData = std::make_unique(soundSize); - file.m_stream->read(soundData.get(), soundSize); - - flac::FlacMetaData metaData; - if (flac::GetFlacMetaData(soundData.get(), soundSize, metaData)) - { - const auto frameRateIndex = INDEX_FOR_FRAMERATE[metaData.m_sample_rate]; - SoundAssetBankEntry entry{ - soundId, - soundSize, - static_cast(m_current_offset), - static_cast(metaData.m_total_samples), - frameRateIndex, - metaData.m_number_of_channels, - sound.m_looping, - 8, - }; - - m_entries.push_back(entry); - } - else - { - std::cerr << "Unable to decode .flac file for sound " << soundFilePath << "\n"; - return false; - } - } - const auto lastEntry = m_entries.rbegin(); if (!sound.m_streamed && lastEntry->frameRateIndex != 6) { - std::cout << "WARNING: Loaded sound \"" << soundFilePath - << "\" should have a framerate of 48000 but doesn't. This sound may not work on all games!\n"; + std::cout << std::format("WARNING: Loaded sound \"{}\" should have a framerate of 48000 but doesn't. This sound may not work on all games!\n", + soundFilePath); } - // calculate checksum SoundAssetBankChecksum checksum{}; const auto md5Crypt = Crypto::CreateMD5(); @@ -276,30 +336,6 @@ public: } private: - class SoundBankEntryInfo - { - public: - SoundBankEntryInfo() - : m_sound_id(0u), - m_looping(false), - m_streamed(false) - { - } - - SoundBankEntryInfo(std::string filePath, const unsigned int soundId, const bool looping, const bool streamed) - : m_file_path(std::move(filePath)), - m_sound_id(soundId), - m_looping(looping), - m_streamed(streamed) - { - } - - std::string m_file_path; - unsigned int m_sound_id; - bool m_looping; - bool m_streamed; - }; - std::string m_file_name; std::ostream& m_stream; ISearchPath* m_asset_search_path; diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp index 45dda9fd..d37b4905 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp @@ -8,6 +8,7 @@ #include "nlohmann/json.hpp" #include +#include #include #include @@ -124,7 +125,7 @@ namespace { std::unordered_map result; for (auto i = 0u; i < std::extent_v; i++) - result.emplace(T6::Common::SND_HashName(SOUND_CURVES[i].data()), SOUND_CURVES[i]); + result.emplace(T6::Common::SND_HashName(SOUND_CURVES[i]), SOUND_CURVES[i]); return result; } @@ -195,7 +196,7 @@ class AssetDumperSndBank::Internal stream.NextRow(); } - static const char* FindNameForDuck(unsigned int id, const SndBank* bank) + static const char* FindNameForDuck(const unsigned int id, const SndBank* bank) { for (auto i = 0u; i < bank->duckCount; i++) { @@ -208,7 +209,7 @@ class AssetDumperSndBank::Internal return ""; } - std::string ConvertSndFormatToExtension(snd_asset_format format) const + static const char* ExtensionForSndFormat(const snd_asset_format format) { switch (format) { @@ -219,17 +220,19 @@ class AssetDumperSndBank::Internal return ".flac"; default: + assert(false); return ""; } } - static void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias, std::string& extension, const SndBank* bank) + static void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias, const std::optional maybeFormat, const SndBank* bank) { // name stream.WriteColumn(alias->name); // file - stream.WriteColumn(alias->assetFileName ? alias->assetFileName + extension : ""); + const auto* extension = maybeFormat ? ExtensionForSndFormat(*maybeFormat) : ""; + stream.WriteColumn(alias->assetFileName ? std::format("{}{}", alias->assetFileName, extension) : ""); // template stream.WriteColumn(""); @@ -435,7 +438,7 @@ class AssetDumperSndBank::Internal const auto outFile = OpenAssetOutputFile(assetFileName, ".wav"); if (!outFile) { - std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; + std::cerr << std::format("Failed to open sound output file: \"{}\"\n", assetFileName); return; } @@ -462,7 +465,7 @@ class AssetDumperSndBank::Internal const auto outFile = OpenAssetOutputFile(assetFileName, extension); if (!outFile) { - std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; + std::cerr << std::format("Failed to open sound output file: \"{}\"\n", assetFileName); return; } @@ -499,33 +502,30 @@ class AssetDumperSndBank::Internal case SND_ASSET_FORMAT_WMA: case SND_ASSET_FORMAT_WIIUADPCM: case SND_ASSET_FORMAT_MPC: - std::cerr << "Cannot dump sound (Unsupported sound format " << format << "): \"" << alias.assetFileName << "\"\n"; + std::cerr << std::format("Cannot dump sound (Unknown sound format {}): \"{}\"\n", static_cast(format), alias.assetFileName); break; default: assert(false); - std::cerr << "Cannot dump sound (Unknown sound format " << format << "): \"" << alias.assetFileName << "\"\n"; + std::cerr << std::format("Cannot dump sound (Unknown sound format {}): \"{}\"\n", static_cast(format), alias.assetFileName); break; } return format; } - else - { - std::cerr << "Could not find data for sound \"" << alias.assetFileName << "\"\n"; - } + std::cerr << std::format("Could not find data for sound \"{}\"\n", alias.assetFileName); return {}; } void DumpSndBankAliases(const SndBank* sndBank) const { - std::unordered_map dumpedAssets; + std::unordered_map> dumpedAssets; - const auto outFile = OpenAssetOutputFile("soundbank/" + std::string(sndBank->name) + ".aliases", ".csv"); + const auto outFile = OpenAssetOutputFile(std::format("soundbank/{}.aliases", sndBank->name), ".csv"); if (!outFile) { - std::cerr << "Failed to open sound alias output file: \"" << sndBank->name << "\"\n"; + std::cerr << std::format("Failed to open sound alias output file: \"\"\n", sndBank->name); return; } @@ -539,28 +539,23 @@ class AssetDumperSndBank::Internal for (auto j = 0; j < aliasList.count; j++) { const auto& alias = aliasList.head[j]; - std::string extension = ""; + std::optional maybeFormat; if (alias.assetId && alias.assetFileName) { - if (dumpedAssets.find(alias.assetId) == dumpedAssets.end()) + const auto previouslyDeterminedFormat = dumpedAssets.find(alias.assetId); + if (previouslyDeterminedFormat == dumpedAssets.end()) { - std::optional format = DumpSndAlias(alias); - - if (format.has_value()) - { - extension = ConvertSndFormatToExtension(format.value()); - } - - dumpedAssets[alias.assetId] = extension; + maybeFormat = DumpSndAlias(alias); + dumpedAssets[alias.assetId] = maybeFormat; } else { - extension = dumpedAssets[alias.assetId]; + maybeFormat = previouslyDeterminedFormat->second; } } - WriteAliasToFile(csvStream, &alias, extension, sndBank); + WriteAliasToFile(csvStream, &alias, maybeFormat, sndBank); csvStream.NextRow(); } } @@ -569,14 +564,12 @@ class AssetDumperSndBank::Internal void DumpSoundRadverb(const SndBank* sndBank) const { if (sndBank->radverbCount <= 0) - { return; - } - const auto outFile = OpenAssetOutputFile("soundbank/" + std::string(sndBank->name) + ".reverbs", ".csv"); + const auto outFile = OpenAssetOutputFile(std::format("soundbank/{}.reverbs", sndBank->name), ".csv"); if (!outFile) { - std::cerr << "Failed to open sound reverb output file: \"" << sndBank->name << "\"\n"; + std::cerr << std::format("Failed to open sound reverb output file: \"{}\"\n", sndBank->name); return; } @@ -610,14 +603,12 @@ class AssetDumperSndBank::Internal void DumpSoundDucks(const SndBank* sndBank) const { if (sndBank->duckCount <= 0) - { return; - } - const auto outFile = OpenAssetOutputFile("soundbank/" + std::string(sndBank->name) + ".ducklist", ".csv"); + const auto outFile = OpenAssetOutputFile(std::format("soundbank/{}.ducklist", sndBank->name), ".csv"); if (!outFile) { - std::cerr << "Failed to open sound reverb output file: \"" << sndBank->name << "\"\n"; + std::cerr << std::format("Failed to open sound reverb output file: \"{}\"\n", sndBank->name); return; } @@ -631,10 +622,10 @@ class AssetDumperSndBank::Internal csvStream.WriteColumn(duck.name); csvStream.NextRow(); - const auto duckFile = OpenAssetOutputFile("soundbank/ducks/" + std::string(duck.name), ".duk"); + const auto duckFile = OpenAssetOutputFile(std::format("soundbank/ducks/{}", duck.name), ".duk"); if (!outFile) { - std::cerr << "Failed to open sound duck output file: \"" << duck.name << "\"\n"; + std::cerr << std::format("Failed to open sound duck output file: \"{}\"\n", duck.name); return; } @@ -661,12 +652,12 @@ class AssetDumperSndBank::Internal } auto values = std::vector{}; - for (auto i = 0u; i < 32u; i++) + for (auto j = 0u; j < 32u; j++) { values.push_back({ - {"duckGroup", SOUND_DUCK_GROUPS[i]}, - {"attenuation", duck.attenuation[i] }, - {"filter", duck.filter[i] } + {"duckGroup", SOUND_DUCK_GROUPS[j]}, + {"attenuation", duck.attenuation[j] }, + {"filter", duck.filter[j] } }); }