mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-04-20 00:02:55 +00:00
Merge pull request #28 from Laupetin/feature/t6-sound-dumping
T6 sound data dumping
This commit is contained in:
commit
1c56bf48c4
@ -38,8 +38,8 @@ struct SoundAssetBankEntry
|
|||||||
unsigned int size;
|
unsigned int size;
|
||||||
unsigned int offset;
|
unsigned int offset;
|
||||||
unsigned int frameCount;
|
unsigned int frameCount;
|
||||||
char frameRateIndex;
|
unsigned char frameRateIndex;
|
||||||
char channelCount;
|
unsigned char channelCount;
|
||||||
char looping;
|
unsigned char looping;
|
||||||
char format;
|
unsigned char format;
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "AssetDumperLoadedSound.h"
|
#include "AssetDumperLoadedSound.h"
|
||||||
|
|
||||||
#include "Sound/WavTypes.h"
|
#include "Sound/WavTypes.h"
|
||||||
|
#include "Sound/WavWriter.h"
|
||||||
|
|
||||||
using namespace IW4;
|
using namespace IW4;
|
||||||
|
|
||||||
@ -11,43 +12,16 @@ bool AssetDumperLoadedSound::ShouldDump(XAssetInfo<LoadedSound>* asset)
|
|||||||
|
|
||||||
void AssetDumperLoadedSound::DumpWavPcm(AssetDumpingContext& context, const LoadedSound* asset, std::ostream& stream)
|
void AssetDumperLoadedSound::DumpWavPcm(AssetDumpingContext& context, const LoadedSound* asset, std::ostream& stream)
|
||||||
{
|
{
|
||||||
const auto riffMasterChunkSize = sizeof(WAV_CHUNK_ID_RIFF)
|
const WavWriter writer(stream);
|
||||||
+ sizeof(uint32_t)
|
|
||||||
+ sizeof(WAV_WAVE_ID)
|
|
||||||
+ sizeof(WavChunkHeader)
|
|
||||||
+ sizeof(WavFormatChunkPcm)
|
|
||||||
+ sizeof(WavChunkHeader)
|
|
||||||
+ sizeof(asset->sound.info.data_len);
|
|
||||||
|
|
||||||
stream.write(reinterpret_cast<const char*>(&WAV_CHUNK_ID_RIFF), sizeof(WAV_CHUNK_ID_RIFF));
|
const WavMetaData metaData{
|
||||||
stream.write(reinterpret_cast<const char*>(&riffMasterChunkSize), sizeof(riffMasterChunkSize));
|
static_cast<unsigned>(asset->sound.info.channels),
|
||||||
stream.write(reinterpret_cast<const char*>(&WAV_WAVE_ID), sizeof(WAV_WAVE_ID));
|
static_cast<unsigned>(asset->sound.info.rate),
|
||||||
|
static_cast<unsigned>(asset->sound.info.bits)
|
||||||
const WavChunkHeader formatChunkHeader
|
|
||||||
{
|
|
||||||
WAV_CHUNK_ID_FMT,
|
|
||||||
sizeof(WavFormatChunkPcm)
|
|
||||||
};
|
};
|
||||||
stream.write(reinterpret_cast<const char*>(&formatChunkHeader), sizeof(formatChunkHeader));
|
|
||||||
|
|
||||||
WavFormatChunkPcm formatChunk
|
writer.WritePcmHeader(metaData, asset->sound.info.data_len);
|
||||||
{
|
writer.WritePcmData(asset->sound.data, asset->sound.info.data_len);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetDumperLoadedSound::DumpAsset(AssetDumpingContext& context, XAssetInfo<LoadedSound>* asset)
|
void AssetDumperLoadedSound::DumpAsset(AssetDumpingContext& context, XAssetInfo<LoadedSound>* asset)
|
||||||
|
@ -2,18 +2,19 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <unordered_map>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "Utils/ClassUtils.h"
|
#include "Utils/ClassUtils.h"
|
||||||
#include "Csv/CsvStream.h"
|
#include "Csv/CsvStream.h"
|
||||||
#include "ObjContainer/SoundBank/SoundBank.h"
|
#include "ObjContainer/SoundBank/SoundBank.h"
|
||||||
|
#include "Sound/WavWriter.h"
|
||||||
|
|
||||||
using namespace T6;
|
using namespace T6;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
class AssetDumperSndBank::Internal
|
namespace
|
||||||
{
|
{
|
||||||
inline static const std::string ALIAS_HEADERS[]
|
const std::string ALIAS_HEADERS[]
|
||||||
{
|
{
|
||||||
"# name",
|
"# name",
|
||||||
"# file",
|
"# file",
|
||||||
@ -80,66 +81,54 @@ class AssetDumperSndBank::Internal
|
|||||||
"# snapshot",
|
"# 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;
|
AssetDumpingContext& m_context;
|
||||||
|
|
||||||
static std::string GetExtensionForFormat(const snd_asset_format format)
|
_NODISCARD std::string GetAssetFilename(std::string outputFileName, const std::string& extension) const
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
fs::path assetPath(m_context.m_base_path);
|
fs::path assetPath(m_context.m_base_path);
|
||||||
|
|
||||||
fs::path assetName(outputFileName);
|
std::replace(outputFileName.begin(), outputFileName.end(), '\\', '/');
|
||||||
auto firstPart = true;
|
for (const auto& droppedPrefix : PREFIXES_TO_DROP)
|
||||||
for (const auto& part : assetName)
|
|
||||||
{
|
{
|
||||||
if (firstPart)
|
if (outputFileName.rfind(droppedPrefix, 0) != std::string::npos)
|
||||||
{
|
{
|
||||||
firstPart = false;
|
outputFileName.erase(0, droppedPrefix.size());
|
||||||
if (part.string() == "raw"
|
break;
|
||||||
|| part.string() == "devraw")
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assetPath.append(part.string());
|
assetPath.append(outputFileName);
|
||||||
}
|
|
||||||
|
|
||||||
const auto extension = GetExtensionForFormat(static_cast<snd_asset_format>(entry.format));
|
|
||||||
if (!extension.empty())
|
if (!extension.empty())
|
||||||
assetPath.concat(extension);
|
assetPath.concat(extension);
|
||||||
|
|
||||||
return assetPath.string();
|
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);
|
auto assetDir(assetPath);
|
||||||
assetDir.remove_filename();
|
assetDir.remove_filename();
|
||||||
@ -156,7 +145,7 @@ class AssetDumperSndBank::Internal
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<std::ostream> OpenAliasOutputFile(const SndBank* sndBank) const
|
static std::unique_ptr<std::ostream> OpenAliasOutputFile(const SndBank* sndBank)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -171,7 +160,7 @@ class AssetDumperSndBank::Internal
|
|||||||
stream.NextRow();
|
stream.NextRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias)
|
static void WriteAliasToFile(CsvOutputStream& stream, const SndAlias* alias)
|
||||||
{
|
{
|
||||||
// name
|
// name
|
||||||
stream.WriteColumn(alias->name);
|
stream.WriteColumn(alias->name);
|
||||||
@ -247,7 +236,7 @@ class AssetDumperSndBank::Internal
|
|||||||
// "# snapshot",
|
// "# snapshot",
|
||||||
}
|
}
|
||||||
|
|
||||||
void DumpSndBankAliases(const SndBank* sndBank, std::unordered_map<unsigned, std::string>& aliasFiles)
|
static void DumpSndBankAliases(const SndBank* sndBank)
|
||||||
{
|
{
|
||||||
const auto outputFile = OpenAliasOutputFile(sndBank);
|
const auto outputFile = OpenAliasOutputFile(sndBank);
|
||||||
|
|
||||||
@ -267,28 +256,60 @@ class AssetDumperSndBank::Internal
|
|||||||
{
|
{
|
||||||
const auto& alias = aliasList.head[j];
|
const auto& alias = aliasList.head[j];
|
||||||
WriteAliasToFile(csvStream, &alias);
|
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)
|
for (const auto* soundBank : SoundBank::Repository)
|
||||||
{
|
{
|
||||||
auto soundFile = soundBank->GetEntryStream(id);
|
auto soundFile = soundBank->GetEntryStream(assetId);
|
||||||
if (soundFile.IsOpen())
|
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)
|
if (!outFile)
|
||||||
{
|
{
|
||||||
std::cout << "Failed to open sound outputfile: \"" << filename << "\"" << std::endl;
|
std::cerr << "Failed to open sound output file: \"" << assetFileName << "\"\n";
|
||||||
break;
|
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())
|
while (!soundFile.m_stream->eof())
|
||||||
@ -298,26 +319,73 @@ class AssetDumperSndBank::Internal
|
|||||||
const auto readSize = soundFile.m_stream->gcount();
|
const auto readSize = soundFile.m_stream->gcount();
|
||||||
outFile->write(buffer, readSize);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!foundEntry)
|
|
||||||
{
|
{
|
||||||
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();
|
const auto* sndBank = sndBankInfo->Asset();
|
||||||
|
|
||||||
std::unordered_map<unsigned int, std::string> aliasMappings;
|
DumpSndBankAliases(sndBank);
|
||||||
DumpSndBankAliases(sndBank, aliasMappings);
|
DumpSoundData(sndBank);
|
||||||
DumpSoundData(aliasMappings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -326,7 +394,7 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void DumpPool(AssetPool<SndBank>* pool)
|
void DumpPool(AssetPool<SndBank>* pool) const
|
||||||
{
|
{
|
||||||
for (const auto* assetInfo : *pool)
|
for (const auto* assetInfo : *pool)
|
||||||
{
|
{
|
||||||
@ -340,6 +408,6 @@ public:
|
|||||||
|
|
||||||
void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool)
|
void AssetDumperSndBank::DumpPool(AssetDumpingContext& context, AssetPool<SndBank>* pool)
|
||||||
{
|
{
|
||||||
Internal internal(context);
|
const Internal internal(context);
|
||||||
internal.DumpPool(pool);
|
internal.DumpPool(pool);
|
||||||
}
|
}
|
||||||
|
53
src/ObjWriting/Sound/WavWriter.cpp
Normal file
53
src/ObjWriting/Sound/WavWriter.cpp
Normal 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);
|
||||||
|
}
|
21
src/ObjWriting/Sound/WavWriter.h
Normal file
21
src/ObjWriting/Sound/WavWriter.h
Normal 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;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user