diff --git a/src/ObjCommon/Csv/CsvHeaderRow.cpp b/src/ObjCommon/Csv/CsvHeaderRow.cpp new file mode 100644 index 00000000..bb17aa46 --- /dev/null +++ b/src/ObjCommon/Csv/CsvHeaderRow.cpp @@ -0,0 +1,34 @@ +#include "Csv/CsvHeaderRow.h" + +CsvHeaderRow::CsvHeaderRow() = default; + +bool CsvHeaderRow::Read(const CsvInputStream& inputStream) +{ + if (!m_header_row.empty()) + return false; + + return inputStream.NextRow(m_header_row); +} + +const std::string& CsvHeaderRow::HeaderNameForColumn(const unsigned columnIndex) const +{ + return m_header_row[columnIndex]; +} + +bool CsvHeaderRow::RequireIndexForHeader(const std::string& headerName, unsigned& out) const +{ + const auto existingHeader = std::ranges::find(m_header_row, headerName); + if (existingHeader == m_header_row.end()) + return false; + + out = std::distance(m_header_row.begin(), existingHeader); + return true; +} + +std::optional CsvHeaderRow::GetIndexForHeader(const std::string& headerName) const +{ + unsigned result; + if (!RequireIndexForHeader(headerName, result)) + return std::nullopt; + return result; +} diff --git a/src/ObjCommon/Csv/CsvHeaderRow.h b/src/ObjCommon/Csv/CsvHeaderRow.h new file mode 100644 index 00000000..e3ab3f65 --- /dev/null +++ b/src/ObjCommon/Csv/CsvHeaderRow.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Csv/CsvStream.h" + +#include +#include +#include + +class CsvHeaderRow +{ +public: + CsvHeaderRow(); + + bool Read(const CsvInputStream& inputStream); + + const std::string& HeaderNameForColumn(unsigned columnIndex) const; + bool RequireIndexForHeader(const std::string& headerName, unsigned& out) const; + [[nodiscard]] std::optional GetIndexForHeader(const std::string& headerName) const; + +private: + std::vector m_header_row; +}; diff --git a/src/ObjCommon/Csv/CsvStream.cpp b/src/ObjCommon/Csv/CsvStream.cpp index cd01eed6..2b434db2 100644 --- a/src/ObjCommon/Csv/CsvStream.cpp +++ b/src/ObjCommon/Csv/CsvStream.cpp @@ -1,14 +1,86 @@ #include "CsvStream.h" +#include #include constexpr char CSV_SEPARATOR = ','; +CsvCell::CsvCell(std::string value) + : m_value(std::move(value)) +{ +} + +bool CsvCell::AsFloat(float& out) const +{ + const char* startPtr = m_value.c_str(); + char* endPtr; + out = std::strtof(startPtr, &endPtr); + + if (endPtr == startPtr) + return false; + + for (const auto* c = endPtr; *c; c++) + { + if (!isspace(*c)) + return false; + } + + return true; +} + +bool CsvCell::AsInt32(int32_t& out) const +{ + const char* startPtr = m_value.c_str(); + char* endPtr; + out = std::strtol(startPtr, &endPtr, 0); + + if (endPtr == startPtr) + return false; + + for (const auto* c = endPtr; *c; c++) + { + if (!isspace(*c)) + return false; + } + + return true; +} + +bool CsvCell::AsUInt32(uint32_t& out) const +{ + const char* startPtr = m_value.c_str(); + char* endPtr; + out = std::strtoul(startPtr, &endPtr, 0); + + if (endPtr == startPtr) + return false; + + for (const auto* c = endPtr; *c; c++) + { + if (!isspace(*c)) + return false; + } + + return true; +} + CsvInputStream::CsvInputStream(std::istream& stream) : m_stream(stream) { } +bool CsvInputStream::NextRow(std::vector& out) const +{ + if (!out.empty()) + out.clear(); + + return EmitNextRow( + [&out](std::string value) + { + out.emplace_back(std::move(value)); + }); +} + bool CsvInputStream::NextRow(std::vector& out) const { if (!out.empty()) diff --git a/src/ObjCommon/Csv/CsvStream.h b/src/ObjCommon/Csv/CsvStream.h index 2524ec41..5c0f2def 100644 --- a/src/ObjCommon/Csv/CsvStream.h +++ b/src/ObjCommon/Csv/CsvStream.h @@ -1,16 +1,30 @@ #pragma once #include "Utils/MemoryManager.h" +#include #include #include #include #include +class CsvCell +{ +public: + explicit CsvCell(std::string value); + + bool AsFloat(float& out) const; + bool AsInt32(int32_t& out) const; + bool AsUInt32(uint32_t& out) const; + + std::string m_value; +}; + class CsvInputStream { public: explicit CsvInputStream(std::istream& stream); + bool NextRow(std::vector& out) const; bool NextRow(std::vector& out) const; bool NextRow(std::vector& out, MemoryManager& memory) const; diff --git a/src/ObjCommon/Csv/ParsedCsv.cpp b/src/ObjCommon/Csv/ParsedCsv.cpp deleted file mode 100644 index fe9c1709..00000000 --- a/src/ObjCommon/Csv/ParsedCsv.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "Csv/ParsedCsv.h" - -ParsedCsvRow::ParsedCsvRow(std::unordered_map& headers, std::vector row) - : headers(headers), - values(std::move(row)) -{ -} - -std::string ParsedCsvRow::GetValue(const std::string& header, const bool required) const -{ - if (this->headers.find(header) == this->headers.end()) - { - if (required) - std::cerr << "ERROR: Required column \"" << header << "\" was not found\n"; - else - std::cerr << "WARNING: Expected column \"" << header << "\" was not found\n"; - - return {}; - } - - auto& value = this->values.at(this->headers[header]); - if (required && value.empty()) - { - std::cerr << "ERROR: Required column \"" << header << "\" does not have a value\n"; - return {}; - } - - return value; -} - -float ParsedCsvRow::GetValueFloat(const std::string& header, const bool required) const -{ - const auto& value = this->GetValue(header, required); - if (!value.empty()) - { - std::istringstream ss(value); - float out; - ss >> out; - return out; - } - - return {}; -} - -ParsedCsv::ParsedCsv(const CsvInputStream& inputStream, const bool hasHeaders) -{ - std::vector> csvLines; - std::vector currentLine; - - while (inputStream.NextRow(currentLine)) - { - csvLines.emplace_back(std::move(currentLine)); - currentLine = std::vector(); - } - - if (hasHeaders) - { - const auto& headersRow = csvLines[0]; - for (auto i = 0u; i < headersRow.size(); i++) - { - this->headers[headersRow[i]] = i; - } - } - - for (auto i = hasHeaders ? 1u : 0u; i < csvLines.size(); i++) - { - auto& rowValues = csvLines[i]; - this->rows.emplace_back(this->headers, std::move(rowValues)); - } -} - -size_t ParsedCsv::Size() const -{ - return this->rows.size(); -} - -ParsedCsvRow ParsedCsv::operator[](const size_t index) const -{ - return this->rows.at(index); -} diff --git a/src/ObjCommon/Csv/ParsedCsv.h b/src/ObjCommon/Csv/ParsedCsv.h deleted file mode 100644 index 6913c80c..00000000 --- a/src/ObjCommon/Csv/ParsedCsv.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "Csv/CsvStream.h" -#include "Utils/ClassUtils.h" - -#include -#include - -class ParsedCsvRow -{ - std::unordered_map& headers; - std::vector values; - -public: - explicit ParsedCsvRow(std::unordered_map& headers, std::vector row); - _NODISCARD std::string GetValue(const std::string& header, bool required = false) const; - _NODISCARD float GetValueFloat(const std::string& header, bool required = false) const; - - template T GetValueInt(const std::string& header, const bool required = false) const - { - const auto& value = this->GetValue(header, required); - if (!value.empty()) - { - std::istringstream ss(value); - long long out; - ss >> out; - return static_cast(out); - } - - return {}; - } -}; - -class ParsedCsv -{ - std::unordered_map headers; - std::vector rows; - -public: - explicit ParsedCsv(const CsvInputStream& inputStream, bool hasHeaders = true); - - _NODISCARD size_t Size() const; - - ParsedCsvRow operator[](size_t index) const; -}; diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp index ccffc7e2..76ddc2ce 100644 --- a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp @@ -1,6 +1,7 @@ #include "AssetLoaderSoundBank.h" -#include "Csv/ParsedCsv.h" +#include "Csv/CsvHeaderRow.h" +#include "Csv/CsvStream.h" #include "Game/T6/CommonT6.h" #include "Game/T6/SoundConstantsT6.h" #include "Game/T6/T6.h" @@ -25,7 +26,7 @@ namespace "devraw/", }; - _NODISCARD std::string GetSoundFilePath(const SndAlias* sndAlias) + [[nodiscard]] std::string GetSoundFilePath(const SndAlias* sndAlias) { std::string soundFilePath(sndAlias->assetFileName); @@ -42,7 +43,7 @@ namespace return soundFilePath; } - _NODISCARD std::unique_ptr OpenSoundBankOutputFile(const std::string& bankName) + [[nodiscard]] std::unique_ptr OpenSoundBankOutputFile(const std::string& bankName) { fs::path assetPath = SoundBankWriter::OutputPath / bankName; @@ -60,6 +61,887 @@ namespace return nullptr; } + + size_t GetValueIndex(const std::string& value, const char* const* lookupTable, const size_t len) + { + if (value.empty()) + return 0; + + for (auto i = 0u; i < len; i++) + { + if (lookupTable[i] == value) + return i; + } + + return 0; + } + + float DbsplToLinear(const float dbsplValue) + { + return std::pow(10.0f, (dbsplValue - 100.0f) / 20.0f); + } + + float CentToHertz(const float cents) + { + return std::pow(2.0f, cents / 1200.0f); + } + + bool ReadColumnString(const std::vector& row, const unsigned columnIndex, const char*& value, MemoryManager& memory) + { + const auto& cell = row[columnIndex]; + if (!cell.m_value.empty()) + value = memory.Dup(cell.m_value.c_str()); + else + value = nullptr; + + return true; + } + + bool ReadColumnVolumeDbspl( + const CsvHeaderRow& headerRow, const std::vector& row, const unsigned columnIndex, const unsigned rowIndex, uint16_t& value) + { + const auto& cell = row[columnIndex]; + + float dbsplValue; + if (!cell.AsFloat(dbsplValue)) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - Must be a float\n", rowIndex + 1, colName); + return false; + } + + if (dbsplValue < 0.0f || dbsplValue > 100.0f) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {} [0.0, 100.0]\n", rowIndex + 1, colName, dbsplValue); + return false; + } + + value = static_cast(DbsplToLinear(dbsplValue) * static_cast(std::numeric_limits::max())); + + return true; + } + + bool ReadColumnPitchCents( + const CsvHeaderRow& headerRow, const std::vector& row, const unsigned columnIndex, const unsigned rowIndex, uint16_t& value) + { + const auto& cell = row[columnIndex]; + + float centValue; + if (!cell.AsFloat(centValue)) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - Must be a float\n", rowIndex + 1, colName); + return false; + } + + if (centValue < -2400.0f || centValue > 1200.0f) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {} [-2400.0, 1200.0]\n", rowIndex + 1, colName, centValue); + return false; + } + + value = static_cast(CentToHertz(centValue) * static_cast(std::numeric_limits::max())); + + return true; + } + + bool ReadColumnUInt8(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint8_t& value, + uint8_t min = std::numeric_limits::min(), + uint8_t max = std::numeric_limits::max()) + { + const auto& cell = row[columnIndex]; + + uint32_t value32; + if (!cell.AsUInt32(value32)) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - Must be a uint\n", rowIndex + 1, colName); + return false; + } + + if (value32 < min || value32 > max) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {} [{}, {}]\n", rowIndex + 1, colName, value32, min, max); + return false; + } + + value = static_cast(value32); + + return true; + } + + bool ReadColumnInt16(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + int16_t& value, + int16_t min = std::numeric_limits::min(), + int16_t max = std::numeric_limits::max()) + { + const auto& cell = row[columnIndex]; + + int32_t value32; + if (!cell.AsInt32(value32)) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - Must be a int\n", rowIndex + 1, colName); + return false; + } + + if (value32 < min || value32 > max) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {} [{}, {}]\n", rowIndex + 1, colName, value32, min, max); + return false; + } + + value = static_cast(value32); + + return true; + } + + bool ReadColumnUInt16(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint16_t& value, + uint16_t min = std::numeric_limits::min(), + uint16_t max = std::numeric_limits::max()) + { + const auto& cell = row[columnIndex]; + + uint32_t value32; + if (!cell.AsUInt32(value32)) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - Must be a uint\n", rowIndex + 1, colName); + return false; + } + + if (value32 < min || value32 > max) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {} [{}, {}]\n", rowIndex + 1, colName, value32, min, max); + return false; + } + + value = static_cast(value32); + + return true; + } + + bool ReadColumnNormByte(const CsvHeaderRow& headerRow, const std::vector& row, const unsigned columnIndex, const unsigned rowIndex, uint8_t& value) + { + const auto& cell = row[columnIndex]; + + float valueFloat; + if (!cell.AsFloat(valueFloat)) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - Must be a float\n", rowIndex + 1, colName); + return false; + } + + if (valueFloat < 0.0f || valueFloat > 1.0f) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {} [0.0, 1.0]\n", rowIndex + 1, colName, valueFloat); + return false; + } + + value = static_cast(valueFloat * static_cast(std::numeric_limits::max())); + + return true; + } + + bool ReadColumnHash(const std::vector& row, const unsigned columnIndex, unsigned& value) + { + const auto& cell = row[columnIndex]; + + if (cell.m_value.empty()) + { + value = 0u; + return true; + } + + if (cell.m_value[0] == '@') + value = std::strtoul(&cell.m_value[1], nullptr, 16); + else + value = Common::SND_HashName(cell.m_value.c_str()); + + return true; + } + + bool ReadColumnEnum_(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint8_t& value, + const char* const* enumValues, + const size_t enumValueCount) + { + const auto& cell = row[columnIndex]; + + assert(enumValueCount <= std::numeric_limits::max()); + for (auto i = 0u; i < enumValueCount; i++) + { + if (cell.m_value == enumValues[i]) + { + value = static_cast(i); + return true; + } + } + + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {}. Must be one of:\n", rowIndex + 1, colName, cell.m_value); + for (auto i = 0u; i < enumValueCount; i++) + std::cerr << std::format(" {}\n", enumValues[i]); + + return false; + } + + template + bool ReadColumnEnum(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint8_t& value, + const char* const (&enumValues)[Size]) + { + return ReadColumnEnum_(headerRow, row, columnIndex, rowIndex, value, enumValues, Size); + } + + bool ReadColumnEnumFlags_(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint8_t& value, + const char* const* enumValues, + const size_t enumValueCount) + { + const auto& cell = row[columnIndex]; + const auto entries = utils::StringSplit(cell.m_value, ' '); + + assert(enumValueCount <= std::numeric_limits::max()); + + value = 0u; + for (const auto& entry : entries) + { + if (entry.empty()) + continue; + + auto foundValue = false; + for (auto i = 0u; i < enumValueCount; i++) + { + if (entry == enumValues[i]) + { + value |= static_cast(1 << i); + foundValue = true; + break; + } + } + + if (!foundValue) + { + const auto& colName = headerRow.HeaderNameForColumn(columnIndex); + std::cerr << std::format("Invalid value for row {} col '{}' - {}. Must be one of:\n", rowIndex + 1, colName, entry); + for (auto i = 0u; i < enumValueCount; i++) + std::cerr << std::format(" {}\n", enumValues[i]); + return false; + } + } + + return true; + } + + template + bool ReadColumnEnumFlags(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint8_t& value, + const char* const (&enumValues)[Size]) + { + return ReadColumnEnumFlags_(headerRow, row, columnIndex, rowIndex, value, enumValues, Size); + } + + class SoundAliasHeaders + { + public: + SoundAliasHeaders() = default; + + bool From(const CsvHeaderRow& headerRow) + { + // clang-format off + return headerRow.RequireIndexForHeader("Name", m_name) + && headerRow.RequireIndexForHeader("FileSource", m_file_source) + && headerRow.RequireIndexForHeader("Secondary", m_secondary) + && headerRow.RequireIndexForHeader("Subtitle", m_subtitle) + && headerRow.RequireIndexForHeader("VolMin", m_vol_min) + && headerRow.RequireIndexForHeader("VolMax", m_vol_max) + && headerRow.RequireIndexForHeader("PitchMin", m_pitch_min) + && headerRow.RequireIndexForHeader("PitchMax", m_pitch_max) + && headerRow.RequireIndexForHeader("DistMin", m_dist_min) + && headerRow.RequireIndexForHeader("DistMaxDry", m_dist_max_dry) + && headerRow.RequireIndexForHeader("DistMaxWet", m_dist_max_wet) + && headerRow.RequireIndexForHeader("Probability", m_probability) + && headerRow.RequireIndexForHeader("EnvelopMin", m_envelop_min) + && headerRow.RequireIndexForHeader("EnvelopMax", m_envelop_max) + && headerRow.RequireIndexForHeader("EnvelopPercentage", m_envelop_percentage) + && headerRow.RequireIndexForHeader("CenterSend", m_center_send) + && headerRow.RequireIndexForHeader("ReverbSend", m_reverb_send) + && headerRow.RequireIndexForHeader("StartDelay", m_start_delay) + && headerRow.RequireIndexForHeader("PriorityThresholdMin", m_priority_threshold_min) + && headerRow.RequireIndexForHeader("PriorityThresholdMax", m_priority_threshold_max) + && headerRow.RequireIndexForHeader("OcclusionLevel", m_occlusion_level) + && headerRow.RequireIndexForHeader("FluxTime", m_flux_time) + && headerRow.RequireIndexForHeader("Duck", m_duck) + && headerRow.RequireIndexForHeader("PriorityMin", m_priority_min) + && headerRow.RequireIndexForHeader("PriorityMax", m_priority_max) + && headerRow.RequireIndexForHeader("LimitCount", m_limit_count) + && headerRow.RequireIndexForHeader("EntityLimitCount", m_entity_limit_count) + && headerRow.RequireIndexForHeader("DryMinCurve", m_dry_min_curve) + && headerRow.RequireIndexForHeader("DryMaxCurve", m_dry_max_curve) + && headerRow.RequireIndexForHeader("WetMinCurve", m_wet_min_curve) + && headerRow.RequireIndexForHeader("WetMaxCurve", m_wet_max_curve) + && headerRow.RequireIndexForHeader("Pan", m_pan) + && headerRow.RequireIndexForHeader("DuckGroup", m_duck_group) + && headerRow.RequireIndexForHeader("ContextType", m_context_type) + && headerRow.RequireIndexForHeader("ContextValue", m_context_value) + && headerRow.RequireIndexForHeader("FadeIn", m_fade_in) + && headerRow.RequireIndexForHeader("FadeOut", m_fade_out) + && headerRow.RequireIndexForHeader("StopOnPlay", m_stop_on_play) + && headerRow.RequireIndexForHeader("DopplerScale", m_doppler_scale) + && headerRow.RequireIndexForHeader("FutzPatch", m_futz_patch) + && headerRow.RequireIndexForHeader("LimitType", m_limit_type) + && headerRow.RequireIndexForHeader("EntityLimitType", m_entity_limit_type) + && headerRow.RequireIndexForHeader("RandomizeType", m_randomize_type) + && headerRow.RequireIndexForHeader("FluxType", m_flux_type) + && headerRow.RequireIndexForHeader("Storage", m_storage) + && headerRow.RequireIndexForHeader("VolumeGroup", m_volume_group) + && headerRow.RequireIndexForHeader("DistanceLpf", m_distance_lpf) + && headerRow.RequireIndexForHeader("Doppler", m_doppler) + && headerRow.RequireIndexForHeader("IsBig", m_is_big) + && headerRow.RequireIndexForHeader("Looping", m_looping) + && headerRow.RequireIndexForHeader("PanType", m_pan_type) + && headerRow.RequireIndexForHeader("IsMusic", m_is_music) + && headerRow.RequireIndexForHeader("Timescale", m_timescale) + && headerRow.RequireIndexForHeader("Pauseable", m_pauseable) + && headerRow.RequireIndexForHeader("StopOnEntDeath", m_stop_on_ent_death) + && headerRow.RequireIndexForHeader("Bus", m_bus) + && headerRow.RequireIndexForHeader("VoiceLimit", m_voice_limit) + && headerRow.RequireIndexForHeader("IgnoreMaxDist", m_ignore_max_dist) + && headerRow.RequireIndexForHeader("NeverPlayTwice", m_never_play_twice) + && headerRow.RequireIndexForHeader("IsCinematic", m_is_cinematic); + // clang-format on + } + + unsigned m_name; + unsigned m_file_source; + unsigned m_secondary; + unsigned m_subtitle; + unsigned m_vol_min; + unsigned m_vol_max; + unsigned m_pitch_min; + unsigned m_pitch_max; + unsigned m_dist_min; + unsigned m_dist_max_dry; + unsigned m_dist_max_wet; + unsigned m_probability; + unsigned m_envelop_min; + unsigned m_envelop_max; + unsigned m_envelop_percentage; + unsigned m_center_send; + unsigned m_reverb_send; + unsigned m_start_delay; + unsigned m_priority_threshold_min; + unsigned m_priority_threshold_max; + unsigned m_occlusion_level; + unsigned m_flux_time; + unsigned m_duck; + unsigned m_priority_min; + unsigned m_priority_max; + unsigned m_limit_count; + unsigned m_entity_limit_count; + unsigned m_dry_min_curve; + unsigned m_dry_max_curve; + unsigned m_wet_min_curve; + unsigned m_wet_max_curve; + unsigned m_pan; + unsigned m_duck_group; + unsigned m_context_type; + unsigned m_context_value; + unsigned m_fade_in; + unsigned m_fade_out; + unsigned m_stop_on_play; + unsigned m_doppler_scale; + unsigned m_futz_patch; + unsigned m_limit_type; + unsigned m_entity_limit_type; + unsigned m_randomize_type; + unsigned m_flux_type; + unsigned m_storage; + unsigned m_volume_group; + unsigned m_distance_lpf; + unsigned m_doppler; + unsigned m_is_big; + unsigned m_looping; + unsigned m_pan_type; + unsigned m_is_music; + unsigned m_timescale; + unsigned m_pauseable; + unsigned m_stop_on_ent_death; + unsigned m_bus; + unsigned m_voice_limit; + unsigned m_ignore_max_dist; + unsigned m_never_play_twice; + unsigned m_is_cinematic; + }; + + bool LoadSoundAlias(SndAlias& alias, + const std::vector& row, + const unsigned int rowIndex, + const CsvHeaderRow& headerRow, + const SoundAliasHeaders& headers, + MemoryManager& memory) + { + memset(&alias, 0, sizeof(SndAlias)); + + const auto& name = row[headers.m_name]; + if (name.m_value.empty()) + return false; + + alias.name = memory.Dup(name.m_value.c_str()); + alias.id = Common::SND_HashName(alias.name); + + const auto& aliasFileName = row[headers.m_file_source]; + if (!aliasFileName.m_value.empty()) + { + + fs::path p(aliasFileName.m_value); + p.replace_extension(); + alias.assetFileName = memory.Dup(p.string().c_str()); + alias.assetId = Common::SND_HashName(alias.assetFileName); + } + + uint8_t dryMinCurve = 0u, dryMaxCurve = 0u, wetMinCurve = 0u, wetMaxCurve = 0u, limitType = 0u, entityLimitType = 0u, randomizeType = 0u, fluxType = 0u, + storage = 0u, volumeGroup = 0u, distanceLpf = 0u, doppler = 0u, isBig = 0u, looping = 0u, panType = 0u, isMusic = 0u, timescale = 0u, + pausable = 0u, stopOnEntDeath = 0u, busType = 0u, voiceLimit = 0u, ignoreMaxDist = 0u, neverPlayTwice = 0u, isCinematic = 0u; + // clang-format off + const auto couldReadSoundAlias = + ReadColumnString(row, headers.m_secondary, alias.secondaryName, memory) + && ReadColumnString(row, headers.m_subtitle, alias.subtitle, memory) + && ReadColumnVolumeDbspl(headerRow, row, headers.m_vol_min, rowIndex, alias.volMin) + && ReadColumnVolumeDbspl(headerRow, row, headers.m_vol_max, rowIndex, alias.volMax) + && ReadColumnPitchCents(headerRow, row, headers.m_pitch_min, rowIndex, alias.pitchMin) + && ReadColumnPitchCents(headerRow, row, headers.m_pitch_max, rowIndex, alias.pitchMax) + && ReadColumnUInt16(headerRow, row, headers.m_dist_min, rowIndex, alias.distMin) + && ReadColumnUInt16(headerRow, row, headers.m_dist_max_dry, rowIndex, alias.distMax) + && ReadColumnUInt16(headerRow, row, headers.m_dist_max_wet, rowIndex, alias.distReverbMax) + && ReadColumnNormByte(headerRow, row, headers.m_probability, rowIndex, alias.probability) + && ReadColumnUInt16(headerRow, row, headers.m_envelop_min, rowIndex, alias.envelopMin) + && ReadColumnUInt16(headerRow, row, headers.m_envelop_max, rowIndex, alias.envelopMax) + && ReadColumnVolumeDbspl(headerRow, row, headers.m_envelop_percentage, rowIndex, alias.envelopPercentage) + && ReadColumnVolumeDbspl(headerRow, row, headers.m_center_send, rowIndex, alias.centerSend) + && ReadColumnVolumeDbspl(headerRow, row, headers.m_reverb_send, rowIndex, alias.reverbSend) + && ReadColumnUInt16(headerRow, row, headers.m_start_delay, rowIndex, alias.startDelay) + && ReadColumnNormByte(headerRow, row, headers.m_priority_threshold_min, rowIndex, alias.minPriorityThreshold) + && ReadColumnNormByte(headerRow, row, headers.m_priority_threshold_max, rowIndex, alias.maxPriorityThreshold) + && ReadColumnNormByte(headerRow, row, headers.m_occlusion_level, rowIndex, alias.occlusionLevel) + && ReadColumnUInt16(headerRow, row, headers.m_flux_time, rowIndex, alias.fluxTime) + && ReadColumnHash(row, headers.m_duck, alias.duck) + && ReadColumnUInt8(headerRow, row, headers.m_priority_min, rowIndex, alias.minPriority, 0u, 128u) + && ReadColumnUInt8(headerRow, row, headers.m_priority_max, rowIndex, alias.maxPriority, 0u, 128u) + && ReadColumnUInt8(headerRow, row, headers.m_limit_count, rowIndex, alias.limitCount, 0u, 128u) + && ReadColumnUInt8(headerRow, row, headers.m_entity_limit_count, rowIndex, alias.entityLimitCount, 0u, 128u) + && ReadColumnEnum(headerRow, row, headers.m_dry_min_curve, rowIndex, dryMinCurve, SOUND_CURVES) + && ReadColumnEnum(headerRow, row, headers.m_dry_max_curve, rowIndex, dryMaxCurve, SOUND_CURVES) + && ReadColumnEnum(headerRow, row, headers.m_wet_min_curve, rowIndex, wetMinCurve, SOUND_CURVES) + && ReadColumnEnum(headerRow, row, headers.m_wet_max_curve, rowIndex, wetMaxCurve, SOUND_CURVES) + && ReadColumnEnum(headerRow, row, headers.m_pan, rowIndex, alias.pan, SOUND_PANS) + && ReadColumnEnum(headerRow, row, headers.m_duck_group, rowIndex, alias.duckGroup, SOUND_DUCK_GROUPS) + && ReadColumnHash(row, headers.m_context_type, alias.contextType) + && ReadColumnHash(row, headers.m_context_value, alias.contextValue) + && ReadColumnInt16(headerRow, row, headers.m_fade_in, rowIndex, alias.fadeIn, 0) + && ReadColumnInt16(headerRow, row, headers.m_fade_out, rowIndex, alias.fadeOut, 0) + && ReadColumnHash(row, headers.m_stop_on_play, alias.stopOnPlay) + && ReadColumnInt16(headerRow, row, headers.m_doppler_scale, rowIndex, alias.dopplerScale, -100, 100) + && ReadColumnHash(row, headers.m_futz_patch, alias.futzPatch) + && ReadColumnEnum(headerRow, row, headers.m_limit_type, rowIndex, limitType, SOUND_LIMIT_TYPES) + && ReadColumnEnum(headerRow, row, headers.m_entity_limit_type, rowIndex, entityLimitType, SOUND_LIMIT_TYPES) + && ReadColumnEnumFlags(headerRow, row, headers.m_randomize_type, rowIndex, randomizeType, SOUND_RANDOMIZE_TYPES) + && ReadColumnEnum(headerRow, row, headers.m_flux_type, rowIndex, fluxType, SOUND_MOVE_TYPES) + && ReadColumnEnum(headerRow, row, headers.m_storage, rowIndex, storage, SOUND_LOAD_TYPES) + && ReadColumnEnum(headerRow, row, headers.m_volume_group, rowIndex, volumeGroup, SOUND_GROUPS) + && ReadColumnEnum(headerRow, row, headers.m_distance_lpf, rowIndex, distanceLpf, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_doppler, rowIndex, doppler, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_is_big, rowIndex, isBig, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_looping, rowIndex, looping, SOUND_LOOP_TYPES) + && ReadColumnEnum(headerRow, row, headers.m_pan_type, rowIndex, panType, SOUND_PAN_TYPES) + && ReadColumnEnum(headerRow, row, headers.m_is_music, rowIndex, isMusic, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_timescale, rowIndex, timescale, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_pauseable, rowIndex, pausable, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_stop_on_ent_death, rowIndex, stopOnEntDeath, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_bus, rowIndex, busType, SOUND_BUS_IDS) + && ReadColumnEnum(headerRow, row, headers.m_voice_limit, rowIndex, voiceLimit, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_ignore_max_dist, rowIndex, ignoreMaxDist, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_never_play_twice, rowIndex, neverPlayTwice, SOUND_NO_YES) + && ReadColumnEnum(headerRow, row, headers.m_is_cinematic, rowIndex, isCinematic, SOUND_NO_YES) + ; + // clang-format on + + if (!couldReadSoundAlias) + return false; + + alias.flags.volumeMinFalloffCurve = dryMinCurve; + alias.flags.volumeFalloffCurve = dryMaxCurve; + alias.flags.reverbMinFalloffCurve = wetMinCurve; + alias.flags.reverbFalloffCurve = wetMaxCurve; + alias.flags.limitType = limitType; + alias.flags.entityLimitType = entityLimitType; + alias.flags.randomizeType = randomizeType; + alias.flags.fluxType = fluxType; + alias.flags.loadType = storage; + alias.flags.volumeGroup = volumeGroup; + alias.flags.distanceLpf = distanceLpf; + alias.flags.doppler = doppler; + alias.flags.isBig = isBig; + alias.flags.looping = looping; + alias.flags.panType = panType; + alias.flags.isMusic = isMusic; + alias.flags.timescale = timescale; + alias.flags.pausable = pausable; + alias.flags.stopOnEntDeath = stopOnEntDeath; + alias.flags.busType = busType; + alias.flags.voiceLimit = voiceLimit; + alias.flags.ignoreMaxDist = ignoreMaxDist; + alias.flags.neverPlayTwice = neverPlayTwice; + alias.flags.isCinematic = isCinematic; + + return true; + } + + SndAliasList CreateAliasList(std::vector& aliases, MemoryManager& memory) + { + SndAliasList aliasList; + aliasList.sequence = 0; + aliasList.count = static_cast(aliases.size()); + if (aliasList.count > 0) + { + aliasList.head = memory.Alloc(aliasList.count); + memcpy(aliasList.head, aliases.data(), sizeof(SndAlias) * aliasList.count); + + aliasList.id = aliasList.head[0].id; + aliasList.name = aliasList.head[0].name; + } + else + { + aliasList.id = 0u; + aliasList.name = nullptr; + aliasList.head = nullptr; + } + + aliases.clear(); + + return aliasList; + } + + bool LoadSoundAliasList(MemoryManager& memory, SndBank* sndBank, const SearchPathOpenFile& file, unsigned& loadedEntryCount, unsigned& streamedEntryCount) + { + const CsvInputStream csv(*file.m_stream); + CsvHeaderRow headerRow; + SoundAliasHeaders headers; + if (!headerRow.Read(csv) || !headers.From(headerRow)) + { + std::cerr << std::format("Invalid headers for aliases of sound bank {}\n", sndBank->name); + return false; + } + + std::vector aliasLists; + std::vector aliasList; + std::vector row; + unsigned rowIndex = 0u; + while (csv.NextRow(row)) + { + SndAlias alias; + if (!LoadSoundAlias(alias, row, rowIndex++, headerRow, headers, memory)) + return false; + + if (alias.flags.loadType == SA_LOADED) + loadedEntryCount++; + else + streamedEntryCount++; + + if (!aliasList.empty() && aliasList[0].id != alias.id) + { + aliasLists.emplace_back(CreateAliasList(aliasList, memory)); + aliasList.emplace_back(alias); + } + else + aliasList.emplace_back(alias); + } + + if (!aliasList.empty()) + aliasLists.emplace_back(CreateAliasList(aliasList, memory)); + + sndBank->aliasCount = aliasLists.size(); + if (sndBank->aliasCount) + { + sndBank->alias = memory.Alloc(sndBank->aliasCount); + memcpy(sndBank->alias, aliasLists.data(), sizeof(SndAliasList) * sndBank->aliasCount); + } + + return true; + } + + bool LoadSoundAliasIndexList(MemoryManager& memory, SndBank* sndBank) + { + // contains a list of all the alias ids in the sound bank + sndBank->aliasIndex = memory.Alloc(sndBank->aliasCount); + memset(sndBank->aliasIndex, 0xFF, sizeof(SndIndexEntry) * sndBank->aliasCount); + + const auto setAliasIndexList = std::make_unique(sndBank->aliasCount); + + for (auto i = 0u; i < sndBank->aliasCount; i++) + { + const auto idx = sndBank->alias[i].id % sndBank->aliasCount; + if (sndBank->aliasIndex[idx].value == std::numeric_limits::max()) + { + sndBank->aliasIndex[idx].value = i; + sndBank->aliasIndex[idx].next = std::numeric_limits::max(); + setAliasIndexList[i] = true; + } + } + + for (auto i = 0u; i < sndBank->aliasCount; i++) + { + if (setAliasIndexList[i]) + continue; + + auto idx = sndBank->alias[i].id % sndBank->aliasCount; + while (sndBank->aliasIndex[idx].next != std::numeric_limits::max()) + { + idx = sndBank->aliasIndex[idx].next; + } + + auto offset = 1u; + auto freeIdx = std::numeric_limits::max(); + while (true) + { + freeIdx = (idx + offset) % sndBank->aliasCount; + if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits::max()) + break; + + freeIdx = (idx + sndBank->aliasCount - offset) % sndBank->aliasCount; + if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits::max()) + break; + + offset++; + freeIdx = std::numeric_limits::max(); + + if (offset >= sndBank->aliasCount) + break; + } + + if (freeIdx == std::numeric_limits::max()) + { + std::cerr << "Unable to allocate sound bank alias index list\n"; + return false; + } + + sndBank->aliasIndex[idx].next = freeIdx; + sndBank->aliasIndex[freeIdx].value = i; + sndBank->aliasIndex[freeIdx].next = std::numeric_limits::max(); + setAliasIndexList[i] = true; + } + + return true; + } + + class RadverbHeaders + { + public: + RadverbHeaders() = default; + + bool From(const CsvHeaderRow& headerRow) + { + // clang-format off + return headerRow.RequireIndexForHeader("name", m_name) + && headerRow.RequireIndexForHeader("smoothing", m_smoothing) + && headerRow.RequireIndexForHeader("earlyTime", m_early_time) + && headerRow.RequireIndexForHeader("lateTime", m_late_time) + && headerRow.RequireIndexForHeader("earlyGain", m_early_gain) + && headerRow.RequireIndexForHeader("lateGain", m_late_gain) + && headerRow.RequireIndexForHeader("returnGain", m_return_gain) + && headerRow.RequireIndexForHeader("earlyLpf", m_early_lpf) + && headerRow.RequireIndexForHeader("lateLpf", m_late_lpf) + && headerRow.RequireIndexForHeader("inputLpf", m_input_lpf) + && headerRow.RequireIndexForHeader("dampLpf", m_damp_lpf) + && headerRow.RequireIndexForHeader("wallReflect", m_wall_reflect) + && headerRow.RequireIndexForHeader("dryGain", m_dry_gain) + && headerRow.RequireIndexForHeader("earlySize", m_early_size) + && headerRow.RequireIndexForHeader("lateSize", m_late_size) + && headerRow.RequireIndexForHeader("diffusion", m_diffusion) + && headerRow.RequireIndexForHeader("returnHighpass", m_return_highpass); + // clang-format on + } + + unsigned m_name; + unsigned m_smoothing; + unsigned m_early_time; + unsigned m_late_time; + unsigned m_early_gain; + unsigned m_late_gain; + unsigned m_return_gain; + unsigned m_early_lpf; + unsigned m_late_lpf; + unsigned m_input_lpf; + unsigned m_damp_lpf; + unsigned m_wall_reflect; + unsigned m_dry_gain; + unsigned m_early_size; + unsigned m_late_size; + unsigned m_diffusion; + unsigned m_return_highpass; + }; + + bool LoadSoundRadverbs(MemoryManager& memory, SndBank* sndBank, const SearchPathOpenFile& file) + { + const CsvInputStream csv(*file.m_stream); + CsvHeaderRow csvHeaders; + RadverbHeaders headers; + if (!csvHeaders.Read(csv) || !headers.From(csvHeaders)) + { + std::cerr << std::format("Invalid headers for radverbs of sound bank {}\n", sndBank->name); + return false; + } + + std::vector radverbs; + std::vector row; + while (csv.NextRow(row)) + { + const auto& name = row[headers.m_name]; + if (name.m_value.empty()) + return false; + + SndRadverb radverb; + strncpy(radverb.name, name.m_value.c_str(), 32); + radverb.id = Common::SND_HashName(name.m_value.c_str()); + + // clang-format off + const auto readRadverb = + row[headers.m_smoothing].AsFloat(radverb.smoothing) + && row[headers.m_early_time].AsFloat(radverb.earlyTime) + && row[headers.m_late_time].AsFloat(radverb.lateTime) + && row[headers.m_early_gain].AsFloat(radverb.earlyGain) + && row[headers.m_late_gain].AsFloat(radverb.lateGain) + && row[headers.m_return_gain].AsFloat(radverb.returnGain) + && row[headers.m_early_lpf].AsFloat(radverb.earlyLpf) + && row[headers.m_late_lpf].AsFloat(radverb.lateLpf) + && row[headers.m_input_lpf].AsFloat(radverb.inputLpf) + && row[headers.m_damp_lpf].AsFloat(radverb.dampLpf) + && row[headers.m_wall_reflect].AsFloat(radverb.wallReflect) + && row[headers.m_dry_gain].AsFloat(radverb.dryGain) + && row[headers.m_early_size].AsFloat(radverb.earlySize) + && row[headers.m_late_size].AsFloat(radverb.lateSize) + && row[headers.m_diffusion].AsFloat(radverb.diffusion) + && row[headers.m_return_highpass].AsFloat(radverb.returnHighpass); + // clang-format on + + if (!readRadverb) + return false; + + radverbs.emplace_back(radverb); + } + + sndBank->radverbCount = radverbs.size(); + if (sndBank->radverbCount) + { + sndBank->radverbs = memory.Alloc(sndBank->radverbCount); + memcpy(sndBank->radverbs, radverbs.data(), sizeof(SndRadverb) * sndBank->radverbCount); + } + + return true; + } + + bool LoadSoundDuckList(ISearchPath* searchPath, MemoryManager& memory, SndBank* sndBank, const SearchPathOpenFile& file) + { + const CsvInputStream csv(*file.m_stream); + CsvHeaderRow headerRow; + headerRow.Read(csv); + const auto nameRow = headerRow.GetIndexForHeader("name"); + if (!nameRow) + { + std::cerr << std::format("Missing name header for ducks of sound bank {}\n", sndBank->name); + return false; + } + + std::vector ducks; + std::vector row; + while (csv.NextRow(row)) + { + const auto& name = row[*nameRow]; + if (name.empty()) + return false; + + const auto duckFile = searchPath->Open(std::format("soundbank/ducks/{}.duk", name)); + if (!duckFile.IsOpen()) + { + std::cerr << std::format("Unable to find .duk file for {} in ducklist for sound bank {}\n", name, sndBank->name); + return false; + } + + SndDuck duck; + strncpy(duck.name, name.data(), 32); + duck.id = Common::SND_HashName(name.data()); + + auto duckJson = nlohmann::json::parse(*duckFile.m_stream); + duck.fadeIn = duckJson["fadeIn"].get(); + duck.fadeOut = duckJson["fadeOut"].get(); + duck.startDelay = duckJson["startDelay"].get(); + duck.distance = duckJson["distance"].get(); + duck.length = duckJson["length"].get(); + duck.updateWhilePaused = duckJson["updateWhilePaused"].get(); + + duck.fadeInCurve = duckJson["fadeInCurveId"].get(); + duck.fadeOutCurve = duckJson["fadeOutCurveId"].get(); + + if (duckJson.contains("fadeInCurve")) + duck.fadeInCurve = Common::SND_HashName(duckJson["fadeInCurve"].get().data()); + + if (duckJson.contains("fadeOutCurve")) + duck.fadeOutCurve = Common::SND_HashName(duckJson["fadeOutCurve"].get().data()); + + duck.attenuation = memory.Alloc(32u); + duck.filter = memory.Alloc(32u); + + for (auto& valueJson : duckJson["values"]) + { + auto index = GetValueIndex(valueJson["duckGroup"].get(), SOUND_DUCK_GROUPS, std::extent_v); + + duck.attenuation[index] = valueJson["attenuation"].get(); + duck.filter[index] = valueJson["filter"].get(); + } + + ducks.emplace_back(duck); + } + + sndBank->duckCount = ducks.size(); + if (sndBank->duckCount) + { + sndBank->ducks = memory.Alloc(sndBank->duckCount); + memcpy(sndBank->ducks, ducks.data(), sizeof(SndDuck) * sndBank->duckCount); + } + + return true; + } } // namespace void* AssetLoaderSoundBank::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) @@ -74,465 +956,47 @@ bool AssetLoaderSoundBank::CanLoadFromRaw() const return true; } -size_t GetValueIndex(const std::string& value, const char* const* lookupTable, const size_t len) -{ - if (value.empty()) - return 0; - - for (auto i = 0u; i < len; i++) - { - if (lookupTable[i] == value) - return i; - } - - return 0; -} - -unsigned int GetAliasSubListCount(const unsigned int startRow, const ParsedCsv& csv) -{ - auto count = 1u; - - const auto name = csv[startRow].GetValue("name", true); - if (name.empty()) - return 0; - - while (true) - { - if (startRow + count >= csv.Size()) - break; - - const auto testName = csv[startRow + count].GetValue("name", true); - if (testName.empty()) - break; - - // if the name of the next entry does not match the first entry checked, it is not part of the sub list - if (name != testName) - break; - - count++; - } - - return count; -} - -float DbsplToLinear(const float dbsplValue) -{ - return std::pow(10.0f, (dbsplValue - 100.0f) / 20.0f); -} - -float CentToHertz(const float cents) -{ - return std::pow(2.0f, cents / 1200.0f); -} - -bool ReadColumnVolumeDbspl(const float dbsplValue, const char* colName, const unsigned rowIndex, uint16_t& value) -{ - if (dbsplValue < 0.0f || dbsplValue > 100.0f) - { - std::cerr << std::format("Invalid value for row {} col '{}' - {} [0.0, 100.0]\n", rowIndex + 1, colName, dbsplValue); - return false; - } - - value = static_cast(DbsplToLinear(dbsplValue) * static_cast(std::numeric_limits::max())); - - return true; -} - -bool ReadColumnPitchCents(const float centValue, const char* colName, const unsigned rowIndex, uint16_t& value) -{ - if (centValue < -2400.0f || centValue > 1200.0f) - { - std::cerr << std::format("Invalid value for row {} col '{}' - {} [-2400.0, 1200.0]\n", rowIndex + 1, colName, centValue); - return false; - } - - value = static_cast(CentToHertz(centValue) * static_cast(std::numeric_limits::max())); - - return true; -} - -bool LoadSoundAlias(MemoryManager* memory, SndAlias* alias, const ParsedCsvRow& row, const unsigned int rowNum) -{ - memset(alias, 0, sizeof(SndAlias)); - - const auto& name = row.GetValue("name", true); - if (name.empty()) - return false; - - alias->name = memory->Dup(name.data()); - alias->id = Common::SND_HashName(name.data()); - - const auto aliasFileName = row.GetValue("file"); - if (!aliasFileName.empty()) - { - alias->assetFileName = memory->Dup(aliasFileName.data()); - alias->assetId = Common::SND_HashName(aliasFileName.data()); - } - - const auto secondaryName = row.GetValue("secondary"); - if (!secondaryName.empty()) - alias->secondaryName = memory->Dup(secondaryName.data()); - - const auto subtitle = row.GetValue("subtitle"); - if (!subtitle.empty()) - alias->subtitle = memory->Dup(subtitle.data()); - - alias->duck = Common::SND_HashName(row.GetValue("duck").data()); - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("vol_min"), "vol_min", rowNum, alias->volMin)) - return false; - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("vol_max"), "vol_max", rowNum, alias->volMax)) - return false; - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("pitch_min"), "pitch_min", rowNum, alias->pitchMin)) - return false; - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("pitch_max"), "pitch_max", rowNum, alias->pitchMax)) - return false; - - alias->distMin = row.GetValueIntdistMin)>("dist_min"); - alias->distMax = row.GetValueIntdistMax)>("dist_max"); - alias->distReverbMax = row.GetValueIntdistReverbMax)>("dist_reverb_max"); - alias->limitCount = row.GetValueIntlimitCount)>("limit_count"); - alias->entityLimitCount = row.GetValueIntentityLimitCount)>("entity_limit_count"); - alias->minPriority = row.GetValueIntminPriority)>("min_priority"); - alias->maxPriority = row.GetValueIntmaxPriority)>("max_priority"); - alias->minPriorityThreshold = row.GetValueIntminPriorityThreshold)>("min_priority_threshold"); - alias->maxPriorityThreshold = row.GetValueIntmaxPriorityThreshold)>("max_priority_threshold"); - alias->probability = row.GetValueIntprobability)>("probability"); - alias->startDelay = row.GetValueIntstartDelay)>("start_delay"); - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("reverb_send"), "reverb_send", rowNum, alias->reverbSend)) - return false; - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("center_send"), "center_send", rowNum, alias->centerSend)) - return false; - - alias->envelopMin = row.GetValueIntenvelopMin)>("envelop_min"); - alias->envelopMax = row.GetValueIntenvelopMax)>("envelop_max"); - - if (!ReadColumnVolumeDbspl(row.GetValueFloat("envelop_percentage"), "envelop_percentage", rowNum, alias->envelopPercentage)) - return false; - - alias->occlusionLevel = row.GetValueIntocclusionLevel)>("occlusion_level"); - alias->fluxTime = row.GetValueIntfluxTime)>("move_time"); - alias->futzPatch = row.GetValueIntfutzPatch)>("futz"); - alias->contextType = row.GetValueIntcontextType)>("context_type"); - alias->contextValue = row.GetValueIntcontextValue)>("context_value"); - alias->fadeIn = row.GetValueIntfadeIn)>("fade_in"); - alias->fadeOut = row.GetValueIntfadeOut)>("fade_out"); - - alias->flags.looping = row.GetValue("loop") == "looping"; - alias->flags.panType = row.GetValue("pan") == "3d"; - alias->flags.isBig = row.GetValue("is_big") == "yes"; - alias->flags.distanceLpf = row.GetValue("distance_lpf") == "yes"; - alias->flags.doppler = row.GetValue("doppler") == "yes"; - alias->flags.timescale = row.GetValue("timescale") == "yes"; - alias->flags.isMusic = row.GetValue("music") == "yes"; - alias->flags.pauseable = row.GetValue("pause") == "yes"; - alias->flags.stopOnEntDeath = row.GetValue("stop_on_death") == "yes"; - - alias->duckGroup = - static_castduckGroup)>(GetValueIndex(row.GetValue("duck_group"), SOUND_DUCK_GROUPS, std::extent_v)); - alias->flags.volumeGroup = GetValueIndex(row.GetValue("group"), SOUND_GROUPS, std::extent_v); - alias->flags.fluxType = GetValueIndex(row.GetValue("move_type"), SOUND_MOVE_TYPES, std::extent_v); - alias->flags.loadType = GetValueIndex(row.GetValue("type"), SOUND_LOAD_TYPES, std::extent_v); - alias->flags.busType = GetValueIndex(row.GetValue("bus"), SOUND_BUS_IDS, std::extent_v); - alias->flags.limitType = GetValueIndex(row.GetValue("limit_type"), SOUND_LIMIT_TYPES, std::extent_v); - alias->flags.volumeFalloffCurve = GetValueIndex(row.GetValue("volume_falloff_curve"), SOUND_CURVES, std::extent_v); - alias->flags.reverbFalloffCurve = GetValueIndex(row.GetValue("reverb_falloff_curve"), SOUND_CURVES, std::extent_v); - alias->flags.entityLimitType = GetValueIndex(row.GetValue("entity_limit_type"), SOUND_LIMIT_TYPES, std::extent_v); - alias->flags.volumeMinFalloffCurve = GetValueIndex(row.GetValue("volume_min_falloff_curve"), SOUND_CURVES, std::extent_v); - alias->flags.reverbMinFalloffCurve = GetValueIndex(row.GetValue("reverb_min_falloff_curve"), SOUND_CURVES, std::extent_v); - alias->flags.randomizeType = GetValueIndex(row.GetValue("randomize_type"), SOUND_RANDOMIZE_TYPES, std::extent_v); - - return true; -} - -bool LoadSoundAliasIndexList(MemoryManager* memory, SndBank* sndBank) -{ - // contains a list of all the alias ids in the sound bank - sndBank->aliasIndex = memory->Alloc(sndBank->aliasCount); - memset(sndBank->aliasIndex, 0xFF, sizeof(SndIndexEntry) * sndBank->aliasCount); - - const auto setAliasIndexList = std::make_unique(sndBank->aliasCount); - - for (auto i = 0u; i < sndBank->aliasCount; i++) - { - const auto idx = sndBank->alias[i].id % sndBank->aliasCount; - if (sndBank->aliasIndex[idx].value == std::numeric_limits::max()) - { - sndBank->aliasIndex[idx].value = i; - sndBank->aliasIndex[idx].next = std::numeric_limits::max(); - setAliasIndexList[i] = true; - } - } - - for (auto i = 0u; i < sndBank->aliasCount; i++) - { - if (setAliasIndexList[i]) - continue; - - auto idx = sndBank->alias[i].id % sndBank->aliasCount; - while (sndBank->aliasIndex[idx].next != std::numeric_limits::max()) - { - idx = sndBank->aliasIndex[idx].next; - } - - auto offset = 1u; - auto freeIdx = std::numeric_limits::max(); - while (true) - { - freeIdx = (idx + offset) % sndBank->aliasCount; - if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits::max()) - break; - - freeIdx = (idx + sndBank->aliasCount - offset) % sndBank->aliasCount; - if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits::max()) - break; - - offset++; - freeIdx = std::numeric_limits::max(); - - if (offset >= sndBank->aliasCount) - break; - } - - if (freeIdx == std::numeric_limits::max()) - { - std::cerr << "Unable to allocate sound bank alias index list\n"; - return false; - } - - sndBank->aliasIndex[idx].next = freeIdx; - sndBank->aliasIndex[freeIdx].value = i; - sndBank->aliasIndex[freeIdx].next = std::numeric_limits::max(); - setAliasIndexList[i] = true; - } - - return true; -} - -bool LoadSoundAliasList( - MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file, unsigned int* loadedEntryCount, unsigned int* streamedEntryCount) -{ - const CsvInputStream aliasCsvStream(*file.m_stream); - const ParsedCsv aliasCsv(aliasCsvStream, true); - - // Ensure there is at least one entry in the csv after the headers - if (aliasCsv.Size() > 0) - { - // should be the total number of assets - sndBank->aliasCount = aliasCsv.Size(); - sndBank->alias = memory->Alloc(sndBank->aliasCount); - - auto row = 0u; - auto listIndex = 0u; - while (row < sndBank->aliasCount) - { - // count how many of the next rows should be in the sound alias sub-list. Aliases are part of the same sub list if they have the same name for a - // different file - const auto subListCount = GetAliasSubListCount(row, aliasCsv); - if (subListCount < 1) - return false; - - // allocate the sub list - sndBank->alias[listIndex].count = subListCount; - sndBank->alias[listIndex].head = memory->Alloc(subListCount); - sndBank->alias[listIndex].sequence = 0; - - // populate the sublist with the next X number of aliases in the file. Note: this will only work correctly if the aliases that are a part of a sub - // list are next to each other in the file - for (auto i = 0u; i < subListCount; i++) - { - if (!LoadSoundAlias(memory, &sndBank->alias[listIndex].head[i], aliasCsv[row], row)) - return false; - - // if this asset is loaded instead of stream, increment the loaded count for later - if (sndBank->alias[listIndex].head[i].flags.loadType == SA_LOADED) - (*loadedEntryCount)++; - else - (*streamedEntryCount)++; - - row++; - } - - // the main alias list id and name should match that of the entries in the sub list (since they all have the same name, all sub entries will be the - // same) - sndBank->alias[listIndex].id = sndBank->alias[listIndex].head[0].id; - sndBank->alias[listIndex].name = sndBank->alias[listIndex].head[0].name; - - listIndex++; - } - - // re-allocate the alias list and count if necessary. We don't know the true aliasCount until after parsing all the aliases in the file - if (listIndex != sndBank->aliasCount) - { - auto* oldAliases = sndBank->alias; - - sndBank->aliasCount = listIndex; - sndBank->alias = memory->Alloc(sndBank->aliasCount); - memcpy(sndBank->alias, oldAliases, sizeof(SndAliasList) * sndBank->aliasCount); - - memory->Free(oldAliases); - } - - if (!LoadSoundAliasIndexList(memory, sndBank)) - return false; - } - - return true; -} - -bool LoadSoundRadverbs(MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file) -{ - const CsvInputStream radverbCsvStream(*file.m_stream); - const ParsedCsv radverbCsv(radverbCsvStream, true); - - if (radverbCsv.Size() > 0) - { - sndBank->radverbCount = radverbCsv.Size(); - sndBank->radverbs = memory->Alloc(sndBank->radverbCount); - - for (auto i = 0u; i < sndBank->radverbCount; i++) - { - auto row = radverbCsv[i]; - - auto name = row.GetValue("name", true); - if (name.empty()) - return false; - - strncpy(sndBank->radverbs[i].name, name.data(), 32); - sndBank->radverbs[i].id = Common::SND_HashName(name.data()); - sndBank->radverbs[i].smoothing = row.GetValueFloat("smoothing"); - sndBank->radverbs[i].earlyTime = row.GetValueFloat("earlyTime"); - sndBank->radverbs[i].lateTime = row.GetValueFloat("lateTime"); - sndBank->radverbs[i].earlyGain = row.GetValueFloat("earlyGain"); - sndBank->radverbs[i].lateGain = row.GetValueFloat("lateGain"); - sndBank->radverbs[i].returnGain = row.GetValueFloat("returnGain"); - sndBank->radverbs[i].earlyLpf = row.GetValueFloat("earlyLpf"); - sndBank->radverbs[i].lateLpf = row.GetValueFloat("lateLpf"); - sndBank->radverbs[i].inputLpf = row.GetValueFloat("inputLpf"); - sndBank->radverbs[i].dampLpf = row.GetValueFloat("dampLpf"); - sndBank->radverbs[i].wallReflect = row.GetValueFloat("wallReflect"); - sndBank->radverbs[i].dryGain = row.GetValueFloat("dryGain"); - sndBank->radverbs[i].earlySize = row.GetValueFloat("earlySize"); - sndBank->radverbs[i].lateSize = row.GetValueFloat("lateSize"); - sndBank->radverbs[i].diffusion = row.GetValueFloat("diffusion"); - sndBank->radverbs[i].returnHighpass = row.GetValueFloat("returnHighpass"); - } - } - - return true; -} - -bool LoadSoundDuckList(ISearchPath* searchPath, MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file) -{ - const CsvInputStream duckListCsvStream(*file.m_stream); - const ParsedCsv duckListCsv(duckListCsvStream, true); - - if (duckListCsv.Size() > 0) - { - sndBank->duckCount = duckListCsv.Size(); - sndBank->ducks = memory->Alloc(sndBank->duckCount); - - for (auto i = 0u; i < sndBank->duckCount; i++) - { - auto row = duckListCsv[i]; - - const auto name = row.GetValue("name", true); - if (name.empty()) - return false; - - const auto duckFile = searchPath->Open("soundbank/ducks/" + name + ".duk"); - if (!duckFile.IsOpen()) - { - std::cerr << "Unable to find .duk file for " << name << " in ducklist for sound bank " << sndBank->name << "\n"; - return false; - } - - auto* duck = &sndBank->ducks[i]; - strncpy(duck->name, name.data(), 32); - duck->id = Common::SND_HashName(name.data()); - - auto duckJson = nlohmann::json::parse(*duckFile.m_stream); - duck->fadeIn = duckJson["fadeIn"].get(); - duck->fadeOut = duckJson["fadeOut"].get(); - duck->startDelay = duckJson["startDelay"].get(); - duck->distance = duckJson["distance"].get(); - duck->length = duckJson["length"].get(); - duck->updateWhilePaused = duckJson["updateWhilePaused"].get(); - - duck->fadeInCurve = duckJson["fadeInCurveId"].get(); - duck->fadeOutCurve = duckJson["fadeOutCurveId"].get(); - - if (duckJson.contains("fadeInCurve")) - duck->fadeInCurve = Common::SND_HashName(duckJson["fadeInCurve"].get().data()); - - if (duckJson.contains("fadeOutCurve")) - duck->fadeOutCurve = Common::SND_HashName(duckJson["fadeOutCurve"].get().data()); - - duck->attenuation = memory->Alloc(32u); - duck->filter = memory->Alloc(32u); - - for (auto& valueJson : duckJson["values"]) - { - auto index = GetValueIndex(valueJson["duckGroup"].get(), SOUND_DUCK_GROUPS, std::extent_v); - - duck->attenuation[index] = valueJson["attenuation"].get(); - duck->filter[index] = valueJson["filter"].get(); - } - } - } - - return true; -} - bool AssetLoaderSoundBank::LoadFromRaw( const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const { if (assetName.find('.') == std::string::npos) { - std::cerr << "A language must be specific in the soundbank asset name! (Ex: mpl_common.all)\n"; + std::cerr << "A language must be specific in the sound bank asset name! (Ex: mpl_common.all)\n"; return false; } - // open the soundbank aliases - const auto aliasFile = searchPath->Open("soundbank/" + assetName + ".aliases.csv"); + const auto aliasFile = searchPath->Open(std::format("soundbank/{}.aliases.csv", assetName)); if (!aliasFile.IsOpen()) return false; - // set the defaults - auto* sndBank = memory->Create(); - memset(sndBank, 0, sizeof(SndBank)); + auto* sndBank = memory->Alloc(); sndBank->name = memory->Dup(assetName.c_str()); const auto sndBankLocalization = utils::StringSplit(assetName, '.'); - // load the soundbank aliases - unsigned int loadedEntryCount = 0u, streamedEntryCount = 0u; - if (!LoadSoundAliasList(memory, sndBank, aliasFile, &loadedEntryCount, &streamedEntryCount)) + unsigned loadedEntryCount = 0u, streamedEntryCount = 0u; + if (!LoadSoundAliasList(*memory, sndBank, aliasFile, loadedEntryCount, streamedEntryCount)) return false; - // load the soundbank reverbs - const auto radverbFile = searchPath->Open("soundbank/" + assetName + ".reverbs.csv"); + if (!LoadSoundAliasIndexList(*memory, sndBank)) + return false; + + const auto radverbFile = searchPath->Open(std::format("soundbank/{}.reverbs.csv", assetName)); if (radverbFile.IsOpen()) { - if (!LoadSoundRadverbs(memory, sndBank, radverbFile)) + if (!LoadSoundRadverbs(*memory, sndBank, radverbFile)) { - std::cerr << "Sound Bank reverbs file for " << assetName << " is invalid\n"; + std::cerr << std::format("Sound bank reverbs file for {} is invalid\n", assetName); return false; } } - // load the soundbank ducks - const auto duckListFile = searchPath->Open("soundbank/" + assetName + ".ducklist.csv"); + const auto duckListFile = searchPath->Open(std::format("soundbank/{}.ducklist.csv", assetName)); if (duckListFile.IsOpen()) { - if (!LoadSoundDuckList(searchPath, memory, sndBank, duckListFile)) + if (!LoadSoundDuckList(searchPath, *memory, sndBank, duckListFile)) { - std::cerr << "Sound Bank ducklist file for " << assetName << " is invalid\n"; + std::cerr << std::format("Sound bank ducklist file for {} is invalid\n", assetName); return false; } } @@ -554,7 +1018,7 @@ bool AssetLoaderSoundBank::LoadFromRaw( sndBank->runtimeAssetLoad = true; - const auto sablName = assetName + ".sabl"; + const auto sablName = std::format("{}.sabl", assetName); sablStream = OpenSoundBankOutputFile(sablName); if (sablStream) sablWriter = SoundBankWriter::Create(sablName, *sablStream, searchPath); @@ -566,7 +1030,7 @@ bool AssetLoaderSoundBank::LoadFromRaw( sndBank->streamAssetBank.language = memory->Dup(sndBankLocalization.at(1).c_str()); memset(sndBank->streamAssetBank.linkTimeChecksum, 0xCC, 16); - const auto sabsName = assetName + ".sabs"; + const auto sabsName = std::format("{}.sabs", assetName); sabsStream = OpenSoundBankOutputFile(sabsName); if (sabsStream) sabsWriter = SoundBankWriter::Create(sabsName, *sabsStream, searchPath); @@ -604,7 +1068,7 @@ bool AssetLoaderSoundBank::LoadFromRaw( } else { - std::cerr << "Loaded Sound Bank for " << assetName << " failed to generate. Please check your build files.\n"; + std::cerr << std::format("Loaded sound bank for {} failed to generate. Please check your build files.\n", assetName); return false; } } @@ -618,7 +1082,7 @@ bool AssetLoaderSoundBank::LoadFromRaw( if (!result) { - std::cerr << "Streamed Sound Bank for " << assetName << " failed to generate. Please check your build files.\n"; + std::cerr << std::format("Streamed sound bank for {} failed to generate. Please check your build files.\n", assetName); return false; } }