Merge pull request #28 from Laupetin/feature/t6-sound-dumping

T6 sound data dumping
This commit is contained in:
Jan 2023-10-26 23:14:01 +02:00 committed by GitHub
commit 1c56bf48c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 240 additions and 124 deletions

View File

@ -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;
};

View File

@ -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<LoadedSound>* 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<const char*>(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF));
stream.write(reinterpret_cast<const char*>(&riffMasterChunkSize), sizeof(riffMasterChunkSize));
stream.write(reinterpret_cast<const char*>(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID));
const WavChunkHeader formatChunkHeader
{
WAV_CHUNK_ID_FMT,
sizeof(WavFormatChunkPcm)
const WavMetaData metaData{
static_cast<unsigned>(asset->sound.info.channels),
static_cast<unsigned>(asset->sound.info.rate),
static_cast<unsigned>(asset->sound.info.bits)
};
stream.write(reinterpret_cast<const char*>(&formatChunkHeader), sizeof(formatChunkHeader));
WavFormatChunkPcm formatChunk
{
WavFormat::PCM,
static_cast<uint16_t>(asset->sound.info.channels),
asset->sound.info.rate,
asset->sound.info.rate * asset->sound.info.channels * asset->sound.info.bits / 8,
static_cast<uint16_t>(asset->sound.info.block_size),
static_cast<uint16_t>(asset->sound.info.bits)
};
stream.write(reinterpret_cast<const char*>(&formatChunk), sizeof(formatChunk));
const WavChunkHeader dataChunkHeader
{
WAV_CHUNK_ID_DATA,
asset->sound.info.data_len
};
stream.write(reinterpret_cast<const char*>(&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<LoadedSound>* asset)

View File

@ -2,18 +2,19 @@
#include <fstream>
#include <filesystem>
#include <unordered_map>
#include <unordered_set>
#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",
};
const std::string PREFIXES_TO_DROP[]
{
"raw/",
"devraw/",
};
constexpr size_t FRAME_RATE_FOR_INDEX[]
{
8000,
12000,
16000,
24000,
32000,
44100,
48000,
96000,
192000
};
}
class AssetDumperSndBank::Internal
{
AssetDumpingContext& m_context;
static std::string GetExtensionForFormat(const snd_asset_format format)
{
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();
}
}
_NODISCARD std::string GetAssetFilename(const std::string& outputFileName, const SoundAssetBankEntry& entry) const
_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<snd_asset_format>(entry.format));
assetPath.append(outputFileName);
if (!extension.empty())
assetPath.concat(extension);
return assetPath.string();
}
_NODISCARD std::unique_ptr<std::ostream> OpenAssetOutputFile(const std::string& outputFileName, const SoundAssetBankEntry& entry) const
_NODISCARD std::unique_ptr<std::ostream> 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<std::ostream> OpenAliasOutputFile(const SndBank* sndBank) const
static std::unique_ptr<std::ostream> 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);
@ -247,7 +236,7 @@ class AssetDumperSndBank::Internal
// "# snapshot",
}
void DumpSndBankAliases(const SndBank* sndBank, std::unordered_map<unsigned, std::string>& aliasFiles)
static void DumpSndBankAliases(const SndBank* sndBank)
{
const auto outputFile = OpenAliasOutputFile(sndBank);
@ -267,28 +256,60 @@ 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<unsigned, std::string>& aliasFiles) const
static SoundBankEntryInputStream FindSoundDataInSoundBanks(const unsigned assetId)
{
for (const auto& [id, filename] : aliasFiles)
{
auto foundEntry = false;
for (const auto* soundBank : SoundBank::Repository)
{
auto soundFile = soundBank->GetEntryStream(id);
auto soundFile = soundBank->GetEntryStream(assetId);
if (soundFile.IsOpen())
return soundFile;
}
return {};
}
void DumpSoundFilePcm(const char* assetFileName, const SoundBankEntryInputStream& soundFile, const unsigned bitsPerSample) const
{
auto outFile = OpenAssetOutputFile(filename, soundFile.m_entry);
const auto outFile = OpenAssetOutputFile(assetFileName, ".wav");
if (!outFile)
{
std::cout << "Failed to open sound outputfile: \"" << filename << "\"" << std::endl;
break;
std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n";
return;
}
const WavWriter writer(*outFile);
if (soundFile.m_entry.frameRateIndex >= std::extent_v<decltype(FRAME_RATE_FOR_INDEX)>)
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())
@ -298,26 +319,73 @@ class AssetDumperSndBank::Internal
const auto readSize = soundFile.m_stream->gcount();
outFile->write(buffer, readSize);
}
}
foundEntry = true;
void DumpSndAlias(const SndAlias& alias) const
{
const auto soundFile = FindSoundDataInSoundBanks(alias.assetId);
if (soundFile.IsOpen())
{
const auto format = static_cast<snd_asset_format>(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, ".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;
}
}
if (!foundEntry)
else
{
std::cout << "Could not find data for sound \"" << filename << "\"" << std::endl;
std::cerr << "Could not find data for sound \"" << alias.assetFileName << "\"\n";
}
}
void DumpSoundData(const SndBank* sndBank) const
{
std::unordered_set<unsigned> 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<SndBank>* sndBankInfo)
void DumpSndBank(const XAssetInfo<SndBank>* sndBankInfo) const
{
const auto* sndBank = sndBankInfo->Asset();
std::unordered_map<unsigned int, std::string> aliasMappings;
DumpSndBankAliases(sndBank, aliasMappings);
DumpSoundData(aliasMappings);
DumpSndBankAliases(sndBank);
DumpSoundData(sndBank);
}
public:
@ -326,7 +394,7 @@ public:
{
}
void DumpPool(AssetPool<SndBank>* pool)
void DumpPool(AssetPool<SndBank>* pool) const
{
for (const auto* assetInfo : *pool)
{
@ -340,6 +408,6 @@ public:
void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool)
{
Internal internal(context);
const Internal internal(context);
internal.DumpPool(pool);
}

View File

@ -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<const char*>(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF));
m_stream.write(reinterpret_cast<const char*>(&riffMasterChunkSize), sizeof(riffMasterChunkSize));
m_stream.write(reinterpret_cast<const char*>(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID));
constexpr WavChunkHeader formatChunkHeader
{
WAV_CHUNK_ID_FMT,
sizeof(WavFormatChunkPcm)
};
m_stream.write(reinterpret_cast<const char*>(&formatChunkHeader), sizeof(formatChunkHeader));
const WavFormatChunkPcm formatChunk
{
WavFormat::PCM,
static_cast<uint16_t>(metaData.channelCount),
metaData.samplesPerSec,
metaData.samplesPerSec * metaData.channelCount * metaData.bitsPerSample / 8,
static_cast<uint16_t>(metaData.channelCount * (metaData.bitsPerSample / 8)),
static_cast<uint16_t>(metaData.bitsPerSample)
};
m_stream.write(reinterpret_cast<const char*>(&formatChunk), sizeof(formatChunk));
const WavChunkHeader dataChunkHeader
{
WAV_CHUNK_ID_DATA,
dataLen
};
m_stream.write(reinterpret_cast<const char*>(&dataChunkHeader), sizeof(dataChunkHeader));
}
void WavWriter::WritePcmData(const void* data, const size_t dataLen) const
{
m_stream.write(static_cast<const char*>(data), dataLen);
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <ostream>
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;
};