From bc51ecb49d79805f8818fce7304cbd6f8dd024fa Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 6 Oct 2024 10:51:04 +0200 Subject: [PATCH 1/5] chore: little improvement on sound bank types --- src/Common/Game/T6/T6_Assets.h | 8 +++++--- .../Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp | 4 ++-- src/ZoneCode/Game/T6/XAssets/SndBank.txt | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index d6bb368e7..89394044a 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -6230,7 +6230,7 @@ namespace T6 unsigned int isBig : 1; // 4 unsigned int pauseable : 1; // 5 unsigned int isMusic : 1; // 6 - unsigned int stopOnDeath : 1; // 7 + unsigned int stopOnEntDeath : 1; // 7 unsigned int timescale : 1; // 8 unsigned int voiceLimit : 1; // 9 unsigned int ignoreMaxDist : 1; // 10 @@ -6249,7 +6249,9 @@ namespace T6 unsigned int reverbFalloffCurve : 6; // 8-13 unsigned int volumeMinFalloffCurve : 6; // 14-19 unsigned int reverbMinFalloffCurve : 6; // 20-25 - unsigned int unknown1_1 : 6; // 26-31 + unsigned int unknown1_1 : 1; // 26 + unsigned int isCinematic : 1; // 27 + unsigned int unknown1_2 : 4; // 28-31 }; struct SndAlias @@ -6257,7 +6259,7 @@ namespace T6 const char* name; unsigned int id; const char* subtitle; - const char* secondaryname; + const char* secondaryName; unsigned int assetId; const char* assetFileName; SndAliasFlags flags; diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp index 32afda9e9..ccffc7e22 100644 --- a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp @@ -171,7 +171,7 @@ bool LoadSoundAlias(MemoryManager* memory, SndAlias* alias, const ParsedCsvRow& const auto secondaryName = row.GetValue("secondary"); if (!secondaryName.empty()) - alias->secondaryname = memory->Dup(secondaryName.data()); + alias->secondaryName = memory->Dup(secondaryName.data()); const auto subtitle = row.GetValue("subtitle"); if (!subtitle.empty()) @@ -231,7 +231,7 @@ bool LoadSoundAlias(MemoryManager* memory, SndAlias* alias, const ParsedCsvRow& alias->flags.timescale = row.GetValue("timescale") == "yes"; alias->flags.isMusic = row.GetValue("music") == "yes"; alias->flags.pauseable = row.GetValue("pause") == "yes"; - alias->flags.stopOnDeath = row.GetValue("stop_on_death") == "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)); diff --git a/src/ZoneCode/Game/T6/XAssets/SndBank.txt b/src/ZoneCode/Game/T6/XAssets/SndBank.txt index 3911a392b..79da365cd 100644 --- a/src/ZoneCode/Game/T6/XAssets/SndBank.txt +++ b/src/ZoneCode/Game/T6/XAssets/SndBank.txt @@ -21,7 +21,7 @@ set count head count; use SndAlias; set string name; set string subtitle; -set string secondaryname; +set string secondaryName; set string assetFileName; // SndDuck From 8620ce864c3bda15c711a7d5381f949fc4e94a4a Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 6 Oct 2024 10:51:59 +0200 Subject: [PATCH 2/5] refactor: refactor sound bank csv writing head --- src/ObjCommon/Game/T6/SoundConstantsT6.h | 53 +- .../T6/AssetDumpers/AssetDumperSndBank.cpp | 729 +++++++++++------- 2 files changed, 502 insertions(+), 280 deletions(-) diff --git a/src/ObjCommon/Game/T6/SoundConstantsT6.h b/src/ObjCommon/Game/T6/SoundConstantsT6.h index 878db002c..f11585c43 100644 --- a/src/ObjCommon/Game/T6/SoundConstantsT6.h +++ b/src/ObjCommon/Game/T6/SoundConstantsT6.h @@ -95,6 +95,43 @@ namespace T6 "snp_x3", }; + // From SndDriverGlobals + inline constexpr const char* SOUND_PANS[]{ + "default", + "music", + "wpn_all", + "wpn_fnt", + "wpn_rear", + "wpn_left", + "wpn_right", + "music_all", + "fly_foot_all", + "front", + "back", + "front_mostly", + "back_mostly", + "all", + "center", + "front_and_center", + "lfe", + "quad", + "front_mostly_some_center", + "front_halfback", + "halffront_back", + "test", + "brass_right", + "brass_left", + "veh_back", + "tst_left", + "tst_center", + "tst_right", + "tst_surround_left", + "tst_surround_right", + "tst_lfe", + "pip", + "movie_vo", + }; + inline constexpr const char* SOUND_LIMIT_TYPES[]{ "none", "oldest", @@ -138,9 +175,23 @@ namespace T6 // From executable inline constexpr const char* SOUND_RANDOMIZE_TYPES[]{ - "", "volume", "pitch", "variant", }; + + inline constexpr const char* SOUND_NO_YES[]{ + "no", + "yes", + }; + + inline constexpr const char* SOUND_LOOP_TYPES[]{ + "nonlooping", + "looping", + }; + + inline constexpr const char* SOUND_PAN_TYPES[]{ + "2d", + "3d", + }; } // namespace T6 diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp index d2afe5465..a1e6ea9d5 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp @@ -2,15 +2,19 @@ #include "Csv/CsvStream.h" #include "Game/T6/CommonT6.h" +#include "Game/T6/GameAssetPoolT6.h" +#include "Game/T6/GameT6.h" #include "Game/T6/SoundConstantsT6.h" #include "ObjContainer/SoundBank/SoundBank.h" #include "Sound/WavWriter.h" #include "nlohmann/json.hpp" +#include #include #include #include #include +#include #include using namespace T6; @@ -19,70 +23,66 @@ namespace fs = std::filesystem; namespace { const std::string ALIAS_HEADERS[]{ - "name", - "file", - "template", - "loadspec", - "secondary", - "group", - "vol_min", - "vol_max", - "team_vol_mod", - "dist_min", - "dist_max", - "dist_reverb_max", - "volume_falloff_curve", - "reverb_falloff_curve", - "volume_min_falloff_curve", - "reverb_min_falloff_curve", - "limit_count", - "limit_type", - "entity_limit_count", - "entity_limit_type", - "pitch_min", - "pitch_max", - "team_pitch_mod", - "min_priority", - "max_priority", - "min_priority_threshold", - "max_priority_threshold", - "spatialized", - "type", - "loop", - "randomize_type", - "probability", - "start_delay", - "reverb_send", - "duck", - "duck_group", - "pan", - "center_send", - "envelop_min", - "envelop_max", - "envelop_percentage", - "occlusion_level", - "occlusion_wet_dry", - "is_big", - "distance_lpf", - "move_type", - "move_time", - "real_delay", - "subtitle", - "mature", - "doppler", - "futz", - "context_type", - "context_value", - "compression", - "timescale", - "music", - "fade_in", - "fade_out", - "pc_format", - "pause", - "stop_on_death", - "bus", - "snapshot", + "Name", + "FileSource", + "Secondary", + "Subtitle", + "VolMin", + "VolMax", + "PitchMin", + "PitchMax", + "DistMin", + "DistMaxDry", + "DistMaxWet", + "Probability", + "EnvelopMin", + "EnvelopMax", + "EnvelopPercentage", + "CenterSend", + "ReverbSend", + "StartDelay", + "PriorityThresholdMin", + "PriorityThresholdMax", + "OcclusionLevel", + "FluxTime", + "Duck", + "PriorityMin", + "PriorityMax", + "LimitCount", + "EntityLimitCount", + "DryMinCurve", + "DryMaxCurve", + "WetMinCurve", + "WetMaxCurve", + "Pan", + "DuckGroup", + "ContextType", + "ContextValue", + "FadeIn", + "FadeOut", + "StopOnPlay", + "DopplerScale", + "FutzPatch", + "LimitType", + "EntityLimitType", + "RandomizeType", + "FluxType", + "Storage", + "VolumeGroup", + "DistanceLpf", + "Doppler", + "IsBig", + "Looping", + "PanType", + "IsMusic", + "Timescale", + "Pauseable", + "StopOnEntDeath", + "Bus", + "VoiceLimit", + "IgnoreMaxDist", + "NeverPlayTwice", + "IsCinematic", }; const std::string REVERB_HEADERS[]{ @@ -122,24 +122,137 @@ namespace 192000, }; - std::unordered_map CreateCurvesMap() + constexpr const char* KNOWN_CONTEXT_TYPES[]{ + "", + "plr_stance", + "grass", + "f35", + "ringoff_plr", + "mature", + }; + + constexpr const char* KNOWN_CONTEXT_VALUES[]{ + "", + "stand", + "crouch", + "prone", + "no_grass", + "in_grass", + "interior", + "exterior", + "outdoor", + "indoor", + "safe", + "explicit", + }; + + constexpr const char* KNOWN_FUTZ_IDS[]{ + "", + "bfutz", + "default", + "defaultbkp", + "dlc_res_1", + "dlc_res_2", + "dlc_res_3", + "dlc_res_4", + "dlc_res_5", + "dlc_res_6", + "dlc_res_7", + "dlc_res_8", + "good_1", + "jet_wing_helmet", + "jet_wing_helmet_flying", + "mpl_agr_pov", + "mpl_chopper_pov", + "mpl_quad_pov", + "mpl_reaper_pov", + "no_gfutz", + "spl_asd_pov", + "spl_bigdog_pov", + "spl_heli_future", + "spl_quad_pov", + "spl_spiderbot_pov", + "spl_spymic", + "spl_tow_missile", + "spl_turret", + "spl_war_command", + "test_1", + "test_2", + "tueyeckert", + }; + + std::unordered_map CreateSoundHashMap(const char* const* values, const unsigned valueCount) { std::unordered_map result; - for (auto i = 0u; i < std::extent_v; i++) - result.emplace(T6::Common::SND_HashName(SOUND_CURVES[i]), SOUND_CURVES[i]); + for (auto i = 0u; i < valueCount; i++) + result.emplace(Common::SND_HashName(values[i]), values[i]); return result; } - const std::unordered_map CURVES_MAP = CreateCurvesMap(); -} // namespace + const auto CURVES_MAP = CreateSoundHashMap(SOUND_CURVES, std::extent_v); + const auto CONTEXT_TYPES_MAP = CreateSoundHashMap(KNOWN_CONTEXT_TYPES, std::extent_v); + const auto CONTEXT_VALUES_MAP = CreateSoundHashMap(KNOWN_CONTEXT_VALUES, std::extent_v); + const auto FUTZ_IDS_MAP = CreateSoundHashMap(KNOWN_FUTZ_IDS, std::extent_v); -class AssetDumperSndBank::Internal -{ - AssetDumpingContext& m_context; + class LoadedSoundBankHashes + { + public: + void Initialize() + { + for (const auto& zone : g_GameT6.GetZones()) + { + auto& sndBankPool = *dynamic_cast(zone->m_pools.get())->m_sound_bank; + for (auto* entry : sndBankPool) + { + const auto& sndBank = *entry->Asset(); + + m_alias_names.reserve(m_alias_names.size() + sndBank.aliasCount); + for (auto aliasIndex = 0u; aliasIndex < sndBank.aliasCount; aliasIndex++) + { + const auto& alias = sndBank.alias[aliasIndex]; + m_alias_names.emplace(alias.id, alias.name); + } - _NODISCARD std::string GetAssetFilename(std::string outputFileName, const std::string& extension) const + m_duck_names.reserve(m_duck_names.size() + sndBank.duckCount); + for (auto duckIndex = 0u; duckIndex < sndBank.duckCount; duckIndex++) + { + const auto& duck = sndBank.ducks[duckIndex]; + m_duck_names.emplace(duck.id, duck.name); + } + } + } + } + + [[nodiscard]] std::optional GetAliasName(const unsigned hash) const + { + if (hash == 0) + return ""; + + const auto knownAliasHash = m_alias_names.find(hash); + if (knownAliasHash != m_alias_names.end()) + return knownAliasHash->second; + return std::nullopt; + } + + [[nodiscard]] std::optional GetDuckName(const unsigned hash) const + { + if (hash == 0) + return ""; + + const auto knownDuckHash = m_duck_names.find(hash); + if (knownDuckHash != m_duck_names.end()) + return knownDuckHash->second; + return std::nullopt; + } + + private: + std::unordered_map m_alias_names; + std::unordered_map m_duck_names; + }; + + [[nodiscard]] std::string GetAssetFilename(const AssetDumpingContext& context, std::string outputFileName, const std::string& extension) { - fs::path assetPath(m_context.m_base_path); + fs::path assetPath(context.m_base_path); std::ranges::replace(outputFileName, '\\', '/'); for (const auto& droppedPrefix : PREFIXES_TO_DROP) @@ -158,9 +271,9 @@ class AssetDumperSndBank::Internal return assetPath.string(); } - _NODISCARD std::unique_ptr OpenAssetOutputFile(const std::string& outputFileName, const std::string& extension) const + std::unique_ptr OpenAssetOutputFile(const AssetDumpingContext& context, const std::string& outputFileName, const std::string& extension) { - fs::path assetPath(GetAssetFilename(outputFileName, extension)); + fs::path assetPath(GetAssetFilename(context, outputFileName, extension)); auto assetDir(assetPath); assetDir.remove_filename(); @@ -177,7 +290,7 @@ class AssetDumperSndBank::Internal return nullptr; } - static void WriteAliasFileHeader(CsvOutputStream& stream) + void WriteAliasFileHeader(CsvOutputStream& stream) { for (const auto& headerField : ALIAS_HEADERS) { @@ -187,7 +300,7 @@ class AssetDumperSndBank::Internal stream.NextRow(); } - static void WriteReverbFileHeader(CsvOutputStream& stream) + void WriteReverbFileHeader(CsvOutputStream& stream) { for (const auto& headerField : REVERB_HEADERS) { @@ -197,20 +310,7 @@ class AssetDumperSndBank::Internal stream.NextRow(); } - static const char* FindNameForDuck(const unsigned int id, const SndBank* bank) - { - for (auto i = 0u; i < bank->duckCount; i++) - { - if (id == bank->ducks[i].id) - { - return bank->ducks[i].name; - } - } - - return ""; - } - - static const char* ExtensionForSndFormat(const snd_asset_format format) + const char* ExtensionForSndFormat(const snd_asset_format format) { switch (format) { @@ -226,7 +326,7 @@ class AssetDumperSndBank::Internal } } - static float LinearToDbspl(float linear) + float LinearToDbspl(float linear) { linear = std::max(linear, 0.0000152879f); @@ -237,222 +337,297 @@ class AssetDumperSndBank::Internal return 0; } - static float HertzToCents(const float hertz) + float HertzToCents(const float hertz) { return 1200.0f * std::log2(hertz); } - static void WriteColumnVolumeLinear(CsvOutputStream& stream, const uint16_t value) + void WriteColumnString(CsvOutputStream& stream, const std::string& stringValue) + { + stream.WriteColumn(stringValue); + } + + void WriteColumnString(CsvOutputStream& stream, const char* stringValue) + { + stream.WriteColumn(stringValue ? std::string(stringValue) : std::string()); + } + + template void WriteColumnIntegral(CsvOutputStream& stream, const T value) + { + stream.WriteColumn(std::format("{}", value)); + } + + void WriteColumnEnumWithSize(CsvOutputStream& stream, const unsigned value, const char* const* enumValues, const size_t enumValueCount) + { + assert(value < enumValueCount); + stream.WriteColumn(value < enumValueCount ? enumValues[value] : ""); + } + + template void WriteColumnEnum(CsvOutputStream& stream, const unsigned value, const char* const (&enumValues)[Size]) + { + WriteColumnEnumWithSize(stream, value, enumValues, Size); + } + + void WriteColumnEnumFlagsWithSize(CsvOutputStream& stream, const unsigned value, const char* const* enumValues, const size_t enumValueCount) + { + assert(enumValueCount <= 32u); + std::ostringstream ss; + auto first = false; + for (auto i = 0u; i < enumValueCount; i++) + { + const auto flagValue = 1u << i; + if (value & flagValue) + { + if (first) + first = false; + else + ss << ' '; + + ss << enumValues[i]; + } + } + + stream.WriteColumn(ss.str()); + } + + template void WriteColumnEnumFlags(CsvOutputStream& stream, const unsigned value, const char* const (&enumValues)[Size]) + { + WriteColumnEnumFlagsWithSize(stream, value, enumValues, Size); + } + + void WriteColumnVolumeLinear(CsvOutputStream& stream, const uint16_t value) { const auto linear = static_cast(value) / static_cast(std::numeric_limits::max()); const auto dbSpl = std::clamp(LinearToDbspl(linear), 0.0f, 100.0f); stream.WriteColumn(std::format("{:.3g}", dbSpl)); } - static void WriteColumnPitchHertz(CsvOutputStream& stream, const uint16_t value) + void WriteColumnPitchHertz(CsvOutputStream& stream, const uint16_t value) { - const auto hertz = static_cast(value) / static_cast(std::numeric_limits::max()); + const auto hertz = static_cast(value) / static_cast(std::numeric_limits::max()); const auto cents = std::clamp(HertzToCents(hertz), -2400.0f, 1200.0f); stream.WriteColumn(std::format("{:.4g}", cents)); } - static void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias, const std::optional maybeFormat, const SndBank* bank) + void WriteColumnNormByte(CsvOutputStream& stream, const uint8_t value) { - // name - stream.WriteColumn(alias->name); + const auto normValue = static_cast(value) / static_cast(std::numeric_limits::max()); + stream.WriteColumn(std::format("{:.3g}", normValue)); + } - // file - const auto* extension = maybeFormat ? ExtensionForSndFormat(*maybeFormat) : ""; - stream.WriteColumn(alias->assetFileName ? std::format("{}{}", alias->assetFileName, extension) : ""); + void WriteColumnWithKnownHashes(CsvOutputStream& stream, const std::unordered_map& knownValues, const unsigned value) + { + const auto knownValue = knownValues.find(value); + if (knownValue != knownValues.end()) + stream.WriteColumn(knownValue->second); + else + stream.WriteColumn(std::format("@{:x}", value)); + } - // template - stream.WriteColumn(""); + void WriteColumnWithAliasHash(CsvOutputStream& stream, const LoadedSoundBankHashes& hashes, const unsigned value) + { + const auto name = hashes.GetAliasName(value); + if (name) + stream.WriteColumn(*name); + else + stream.WriteColumn(std::format("@{:x}", value)); + } - // loadspec - stream.WriteColumn(""); + void WriteColumnWithDuckHash(CsvOutputStream& stream, const LoadedSoundBankHashes& hashes, const unsigned value) + { + const auto name = hashes.GetDuckName(value); + if (name) + stream.WriteColumn(*name); + else + stream.WriteColumn(std::format("@{:x}", value)); + } - // secondary - stream.WriteColumn((alias->secondaryname && *alias->secondaryname) ? alias->secondaryname : ""); + void + WriteAliasToFile(CsvOutputStream& stream, const SndAlias& alias, const std::optional maybeFormat, const LoadedSoundBankHashes& hashes) + { + // Name + WriteColumnString(stream, alias.name); - // group - stream.WriteColumn(SOUND_GROUPS[alias->flags.volumeGroup]); + // FileSource + const auto* extension = maybeFormat ? ExtensionForSndFormat(*maybeFormat) : ""; + WriteColumnString(stream, alias.assetFileName ? std::format("{}{}", alias.assetFileName, extension) : ""); - // vol_min - WriteColumnVolumeLinear(stream, alias->volMin); + // Secondary + WriteColumnString(stream, alias.secondaryName); - // vol_max - WriteColumnVolumeLinear(stream, alias->volMax); + // Subtitle + WriteColumnString(stream, alias.subtitle); - // team_vol_mod - stream.WriteColumn(""); + // VolMin + WriteColumnVolumeLinear(stream, alias.volMin); - // dist_min - stream.WriteColumn(std::to_string(alias->distMin)); + // VolMax + WriteColumnVolumeLinear(stream, alias.volMax); - // dist_max - stream.WriteColumn(std::to_string(alias->distMax)); + // PitchMin + WriteColumnPitchHertz(stream, alias.pitchMin); - // dist_reverb_max - stream.WriteColumn(std::to_string(alias->distReverbMax)); + // PitchMax + WriteColumnPitchHertz(stream, alias.pitchMax); - // volume_falloff_curve - stream.WriteColumn(SOUND_CURVES[alias->flags.volumeFalloffCurve]); + // DistMin + WriteColumnIntegral(stream, alias.distMin); - // reverb_falloff_curve - stream.WriteColumn(SOUND_CURVES[alias->flags.reverbFalloffCurve]); + // DistMaxDry + WriteColumnIntegral(stream, alias.distMax); - // volume_min_falloff_curve - stream.WriteColumn(SOUND_CURVES[alias->flags.volumeMinFalloffCurve]); + // DistMaxWet + WriteColumnIntegral(stream, alias.distReverbMax); - // reverb_min_falloff_curve - stream.WriteColumn(SOUND_CURVES[alias->flags.reverbMinFalloffCurve]); + // Probability + WriteColumnNormByte(stream, alias.probability); - // limit_count - stream.WriteColumn(std::to_string(alias->limitCount)); + // EnvelopMin + WriteColumnIntegral(stream, alias.envelopMin); - // limit_type - stream.WriteColumn(SOUND_LIMIT_TYPES[alias->flags.limitType]); + // EnvelopMax + WriteColumnIntegral(stream, alias.envelopMax); - // entity_limit_count - stream.WriteColumn(std::to_string(alias->entityLimitCount)); + // EnvelopPercentage + WriteColumnVolumeLinear(stream, alias.envelopPercentage); - // entity_limit_type - stream.WriteColumn(SOUND_LIMIT_TYPES[alias->flags.entityLimitType]); + // CenterSend + WriteColumnVolumeLinear(stream, alias.centerSend); - // pitch_min - WriteColumnPitchHertz(stream, alias->pitchMin); + // ReverbSend + WriteColumnVolumeLinear(stream, alias.reverbSend); - // pitch_max - WriteColumnPitchHertz(stream, alias->pitchMax); + // StartDelay + WriteColumnIntegral(stream, alias.startDelay); - // team_pitch_mod - stream.WriteColumn(""); + // PriorityThresholdMin + WriteColumnNormByte(stream, alias.minPriorityThreshold); - // min_priority - stream.WriteColumn(std::to_string(alias->minPriority)); + // PriorityThresholdMax + WriteColumnNormByte(stream, alias.maxPriorityThreshold); - // max_priority - stream.WriteColumn(std::to_string(alias->maxPriority)); + // OcclusionLevel + WriteColumnNormByte(stream, alias.occlusionLevel); - // min_priority_threshold - stream.WriteColumn(std::to_string(alias->minPriorityThreshold)); + // FluxTime + WriteColumnIntegral(stream, alias.fluxTime); - // max_priority_threshold - stream.WriteColumn(std::to_string(alias->maxPriorityThreshold)); + // Duck + WriteColumnWithDuckHash(stream, hashes, alias.duck); - // spatialized - stream.WriteColumn(""); + // PriorityMin + WriteColumnIntegral(stream, alias.minPriority); - // type - stream.WriteColumn(SOUND_LOAD_TYPES[alias->flags.loadType]); + // PriorityMax + WriteColumnIntegral(stream, alias.maxPriority); - // loop - stream.WriteColumn(alias->flags.looping == T6::SA_NON_LOOPING ? "nonlooping" : "looping"); + // LimitCount + WriteColumnIntegral(stream, alias.limitCount); - // randomize_type - stream.WriteColumn(SOUND_RANDOMIZE_TYPES[std::min(alias->flags.randomizeType, 3u)]); + // EntityLimitCount + WriteColumnIntegral(stream, alias.entityLimitCount); - // probability - stream.WriteColumn(std::to_string(alias->probability)); + // DryMinCurve + WriteColumnEnum(stream, alias.flags.volumeMinFalloffCurve, SOUND_CURVES); - // start_delay - stream.WriteColumn(std::to_string(alias->startDelay)); + // DryMaxCurve + WriteColumnEnum(stream, alias.flags.volumeFalloffCurve, SOUND_CURVES); - // reverb_send - WriteColumnVolumeLinear(stream, alias->reverbSend); + // WetMinCurve + WriteColumnEnum(stream, alias.flags.reverbMinFalloffCurve, SOUND_CURVES); - // duck - stream.WriteColumn(FindNameForDuck(alias->duck, bank)); + // WetMaxCurve + WriteColumnEnum(stream, alias.flags.reverbFalloffCurve, SOUND_CURVES); - // duck_group - stream.WriteColumn(SOUND_DUCK_GROUPS[alias->duckGroup]); + // Pan + WriteColumnEnum(stream, alias.pan, SOUND_PANS); - // pan - stream.WriteColumn(alias->flags.panType == SA_PAN_2D ? "2d" : "3d"); + // DuckGroup + WriteColumnEnum(stream, alias.duckGroup, SOUND_DUCK_GROUPS); - // center_send - WriteColumnVolumeLinear(stream, alias->centerSend); + // ContextType + WriteColumnWithKnownHashes(stream, CONTEXT_TYPES_MAP, alias.contextType); - // envelop_min - stream.WriteColumn(std::to_string(alias->envelopMin)); + // ContextValue + WriteColumnWithKnownHashes(stream, CONTEXT_VALUES_MAP, alias.contextValue); - // envelop_max - stream.WriteColumn(std::to_string(alias->envelopMax)); + // FadeIn + WriteColumnIntegral(stream, alias.fadeIn); - // envelop_percentage - WriteColumnVolumeLinear(stream, alias->envelopPercentage); + // FadeOut + WriteColumnIntegral(stream, alias.fadeOut); - // occlusion_level - stream.WriteColumn(std::to_string(alias->occlusionLevel)); + // StopOnPlay + WriteColumnWithAliasHash(stream, hashes, alias.stopOnPlay); - // occlusion_wet_dry - stream.WriteColumn(""); + // DopplerScale + WriteColumnIntegral(stream, alias.dopplerScale); - // is_big - stream.WriteColumn(alias->flags.isBig ? "yes" : "no"); + // FutzPatch + WriteColumnWithKnownHashes(stream, FUTZ_IDS_MAP, alias.futzPatch); - // distance_lpf - stream.WriteColumn(alias->flags.distanceLpf ? "yes" : "no"); + // LimitType + WriteColumnEnum(stream, alias.flags.limitType, SOUND_LIMIT_TYPES); - // move_type - stream.WriteColumn(SOUND_MOVE_TYPES[alias->flags.fluxType]); + // EntityLimitType + WriteColumnEnum(stream, alias.flags.entityLimitType, SOUND_LIMIT_TYPES); - // move_time - stream.WriteColumn(std::to_string(alias->fluxTime)); + // RandomizeType + WriteColumnEnumFlags(stream, alias.flags.randomizeType, SOUND_RANDOMIZE_TYPES); - // real_delay - stream.WriteColumn(""); + // FluxType + WriteColumnEnum(stream, alias.flags.fluxType, SOUND_MOVE_TYPES); - // subtitle - stream.WriteColumn((alias->subtitle && *alias->subtitle) ? alias->subtitle : ""); + // Storage + WriteColumnEnum(stream, alias.flags.loadType, SOUND_LOAD_TYPES); - // mature - stream.WriteColumn(""); + // VolumeGroup + WriteColumnEnum(stream, alias.flags.volumeGroup, SOUND_GROUPS); - // doppler - stream.WriteColumn(alias->flags.doppler ? "yes" : "no"); + // DistanceLpf + WriteColumnEnum(stream, alias.flags.distanceLpf, SOUND_NO_YES); - // futz - stream.WriteColumn(std::to_string(alias->futzPatch)); + // Doppler + WriteColumnEnum(stream, alias.flags.doppler, SOUND_NO_YES); - // context_type - stream.WriteColumn(std::to_string(alias->contextType)); + // IsBig + WriteColumnEnum(stream, alias.flags.isBig, SOUND_NO_YES); - // context_value - stream.WriteColumn(std::to_string(alias->contextValue)); + // Looping + WriteColumnEnum(stream, alias.flags.looping, SOUND_LOOP_TYPES); - // compression - stream.WriteColumn(""); + // PanType + WriteColumnEnum(stream, alias.flags.panType, SOUND_PAN_TYPES); - // timescale - stream.WriteColumn(alias->flags.timescale ? "yes" : "no"); + // IsMusic + WriteColumnEnum(stream, alias.flags.isMusic, SOUND_NO_YES); - // music - stream.WriteColumn(alias->flags.isMusic ? "yes" : "no"); + // Timescale + WriteColumnEnum(stream, alias.flags.timescale, SOUND_NO_YES); - // fade_in - stream.WriteColumn(std::to_string(alias->fadeIn)); + // Pauseable + WriteColumnEnum(stream, alias.flags.pauseable, SOUND_NO_YES); - // fade_out - stream.WriteColumn(std::to_string(alias->fadeOut)); + // StopOnEntDeath + WriteColumnEnum(stream, alias.flags.stopOnEntDeath, SOUND_NO_YES); - // pc_format - stream.WriteColumn(""); + // Bus + WriteColumnEnum(stream, alias.flags.busType, SOUND_BUS_IDS); - // pause - stream.WriteColumn(alias->flags.pauseable ? "yes" : "no"); + // VoiceLimit + WriteColumnEnum(stream, alias.flags.voiceLimit, SOUND_NO_YES); - // stop_on_death - stream.WriteColumn(alias->flags.stopOnDeath ? "yes" : "no"); + // IgnoreMaxDist + WriteColumnEnum(stream, alias.flags.ignoreMaxDist, SOUND_NO_YES); - // bus - stream.WriteColumn(SOUND_BUS_IDS[alias->flags.busType]); + // NeverPlayTwice + WriteColumnEnum(stream, alias.flags.neverPlayTwice, SOUND_NO_YES); - // snapshot - stream.WriteColumn(""); + // IsCinematic + WriteColumnEnum(stream, alias.flags.isCinematic, SOUND_NO_YES); } - static SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId) + SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId) { for (const auto* soundBank : SoundBank::Repository) { @@ -464,9 +639,12 @@ class AssetDumperSndBank::Internal return {}; } - void DumpSoundFilePcm(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const unsigned bitsPerSample) const + void DumpSoundFilePcm(const AssetDumpingContext& context, + const char* assetFileName, + const SoundBankEntryInputStream& soundFile, + const unsigned bitsPerSample) { - const auto outFile = OpenAssetOutputFile(assetFileName, ".wav"); + const auto outFile = OpenAssetOutputFile(context, assetFileName, ".wav"); if (!outFile) { std::cerr << std::format("Failed to open sound output file: \"{}\"\n", assetFileName); @@ -491,9 +669,12 @@ class AssetDumperSndBank::Internal } } - void DumpSoundFilePassthrough(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const std::string& extension) const + void DumpSoundFilePassthrough(const AssetDumpingContext& context, + const char* assetFileName, + const SoundBankEntryInputStream& soundFile, + const std::string& extension) { - const auto outFile = OpenAssetOutputFile(assetFileName, extension); + const auto outFile = OpenAssetOutputFile(context, assetFileName, extension); if (!outFile) { std::cerr << std::format("Failed to open sound output file: \"{}\"\n", assetFileName); @@ -509,7 +690,7 @@ class AssetDumperSndBank::Internal } } - std::optional DumpSndAlias(const SndAlias& alias) const + [[nodiscard]] std::optional DumpSndAlias(const AssetDumpingContext& context, const SndAlias& alias) { const auto soundFile = FindSoundDataInSoundBanks(alias.assetId); if (soundFile.IsOpen()) @@ -518,11 +699,11 @@ class AssetDumperSndBank::Internal switch (format) { case SND_ASSET_FORMAT_PCMS16: - DumpSoundFilePcm(alias.assetFileName, soundFile, 16u); + DumpSoundFilePcm(context, alias.assetFileName, soundFile, 16u); break; case SND_ASSET_FORMAT_FLAC: - DumpSoundFilePassthrough(alias.assetFileName, soundFile, ".flac"); + DumpSoundFilePassthrough(context, alias.assetFileName, soundFile, ".flac"); break; case SND_ASSET_FORMAT_PCMS24: @@ -549,23 +730,23 @@ class AssetDumperSndBank::Internal return {}; } - void DumpSndBankAliases(const SndBank* sndBank) const + void DumpSndBankAliases(const AssetDumpingContext& context, const LoadedSoundBankHashes& hashes, const SndBank& sndBank) { std::unordered_map> dumpedAssets; - const auto outFile = OpenAssetOutputFile(std::format("soundbank/{}.aliases", sndBank->name), ".csv"); + const auto outFile = OpenAssetOutputFile(context, std::format("soundbank/{}.aliases", sndBank.name), ".csv"); if (!outFile) { - std::cerr << std::format("Failed to open sound alias output file: \"\"\n", sndBank->name); + std::cerr << std::format("Failed to open sound alias output file: \"\"\n", sndBank.name); return; } CsvOutputStream csvStream(*outFile); WriteAliasFileHeader(csvStream); - for (auto i = 0u; i < sndBank->aliasCount; i++) + for (auto i = 0u; i < sndBank.aliasCount; i++) { - const auto& aliasList = sndBank->alias[i]; + const auto& aliasList = sndBank.alias[i]; for (auto j = 0; j < aliasList.count; j++) { @@ -577,7 +758,7 @@ class AssetDumperSndBank::Internal const auto previouslyDeterminedFormat = dumpedAssets.find(alias.assetId); if (previouslyDeterminedFormat == dumpedAssets.end()) { - maybeFormat = DumpSndAlias(alias); + maybeFormat = DumpSndAlias(context, alias); dumpedAssets[alias.assetId] = maybeFormat; } else @@ -586,30 +767,30 @@ class AssetDumperSndBank::Internal } } - WriteAliasToFile(csvStream, &alias, maybeFormat, sndBank); + WriteAliasToFile(csvStream, alias, maybeFormat, hashes); csvStream.NextRow(); } } } - void DumpSoundRadverb(const SndBank* sndBank) const + void DumpSoundRadverb(const AssetDumpingContext& context, const SndBank& sndBank) { - if (sndBank->radverbCount <= 0) + if (sndBank.radverbCount <= 0) return; - const auto outFile = OpenAssetOutputFile(std::format("soundbank/{}.reverbs", sndBank->name), ".csv"); + const auto outFile = OpenAssetOutputFile(context, std::format("soundbank/{}.reverbs", sndBank.name), ".csv"); if (!outFile) { - std::cerr << std::format("Failed to open sound reverb output file: \"{}\"\n", sndBank->name); + std::cerr << std::format("Failed to open sound reverb output file: \"{}\"\n", sndBank.name); return; } CsvOutputStream csvStream(*outFile); WriteReverbFileHeader(csvStream); - for (auto i = 0u; i < sndBank->radverbCount; i++) + for (auto i = 0u; i < sndBank.radverbCount; i++) { - const auto& reverb = sndBank->radverbs[i]; + const auto& reverb = sndBank.radverbs[i]; csvStream.WriteColumn(reverb.name); csvStream.WriteColumn(std::to_string(reverb.smoothing)); csvStream.WriteColumn(std::to_string(reverb.earlyTime)); @@ -631,15 +812,15 @@ class AssetDumperSndBank::Internal } } - void DumpSoundDucks(const SndBank* sndBank) const + void DumpSoundDucks(const AssetDumpingContext& context, const SndBank& sndBank) { - if (sndBank->duckCount <= 0) + if (sndBank.duckCount <= 0) return; - const auto outFile = OpenAssetOutputFile(std::format("soundbank/{}.ducklist", sndBank->name), ".csv"); + const auto outFile = OpenAssetOutputFile(context, std::format("soundbank/{}.ducklist", sndBank.name), ".csv"); if (!outFile) { - std::cerr << std::format("Failed to open sound reverb output file: \"{}\"\n", sndBank->name); + std::cerr << std::format("Failed to open sound reverb output file: \"{}\"\n", sndBank.name); return; } @@ -647,13 +828,13 @@ class AssetDumperSndBank::Internal csvStream.WriteColumn("name"); csvStream.NextRow(); - for (auto i = 0u; i < sndBank->duckCount; i++) + for (auto i = 0u; i < sndBank.duckCount; i++) { - const auto& duck = sndBank->ducks[i]; + const auto& duck = sndBank.ducks[i]; csvStream.WriteColumn(duck.name); csvStream.NextRow(); - const auto duckFile = OpenAssetOutputFile(std::format("soundbank/ducks/{}", duck.name), ".duk"); + const auto duckFile = OpenAssetOutputFile(context, std::format("soundbank/ducks/{}", duck.name), ".duk"); if (!outFile) { std::cerr << std::format("Failed to open sound duck output file: \"{}\"\n", duck.name); @@ -697,35 +878,25 @@ class AssetDumperSndBank::Internal } } - void DumpSndBank(const XAssetInfo* sndBankInfo) const + void DumpSndBank(const AssetDumpingContext& context, const LoadedSoundBankHashes& hashes, const XAssetInfo& sndBankInfo) { - const auto* sndBank = sndBankInfo->Asset(); + const auto* sndBank = sndBankInfo.Asset(); - DumpSndBankAliases(sndBank); - DumpSoundRadverb(sndBank); - DumpSoundDucks(sndBank); + DumpSndBankAliases(context, hashes, *sndBank); + DumpSoundRadverb(context, *sndBank); + DumpSoundDucks(context, *sndBank); } +} // namespace -public: - explicit Internal(AssetDumpingContext& context) - : m_context(context) +void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool* pool) +{ + LoadedSoundBankHashes soundBankHashes; + soundBankHashes.Initialize(); + for (const auto* assetInfo : *pool) { - } + if (!assetInfo->m_name.empty() && assetInfo->m_name[0] == ',') + continue; - void DumpPool(AssetPool* pool) const - { - for (const auto* assetInfo : *pool) - { - if (!assetInfo->m_name.empty() && assetInfo->m_name[0] == ',') - continue; - - DumpSndBank(assetInfo); - } + DumpSndBank(context, soundBankHashes, *assetInfo); } -}; - -void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool* pool) -{ - const Internal internal(context); - internal.DumpPool(pool); } From a3b9d2693c61afa521b6d5722613e64059a1e3ce Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 6 Oct 2024 16:33:40 +0200 Subject: [PATCH 3/5] chore: rename pauseable to pausable --- src/Common/Game/T6/T6_Assets.h | 2 +- src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 89394044a..fca7250e1 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -6228,7 +6228,7 @@ namespace T6 unsigned int distanceLpf : 1; // 2 unsigned int doppler : 1; // 3 unsigned int isBig : 1; // 4 - unsigned int pauseable : 1; // 5 + unsigned int pausable : 1; // 5 unsigned int isMusic : 1; // 6 unsigned int stopOnEntDeath : 1; // 7 unsigned int timescale : 1; // 8 diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp index a1e6ea9d5..b7cea9990 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp @@ -404,7 +404,7 @@ namespace void WriteColumnPitchHertz(CsvOutputStream& stream, const uint16_t value) { - const auto hertz = static_cast(value) / static_cast(std::numeric_limits::max()); + const auto hertz = static_cast(value) / static_cast(std::numeric_limits::max()); const auto cents = std::clamp(HertzToCents(hertz), -2400.0f, 1200.0f); stream.WriteColumn(std::format("{:.4g}", cents)); } @@ -605,8 +605,8 @@ namespace // Timescale WriteColumnEnum(stream, alias.flags.timescale, SOUND_NO_YES); - // Pauseable - WriteColumnEnum(stream, alias.flags.pauseable, SOUND_NO_YES); + // Pausable + WriteColumnEnum(stream, alias.flags.pausable, SOUND_NO_YES); // StopOnEntDeath WriteColumnEnum(stream, alias.flags.stopOnEntDeath, SOUND_NO_YES); From d814fe7b9503a966d948452b1c800442837a31f4 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 6 Oct 2024 16:34:01 +0200 Subject: [PATCH 4/5] refactor: refactor sound csv loading code --- src/ObjCommon/Csv/CsvHeaderRow.cpp | 34 + src/ObjCommon/Csv/CsvHeaderRow.h | 22 + src/ObjCommon/Csv/CsvStream.cpp | 72 ++ src/ObjCommon/Csv/CsvStream.h | 14 + src/ObjCommon/Csv/ParsedCsv.cpp | 80 -- src/ObjCommon/Csv/ParsedCsv.h | 45 - .../T6/AssetLoaders/AssetLoaderSoundBank.cpp | 1104 ++++++++++++----- 7 files changed, 926 insertions(+), 445 deletions(-) create mode 100644 src/ObjCommon/Csv/CsvHeaderRow.cpp create mode 100644 src/ObjCommon/Csv/CsvHeaderRow.h delete mode 100644 src/ObjCommon/Csv/ParsedCsv.cpp delete mode 100644 src/ObjCommon/Csv/ParsedCsv.h diff --git a/src/ObjCommon/Csv/CsvHeaderRow.cpp b/src/ObjCommon/Csv/CsvHeaderRow.cpp new file mode 100644 index 000000000..bb17aa466 --- /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 000000000..e3ab3f65d --- /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 cd01eed6b..2b434db2f 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 2524ec413..5c0f2def0 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 fe9c17092..000000000 --- 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 6913c80c4..000000000 --- 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 ccffc7e22..76ddc2ce1 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,432 +61,898 @@ namespace return nullptr; } -} // namespace -void* AssetLoaderSoundBank::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) -{ - auto* asset = memory->Alloc(); - asset->name = memory->Dup(assetName.c_str()); - return asset; -} + size_t GetValueIndex(const std::string& value, const char* const* lookupTable, const size_t len) + { + if (value.empty()) + return 0; -bool AssetLoaderSoundBank::CanLoadFromRaw() const -{ - return true; -} + for (auto i = 0u; i < len; i++) + { + if (lookupTable[i] == value) + return i; + } -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++) + float DbsplToLinear(const float dbsplValue) { - if (lookupTable[i] == value) - return i; + return std::pow(10.0f, (dbsplValue - 100.0f) / 20.0f); } - return 0; -} + float CentToHertz(const float cents) + { + return std::pow(2.0f, cents / 1200.0f); + } -unsigned int GetAliasSubListCount(const unsigned int startRow, const ParsedCsv& csv) -{ - auto count = 1u; + 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; - const auto name = csv[startRow].GetValue("name", true); - if (name.empty()) - return 0; + return true; + } - while (true) + bool ReadColumnVolumeDbspl( + const CsvHeaderRow& headerRow, const std::vector& row, const unsigned columnIndex, const unsigned rowIndex, uint16_t& value) { - if (startRow + count >= csv.Size()) - break; + 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; + } - const auto testName = csv[startRow + count].GetValue("name", true); - if (testName.empty()) - break; + 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; + } - // 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; + value = static_cast(DbsplToLinear(dbsplValue) * static_cast(std::numeric_limits::max())); - count++; + return true; } - return count; -} + bool ReadColumnPitchCents( + const CsvHeaderRow& headerRow, const std::vector& row, const unsigned columnIndex, const unsigned rowIndex, uint16_t& value) + { + const auto& cell = row[columnIndex]; -float DbsplToLinear(const float dbsplValue) -{ - return std::pow(10.0f, (dbsplValue - 100.0f) / 20.0f); -} + 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; + } -float CentToHertz(const float cents) -{ - return std::pow(2.0f, cents / 1200.0f); -} + 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; + } -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(CentToHertz(centValue) * static_cast(std::numeric_limits::max())); + + return true; } - value = static_cast(DbsplToLinear(dbsplValue) * static_cast(std::numeric_limits::max())); + 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]; - return true; -} + 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; + } -bool ReadColumnPitchCents(const float centValue, const char* colName, const unsigned rowIndex, uint16_t& value) -{ - if (centValue < -2400.0f || centValue > 1200.0f) + 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()) { - std::cerr << std::format("Invalid value for row {} col '{}' - {} [-2400.0, 1200.0]\n", rowIndex + 1, colName, centValue); - return false; + 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; } - value = static_cast(CentToHertz(centValue) * static_cast(std::numeric_limits::max())); + 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]; - return true; -} + 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; + } -bool LoadSoundAlias(MemoryManager* memory, SndAlias* alias, const ParsedCsvRow& row, const unsigned int rowNum) -{ - memset(alias, 0, sizeof(SndAlias)); + 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; + } - const auto& name = row.GetValue("name", true); - if (name.empty()) - return false; + value = static_cast(value32); - alias->name = memory->Dup(name.data()); - alias->id = Common::SND_HashName(name.data()); + return true; + } - const auto aliasFileName = row.GetValue("file"); - if (!aliasFileName.empty()) + bool ReadColumnNormByte(const CsvHeaderRow& headerRow, const std::vector& row, const unsigned columnIndex, const unsigned rowIndex, uint8_t& value) { - alias->assetFileName = memory->Dup(aliasFileName.data()); - alias->assetId = Common::SND_HashName(aliasFileName.data()); + 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; } - const auto secondaryName = row.GetValue("secondary"); - if (!secondaryName.empty()) - alias->secondaryName = memory->Dup(secondaryName.data()); + bool ReadColumnHash(const std::vector& row, const unsigned columnIndex, unsigned& value) + { + const auto& cell = row[columnIndex]; - const auto subtitle = row.GetValue("subtitle"); - if (!subtitle.empty()) - alias->subtitle = memory->Dup(subtitle.data()); + if (cell.m_value.empty()) + { + value = 0u; + return true; + } - alias->duck = Common::SND_HashName(row.GetValue("duck").data()); + if (cell.m_value[0] == '@') + value = std::strtoul(&cell.m_value[1], nullptr, 16); + else + value = Common::SND_HashName(cell.m_value.c_str()); - if (!ReadColumnVolumeDbspl(row.GetValueFloat("vol_min"), "vol_min", rowNum, alias->volMin)) - return false; + return true; + } - if (!ReadColumnVolumeDbspl(row.GetValueFloat("vol_max"), "vol_max", rowNum, alias->volMax)) - return false; + 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]; - if (!ReadColumnVolumeDbspl(row.GetValueFloat("pitch_min"), "pitch_min", rowNum, alias->pitchMin)) - return false; + 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; + } + } - if (!ReadColumnVolumeDbspl(row.GetValueFloat("pitch_max"), "pitch_max", rowNum, alias->pitchMax)) - return false; + 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]); - 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; + 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); + } - alias->envelopMin = row.GetValueIntenvelopMin)>("envelop_min"); - alias->envelopMax = row.GetValueIntenvelopMax)>("envelop_max"); + 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, ' '); - if (!ReadColumnVolumeDbspl(row.GetValueFloat("envelop_percentage"), "envelop_percentage", rowNum, alias->envelopPercentage)) - return false; + assert(enumValueCount <= std::numeric_limits::max()); - 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); + value = 0u; + for (const auto& entry : entries) + { + if (entry.empty()) + continue; - return true; -} + auto foundValue = false; + for (auto i = 0u; i < enumValueCount; i++) + { + if (entry == enumValues[i]) + { + value |= static_cast(1 << i); + foundValue = true; + break; + } + } -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); + 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; + } + } - const auto setAliasIndexList = std::make_unique(sndBank->aliasCount); + return true; + } - for (auto i = 0u; i < sndBank->aliasCount; i++) + template + bool ReadColumnEnumFlags(const CsvHeaderRow& headerRow, + const std::vector& row, + const unsigned columnIndex, + const unsigned rowIndex, + uint8_t& value, + const char* const (&enumValues)[Size]) { - 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; - } + return ReadColumnEnumFlags_(headerRow, row, columnIndex, rowIndex, value, enumValues, Size); } - for (auto i = 0u; i < sndBank->aliasCount; i++) + class SoundAliasHeaders { - if (setAliasIndexList[i]) - continue; + public: + SoundAliasHeaders() = default; - auto idx = sndBank->alias[i].id % sndBank->aliasCount; - while (sndBank->aliasIndex[idx].next != std::numeric_limits::max()) + bool From(const CsvHeaderRow& headerRow) { - idx = sndBank->aliasIndex[idx].next; + // 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 } - 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; + 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; + }; - freeIdx = (idx + sndBank->aliasCount - offset) % sndBank->aliasCount; - if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits::max()) - break; + 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)); - offset++; - freeIdx = std::numeric_limits::max(); + const auto& name = row[headers.m_name]; + if (name.m_value.empty()) + return false; - if (offset >= sndBank->aliasCount) - break; - } + alias.name = memory.Dup(name.m_value.c_str()); + alias.id = Common::SND_HashName(alias.name); - if (freeIdx == std::numeric_limits::max()) + const auto& aliasFileName = row[headers.m_file_source]; + if (!aliasFileName.m_value.empty()) { - std::cerr << "Unable to allocate sound bank alias index list\n"; - return false; + + fs::path p(aliasFileName.m_value); + p.replace_extension(); + alias.assetFileName = memory.Dup(p.string().c_str()); + alias.assetId = Common::SND_HashName(alias.assetFileName); } - sndBank->aliasIndex[idx].next = freeIdx; - sndBank->aliasIndex[freeIdx].value = i; - sndBank->aliasIndex[freeIdx].next = std::numeric_limits::max(); - setAliasIndexList[i] = true; + 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; } - 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); -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); + 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; + } - // Ensure there is at least one entry in the csv after the headers - if (aliasCsv.Size() > 0) + bool LoadSoundAliasList(MemoryManager& memory, SndBank* sndBank, const SearchPathOpenFile& file, unsigned& loadedEntryCount, unsigned& streamedEntryCount) { - // should be the total number of assets - sndBank->aliasCount = aliasCsv.Size(); - sndBank->alias = memory->Alloc(sndBank->aliasCount); + 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; + } - auto row = 0u; - auto listIndex = 0u; - while (row < sndBank->aliasCount) + std::vector aliasLists; + std::vector aliasList; + std::vector row; + unsigned rowIndex = 0u; + while (csv.NextRow(row)) { - // 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) + SndAlias alias; + if (!LoadSoundAlias(alias, row, rowIndex++, headerRow, headers, memory)) return false; - // allocate the sub list - sndBank->alias[listIndex].count = subListCount; - sndBank->alias[listIndex].head = memory->Alloc(subListCount); - sndBank->alias[listIndex].sequence = 0; + if (alias.flags.loadType == SA_LOADED) + loadedEntryCount++; + else + streamedEntryCount++; - // 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 (!aliasList.empty() && aliasList[0].id != alias.id) { - if (!LoadSoundAlias(memory, &sndBank->alias[listIndex].head[i], aliasCsv[row], row)) - return false; + aliasLists.emplace_back(CreateAliasList(aliasList, memory)); + aliasList.emplace_back(alias); + } + else + aliasList.emplace_back(alias); + } - // 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)++; + if (!aliasList.empty()) + aliasLists.emplace_back(CreateAliasList(aliasList, memory)); - row++; - } + sndBank->aliasCount = aliasLists.size(); + if (sndBank->aliasCount) + { + sndBank->alias = memory.Alloc(sndBank->aliasCount); + memcpy(sndBank->alias, aliasLists.data(), sizeof(SndAliasList) * sndBank->aliasCount); + } + + return true; + } - // 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; + 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); - listIndex++; + 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; + } } - // 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) + for (auto i = 0u; i < sndBank->aliasCount; i++) { - auto* oldAliases = sndBank->alias; + 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; - sndBank->aliasCount = listIndex; - sndBank->alias = memory->Alloc(sndBank->aliasCount); - memcpy(sndBank->alias, oldAliases, sizeof(SndAliasList) * sndBank->aliasCount); + offset++; + freeIdx = std::numeric_limits::max(); - memory->Free(oldAliases); + 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; } - if (!LoadSoundAliasIndexList(memory, sndBank)) - return false; + return true; } - return true; -} + class RadverbHeaders + { + public: + RadverbHeaders() = default; -bool LoadSoundRadverbs(MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file) -{ - const CsvInputStream radverbCsvStream(*file.m_stream); - const ParsedCsv radverbCsv(radverbCsvStream, true); + 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 + } - if (radverbCsv.Size() > 0) + 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) { - sndBank->radverbCount = radverbCsv.Size(); - sndBank->radverbs = memory->Alloc(sndBank->radverbCount); + 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; + } - for (auto i = 0u; i < sndBank->radverbCount; i++) + std::vector radverbs; + std::vector row; + while (csv.NextRow(row)) { - auto row = radverbCsv[i]; + const auto& name = row[headers.m_name]; + if (name.m_value.empty()) + return false; - auto name = row.GetValue("name", true); - if (name.empty()) + 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; - 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"); + radverbs.emplace_back(radverb); } - } - return true; -} + sndBank->radverbCount = radverbs.size(); + if (sndBank->radverbCount) + { + sndBank->radverbs = memory.Alloc(sndBank->radverbCount); + memcpy(sndBank->radverbs, radverbs.data(), sizeof(SndRadverb) * sndBank->radverbCount); + } -bool LoadSoundDuckList(ISearchPath* searchPath, MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file) -{ - const CsvInputStream duckListCsvStream(*file.m_stream); - const ParsedCsv duckListCsv(duckListCsvStream, true); + return true; + } - if (duckListCsv.Size() > 0) + bool LoadSoundDuckList(ISearchPath* searchPath, MemoryManager& memory, SndBank* sndBank, const SearchPathOpenFile& file) { - sndBank->duckCount = duckListCsv.Size(); - sndBank->ducks = memory->Alloc(sndBank->duckCount); - - for (auto i = 0u; i < sndBank->duckCount; i++) + const CsvInputStream csv(*file.m_stream); + CsvHeaderRow headerRow; + headerRow.Read(csv); + const auto nameRow = headerRow.GetIndexForHeader("name"); + if (!nameRow) { - auto row = duckListCsv[i]; + std::cerr << std::format("Missing name header for ducks of sound bank {}\n", sndBank->name); + return false; + } - const auto name = row.GetValue("name", true); + 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("soundbank/ducks/" + name + ".duk"); + const auto duckFile = searchPath->Open(std::format("soundbank/ducks/{}.duk", name)); if (!duckFile.IsOpen()) { - std::cerr << "Unable to find .duk file for " << name << " in ducklist for sound bank " << sndBank->name << "\n"; + std::cerr << std::format("Unable to find .duk file for {} in ducklist for sound bank {}\n", name, sndBank->name); return false; } - auto* duck = &sndBank->ducks[i]; - strncpy(duck->name, name.data(), 32); - duck->id = Common::SND_HashName(name.data()); + 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.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(); + duck.fadeInCurve = duckJson["fadeInCurveId"].get(); + duck.fadeOutCurve = duckJson["fadeOutCurveId"].get(); if (duckJson.contains("fadeInCurve")) - duck->fadeInCurve = Common::SND_HashName(duckJson["fadeInCurve"].get().data()); + duck.fadeInCurve = Common::SND_HashName(duckJson["fadeInCurve"].get().data()); if (duckJson.contains("fadeOutCurve")) - duck->fadeOutCurve = Common::SND_HashName(duckJson["fadeOutCurve"].get().data()); + duck.fadeOutCurve = Common::SND_HashName(duckJson["fadeOutCurve"].get().data()); - duck->attenuation = memory->Alloc(32u); - duck->filter = memory->Alloc(32u); + 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(); + 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) +{ + auto* asset = memory->Alloc(); + asset->name = memory->Dup(assetName.c_str()); + return asset; +} +bool AssetLoaderSoundBank::CanLoadFromRaw() const +{ return true; } @@ -494,45 +961,42 @@ bool AssetLoaderSoundBank::LoadFromRaw( { 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; + + if (!LoadSoundAliasIndexList(*memory, sndBank)) return false; - // load the soundbank reverbs - const auto radverbFile = searchPath->Open("soundbank/" + assetName + ".reverbs.csv"); + 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; } } From d2b95b4ebe8ef6e0b222a3e62e85e6aaeb6231a6 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 6 Oct 2024 17:10:50 +0200 Subject: [PATCH 5/5] chore: trim csv reader values --- src/ObjCommon/Csv/CsvStream.cpp | 18 +++++++++++++++-- src/Utils/Utils/StringUtils.cpp | 35 ++++++++++++++++++++++++++++++--- src/Utils/Utils/StringUtils.h | 6 +++++- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/ObjCommon/Csv/CsvStream.cpp b/src/ObjCommon/Csv/CsvStream.cpp index 2b434db2f..884c5e5dc 100644 --- a/src/ObjCommon/Csv/CsvStream.cpp +++ b/src/ObjCommon/Csv/CsvStream.cpp @@ -1,5 +1,7 @@ #include "CsvStream.h" +#include "Utils/StringUtils.h" + #include #include @@ -110,13 +112,17 @@ bool CsvInputStream::EmitNextRow(const std::function& cb) con auto c = m_stream.get(); const auto isEof = c == EOF; std::ostringstream col; + auto content = false; while (c != EOF) { if (c == CSV_SEPARATOR) { - cb(col.str()); + auto value = col.str(); + utils::StringTrimR(value); + cb(std::move(value)); col.clear(); col.str(std::string()); + content = false; } else if (c == '\r') { @@ -129,8 +135,14 @@ bool CsvInputStream::EmitNextRow(const std::function& cb) con { break; } + else if (isspace(c)) + { + if (content) + col << static_cast(c); + } else { + content = true; col << static_cast(c); } @@ -139,7 +151,9 @@ bool CsvInputStream::EmitNextRow(const std::function& cb) con if (!isEof) { - cb(col.str()); + auto value = col.str(); + utils::StringTrimR(value); + cb(std::move(value)); } return !isEof; diff --git a/src/Utils/Utils/StringUtils.cpp b/src/Utils/Utils/StringUtils.cpp index d582fa1f6..08e4f5442 100644 --- a/src/Utils/Utils/StringUtils.cpp +++ b/src/Utils/Utils/StringUtils.cpp @@ -1,6 +1,7 @@ #include "StringUtils.h" -#include +#include +#include #include namespace utils @@ -102,13 +103,41 @@ namespace utils c = static_cast(toupper(static_cast(c))); } - std::vector StringSplit(const std::string& str, const char delim) + void StringTrimL(std::string& str) + { + str.erase(str.begin(), + std::ranges::find_if(str, + [](const unsigned char ch) + { + return !std::isspace(ch); + })); + } + + void StringTrimR(std::string& str) + { + str.erase(std::find_if(str.rbegin(), + str.rend(), + [](const unsigned char ch) + { + return !std::isspace(ch); + }) + .base(), + str.end()); + } + + void StringTrim(std::string& str) + { + StringTrimR(str); + StringTrimL(str); + } + + std::vector StringSplit(const std::string& str, const char delimiter) { std::vector strings; std::istringstream stream(str); std::string s; - while (std::getline(stream, s, delim)) + while (std::getline(stream, s, delimiter)) { strings.emplace_back(std::move(s)); } diff --git a/src/Utils/Utils/StringUtils.h b/src/Utils/Utils/StringUtils.h index 4104c4f10..38c7ba04f 100644 --- a/src/Utils/Utils/StringUtils.h +++ b/src/Utils/Utils/StringUtils.h @@ -16,5 +16,9 @@ namespace utils void MakeStringLowerCase(std::string& str); void MakeStringUpperCase(std::string& str); - std::vector StringSplit(const std::string& str, const char delim); + void StringTrimL(std::string& str); + void StringTrimR(std::string& str); + void StringTrim(std::string& str); + + std::vector StringSplit(const std::string& str, char delimiter); } // namespace utils