diff --git a/src/ObjLoading/ObjContainer/SoundBank/SoundBankTypes.h b/src/ObjLoading/ObjContainer/SoundBank/SoundBankTypes.h index 46927c27c..4254ec8ea 100644 --- a/src/ObjLoading/ObjContainer/SoundBank/SoundBankTypes.h +++ b/src/ObjLoading/ObjContainer/SoundBank/SoundBankTypes.h @@ -38,8 +38,8 @@ struct SoundAssetBankEntry unsigned int size; unsigned int offset; unsigned int frameCount; - char frameRateIndex; - char channelCount; - char looping; - char format; + unsigned char frameRateIndex; + unsigned char channelCount; + unsigned char looping; + unsigned char format; }; diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLoadedSound.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLoadedSound.cpp index f7c97209d..66ab28e3d 100644 --- a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLoadedSound.cpp +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperLoadedSound.cpp @@ -1,6 +1,7 @@ #include "AssetDumperLoadedSound.h" #include "Sound/WavTypes.h" +#include "Sound/WavWriter.h" using namespace IW4; @@ -11,43 +12,16 @@ bool AssetDumperLoadedSound::ShouldDump(XAssetInfo* asset) void AssetDumperLoadedSound::DumpWavPcm(AssetDumpingContext& context, const LoadedSound* asset, std::ostream& stream) { - const auto riffMasterChunkSize = sizeof(WAV_CHUNK_ID_RIFF) - + sizeof(uint32_t) - + sizeof(WAV_WAVE_ID) - + sizeof(WavChunkHeader) - + sizeof(WavFormatChunkPcm) - + sizeof(WavChunkHeader) - + sizeof(asset->sound.info.data_len); + const WavWriter writer(stream); - stream.write(reinterpret_cast(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF)); - stream.write(reinterpret_cast(&riffMasterChunkSize), sizeof(riffMasterChunkSize)); - stream.write(reinterpret_cast(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID)); - - const WavChunkHeader formatChunkHeader - { - WAV_CHUNK_ID_FMT, - sizeof(WavFormatChunkPcm) + const WavMetaData metaData{ + static_cast(asset->sound.info.channels), + static_cast(asset->sound.info.rate), + static_cast(asset->sound.info.bits) }; - stream.write(reinterpret_cast(&formatChunkHeader), sizeof(formatChunkHeader)); - WavFormatChunkPcm formatChunk - { - WavFormat::PCM, - static_cast(asset->sound.info.channels), - asset->sound.info.rate, - asset->sound.info.rate * asset->sound.info.channels * asset->sound.info.bits / 8, - static_cast(asset->sound.info.block_size), - static_cast(asset->sound.info.bits) - }; - stream.write(reinterpret_cast(&formatChunk), sizeof(formatChunk)); - - const WavChunkHeader dataChunkHeader - { - WAV_CHUNK_ID_DATA, - asset->sound.info.data_len - }; - stream.write(reinterpret_cast(&dataChunkHeader), sizeof(dataChunkHeader)); - stream.write(asset->sound.data, asset->sound.info.data_len); + writer.WritePcmHeader(metaData, asset->sound.info.data_len); + writer.WritePcmData(asset->sound.data, asset->sound.info.data_len); } void AssetDumperLoadedSound::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp index af3a41707..351c51cb7 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperSndBank.cpp @@ -2,18 +2,19 @@ #include #include -#include +#include #include "Utils/ClassUtils.h" #include "Csv/CsvStream.h" #include "ObjContainer/SoundBank/SoundBank.h" +#include "Sound/WavWriter.h" using namespace T6; namespace fs = std::filesystem; -class AssetDumperSndBank::Internal +namespace { - inline static const std::string ALIAS_HEADERS[] + const std::string ALIAS_HEADERS[] { "# name", "# file", @@ -80,66 +81,54 @@ class AssetDumperSndBank::Internal "# snapshot", }; - AssetDumpingContext& m_context; + const std::string PREFIXES_TO_DROP[] + { + "raw/", + "devraw/", + }; - static std::string GetExtensionForFormat(const snd_asset_format format) + constexpr size_t FRAME_RATE_FOR_INDEX[] { - switch (format) - { - case SND_ASSET_FORMAT_MP3: - return ".mp3"; - - case SND_ASSET_FORMAT_FLAC: - return ".flac"; - - case SND_ASSET_FORMAT_PCMS16: - 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::cout << "Unsupported sound format " << format << std::endl; - return std::string(); - - default: - assert(false); - std::cout << "Unknown sound format " << format << std::endl; - return std::string(); - } - } + 8000, + 12000, + 16000, + 24000, + 32000, + 44100, + 48000, + 96000, + 192000 + }; +} - _NODISCARD std::string GetAssetFilename(const std::string& outputFileName, const SoundAssetBankEntry& entry) const +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); - fs::path assetName(outputFileName); - auto firstPart = true; - for (const auto& part : assetName) + std::replace(outputFileName.begin(), outputFileName.end(), '\\', '/'); + for (const auto& droppedPrefix : PREFIXES_TO_DROP) { - if (firstPart) + if (outputFileName.rfind(droppedPrefix, 0) != std::string::npos) { - firstPart = false; - if (part.string() == "raw" - || part.string() == "devraw") - continue; + outputFileName.erase(0, droppedPrefix.size()); + break; } - - assetPath.append(part.string()); } - const auto extension = GetExtensionForFormat(static_cast(entry.format)); + assetPath.append(outputFileName); if (!extension.empty()) assetPath.concat(extension); return assetPath.string(); } - _NODISCARD std::unique_ptr OpenAssetOutputFile(const std::string& outputFileName, const SoundAssetBankEntry& entry) const + _NODISCARD std::unique_ptr OpenAssetOutputFile(const std::string& outputFileName, const std::string& extension) const { - fs::path assetPath(GetAssetFilename(outputFileName, entry)); + fs::path assetPath(GetAssetFilename(outputFileName, extension)); auto assetDir(assetPath); assetDir.remove_filename(); @@ -156,7 +145,7 @@ class AssetDumperSndBank::Internal return nullptr; } - std::unique_ptr OpenAliasOutputFile(const SndBank* sndBank) const + static std::unique_ptr OpenAliasOutputFile(const SndBank* sndBank) { return nullptr; } @@ -171,7 +160,7 @@ class AssetDumperSndBank::Internal stream.NextRow(); } - void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias) + static void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias) { // name stream.WriteColumn(alias->name); @@ -185,7 +174,7 @@ class AssetDumperSndBank::Internal // loadspec stream.WriteColumn(""); - + // "# secondary", // "# group", // "# vol_min", @@ -247,7 +236,7 @@ class AssetDumperSndBank::Internal // "# snapshot", } - void DumpSndBankAliases(const SndBank* sndBank, std::unordered_map& aliasFiles) + static void DumpSndBankAliases(const SndBank* sndBank) { const auto outputFile = OpenAliasOutputFile(sndBank); @@ -267,57 +256,136 @@ class AssetDumperSndBank::Internal { const auto& alias = aliasList.head[j]; WriteAliasToFile(csvStream, &alias); - if (alias.assetId && alias.assetFileName) - aliasFiles[alias.assetId] = alias.assetFileName; } } } - void DumpSoundData(std::unordered_map& aliasFiles) const + static SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId) { - for (const auto& [id, filename] : aliasFiles) + for (const auto* soundBank : SoundBank::Repository) { - auto foundEntry = false; + auto soundFile = soundBank->GetEntryStream(assetId); + if (soundFile.IsOpen()) + return soundFile; + } + + return {}; + } + + void DumpSoundFilePcm(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const unsigned bitsPerSample) const + { + const auto outFile = OpenAssetOutputFile(assetFileName, ".wav"); + if (!outFile) + { + std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n"; + return; + } - for (const auto* soundBank : SoundBank::Repository) + 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 std::string& extension) const + { + const auto outFile = OpenAssetOutputFile(assetFileName, extension); + 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) { - auto soundFile = soundBank->GetEntryStream(id); - if (soundFile.IsOpen()) - { - auto outFile = OpenAssetOutputFile(filename, soundFile.m_entry); - if (!outFile) - { - std::cout << "Failed to open sound outputfile: \"" << filename << "\"" << std::endl; - break; - } - - 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); - } - - foundEntry = true; - break; - } + case SND_ASSET_FORMAT_PCMS16: + DumpSoundFilePcm(alias.assetFileName, soundFile, 16u); + break; + + case SND_ASSET_FORMAT_FLAC: + DumpSoundFilePassthrough(alias.assetFileName, soundFile, ".flac"); + 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]; - if (!foundEntry) + for (auto j = 0; j < aliasList.count; j++) { - std::cout << "Could not find data for sound \"" << filename << "\"" << std::endl; + 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) + void DumpSndBank(const XAssetInfo* sndBankInfo) const { const auto* sndBank = sndBankInfo->Asset(); - std::unordered_map aliasMappings; - DumpSndBankAliases(sndBank, aliasMappings); - DumpSoundData(aliasMappings); + DumpSndBankAliases(sndBank); + DumpSoundData(sndBank); } public: @@ -326,7 +394,7 @@ class AssetDumperSndBank::Internal { } - void DumpPool(AssetPool* pool) + void DumpPool(AssetPool* pool) const { for (const auto* assetInfo : *pool) { @@ -340,6 +408,6 @@ class AssetDumperSndBank::Internal void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool* pool) { - Internal internal(context); + const Internal internal(context); internal.DumpPool(pool); } diff --git a/src/ObjWriting/Sound/WavWriter.cpp b/src/ObjWriting/Sound/WavWriter.cpp new file mode 100644 index 000000000..160aa0f99 --- /dev/null +++ b/src/ObjWriting/Sound/WavWriter.cpp @@ -0,0 +1,53 @@ +#include "WavWriter.h" + +#include "Sound/WavTypes.h" + +WavWriter::WavWriter(std::ostream& stream) + : m_stream(stream) +{ +} + +void WavWriter::WritePcmHeader(const WavMetaData& metaData, const size_t dataLen) const +{ + constexpr auto riffMasterChunkSize = sizeof(WAV_CHUNK_ID_RIFF) + + sizeof(uint32_t) + + sizeof(WAV_WAVE_ID) + + sizeof(WavChunkHeader) + + sizeof(WavFormatChunkPcm) + + sizeof(WavChunkHeader) + + sizeof(dataLen); + + m_stream.write(reinterpret_cast(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF)); + m_stream.write(reinterpret_cast(&riffMasterChunkSize), sizeof(riffMasterChunkSize)); + m_stream.write(reinterpret_cast(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID)); + + constexpr WavChunkHeader formatChunkHeader + { + WAV_CHUNK_ID_FMT, + sizeof(WavFormatChunkPcm) + }; + m_stream.write(reinterpret_cast(&formatChunkHeader), sizeof(formatChunkHeader)); + + const WavFormatChunkPcm formatChunk + { + WavFormat::PCM, + static_cast(metaData.channelCount), + metaData.samplesPerSec, + metaData.samplesPerSec * metaData.channelCount * metaData.bitsPerSample / 8, + static_cast(metaData.channelCount * (metaData.bitsPerSample / 8)), + static_cast(metaData.bitsPerSample) + }; + m_stream.write(reinterpret_cast(&formatChunk), sizeof(formatChunk)); + + const WavChunkHeader dataChunkHeader + { + WAV_CHUNK_ID_DATA, + dataLen + }; + m_stream.write(reinterpret_cast(&dataChunkHeader), sizeof(dataChunkHeader)); +} + +void WavWriter::WritePcmData(const void* data, const size_t dataLen) const +{ + m_stream.write(static_cast(data), dataLen); +} diff --git a/src/ObjWriting/Sound/WavWriter.h b/src/ObjWriting/Sound/WavWriter.h new file mode 100644 index 000000000..4e7c27843 --- /dev/null +++ b/src/ObjWriting/Sound/WavWriter.h @@ -0,0 +1,21 @@ +#pragma once +#include + +struct WavMetaData +{ + unsigned channelCount; + unsigned samplesPerSec; + unsigned bitsPerSample; +}; + +class WavWriter +{ +public: + explicit WavWriter(std::ostream& stream); + + void WritePcmHeader(const WavMetaData& metaData, size_t dataLen) const; + void WritePcmData(const void* data, size_t dataLen) const; + +private: + std::ostream& m_stream; +};