From 55f97267ff29858afd05432be775f75d9287a847 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 5 Dec 2023 23:52:04 +0100 Subject: [PATCH] Move sound bank dumping code into separate files --- .../T6/AssetDumpers/AssetDumperSndBank.cpp | 617 +----------------- .../Game/T6/Sound/AliasDumperCsv.cpp | 394 +++++++++++ src/ObjWriting/Game/T6/Sound/AliasDumperCsv.h | 30 + src/ObjWriting/Game/T6/Sound/SoundCommon.cpp | 34 + src/ObjWriting/Game/T6/Sound/SoundCommon.h | 36 + .../Game/T6/Sound/SoundFileDumper.cpp | 160 +++++ .../Game/T6/Sound/SoundFileDumper.h | 9 + 7 files changed, 678 insertions(+), 602 deletions(-) create mode 100644 src/ObjWriting/Game/T6/Sound/AliasDumperCsv.cpp create mode 100644 src/ObjWriting/Game/T6/Sound/AliasDumperCsv.h create mode 100644 src/ObjWriting/Game/T6/Sound/SoundCommon.cpp create mode 100644 src/ObjWriting/Game/T6/Sound/SoundCommon.h create mode 100644 src/ObjWriting/Game/T6/Sound/SoundFileDumper.cpp create mode 100644 src/ObjWriting/Game/T6/Sound/SoundFileDumper.h diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp index e556629e..320ea76b 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp @@ -1,617 +1,30 @@ #include "AssetDumperSndBank.h" -#include "Csv/CsvStream.h" -#include "ObjContainer/SoundBank/SoundBank.h" -#include "Sound/WavWriter.h" -#include "Utils/ClassUtils.h" - -#include -#include -#include -#include -#include -#include +#include "Game/T6/Sound/AliasDumperCsv.h" +#include "Game/T6/Sound/SoundFileDumper.h" using namespace T6; -namespace fs = std::filesystem; -namespace +namespace T6::sound { - 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", - "# 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", - }; - - const std::string PREFIXES_TO_DROP[]{ - "raw/", - "devraw/", - }; - - constexpr size_t FRAME_RATE_FOR_INDEX[]{ - 8000, - 12000, - 16000, - 24000, - 32000, - 44100, - 48000, - 96000, - 192000, - }; - - constexpr const char* EXTENSION_BY_FORMAT[SND_ASSET_FORMAT_COUNT]{ - ".wav", // SND_ASSET_FORMAT_PCMS16 - ".wav", // SND_ASSET_FORMAT_PCMS24 - ".wav", // SND_ASSET_FORMAT_PCMS32 - ".wav", // SND_ASSET_FORMAT_IEEE - ".xma", // SND_ASSET_FORMAT_XMA4 - ".mp3", // SND_ASSET_FORMAT_MP3 - ".wav", // SND_ASSET_FORMAT_MSADPCM - ".wma", // SND_ASSET_FORMAT_WMA - ".flac", // SND_ASSET_FORMAT_FLAC - ".wav", // SND_ASSET_FORMAT_WIIUADPCM - ".mpc", // SND_ASSET_FORMAT_MPC - }; - -} // namespace - -class AssetDumperSndBank::Internal -{ - AssetDumpingContext& m_context; - - _NODISCARD std::string GetAssetFilename(std::string outputFileName, const std::string& extension) const - { - fs::path assetPath(m_context.m_base_path); - - std::replace(outputFileName.begin(), outputFileName.end(), '\\', '/'); - for (const auto& droppedPrefix : PREFIXES_TO_DROP) - { - if (outputFileName.rfind(droppedPrefix, 0) != std::string::npos) - { - outputFileName.erase(0, droppedPrefix.size()); - break; - } - } - - assetPath.append(outputFileName); - if (!extension.empty()) - assetPath.concat(extension); - - return assetPath.string(); - } - - _NODISCARD std::unique_ptr OpenAssetOutputFile(const std::string& outputFileName, const std::string& extension) const - { - fs::path assetPath(GetAssetFilename(outputFileName, extension)); - - auto assetDir(assetPath); - assetDir.remove_filename(); - - create_directories(assetDir); - - auto outputStream = std::make_unique(assetPath, std::ios_base::out | std::ios_base::binary); - - if (outputStream->is_open()) - { - return std::move(outputStream); - } - - return nullptr; - } - - std::unique_ptr OpenAliasOutputFile(const SndBank* sndBank) const - { - std::ostringstream ss; - - const char* name; - if (sndBank->streamAssetBank.zone) - name = sndBank->streamAssetBank.zone; - else if (sndBank->loadAssetBank.zone) - name = sndBank->loadAssetBank.zone; - else if (sndBank->loadedAssets.zone) - name = sndBank->loadedAssets.zone; - else - name = sndBank->name; - - ss << "soundaliases/" << name << "_aliases.csv"; - - return m_context.OpenAssetFile(ss.str()); - } - - static void WriteAliasFileHeader(CsvOutputStream& stream) - { - for (const auto& headerField : ALIAS_HEADERS) - { - stream.WriteColumn(headerField); - } - - stream.NextRow(); - } - - void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias) const - { - SoundAssetBankEntry entry; - std::string extension; - if (FindSoundBankEntry(alias->assetId, entry)) - { - assert(entry.format < SND_ASSET_FORMAT_COUNT); - if (entry.format < SND_ASSET_FORMAT_COUNT) - extension = EXTENSION_BY_FORMAT[entry.format]; - } - - // name - WriteColumnNullSafe(stream, alias->name); - - // file - if (alias->assetFileName) - stream.WriteColumn(GetAssetFilename(alias->assetFileName, extension)); - else - stream.WriteColumn(""); - - // template - stream.WriteColumn(""); - - // loadspec - stream.WriteColumn(""); - - // secondary - WriteColumnNullSafe(stream, alias->secondaryname); - - // group - stream.WriteColumn(""); - - // vol_min - WriteColumnFloat16(stream, alias->volMin); - - // vol_max - WriteColumnFloat16(stream, alias->volMax); - - // team_vol_mod - stream.WriteColumn(""); - - // dist_min - WriteColumnFloat16(stream, alias->distMin); - - // dist_max - WriteColumnFloat16(stream, alias->distMax); - - // dist_reverb_max - WriteColumnFloat16(stream, alias->distReverbMax); - - // volume_falloff_curve - stream.WriteColumn(""); - - // reverb_falloff_curve - stream.WriteColumn(""); - - // volume_min_falloff_curve - stream.WriteColumn(""); - - // reverb_min_falloff_curve - stream.WriteColumn(""); - - // limit_count - WriteColumnUnsignedNumeric(stream, alias->limitCount); - - // limit_type - stream.WriteColumn(""); - - // entity_limit_count - WriteColumnUnsignedNumeric(stream, alias->entityLimitCount); - - // entity_limit_type - stream.WriteColumn(""); - - // pitch_min - WriteColumnFloat16(stream, alias->pitchMin); - - // pitch_max - WriteColumnFloat16(stream, alias->pitchMax); - - // team_pitch_mod - stream.WriteColumn(""); - - // min_priority - WriteColumnUnsignedNumeric(stream, alias->minPriority); - - // max_priority - WriteColumnUnsignedNumeric(stream, alias->maxPriority); - - // min_priority_threshold - WriteColumnUnsignedNumeric(stream, alias->minPriorityThreshold); - - // max_priority_threshold - WriteColumnUnsignedNumeric(stream, alias->maxPriorityThreshold); - - // spatialized - stream.WriteColumn(""); - - // type - stream.WriteColumn(""); - - // loop - stream.WriteColumn(""); - - // randomize_type - stream.WriteColumn(""); - - // probability - WriteColumnUnsignedNumeric(stream, alias->probability); - - // start_delay - WriteColumnUnsignedNumeric(stream, alias->startDelay); - - // reverb_send - WriteColumnUnsignedNumeric(stream, alias->reverbSend); - - // duck - WriteColumnUnsignedNumeric(stream, alias->duck); - - // pan - WriteColumnUnsignedNumeric(stream, alias->pan); - - // center_send - WriteColumnUnsignedNumeric(stream, alias->centerSend); - - // envelop_min - WriteColumnUnsignedNumeric(stream, alias->envelopMin); - - // envelop_max - WriteColumnUnsignedNumeric(stream, alias->envelopMax); - - // envelop_percentage - WriteColumnUnsignedNumeric(stream, alias->envelopPercentage); - - // occlusion_level - WriteColumnFloat8(stream, alias->occlusionLevel); - - // occlusion_wet_dry - stream.WriteColumn(""); - - // is_big - stream.WriteColumn(""); - - // distance_lpf - stream.WriteColumn(""); - - // move_type - stream.WriteColumn(""); - - // move_time - stream.WriteColumn(""); - - // real_delay - stream.WriteColumn(""); - - // subtitle - WriteColumnNullSafe(stream, alias->subtitle); - - // mature - stream.WriteColumn(""); - - // doppler - stream.WriteColumn(""); - - // futz - WriteColumnUnsignedNumeric(stream, alias->futzPatch); - - // context_type - stream.WriteColumn(""); - - // context_value - stream.WriteColumn(""); - - // compression - stream.WriteColumn(""); - - // timescale - stream.WriteColumn(""); - - // music - stream.WriteColumn(""); - - // fade_in - WriteColumnSignedNumeric(stream, alias->fadeIn); - - // fade_out - WriteColumnSignedNumeric(stream, alias->fadeOut); - - // pc_format - stream.WriteColumn(""); - - // pause - stream.WriteColumn(""); - - // stop_on_death - stream.WriteColumn(""); - - // bus - stream.WriteColumn(""); - - // snapshot - stream.WriteColumn(""); - - stream.NextRow(); - } - - static void WriteColumnNullSafe(CsvOutputStream& stream, const char* value) - { - if (value) - stream.WriteColumn(value); - else - stream.WriteColumn(""); - } - - static void WriteColumnFloat8(CsvOutputStream& stream, const uint8_t value) - { - std::ostringstream ss; - - ss << std::setprecision(6) << std::fixed << (static_cast(value) / static_cast(std::numeric_limits::max())); - - stream.WriteColumn(ss.str()); - } - - static void WriteColumnFloat16(CsvOutputStream& stream, const uint16_t value) - { - std::ostringstream ss; - - ss << std::setprecision(6) << std::fixed << (static_cast(value) / static_cast(std::numeric_limits::max())); - - stream.WriteColumn(ss.str()); - } - - static void WriteColumnSignedNumeric(CsvOutputStream& stream, const int value) - { - stream.WriteColumn(std::to_string(value)); - } - - static void WriteColumnUnsignedNumeric(CsvOutputStream& stream, const unsigned int value) - { - stream.WriteColumn(std::to_string(value)); - } - - void DumpSndBankAliases(const SndBank* sndBank) const - { - const auto outputFile = OpenAliasOutputFile(sndBank); - - if (outputFile == nullptr) - { - std::cout << "Failed to open sound alias output file for: \"" << sndBank->name << "\"" << std::endl; - return; - } - - CsvOutputStream csvStream(*outputFile); - WriteAliasFileHeader(csvStream); - - for (auto i = 0u; i < sndBank->aliasCount; i++) - { - const auto& aliasList = sndBank->alias[i]; - for (auto j = 0; j < aliasList.count; j++) - { - const auto& alias = aliasList.head[j]; - WriteAliasToFile(csvStream, &alias); - } - } - } - - static bool FindSoundBankEntry(const unsigned assetId, SoundAssetBankEntry& entry) - { - for (const auto* soundBank : SoundBank::Repository) - { - if (soundBank->GetEntry(assetId, entry)) - return true; - } - - return false; - } - - static SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId) - { - for (const auto* soundBank : SoundBank::Repository) - { - auto soundFile = soundBank->GetEntryStream(assetId); - if (soundFile.IsOpen()) - return soundFile; - } - - return {}; - } - - void DumpSoundFilePcm(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const unsigned bitsPerSample) const - { - assert(soundFile.m_entry.format < SND_ASSET_FORMAT_COUNT); - const auto outFile = OpenAssetOutputFile(assetFileName, EXTENSION_BY_FORMAT[soundFile.m_entry.format]); - if (!outFile) - { - std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; - return; - } - - const WavWriter writer(*outFile); - - if (soundFile.m_entry.frameRateIndex >= std::extent_v) - return; - - const WavMetaData metaData{soundFile.m_entry.channelCount, FRAME_RATE_FOR_INDEX[soundFile.m_entry.frameRateIndex], bitsPerSample}; - - writer.WritePcmHeader(metaData, soundFile.m_entry.size); - - while (!soundFile.m_stream->eof()) - { - char buffer[2048]; - soundFile.m_stream->read(buffer, sizeof(buffer)); - const auto readSize = soundFile.m_stream->gcount(); - outFile->write(buffer, readSize); - } - } - - void DumpSoundFilePassthrough(const char* assetFileName, const SoundBankEntryInputStream& soundFile) const - { - assert(soundFile.m_entry.format < SND_ASSET_FORMAT_COUNT); - const auto outFile = OpenAssetOutputFile(assetFileName, EXTENSION_BY_FORMAT[soundFile.m_entry.format]); - if (!outFile) - { - std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; - return; - } - - while (!soundFile.m_stream->eof()) - { - char buffer[2048]; - soundFile.m_stream->read(buffer, sizeof(buffer)); - const auto readSize = soundFile.m_stream->gcount(); - outFile->write(buffer, readSize); - } - } - - void DumpSndAlias(const SndAlias& alias) const - { - const auto soundFile = FindSoundDataInSoundBanks(alias.assetId); - if (soundFile.IsOpen()) - { - const auto format = static_cast(soundFile.m_entry.format); - switch (format) - { - case SND_ASSET_FORMAT_PCMS16: - DumpSoundFilePcm(alias.assetFileName, soundFile, 16u); - break; - - case SND_ASSET_FORMAT_FLAC: - DumpSoundFilePassthrough(alias.assetFileName, soundFile); - break; - - case SND_ASSET_FORMAT_PCMS24: - case SND_ASSET_FORMAT_PCMS32: - case SND_ASSET_FORMAT_IEEE: - case SND_ASSET_FORMAT_XMA4: - case SND_ASSET_FORMAT_MSADPCM: - case SND_ASSET_FORMAT_WMA: - case SND_ASSET_FORMAT_WIIUADPCM: - case SND_ASSET_FORMAT_MPC: - std::cerr << "Cannot dump sound (Unsupported sound format " << format << "): \"" << alias.assetFileName << "\"\n"; - break; - - default: - assert(false); - std::cerr << "Cannot dump sound (Unknown sound format " << format << "): \"" << alias.assetFileName << "\"\n"; - break; - } - } - else - { - std::cerr << "Could not find data for sound \"" << alias.assetFileName << "\"\n"; - } - } - - void DumpSoundData(const SndBank* sndBank) const - { - std::unordered_set dumpedAssets; - - for (auto i = 0u; i < sndBank->aliasCount; i++) - { - const auto& aliasList = sndBank->alias[i]; - - for (auto j = 0; j < aliasList.count; j++) - { - const auto& alias = aliasList.head[j]; - if (alias.assetId && alias.assetFileName && dumpedAssets.find(alias.assetId) == dumpedAssets.end()) - { - DumpSndAlias(alias); - dumpedAssets.emplace(alias.assetId); - } - } - } - } - - void DumpSndBank(const XAssetInfo* sndBankInfo) const + void DumpSndBank(const AssetDumpingContext& context, const XAssetInfo* sndBankInfo) { const auto* sndBank = sndBankInfo->Asset(); - DumpSndBankAliases(sndBank); - DumpSoundData(sndBank); - } + const AliasDumperCsv aliasDumper(context); + aliasDumper.DumpSndBankAliases(sndBank); -public: - explicit Internal(AssetDumpingContext& context) - : m_context(context) - { + DumpSoundData(context, sndBank); } - - void DumpPool(AssetPool* pool) const - { - for (const auto* assetInfo : *pool) - { - if (!assetInfo->m_name.empty() && assetInfo->m_name[0] == ',') - continue; - - DumpSndBank(assetInfo); - } - } -}; +}; // namespace T6::sound void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool* pool) { - const Internal internal(context); - internal.DumpPool(pool); + for (const auto* assetInfo : *pool) + { + if (!assetInfo->m_name.empty() && assetInfo->m_name[0] == ',') + continue; + + sound::DumpSndBank(context, assetInfo); + } } diff --git a/src/ObjWriting/Game/T6/Sound/AliasDumperCsv.cpp b/src/ObjWriting/Game/T6/Sound/AliasDumperCsv.cpp new file mode 100644 index 00000000..269608d6 --- /dev/null +++ b/src/ObjWriting/Game/T6/Sound/AliasDumperCsv.cpp @@ -0,0 +1,394 @@ +#include "AliasDumperCsv.h" + +#include "Csv/CsvStream.h" +#include "Game/T6/AssetDumpers/AssetDumperSndBank.h" +#include "ObjContainer/SoundBank/SoundBank.h" +#include "ObjContainer/SoundBank/SoundBankTypes.h" +#include "SoundCommon.h" + +#include +#include +#include +#include + +using namespace T6; + +namespace T6::sound +{ + 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", + "# 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", + }; + + AliasDumperCsv::AliasDumperCsv(const AssetDumpingContext& context) + : m_context(context) + { + } + + std::unique_ptr AliasDumperCsv::OpenAliasOutputFile(const SndBank* sndBank) const + { + std::ostringstream ss; + + const char* name; + if (sndBank->streamAssetBank.zone) + name = sndBank->streamAssetBank.zone; + else if (sndBank->loadAssetBank.zone) + name = sndBank->loadAssetBank.zone; + else if (sndBank->loadedAssets.zone) + name = sndBank->loadedAssets.zone; + else + name = sndBank->name; + + ss << "soundaliases/" << name << "_aliases.csv"; + + return m_context.OpenAssetFile(ss.str()); + } + + void AliasDumperCsv::WriteAliasFileHeader(CsvOutputStream& stream) + { + for (const auto& headerField : ALIAS_HEADERS) + { + stream.WriteColumn(headerField); + } + + stream.NextRow(); + } + + bool AliasDumperCsv::FindSoundBankEntry(const unsigned assetId, SoundAssetBankEntry& entry) + { + for (const auto* soundBank : SoundBank::Repository) + { + if (soundBank->GetEntry(assetId, entry)) + return true; + } + + return false; + } + + void AliasDumperCsv::WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias) const + { + SoundAssetBankEntry entry; + std::string extension; + if (FindSoundBankEntry(alias->assetId, entry)) + { + assert(entry.format < SND_ASSET_FORMAT_COUNT); + if (entry.format < SND_ASSET_FORMAT_COUNT) + extension = sound::EXTENSION_BY_FORMAT[entry.format]; + } + + // name + WriteColumnNullSafe(stream, alias->name); + + // file + if (alias->assetFileName) + stream.WriteColumn(GetAssetFilename(m_context.m_base_path, alias->assetFileName, extension)); + else + stream.WriteColumn(""); + + // template + stream.WriteColumn(""); + + // loadspec + stream.WriteColumn(""); + + // secondary + WriteColumnNullSafe(stream, alias->secondaryname); + + // group + stream.WriteColumn(""); + + // vol_min + WriteColumnFloat16(stream, alias->volMin); + + // vol_max + WriteColumnFloat16(stream, alias->volMax); + + // team_vol_mod + stream.WriteColumn(""); + + // dist_min + WriteColumnFloat16(stream, alias->distMin); + + // dist_max + WriteColumnFloat16(stream, alias->distMax); + + // dist_reverb_max + WriteColumnFloat16(stream, alias->distReverbMax); + + // volume_falloff_curve + stream.WriteColumn(""); + + // reverb_falloff_curve + stream.WriteColumn(""); + + // volume_min_falloff_curve + stream.WriteColumn(""); + + // reverb_min_falloff_curve + stream.WriteColumn(""); + + // limit_count + WriteColumnUnsignedNumeric(stream, alias->limitCount); + + // limit_type + stream.WriteColumn(""); + + // entity_limit_count + WriteColumnUnsignedNumeric(stream, alias->entityLimitCount); + + // entity_limit_type + stream.WriteColumn(""); + + // pitch_min + WriteColumnFloat16(stream, alias->pitchMin); + + // pitch_max + WriteColumnFloat16(stream, alias->pitchMax); + + // team_pitch_mod + stream.WriteColumn(""); + + // min_priority + WriteColumnUnsignedNumeric(stream, alias->minPriority); + + // max_priority + WriteColumnUnsignedNumeric(stream, alias->maxPriority); + + // min_priority_threshold + WriteColumnUnsignedNumeric(stream, alias->minPriorityThreshold); + + // max_priority_threshold + WriteColumnUnsignedNumeric(stream, alias->maxPriorityThreshold); + + // spatialized + stream.WriteColumn(""); + + // type + stream.WriteColumn(""); + + // loop + stream.WriteColumn(""); + + // randomize_type + stream.WriteColumn(""); + + // probability + WriteColumnUnsignedNumeric(stream, alias->probability); + + // start_delay + WriteColumnUnsignedNumeric(stream, alias->startDelay); + + // reverb_send + WriteColumnUnsignedNumeric(stream, alias->reverbSend); + + // duck + WriteColumnUnsignedNumeric(stream, alias->duck); + + // pan + WriteColumnUnsignedNumeric(stream, alias->pan); + + // center_send + WriteColumnUnsignedNumeric(stream, alias->centerSend); + + // envelop_min + WriteColumnUnsignedNumeric(stream, alias->envelopMin); + + // envelop_max + WriteColumnUnsignedNumeric(stream, alias->envelopMax); + + // envelop_percentage + WriteColumnUnsignedNumeric(stream, alias->envelopPercentage); + + // occlusion_level + WriteColumnFloat8(stream, alias->occlusionLevel); + + // occlusion_wet_dry + stream.WriteColumn(""); + + // is_big + stream.WriteColumn(""); + + // distance_lpf + stream.WriteColumn(""); + + // move_type + stream.WriteColumn(""); + + // move_time + stream.WriteColumn(""); + + // real_delay + stream.WriteColumn(""); + + // subtitle + WriteColumnNullSafe(stream, alias->subtitle); + + // mature + stream.WriteColumn(""); + + // doppler + stream.WriteColumn(""); + + // futz + WriteColumnUnsignedNumeric(stream, alias->futzPatch); + + // context_type + stream.WriteColumn(""); + + // context_value + stream.WriteColumn(""); + + // compression + stream.WriteColumn(""); + + // timescale + stream.WriteColumn(""); + + // music + stream.WriteColumn(""); + + // fade_in + WriteColumnSignedNumeric(stream, alias->fadeIn); + + // fade_out + WriteColumnSignedNumeric(stream, alias->fadeOut); + + // pc_format + stream.WriteColumn(""); + + // pause + stream.WriteColumn(""); + + // stop_on_death + stream.WriteColumn(""); + + // bus + stream.WriteColumn(""); + + // snapshot + stream.WriteColumn(""); + + stream.NextRow(); + } + + void AliasDumperCsv::WriteColumnNullSafe(CsvOutputStream& stream, const char* value) + { + if (value) + stream.WriteColumn(value); + else + stream.WriteColumn(""); + } + + void AliasDumperCsv::WriteColumnFloat8(CsvOutputStream& stream, const uint8_t value) + { + std::ostringstream ss; + + ss << std::setprecision(6) << std::fixed << (static_cast(value) / static_cast(std::numeric_limits::max())); + + stream.WriteColumn(ss.str()); + } + + void AliasDumperCsv::WriteColumnFloat16(CsvOutputStream& stream, const uint16_t value) + { + std::ostringstream ss; + + ss << std::setprecision(6) << std::fixed << (static_cast(value) / static_cast(std::numeric_limits::max())); + + stream.WriteColumn(ss.str()); + } + + void AliasDumperCsv::WriteColumnSignedNumeric(CsvOutputStream& stream, const int value) + { + stream.WriteColumn(std::to_string(value)); + } + + void AliasDumperCsv::WriteColumnUnsignedNumeric(CsvOutputStream& stream, const unsigned int value) + { + stream.WriteColumn(std::to_string(value)); + } + + void AliasDumperCsv::DumpSndBankAliases(const SndBank* sndBank) const + { + const auto outputFile = OpenAliasOutputFile(sndBank); + + if (outputFile == nullptr) + { + std::cout << "Failed to open sound alias output file for: \"" << sndBank->name << "\"" << std::endl; + return; + } + + CsvOutputStream csvStream(*outputFile); + WriteAliasFileHeader(csvStream); + + for (auto i = 0u; i < sndBank->aliasCount; i++) + { + const auto& aliasList = sndBank->alias[i]; + for (auto j = 0; j < aliasList.count; j++) + { + const auto& alias = aliasList.head[j]; + WriteAliasToFile(csvStream, &alias); + } + } + } +} // namespace T6::sound diff --git a/src/ObjWriting/Game/T6/Sound/AliasDumperCsv.h b/src/ObjWriting/Game/T6/Sound/AliasDumperCsv.h new file mode 100644 index 00000000..bcee5afe --- /dev/null +++ b/src/ObjWriting/Game/T6/Sound/AliasDumperCsv.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Csv/CsvStream.h" +#include "Dumping/AssetDumpingContext.h" +#include "Game/T6/T6.h" +#include "ObjContainer/SoundBank/SoundBankTypes.h" + +namespace T6::sound +{ + class AliasDumperCsv + { + public: + explicit AliasDumperCsv(const AssetDumpingContext& context); + + void DumpSndBankAliases(const SndBank* sndBank) const; + + private: + std::unique_ptr OpenAliasOutputFile(const SndBank* sndBank) const; + static void WriteAliasFileHeader(CsvOutputStream& stream); + static bool FindSoundBankEntry(unsigned assetId, SoundAssetBankEntry& entry); + void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias) const; + static void WriteColumnNullSafe(CsvOutputStream& stream, const char* value); + static void WriteColumnFloat8(CsvOutputStream& stream, uint8_t value); + static void WriteColumnFloat16(CsvOutputStream& stream, uint16_t value); + static void WriteColumnSignedNumeric(CsvOutputStream& stream, int value); + static void WriteColumnUnsignedNumeric(CsvOutputStream& stream, unsigned value); + + const AssetDumpingContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjWriting/Game/T6/Sound/SoundCommon.cpp b/src/ObjWriting/Game/T6/Sound/SoundCommon.cpp new file mode 100644 index 00000000..593f4961 --- /dev/null +++ b/src/ObjWriting/Game/T6/Sound/SoundCommon.cpp @@ -0,0 +1,34 @@ +#include "SoundCommon.h" + +#include + +namespace fs = std::filesystem; + +namespace T6::sound +{ + const std::string PREFIXES_TO_DROP[]{ + "raw/", + "devraw/", + }; + + _NODISCARD std::string GetAssetFilename(const std::string& basePath, std::string outputFileName, const std::string& extension) + { + fs::path assetPath(basePath); + + std::replace(outputFileName.begin(), outputFileName.end(), '\\', '/'); + for (const auto& droppedPrefix : PREFIXES_TO_DROP) + { + if (outputFileName.rfind(droppedPrefix, 0) != std::string::npos) + { + outputFileName.erase(0, droppedPrefix.size()); + break; + } + } + + assetPath.append(outputFileName); + if (!extension.empty()) + assetPath.concat(extension); + + return assetPath.string(); + } +} \ No newline at end of file diff --git a/src/ObjWriting/Game/T6/Sound/SoundCommon.h b/src/ObjWriting/Game/T6/Sound/SoundCommon.h new file mode 100644 index 00000000..2d3cd517 --- /dev/null +++ b/src/ObjWriting/Game/T6/Sound/SoundCommon.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Game/T6/T6.h" + +#include + +namespace T6::sound +{ + constexpr size_t FRAME_RATE_FOR_INDEX[]{ + 8000, + 12000, + 16000, + 24000, + 32000, + 44100, + 48000, + 96000, + 192000, + }; + + constexpr const char* EXTENSION_BY_FORMAT[SND_ASSET_FORMAT_COUNT]{ + ".wav", // SND_ASSET_FORMAT_PCMS16 + ".wav", // SND_ASSET_FORMAT_PCMS24 + ".wav", // SND_ASSET_FORMAT_PCMS32 + ".wav", // SND_ASSET_FORMAT_IEEE + ".xma", // SND_ASSET_FORMAT_XMA4 + ".mp3", // SND_ASSET_FORMAT_MP3 + ".wav", // SND_ASSET_FORMAT_MSADPCM + ".wma", // SND_ASSET_FORMAT_WMA + ".flac", // SND_ASSET_FORMAT_FLAC + ".wav", // SND_ASSET_FORMAT_WIIUADPCM + ".mpc", // SND_ASSET_FORMAT_MPC + }; + + _NODISCARD std::string GetAssetFilename(const std::string& basePath, std::string outputFileName, const std::string& extension); +} \ No newline at end of file diff --git a/src/ObjWriting/Game/T6/Sound/SoundFileDumper.cpp b/src/ObjWriting/Game/T6/Sound/SoundFileDumper.cpp new file mode 100644 index 00000000..eb525934 --- /dev/null +++ b/src/ObjWriting/Game/T6/Sound/SoundFileDumper.cpp @@ -0,0 +1,160 @@ +#include "SoundFileDumper.h" + +#include "Csv/CsvStream.h" +#include "Dumping/AssetDumpingContext.h" +#include "Game/T6/Sound/SoundCommon.h" +#include "ObjContainer/SoundBank/SoundBank.h" +#include "Sound/WavWriter.h" +#include "Utils/ClassUtils.h" + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace T6::sound +{ + _NODISCARD std::unique_ptr + OpenAssetOutputFile(const AssetDumpingContext& context, const std::string& outputFileName, const std::string& extension) + { + fs::path assetPath(GetAssetFilename(context.m_base_path, outputFileName, extension)); + + auto assetDir(assetPath); + assetDir.remove_filename(); + + create_directories(assetDir); + + auto outputStream = std::make_unique(assetPath, std::ios_base::out | std::ios_base::binary); + + if (outputStream->is_open()) + { + return std::move(outputStream); + } + + return nullptr; + } + + static SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId) + { + for (const auto* soundBank : SoundBank::Repository) + { + auto soundFile = soundBank->GetEntryStream(assetId); + if (soundFile.IsOpen()) + return soundFile; + } + + return {}; + } + + void DumpSoundFilePcm(const AssetDumpingContext& context, + const char* assetFileName, + const SoundBankEntryInputStream& soundFile, + const unsigned bitsPerSample) + { + assert(soundFile.m_entry.format < SND_ASSET_FORMAT_COUNT); + const auto outFile = OpenAssetOutputFile(context, assetFileName, EXTENSION_BY_FORMAT[soundFile.m_entry.format]); + if (!outFile) + { + std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; + return; + } + + const WavWriter writer(*outFile); + + if (soundFile.m_entry.frameRateIndex >= std::extent_v) + return; + + const WavMetaData metaData{soundFile.m_entry.channelCount, FRAME_RATE_FOR_INDEX[soundFile.m_entry.frameRateIndex], bitsPerSample}; + + writer.WritePcmHeader(metaData, soundFile.m_entry.size); + + while (!soundFile.m_stream->eof()) + { + char buffer[2048]; + soundFile.m_stream->read(buffer, sizeof(buffer)); + const auto readSize = soundFile.m_stream->gcount(); + outFile->write(buffer, readSize); + } + } + + void DumpSoundFilePassthrough(const AssetDumpingContext& context, const char* assetFileName, const SoundBankEntryInputStream& soundFile) + { + assert(soundFile.m_entry.format < SND_ASSET_FORMAT_COUNT); + const auto outFile = OpenAssetOutputFile(context, assetFileName, EXTENSION_BY_FORMAT[soundFile.m_entry.format]); + if (!outFile) + { + std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; + return; + } + + while (!soundFile.m_stream->eof()) + { + char buffer[2048]; + soundFile.m_stream->read(buffer, sizeof(buffer)); + const auto readSize = soundFile.m_stream->gcount(); + outFile->write(buffer, readSize); + } + } + + void DumpSndAlias(const AssetDumpingContext& context, const SndAlias& alias) + { + const auto soundFile = FindSoundDataInSoundBanks(alias.assetId); + if (soundFile.IsOpen()) + { + const auto format = static_cast(soundFile.m_entry.format); + switch (format) + { + case SND_ASSET_FORMAT_PCMS16: + DumpSoundFilePcm(context, alias.assetFileName, soundFile, 16u); + break; + + case SND_ASSET_FORMAT_FLAC: + DumpSoundFilePassthrough(context, alias.assetFileName, soundFile); + break; + + case SND_ASSET_FORMAT_PCMS24: + case SND_ASSET_FORMAT_PCMS32: + case SND_ASSET_FORMAT_IEEE: + case SND_ASSET_FORMAT_XMA4: + case SND_ASSET_FORMAT_MSADPCM: + case SND_ASSET_FORMAT_WMA: + case SND_ASSET_FORMAT_WIIUADPCM: + case SND_ASSET_FORMAT_MPC: + std::cerr << "Cannot dump sound (Unsupported sound format " << format << "): \"" << alias.assetFileName << "\"\n"; + break; + + default: + assert(false); + std::cerr << "Cannot dump sound (Unknown sound format " << format << "): \"" << alias.assetFileName << "\"\n"; + break; + } + } + else + { + std::cerr << "Could not find data for sound \"" << alias.assetFileName << "\"\n"; + } + } + + void DumpSoundData(const AssetDumpingContext& context, const SndBank* sndBank) + { + std::unordered_set dumpedAssets; + + for (auto i = 0u; i < sndBank->aliasCount; i++) + { + const auto& aliasList = sndBank->alias[i]; + + for (auto j = 0; j < aliasList.count; j++) + { + const auto& alias = aliasList.head[j]; + if (alias.assetId && alias.assetFileName && dumpedAssets.find(alias.assetId) == dumpedAssets.end()) + { + DumpSndAlias(context, alias); + dumpedAssets.emplace(alias.assetId); + } + } + } + } +} \ No newline at end of file diff --git a/src/ObjWriting/Game/T6/Sound/SoundFileDumper.h b/src/ObjWriting/Game/T6/Sound/SoundFileDumper.h new file mode 100644 index 00000000..36b407db --- /dev/null +++ b/src/ObjWriting/Game/T6/Sound/SoundFileDumper.h @@ -0,0 +1,9 @@ +#pragma once + +#include "Dumping/AssetDumpingContext.h" +#include "Game/T6/T6.h" + +namespace T6::sound +{ + void DumpSoundData(const AssetDumpingContext& context, const SndBank* sndBank); +} \ No newline at end of file