mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-04-19 15:52:53 +00:00
Merge pull request #99 from skiff/main
[T6] - SndBank Linking and Sound Bank (.sabs/.sabl) Writing
This commit is contained in:
commit
b741fe3604
@ -1,10 +1,16 @@
|
||||
#include "Crypto.h"
|
||||
|
||||
#include "Impl/AlgorithmMD5.h"
|
||||
#include "Impl/AlgorithmRSA.h"
|
||||
#include "Impl/AlgorithmSHA1.h"
|
||||
#include "Impl/AlgorithmSHA256.h"
|
||||
#include "Impl/AlgorithmSalsa20.h"
|
||||
|
||||
std::unique_ptr<IHashFunction> Crypto::CreateMD5()
|
||||
{
|
||||
return std::make_unique<AlgorithmMD5>();
|
||||
}
|
||||
|
||||
std::unique_ptr<IHashFunction> Crypto::CreateSHA1()
|
||||
{
|
||||
return std::make_unique<AlgorithmSHA1>();
|
||||
|
@ -16,6 +16,8 @@ public:
|
||||
RSA_PADDING_PSS,
|
||||
};
|
||||
|
||||
static std::unique_ptr<IHashFunction> CreateMD5();
|
||||
|
||||
static std::unique_ptr<IHashFunction> CreateSHA1();
|
||||
static std::unique_ptr<IHashFunction> CreateSHA256();
|
||||
|
||||
|
64
src/Crypto/Impl/AlgorithmMD5.cpp
Normal file
64
src/Crypto/Impl/AlgorithmMD5.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "AlgorithmMD5.h"
|
||||
|
||||
#include "CryptoLibrary.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class AlgorithmMD5::AlgorithmMD5Impl
|
||||
{
|
||||
hash_state m_state{};
|
||||
|
||||
public:
|
||||
AlgorithmMD5Impl()
|
||||
{
|
||||
CryptoLibrary::Init();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
md5_init(&m_state);
|
||||
}
|
||||
|
||||
void Process(const void* input, const size_t inputSize)
|
||||
{
|
||||
md5_process(&m_state, static_cast<const uint8_t*>(input), inputSize);
|
||||
}
|
||||
|
||||
void Finish(void* hashBuffer)
|
||||
{
|
||||
md5_done(&m_state, static_cast<uint8_t*>(hashBuffer));
|
||||
}
|
||||
};
|
||||
|
||||
AlgorithmMD5::AlgorithmMD5()
|
||||
{
|
||||
m_impl = new AlgorithmMD5Impl();
|
||||
}
|
||||
|
||||
AlgorithmMD5::~AlgorithmMD5()
|
||||
{
|
||||
delete m_impl;
|
||||
m_impl = nullptr;
|
||||
}
|
||||
|
||||
size_t AlgorithmMD5::GetHashSize()
|
||||
{
|
||||
return HASH_SIZE;
|
||||
}
|
||||
|
||||
void AlgorithmMD5::Init()
|
||||
{
|
||||
m_impl->Init();
|
||||
}
|
||||
|
||||
void AlgorithmMD5::Process(const void* input, const size_t inputSize)
|
||||
{
|
||||
m_impl->Process(input, inputSize);
|
||||
}
|
||||
|
||||
void AlgorithmMD5::Finish(void* hashBuffer)
|
||||
{
|
||||
m_impl->Finish(hashBuffer);
|
||||
}
|
20
src/Crypto/Impl/AlgorithmMD5.h
Normal file
20
src/Crypto/Impl/AlgorithmMD5.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "IHashFunction.h"
|
||||
|
||||
class AlgorithmMD5 : public IHashFunction
|
||||
{
|
||||
class AlgorithmMD5Impl;
|
||||
AlgorithmMD5Impl* m_impl;
|
||||
|
||||
public:
|
||||
static const int HASH_SIZE = 16;
|
||||
|
||||
AlgorithmMD5();
|
||||
~AlgorithmMD5() override;
|
||||
|
||||
size_t GetHashSize() override;
|
||||
|
||||
void Init() override;
|
||||
void Process(const void* input, size_t inputSize) override;
|
||||
void Finish(void* hashBuffer) override;
|
||||
};
|
@ -9,6 +9,7 @@
|
||||
#include "LinkerSearchPaths.h"
|
||||
#include "ObjContainer/IPak/IPakWriter.h"
|
||||
#include "ObjContainer/IWD/IWD.h"
|
||||
#include "ObjContainer/SoundBank/SoundBankWriter.h"
|
||||
#include "ObjLoading.h"
|
||||
#include "ObjWriting.h"
|
||||
#include "SearchPath/SearchPaths.h"
|
||||
@ -419,6 +420,8 @@ class LinkerImpl final : public Linker
|
||||
SearchPaths& gdtSearchPaths,
|
||||
SearchPaths& sourceSearchPaths) const
|
||||
{
|
||||
SoundBankWriter::OutputPath = fs::path(m_args.GetOutputFolderPathForProject(projectName));
|
||||
|
||||
const auto zone = CreateZoneForDefinition(targetName, zoneDefinition, &assetSearchPaths, &gdtSearchPaths, &sourceSearchPaths);
|
||||
auto result = zone != nullptr;
|
||||
if (zone)
|
||||
|
80
src/ObjCommon/Csv/ParsedCsv.cpp
Normal file
80
src/ObjCommon/Csv/ParsedCsv.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "Csv/ParsedCsv.h"
|
||||
|
||||
ParsedCsvRow::ParsedCsvRow(std::unordered_map<std::string, size_t>& headers, std::vector<std::string> 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" << std::endl;
|
||||
else
|
||||
std::cerr << "WARNING: Expected column \"" << header << "\" was not found" << std::endl;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& value = this->values.at(this->headers[header]);
|
||||
if (required && value.empty())
|
||||
{
|
||||
std::cerr << "ERROR: Required column \"" << header << "\" does not have a value" << std::endl;
|
||||
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<std::vector<std::string>> csvLines;
|
||||
std::vector<std::string> currentLine;
|
||||
|
||||
while (inputStream.NextRow(currentLine))
|
||||
{
|
||||
csvLines.emplace_back(std::move(currentLine));
|
||||
currentLine = std::vector<std::string>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
45
src/ObjCommon/Csv/ParsedCsv.h
Normal file
45
src/ObjCommon/Csv/ParsedCsv.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "Csv/CsvStream.h"
|
||||
#include "Utils/ClassUtils.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
class ParsedCsvRow
|
||||
{
|
||||
std::unordered_map<std::string, size_t>& headers;
|
||||
std::vector<std::string> values;
|
||||
|
||||
public:
|
||||
explicit ParsedCsvRow(std::unordered_map<std::string, size_t>& headers, std::vector<std::string> 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<typename T> 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<T>(out);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class ParsedCsv
|
||||
{
|
||||
std::unordered_map<std::string, size_t> headers;
|
||||
std::vector<ParsedCsvRow> rows;
|
||||
|
||||
public:
|
||||
explicit ParsedCsv(const CsvInputStream& inputStream, bool hasHeaders = true);
|
||||
|
||||
_NODISCARD size_t Size() const;
|
||||
|
||||
ParsedCsvRow operator[](size_t index) const;
|
||||
};
|
137
src/ObjCommon/Game/T6/SoundConstantsT6.h
Normal file
137
src/ObjCommon/Game/T6/SoundConstantsT6.h
Normal file
@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace T6
|
||||
{
|
||||
inline const std::string SOUND_GROUPS[]{
|
||||
"grp_reference",
|
||||
"grp_master",
|
||||
"grp_wpn_lfe",
|
||||
"grp_lfe",
|
||||
"grp_hdrfx",
|
||||
"grp_music",
|
||||
"grp_voice",
|
||||
"grp_set_piece",
|
||||
"grp_igc",
|
||||
"grp_mp_game",
|
||||
"grp_explosion",
|
||||
"grp_player_impacts",
|
||||
"grp_scripted_moment",
|
||||
"grp_menu",
|
||||
"grp_whizby",
|
||||
"grp_weapon",
|
||||
"grp_vehicle",
|
||||
"grp_impacts",
|
||||
"grp_foley",
|
||||
"grp_destructible",
|
||||
"grp_physics",
|
||||
"grp_ambience",
|
||||
"grp_alerts",
|
||||
"grp_air",
|
||||
"grp_bink",
|
||||
"grp_announcer",
|
||||
"",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_CURVES[]{
|
||||
"default",
|
||||
"defaultmin",
|
||||
"allon",
|
||||
"alloff",
|
||||
"rcurve0",
|
||||
"rcurve1",
|
||||
"rcurve2",
|
||||
"rcurve3",
|
||||
"rcurve4",
|
||||
"rcurve5",
|
||||
"steep",
|
||||
"sindelay",
|
||||
"cosdelay",
|
||||
"sin",
|
||||
"cos",
|
||||
"rev60",
|
||||
"rev65",
|
||||
"",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_DUCK_GROUPS[]{
|
||||
"snp_alerts_gameplay",
|
||||
"snp_ambience",
|
||||
"snp_claw",
|
||||
"snp_destructible",
|
||||
"snp_dying",
|
||||
"snp_dying_ice",
|
||||
"snp_evt_2d",
|
||||
"snp_explosion",
|
||||
"snp_foley",
|
||||
"snp_grenade",
|
||||
"snp_hdrfx",
|
||||
"snp_igc",
|
||||
"snp_impacts",
|
||||
"snp_menu",
|
||||
"snp_movie",
|
||||
"snp_music",
|
||||
"snp_never_duck",
|
||||
"snp_player_dead",
|
||||
"snp_player_impacts",
|
||||
"snp_scripted_moment",
|
||||
"snp_set_piece",
|
||||
"snp_special",
|
||||
"snp_vehicle",
|
||||
"snp_vehicle_interior",
|
||||
"snp_voice",
|
||||
"snp_weapon_decay_1p",
|
||||
"snp_whizby",
|
||||
"snp_wpn_1p",
|
||||
"snp_wpn_3p",
|
||||
"snp_wpn_turret",
|
||||
"snp_x2",
|
||||
"snp_x3",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_LIMIT_TYPES[]{
|
||||
"none",
|
||||
"oldest",
|
||||
"reject",
|
||||
"priority",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_MOVE_TYPES[]{
|
||||
"none",
|
||||
"left_player",
|
||||
"center_player",
|
||||
"right_player",
|
||||
"random",
|
||||
"left_shot",
|
||||
"center_shot",
|
||||
"right_shot",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_LOAD_TYPES[]{
|
||||
"unknown",
|
||||
"loaded",
|
||||
"streamed",
|
||||
"primed",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_BUS_IDS[]{
|
||||
"bus_reverb",
|
||||
"bus_fx",
|
||||
"bus_voice",
|
||||
"bus_pfutz",
|
||||
"bus_hdrfx",
|
||||
"bus_ui",
|
||||
"bus_reference",
|
||||
"bus_music",
|
||||
"bus_movie",
|
||||
"bus_reference",
|
||||
"",
|
||||
};
|
||||
|
||||
inline const std::string SOUND_RANDOMIZE_TYPES[]{
|
||||
"volume",
|
||||
"pitch",
|
||||
"variant",
|
||||
"",
|
||||
};
|
||||
} // namespace T6
|
246
src/ObjCommon/Sound/FlacDecoder.cpp
Normal file
246
src/ObjCommon/Sound/FlacDecoder.cpp
Normal file
@ -0,0 +1,246 @@
|
||||
#include "FlacDecoder.h"
|
||||
|
||||
#include "Utils/Alignment.h"
|
||||
#include "Utils/ClassUtils.h"
|
||||
#include "Utils/Endianness.h"
|
||||
#include "Utils/FileUtils.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto FLAC_MAGIC = FileUtils::MakeMagic32('f', 'L', 'a', 'C');
|
||||
|
||||
enum class MetaDataBlockType : unsigned
|
||||
{
|
||||
STREAMINFO = 0,
|
||||
PADDING = 1,
|
||||
APPLICATION = 2,
|
||||
SEEKTABLE = 3,
|
||||
VORBIS_COMMENT = 4,
|
||||
CUESHEET = 5,
|
||||
PICTURE = 6
|
||||
};
|
||||
|
||||
struct MetaDataBlockHeader
|
||||
{
|
||||
uint8_t isLastMetaDataBlock;
|
||||
MetaDataBlockType blockType;
|
||||
uint32_t blockLength;
|
||||
};
|
||||
|
||||
constexpr auto STREAM_INFO_BLOCK_SIZE = 34;
|
||||
|
||||
class FlacReadingException final : public std::exception
|
||||
{
|
||||
public:
|
||||
explicit FlacReadingException(std::string message)
|
||||
: m_message(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
_NODISCARD char const* what() const noexcept override
|
||||
{
|
||||
return m_message.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
class FlacBitReader
|
||||
{
|
||||
public:
|
||||
explicit FlacBitReader(std::istream& stream)
|
||||
: m_stream(stream),
|
||||
m_last_byte(0u),
|
||||
m_remaining_bits_last_byte(0u)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T> T ReadBits(const size_t bitCount)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint8_t buffer[sizeof(T)];
|
||||
T result;
|
||||
} data{};
|
||||
|
||||
const auto byteCount = utils::Align(bitCount, 8u) / 8u;
|
||||
assert(byteCount <= sizeof(T));
|
||||
|
||||
const auto shiftCount = (8u - bitCount % 8) % 8;
|
||||
|
||||
auto remainingBits = bitCount;
|
||||
|
||||
#if HOST_ENDIANNESS == LITTLE_ENDIAN_ENDIANNESS
|
||||
auto offset = byteCount - 1;
|
||||
#else
|
||||
auto offset = 0u;
|
||||
#endif
|
||||
|
||||
while (remainingBits > 0)
|
||||
{
|
||||
const auto curBits = static_cast<uint8_t>(std::min(remainingBits, 8u));
|
||||
|
||||
if (m_remaining_bits_last_byte > 0)
|
||||
{
|
||||
if (m_remaining_bits_last_byte < curBits)
|
||||
{
|
||||
const auto bitsFromFirstByte = m_remaining_bits_last_byte;
|
||||
data.buffer[offset] = static_cast<uint8_t>(m_last_byte << (8u - bitsFromFirstByte));
|
||||
|
||||
m_stream.read(reinterpret_cast<char*>(&m_last_byte), sizeof(m_last_byte));
|
||||
if (m_stream.gcount() != sizeof(m_last_byte))
|
||||
throw FlacReadingException("Unexpected eof");
|
||||
|
||||
const auto bitsFromSecondByte = static_cast<uint8_t>(curBits - m_remaining_bits_last_byte);
|
||||
m_remaining_bits_last_byte = 8u - bitsFromSecondByte;
|
||||
const auto maskForSecondByte = static_cast<uint8_t>(0xFF << (8u - bitsFromSecondByte));
|
||||
data.buffer[offset] |= (m_last_byte & maskForSecondByte) >> bitsFromFirstByte;
|
||||
}
|
||||
else if (m_remaining_bits_last_byte == curBits)
|
||||
{
|
||||
data.buffer[offset] = static_cast<uint8_t>(m_last_byte << (8u - curBits));
|
||||
m_remaining_bits_last_byte = 0u;
|
||||
}
|
||||
else // m_remaining_bits_last_byte > curBits
|
||||
{
|
||||
const auto maskForCurBits = 0xFF >> (8u - curBits);
|
||||
const auto maskForCurBitsInRemainingBits = static_cast<uint8_t>(maskForCurBits << (m_remaining_bits_last_byte - curBits));
|
||||
const auto selectedData = static_cast<uint8_t>(m_last_byte & maskForCurBitsInRemainingBits);
|
||||
data.buffer[offset] = static_cast<uint8_t>(selectedData << (8u - m_remaining_bits_last_byte));
|
||||
m_remaining_bits_last_byte -= curBits;
|
||||
}
|
||||
}
|
||||
else if (curBits >= 8u)
|
||||
{
|
||||
m_stream.read(reinterpret_cast<char*>(&data.buffer[offset]), sizeof(uint8_t));
|
||||
if (m_stream.gcount() != sizeof(uint8_t))
|
||||
throw FlacReadingException("Unexpected eof");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stream.read(reinterpret_cast<char*>(&m_last_byte), sizeof(m_last_byte));
|
||||
if (m_stream.gcount() != sizeof(m_last_byte))
|
||||
throw FlacReadingException("Unexpected eof");
|
||||
|
||||
data.buffer[offset] = m_last_byte & (0xFF << (8u - curBits));
|
||||
m_remaining_bits_last_byte = static_cast<uint8_t>(8u - curBits);
|
||||
}
|
||||
|
||||
remainingBits -= curBits;
|
||||
#if HOST_ENDIANNESS == LITTLE_ENDIAN_ENDIANNESS
|
||||
--offset;
|
||||
#else
|
||||
++offset;
|
||||
#endif
|
||||
}
|
||||
|
||||
data.result >>= shiftCount;
|
||||
return data.result;
|
||||
}
|
||||
|
||||
void ReadBuffer(void* buffer, const size_t bitCount)
|
||||
{
|
||||
assert(m_remaining_bits_last_byte == 0);
|
||||
assert(bitCount % 8 == 0);
|
||||
|
||||
m_remaining_bits_last_byte = 0;
|
||||
m_stream.read(static_cast<char*>(buffer), bitCount / 8);
|
||||
}
|
||||
|
||||
void Seek(const size_t offset)
|
||||
{
|
||||
assert(m_remaining_bits_last_byte == 0);
|
||||
m_remaining_bits_last_byte = 0;
|
||||
m_stream.seekg(offset, std::ios::cur);
|
||||
}
|
||||
|
||||
private:
|
||||
std::istream& m_stream;
|
||||
uint8_t m_last_byte;
|
||||
uint8_t m_remaining_bits_last_byte;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace flac
|
||||
{
|
||||
FlacMetaData::FlacMetaData()
|
||||
: m_minimum_block_size(),
|
||||
m_maximum_block_size(),
|
||||
m_minimum_frame_size(),
|
||||
m_maximum_frame_size(),
|
||||
m_sample_rate(),
|
||||
m_number_of_channels(),
|
||||
m_bits_per_sample(),
|
||||
m_total_samples(),
|
||||
m_md5_signature{}
|
||||
{
|
||||
}
|
||||
|
||||
void FlacReadStreamInfo(FlacBitReader& reader, FlacMetaData& metaData)
|
||||
{
|
||||
metaData.m_minimum_block_size = reader.ReadBits<uint16_t>(16);
|
||||
metaData.m_maximum_block_size = reader.ReadBits<uint16_t>(16);
|
||||
metaData.m_minimum_frame_size = reader.ReadBits<uint32_t>(24);
|
||||
metaData.m_maximum_frame_size = reader.ReadBits<uint32_t>(24);
|
||||
metaData.m_sample_rate = reader.ReadBits<uint32_t>(20);
|
||||
metaData.m_number_of_channels = static_cast<uint8_t>(reader.ReadBits<uint8_t>(3) + 1);
|
||||
metaData.m_bits_per_sample = static_cast<uint8_t>(reader.ReadBits<uint8_t>(5) + 1);
|
||||
metaData.m_total_samples = reader.ReadBits<uint64_t>(36);
|
||||
reader.ReadBuffer(metaData.m_md5_signature, 128);
|
||||
}
|
||||
|
||||
bool GetFlacMetaData(std::istream& stream, FlacMetaData& metaData)
|
||||
{
|
||||
try
|
||||
{
|
||||
uint32_t readMagic;
|
||||
stream.read(reinterpret_cast<char*>(&readMagic), sizeof(readMagic));
|
||||
if (stream.gcount() != sizeof(readMagic) || readMagic != FLAC_MAGIC)
|
||||
throw FlacReadingException("Invalid flac magic");
|
||||
|
||||
FlacBitReader reader(stream);
|
||||
while (true)
|
||||
{
|
||||
const MetaDataBlockHeader header{
|
||||
|
||||
reader.ReadBits<uint8_t>(1),
|
||||
static_cast<MetaDataBlockType>(reader.ReadBits<uint8_t>(7)),
|
||||
reader.ReadBits<uint32_t>(24),
|
||||
};
|
||||
|
||||
if (header.blockType == MetaDataBlockType::STREAMINFO)
|
||||
{
|
||||
if (header.blockLength != STREAM_INFO_BLOCK_SIZE)
|
||||
throw FlacReadingException("Flac stream info block size invalid");
|
||||
|
||||
FlacReadStreamInfo(reader, metaData);
|
||||
return true;
|
||||
}
|
||||
|
||||
reader.Seek(header.blockLength * 8u);
|
||||
|
||||
if (header.isLastMetaDataBlock)
|
||||
break;
|
||||
}
|
||||
|
||||
throw FlacReadingException("Missing flac stream info block");
|
||||
}
|
||||
catch (const FlacReadingException& e)
|
||||
{
|
||||
std::cerr << e.what() << "\n";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetFlacMetaData(const void* data, const size_t dataSize, FlacMetaData& metaData)
|
||||
{
|
||||
std::istringstream ss(std::string(static_cast<const char*>(data), dataSize));
|
||||
return GetFlacMetaData(ss, metaData);
|
||||
}
|
||||
} // namespace flac
|
26
src/ObjCommon/Sound/FlacDecoder.h
Normal file
26
src/ObjCommon/Sound/FlacDecoder.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <istream>
|
||||
|
||||
namespace flac
|
||||
{
|
||||
class FlacMetaData
|
||||
{
|
||||
public:
|
||||
uint16_t m_minimum_block_size;
|
||||
uint16_t m_maximum_block_size;
|
||||
uint32_t m_minimum_frame_size;
|
||||
uint32_t m_maximum_frame_size;
|
||||
uint32_t m_sample_rate;
|
||||
uint8_t m_number_of_channels;
|
||||
uint8_t m_bits_per_sample;
|
||||
uint64_t m_total_samples;
|
||||
uint8_t m_md5_signature[16];
|
||||
|
||||
FlacMetaData();
|
||||
};
|
||||
|
||||
bool GetFlacMetaData(std::istream& stream, FlacMetaData& metaData);
|
||||
bool GetFlacMetaData(const void* data, size_t dataSize, FlacMetaData& metaData);
|
||||
} // namespace flac
|
@ -28,3 +28,20 @@ struct WavFormatChunkPcm
|
||||
uint16_t nBlockAlign;
|
||||
uint16_t wBitsPerSample;
|
||||
};
|
||||
|
||||
struct WavMetaData
|
||||
{
|
||||
unsigned channelCount;
|
||||
unsigned samplesPerSec;
|
||||
unsigned bitsPerSample;
|
||||
};
|
||||
|
||||
struct WavHeader
|
||||
{
|
||||
unsigned int chunkIdRiff;
|
||||
unsigned int chunkIdSize;
|
||||
unsigned int format;
|
||||
WavChunkHeader chunkHeader;
|
||||
WavFormatChunkPcm formatChunk;
|
||||
WavChunkHeader subChunkHeader;
|
||||
};
|
||||
|
@ -55,4 +55,5 @@ function ObjLoading:project()
|
||||
minilzo:include(includes)
|
||||
minizip:include(includes)
|
||||
zlib:include(includes)
|
||||
json:include(includes)
|
||||
end
|
||||
|
575
src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp
Normal file
575
src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.cpp
Normal file
@ -0,0 +1,575 @@
|
||||
#include "AssetLoaderSoundBank.h"
|
||||
|
||||
#include "Csv/ParsedCsv.h"
|
||||
#include "Game/T6/CommonT6.h"
|
||||
#include "Game/T6/SoundConstantsT6.h"
|
||||
#include "Game/T6/T6.h"
|
||||
#include "ObjContainer/SoundBank/SoundBankWriter.h"
|
||||
#include "Pool/GlobalAssetPool.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace T6;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
const std::string PREFIXES_TO_DROP[]{
|
||||
"raw/",
|
||||
"devraw/",
|
||||
};
|
||||
|
||||
_NODISCARD std::string GetSoundFilePath(const SndAlias* sndAlias)
|
||||
{
|
||||
std::string soundFilePath(sndAlias->assetFileName);
|
||||
|
||||
std::replace(soundFilePath.begin(), soundFilePath.end(), '\\', '/');
|
||||
for (const auto& droppedPrefix : PREFIXES_TO_DROP)
|
||||
{
|
||||
if (soundFilePath.rfind(droppedPrefix, 0) != std::string::npos)
|
||||
{
|
||||
soundFilePath.erase(0, droppedPrefix.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return soundFilePath;
|
||||
}
|
||||
|
||||
_NODISCARD std::unique_ptr<std::ofstream> OpenSoundBankOutputFile(const std::string& bankName)
|
||||
{
|
||||
fs::path assetPath = SoundBankWriter::OutputPath / bankName;
|
||||
|
||||
auto assetDir(assetPath);
|
||||
assetDir.remove_filename();
|
||||
|
||||
create_directories(assetDir);
|
||||
|
||||
auto outputStream = std::make_unique<std::ofstream>(assetPath, std::ios_base::out | std::ios_base::binary);
|
||||
|
||||
if (outputStream->is_open())
|
||||
{
|
||||
return std::move(outputStream);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void* AssetLoaderSoundBank::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
||||
{
|
||||
auto* soundBank = memory->Create<SndBank>();
|
||||
memset(soundBank, 0, sizeof(SndBank));
|
||||
soundBank->name = memory->Dup(assetName.c_str());
|
||||
return soundBank;
|
||||
}
|
||||
|
||||
bool AssetLoaderSoundBank::CanLoadFromRaw() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t GetValueIndex(const std::string& value, const std::string* lookupTable, size_t len)
|
||||
{
|
||||
if (value.empty())
|
||||
return 0;
|
||||
|
||||
for (auto i = 0u; i < len; i++)
|
||||
{
|
||||
if (lookupTable[i] == value)
|
||||
return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int GetAliasSubListCount(const unsigned int startRow, const ParsedCsv& csv)
|
||||
{
|
||||
auto count = 1u;
|
||||
|
||||
const auto name = csv[startRow].GetValue("name", true);
|
||||
if (name.empty())
|
||||
return 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (startRow + count >= csv.Size())
|
||||
break;
|
||||
|
||||
const auto testName = csv[startRow + count].GetValue("name", true);
|
||||
if (testName.empty())
|
||||
break;
|
||||
|
||||
// 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;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool LoadSoundAlias(MemoryManager* memory, SndAlias* alias, const ParsedCsvRow& row)
|
||||
{
|
||||
memset(alias, 0, sizeof(SndAlias));
|
||||
|
||||
const auto& name = row.GetValue("name", true);
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
const auto& aliasFileName = row.GetValue("file", true);
|
||||
if (aliasFileName.empty())
|
||||
return false;
|
||||
|
||||
alias->name = memory->Dup(name.data());
|
||||
alias->id = Common::SND_HashName(name.data());
|
||||
alias->assetFileName = memory->Dup(aliasFileName.data());
|
||||
alias->assetId = Common::SND_HashName(aliasFileName.data());
|
||||
|
||||
const auto secondaryName = row.GetValue("secondary");
|
||||
if (!secondaryName.empty())
|
||||
alias->secondaryname = memory->Dup(secondaryName.data());
|
||||
|
||||
const auto subtitle = row.GetValue("subtitle");
|
||||
if (!subtitle.empty())
|
||||
alias->subtitle = memory->Dup(subtitle.data());
|
||||
|
||||
alias->duck = Common::SND_HashName(row.GetValue("duck").data());
|
||||
|
||||
alias->volMin = row.GetValueInt<uint16_t>("vol_min");
|
||||
alias->volMax = row.GetValueInt<uint16_t>("vol_max");
|
||||
alias->distMin = row.GetValueInt<uint16_t>("dist_min");
|
||||
alias->distMax = row.GetValueInt<uint16_t>("dist_max");
|
||||
alias->distReverbMax = row.GetValueInt<uint16_t>("dist_reverb_max");
|
||||
alias->limitCount = row.GetValueInt<uint8_t>("limit_count");
|
||||
alias->entityLimitCount = row.GetValueInt<uint8_t>("entity_limit_count");
|
||||
alias->pitchMin = row.GetValueInt<uint16_t>("pitch_min");
|
||||
alias->pitchMax = row.GetValueInt<uint16_t>("pitch_max");
|
||||
alias->minPriority = row.GetValueInt<uint8_t>("min_priority");
|
||||
alias->maxPriority = row.GetValueInt<uint8_t>("max_priority");
|
||||
alias->minPriorityThreshold = row.GetValueInt<uint8_t>("min_priority_threshold");
|
||||
alias->maxPriorityThreshold = row.GetValueInt<uint8_t>("max_priority_threshold");
|
||||
alias->probability = row.GetValueInt<uint8_t>("probability");
|
||||
alias->startDelay = row.GetValueInt<uint16_t>("start_delay");
|
||||
alias->reverbSend = row.GetValueInt<uint16_t>("reverb_send");
|
||||
alias->centerSend = row.GetValueInt<uint16_t>("center_send");
|
||||
alias->envelopMin = row.GetValueInt<uint16_t>("envelop_min");
|
||||
alias->envelopMax = row.GetValueInt<uint16_t>("envelop_max");
|
||||
alias->envelopPercentage = row.GetValueInt<uint16_t>("envelop_percentage");
|
||||
alias->occlusionLevel = row.GetValueInt<uint8_t>("occlusion_level");
|
||||
alias->fluxTime = row.GetValueInt<uint16_t>("move_time");
|
||||
alias->futzPatch = row.GetValueInt<unsigned int>("futz");
|
||||
alias->contextType = row.GetValueInt<unsigned int>("context_type");
|
||||
alias->contextValue = row.GetValueInt<unsigned int>("context_value");
|
||||
alias->fadeIn = row.GetValueInt<int16_t>("fade_in");
|
||||
alias->fadeOut = row.GetValueInt<int16_t>("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.stopOnDeath = row.GetValue("stop_on_death") == "yes";
|
||||
|
||||
alias->duckGroup = static_cast<char>(GetValueIndex(row.GetValue("duck_group"), SOUND_DUCK_GROUPS, std::extent_v<decltype(SOUND_DUCK_GROUPS)>));
|
||||
alias->flags.volumeGroup = GetValueIndex(row.GetValue("group"), SOUND_GROUPS, std::extent_v<decltype(SOUND_GROUPS)>);
|
||||
alias->flags.fluxType = GetValueIndex(row.GetValue("move_type"), SOUND_MOVE_TYPES, std::extent_v<decltype(SOUND_MOVE_TYPES)>);
|
||||
alias->flags.loadType = GetValueIndex(row.GetValue("type"), SOUND_LOAD_TYPES, std::extent_v<decltype(SOUND_LOAD_TYPES)>);
|
||||
alias->flags.busType = GetValueIndex(row.GetValue("bus"), SOUND_BUS_IDS, std::extent_v<decltype(SOUND_BUS_IDS)>);
|
||||
alias->flags.limitType = GetValueIndex(row.GetValue("limit_type"), SOUND_LIMIT_TYPES, std::extent_v<decltype(SOUND_LIMIT_TYPES)>);
|
||||
alias->flags.volumeFalloffCurve = GetValueIndex(row.GetValue("volume_falloff_curve"), SOUND_CURVES, std::extent_v<decltype(SOUND_CURVES)>);
|
||||
alias->flags.reverbFalloffCurve = GetValueIndex(row.GetValue("reverb_falloff_curve"), SOUND_CURVES, std::extent_v<decltype(SOUND_CURVES)>);
|
||||
alias->flags.entityLimitType = GetValueIndex(row.GetValue("entity_limit_type"), SOUND_LIMIT_TYPES, std::extent_v<decltype(SOUND_LIMIT_TYPES)>);
|
||||
alias->flags.volumeMinFalloffCurve = GetValueIndex(row.GetValue("volume_min_falloff_curve"), SOUND_CURVES, std::extent_v<decltype(SOUND_CURVES)>);
|
||||
alias->flags.reverbMinFalloffCurve = GetValueIndex(row.GetValue("reverb_min_falloff_curve"), SOUND_CURVES, std::extent_v<decltype(SOUND_CURVES)>);
|
||||
alias->flags.randomizeType = GetValueIndex(row.GetValue("randomize_type"), SOUND_RANDOMIZE_TYPES, std::extent_v<decltype(SOUND_RANDOMIZE_TYPES)>);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadSoundAliasIndexList(MemoryManager* memory, SndBank* sndBank)
|
||||
{
|
||||
// contains a list of all the alias ids in the sound bank
|
||||
sndBank->aliasIndex = static_cast<SndIndexEntry*>(memory->Alloc(sizeof(SndIndexEntry) * sndBank->aliasCount));
|
||||
memset(sndBank->aliasIndex, 0xFF, sizeof(SndIndexEntry) * sndBank->aliasCount);
|
||||
|
||||
const auto setAliasIndexList = std::make_unique<bool[]>(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<unsigned short>::max())
|
||||
{
|
||||
sndBank->aliasIndex[idx].value = i;
|
||||
sndBank->aliasIndex[idx].next = std::numeric_limits<unsigned short>::max();
|
||||
setAliasIndexList[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 0u; i < sndBank->aliasCount; i++)
|
||||
{
|
||||
if (setAliasIndexList[i])
|
||||
continue;
|
||||
|
||||
auto idx = sndBank->alias[i].id % sndBank->aliasCount;
|
||||
while (sndBank->aliasIndex[idx].next != std::numeric_limits<unsigned short>::max())
|
||||
{
|
||||
idx = sndBank->aliasIndex[idx].next;
|
||||
}
|
||||
|
||||
auto offset = 1u;
|
||||
auto freeIdx = std::numeric_limits<unsigned short>::max();
|
||||
while (true)
|
||||
{
|
||||
freeIdx = (idx + offset) % sndBank->aliasCount;
|
||||
if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits<unsigned short>::max())
|
||||
break;
|
||||
|
||||
freeIdx = (idx + sndBank->aliasCount - offset) % sndBank->aliasCount;
|
||||
if (sndBank->aliasIndex[freeIdx].value == std::numeric_limits<unsigned short>::max())
|
||||
break;
|
||||
|
||||
offset++;
|
||||
freeIdx = std::numeric_limits<unsigned short>::max();
|
||||
|
||||
if (offset >= sndBank->aliasCount)
|
||||
break;
|
||||
}
|
||||
|
||||
if (freeIdx == std::numeric_limits<unsigned short>::max())
|
||||
{
|
||||
std::cerr << "Unable to allocate sound bank alias index list" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
sndBank->aliasIndex[idx].next = freeIdx;
|
||||
sndBank->aliasIndex[freeIdx].value = i;
|
||||
sndBank->aliasIndex[freeIdx].next = std::numeric_limits<unsigned short>::max();
|
||||
setAliasIndexList[i] = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Ensure there is at least one entry in the csv after the headers
|
||||
if (aliasCsv.Size() > 0)
|
||||
{
|
||||
// should be the total number of assets
|
||||
sndBank->aliasCount = aliasCsv.Size();
|
||||
sndBank->alias = static_cast<SndAliasList*>(memory->Alloc(sizeof(SndAliasList) * sndBank->aliasCount));
|
||||
memset(sndBank->alias, 0, sizeof(SndAliasList) * sndBank->aliasCount);
|
||||
|
||||
auto row = 0u;
|
||||
auto listIndex = 0u;
|
||||
while (row < sndBank->aliasCount)
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
|
||||
// allocate the sub list
|
||||
sndBank->alias[listIndex].count = subListCount;
|
||||
sndBank->alias[listIndex].head = static_cast<SndAlias*>(memory->Alloc(sizeof(SndAlias) * subListCount));
|
||||
sndBank->alias[listIndex].sequence = 0;
|
||||
|
||||
// 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 (!LoadSoundAlias(memory, &sndBank->alias[listIndex].head[i], aliasCsv[row]))
|
||||
return false;
|
||||
|
||||
// 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)++;
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
listIndex++;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
auto* oldAliases = sndBank->alias;
|
||||
|
||||
sndBank->aliasCount = listIndex;
|
||||
sndBank->alias = static_cast<SndAliasList*>(memory->Alloc(sizeof(SndAliasList) * sndBank->aliasCount));
|
||||
memcpy(sndBank->alias, oldAliases, sizeof(SndAliasList) * sndBank->aliasCount);
|
||||
|
||||
memory->Free(oldAliases);
|
||||
}
|
||||
|
||||
if (!LoadSoundAliasIndexList(memory, sndBank))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadSoundRadverbs(MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file)
|
||||
{
|
||||
const CsvInputStream radverbCsvStream(*file.m_stream);
|
||||
const ParsedCsv radverbCsv(radverbCsvStream, true);
|
||||
|
||||
if (radverbCsv.Size() > 0)
|
||||
{
|
||||
sndBank->radverbCount = radverbCsv.Size();
|
||||
sndBank->radverbs = static_cast<SndRadverb*>(memory->Alloc(sizeof(SndRadverb) * sndBank->radverbCount));
|
||||
memset(sndBank->radverbs, 0, sizeof(SndRadverb) * sndBank->radverbCount);
|
||||
|
||||
for (auto i = 0u; i < sndBank->radverbCount; i++)
|
||||
{
|
||||
auto row = radverbCsv[i];
|
||||
|
||||
auto name = row.GetValue("name", true);
|
||||
if (name.empty())
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadSoundDuckList(ISearchPath* searchPath, MemoryManager* memory, SndBank* sndBank, const SearchPathOpenFile& file)
|
||||
{
|
||||
const CsvInputStream duckListCsvStream(*file.m_stream);
|
||||
const ParsedCsv duckListCsv(duckListCsvStream, true);
|
||||
|
||||
if (duckListCsv.Size() > 0)
|
||||
{
|
||||
sndBank->duckCount = duckListCsv.Size();
|
||||
sndBank->ducks = static_cast<SndDuck*>(memory->Alloc(sizeof(SndDuck) * sndBank->duckCount));
|
||||
memset(sndBank->ducks, 0, sizeof(SndDuck) * sndBank->duckCount);
|
||||
|
||||
for (auto i = 0u; i < sndBank->duckCount; i++)
|
||||
{
|
||||
auto row = duckListCsv[i];
|
||||
|
||||
const auto name = row.GetValue("name", true);
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
const auto duckFile = searchPath->Open("soundbank/ducks/" + name + ".duk");
|
||||
if (!duckFile.IsOpen())
|
||||
{
|
||||
std::cerr << "Unable to find .duk file for " << name << " in ducklist for sound bank " << sndBank->name << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* duck = &sndBank->ducks[i];
|
||||
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<float>();
|
||||
duck->fadeOut = duckJson["fadeOut"].get<float>();
|
||||
duck->startDelay = duckJson["startDelay"].get<float>();
|
||||
duck->distance = duckJson["distance"].get<float>();
|
||||
duck->length = duckJson["length"].get<float>();
|
||||
duck->updateWhilePaused = duckJson["updateWhilePaused"].get<int>();
|
||||
|
||||
duck->fadeInCurve = duckJson["fadeInCurveId"].get<unsigned int>();
|
||||
duck->fadeOutCurve = duckJson["fadeOutCurveId"].get<unsigned int>();
|
||||
|
||||
if (duckJson.contains("fadeInCurve"))
|
||||
duck->fadeInCurve = Common::SND_HashName(duckJson["fadeInCurve"].get<std::string>().data());
|
||||
|
||||
if (duckJson.contains("fadeOutCurve"))
|
||||
duck->fadeOutCurve = Common::SND_HashName(duckJson["fadeOutCurve"].get<std::string>().data());
|
||||
|
||||
duck->attenuation = static_cast<SndFloatAlign16*>(memory->Alloc(sizeof(SndFloatAlign16) * 32));
|
||||
duck->filter = static_cast<SndFloatAlign16*>(memory->Alloc(sizeof(SndFloatAlign16) * 32));
|
||||
|
||||
for (auto& valueJson : duckJson["values"])
|
||||
{
|
||||
auto index = GetValueIndex(valueJson["duckGroup"].get<std::string>(), SOUND_DUCK_GROUPS, std::extent_v<decltype(SOUND_DUCK_GROUPS)>);
|
||||
|
||||
duck->attenuation[index] = valueJson["attenuation"].get<float>();
|
||||
duck->filter[index] = valueJson["filter"].get<float>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetLoaderSoundBank::LoadFromRaw(
|
||||
const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const
|
||||
{
|
||||
if (assetName.find('.') == std::string::npos)
|
||||
{
|
||||
std::cerr << "A language must be specific in the soundbank asset name! (Ex: mpl_common.all)" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// open the soundbank aliases
|
||||
const auto aliasFile = searchPath->Open("soundbank/" + assetName + ".aliases.csv");
|
||||
if (!aliasFile.IsOpen())
|
||||
return false;
|
||||
|
||||
// set the defaults
|
||||
auto* sndBank = memory->Create<SndBank>();
|
||||
memset(sndBank, 0, sizeof(SndBank));
|
||||
|
||||
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))
|
||||
return false;
|
||||
|
||||
// load the soundbank reverbs
|
||||
const auto radverbFile = searchPath->Open("soundbank/" + assetName + ".reverbs.csv");
|
||||
if (radverbFile.IsOpen())
|
||||
{
|
||||
if (!LoadSoundRadverbs(memory, sndBank, radverbFile))
|
||||
{
|
||||
std::cerr << "Sound Bank reverbs file for " << assetName << " is invalid" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// load the soundbank ducks
|
||||
const auto duckListFile = searchPath->Open("soundbank/" + assetName + ".ducklist.csv");
|
||||
if (duckListFile.IsOpen())
|
||||
{
|
||||
if (!LoadSoundDuckList(searchPath, memory, sndBank, duckListFile))
|
||||
{
|
||||
std::cerr << "Sound Bank ducklist file for " << assetName << " is invalid" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<std::ofstream> sablStream, sabsStream;
|
||||
std::unique_ptr<SoundBankWriter> sablWriter, sabsWriter;
|
||||
|
||||
if (loadedEntryCount > 0)
|
||||
{
|
||||
sndBank->loadAssetBank.zone = memory->Dup(sndBankLocalization.at(0).c_str());
|
||||
sndBank->loadAssetBank.language = memory->Dup(sndBankLocalization.at(1).c_str());
|
||||
memset(sndBank->loadAssetBank.linkTimeChecksum, 0xCC, 16);
|
||||
|
||||
sndBank->loadedAssets.loadedCount = 0;
|
||||
sndBank->loadedAssets.zone = memory->Dup(sndBankLocalization.at(0).c_str());
|
||||
sndBank->loadedAssets.language = memory->Dup(sndBankLocalization.at(1).c_str());
|
||||
sndBank->loadedAssets.entryCount = loadedEntryCount;
|
||||
sndBank->loadedAssets.entries = static_cast<SndAssetBankEntry*>(memory->Alloc(sizeof(SndAssetBankEntry) * loadedEntryCount));
|
||||
memset(sndBank->loadedAssets.entries, 0, sizeof(SndAssetBankEntry) * loadedEntryCount);
|
||||
|
||||
sndBank->runtimeAssetLoad = true;
|
||||
|
||||
const auto sablName = assetName + ".sabl";
|
||||
sablStream = OpenSoundBankOutputFile(sablName);
|
||||
if (sablStream)
|
||||
sablWriter = SoundBankWriter::Create(sablName, *sablStream, searchPath);
|
||||
}
|
||||
|
||||
if (streamedEntryCount > 0)
|
||||
{
|
||||
sndBank->streamAssetBank.zone = memory->Dup(sndBankLocalization.at(0).c_str());
|
||||
sndBank->streamAssetBank.language = memory->Dup(sndBankLocalization.at(1).c_str());
|
||||
memset(sndBank->streamAssetBank.linkTimeChecksum, 0xCC, 16);
|
||||
|
||||
const auto sabsName = assetName + ".sabs";
|
||||
sabsStream = OpenSoundBankOutputFile(sabsName);
|
||||
if (sabsStream)
|
||||
sabsWriter = SoundBankWriter::Create(sabsName, *sabsStream, searchPath);
|
||||
}
|
||||
|
||||
// add aliases to the correct sound bank writer
|
||||
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 (sabsWriter && alias->flags.loadType == SA_STREAMED)
|
||||
sabsWriter->AddSound(GetSoundFilePath(alias), alias->assetId, alias->flags.looping, true);
|
||||
else if (sablWriter)
|
||||
sablWriter->AddSound(GetSoundFilePath(alias), alias->assetId, alias->flags.looping);
|
||||
}
|
||||
}
|
||||
|
||||
// write the output linked sound bank
|
||||
if (sablWriter)
|
||||
{
|
||||
size_t dataSize = 0u;
|
||||
const auto result = sablWriter->Write(dataSize);
|
||||
sablStream->close();
|
||||
|
||||
if (result)
|
||||
{
|
||||
sndBank->loadedAssets.dataSize = dataSize;
|
||||
sndBank->loadedAssets.data = static_cast<SndChar2048*>(memory->Alloc(dataSize));
|
||||
memset(sndBank->loadedAssets.data, 0, dataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Loaded Sound Bank for " << assetName << " failed to generate. Please check your build files." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// write the output streamed sound bank
|
||||
if (sabsWriter)
|
||||
{
|
||||
size_t dataSize = 0u;
|
||||
const auto result = sabsWriter->Write(dataSize);
|
||||
sabsStream->close();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
std::cerr << "Streamed Sound Bank for " << assetName << " failed to generate. Please check your build files." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
manager->AddAsset(ASSET_TYPE_SOUND, assetName, sndBank);
|
||||
return true;
|
||||
}
|
17
src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.h
Normal file
17
src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderSoundBank.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "AssetLoading/BasicAssetLoader.h"
|
||||
#include "AssetLoading/IAssetLoadingManager.h"
|
||||
#include "Game/T6/T6.h"
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
|
||||
namespace T6
|
||||
{
|
||||
class AssetLoaderSoundBank final : public BasicAssetLoader<ASSET_TYPE_SOUND, SndBank>
|
||||
{
|
||||
public:
|
||||
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
|
||||
_NODISCARD bool CanLoadFromRaw() const override;
|
||||
bool
|
||||
LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override;
|
||||
};
|
||||
} // namespace T6
|
@ -9,6 +9,7 @@
|
||||
#include "AssetLoaders/AssetLoaderRawFile.h"
|
||||
#include "AssetLoaders/AssetLoaderScriptParseTree.h"
|
||||
#include "AssetLoaders/AssetLoaderSlug.h"
|
||||
#include "AssetLoaders/AssetLoaderSoundBank.h"
|
||||
#include "AssetLoaders/AssetLoaderStringTable.h"
|
||||
#include "AssetLoaders/AssetLoaderTracer.h"
|
||||
#include "AssetLoaders/AssetLoaderVehicle.h"
|
||||
@ -51,7 +52,7 @@ namespace T6
|
||||
REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MATERIAL, Material))
|
||||
REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_TECHNIQUE_SET, MaterialTechniqueSet))
|
||||
REGISTER_ASSET_LOADER(AssetLoaderGfxImage)
|
||||
REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_SOUND, SndBank))
|
||||
REGISTER_ASSET_LOADER(AssetLoaderSoundBank)
|
||||
REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_SOUND_PATCH, SndPatch))
|
||||
REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_CLIPMAP, clipMap_t))
|
||||
REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_CLIPMAP_PVS, clipMap_t))
|
||||
|
314
src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp
Normal file
314
src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
#include "SoundBankWriter.h"
|
||||
|
||||
#include "Crypto.h"
|
||||
#include "ObjContainer/SoundBank/SoundBankTypes.h"
|
||||
#include "Sound/FlacDecoder.h"
|
||||
#include "Sound/WavTypes.h"
|
||||
#include "Utils/FileUtils.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
std::unordered_map<unsigned int, unsigned char> INDEX_FOR_FRAMERATE{
|
||||
{8000, 0},
|
||||
{12000, 1},
|
||||
{16000, 2},
|
||||
{24000, 3},
|
||||
{32000, 4},
|
||||
{44100, 5},
|
||||
{48000, 6},
|
||||
{96000, 7},
|
||||
{192000, 8},
|
||||
};
|
||||
|
||||
class SoundBankWriterImpl : public SoundBankWriter
|
||||
{
|
||||
static constexpr char BRANDING[] = "Created with OAT - OpenAssetTools";
|
||||
static constexpr int64_t DATA_OFFSET = 0x800;
|
||||
static constexpr uint32_t MAGIC = FileUtils::MakeMagic32('2', 'U', 'X', '#');
|
||||
static constexpr uint32_t VERSION = 14u;
|
||||
|
||||
inline static const std::string PAD_DATA = std::string(16, '\x00');
|
||||
|
||||
public:
|
||||
explicit SoundBankWriterImpl(std::string fileName, std::ostream& stream, ISearchPath* assetSearchPath)
|
||||
: m_file_name(std::move(fileName)),
|
||||
m_stream(stream),
|
||||
m_asset_search_path(assetSearchPath),
|
||||
m_current_offset(0),
|
||||
m_total_size(0),
|
||||
m_entry_section_offset(0),
|
||||
m_checksum_section_offset(0)
|
||||
{
|
||||
}
|
||||
|
||||
void AddSound(const std::string& soundFilePath, unsigned int soundId, bool looping, bool streamed) override
|
||||
{
|
||||
this->m_sounds.emplace_back(soundFilePath, soundId, looping, streamed);
|
||||
}
|
||||
|
||||
void GoTo(const int64_t offset)
|
||||
{
|
||||
m_stream.seekp(offset, std::ios::beg);
|
||||
m_current_offset = offset;
|
||||
}
|
||||
|
||||
void Write(const void* data, const size_t dataSize)
|
||||
{
|
||||
m_stream.write(static_cast<const char*>(data), dataSize);
|
||||
m_current_offset += dataSize;
|
||||
}
|
||||
|
||||
void Pad(const size_t paddingSize)
|
||||
{
|
||||
auto paddingSizeLeft = paddingSize;
|
||||
while (paddingSizeLeft > 0)
|
||||
{
|
||||
const auto writeSize = std::min(paddingSizeLeft, PAD_DATA.size());
|
||||
Write(PAD_DATA.data(), writeSize);
|
||||
|
||||
paddingSizeLeft -= writeSize;
|
||||
}
|
||||
}
|
||||
|
||||
void AlignToChunk()
|
||||
{
|
||||
if (m_current_offset % 16 != 0)
|
||||
Pad(16 - (m_current_offset % 16));
|
||||
}
|
||||
|
||||
void WriteHeader()
|
||||
{
|
||||
GoTo(0);
|
||||
|
||||
// The checksum here is supposed to be a MD5 of the entire data portion of the file. However T6 does not validate this.
|
||||
// As long as the values here match the values in the SndBank asset, everything loads fine
|
||||
SoundAssetBankChecksum checksum{};
|
||||
memset(&checksum, 0xCC, sizeof(SoundAssetBankChecksum));
|
||||
|
||||
SoundAssetBankHeader header{
|
||||
MAGIC,
|
||||
VERSION,
|
||||
sizeof(SoundAssetBankEntry),
|
||||
sizeof(SoundAssetBankChecksum),
|
||||
0x40,
|
||||
m_entries.size(),
|
||||
0,
|
||||
0,
|
||||
m_total_size,
|
||||
m_entry_section_offset,
|
||||
m_checksum_section_offset,
|
||||
checksum,
|
||||
{},
|
||||
};
|
||||
|
||||
strncpy(header.dependencies, m_file_name.data(), header.dependencySize);
|
||||
|
||||
Write(&header, sizeof(header));
|
||||
}
|
||||
|
||||
bool WriteEntries()
|
||||
{
|
||||
GoTo(DATA_OFFSET);
|
||||
|
||||
for (auto& sound : m_sounds)
|
||||
{
|
||||
const auto& soundFilePath = sound.m_file_path;
|
||||
const auto soundId = sound.m_sound_id;
|
||||
|
||||
size_t soundSize;
|
||||
std::unique_ptr<char[]> soundData;
|
||||
|
||||
// try to find a wav file for the sound path
|
||||
const auto wavFile = m_asset_search_path->Open(soundFilePath + ".wav");
|
||||
if (wavFile.IsOpen())
|
||||
{
|
||||
WavHeader header{};
|
||||
wavFile.m_stream->read(reinterpret_cast<char*>(&header), sizeof(WavHeader));
|
||||
|
||||
soundSize = static_cast<size_t>(wavFile.m_length - sizeof(WavHeader));
|
||||
const auto frameCount = soundSize / (header.formatChunk.nChannels * (header.formatChunk.wBitsPerSample / 8));
|
||||
const auto frameRateIndex = INDEX_FOR_FRAMERATE[header.formatChunk.nSamplesPerSec];
|
||||
|
||||
SoundAssetBankEntry entry{
|
||||
soundId,
|
||||
soundSize,
|
||||
static_cast<size_t>(m_current_offset),
|
||||
frameCount,
|
||||
frameRateIndex,
|
||||
static_cast<unsigned char>(header.formatChunk.nChannels),
|
||||
sound.m_looping,
|
||||
0,
|
||||
};
|
||||
|
||||
m_entries.push_back(entry);
|
||||
|
||||
soundData = std::make_unique<char[]>(soundSize);
|
||||
wavFile.m_stream->read(soundData.get(), soundSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there is no wav file, try flac file
|
||||
const auto flacFile = m_asset_search_path->Open(soundFilePath + ".flac");
|
||||
if (flacFile.IsOpen())
|
||||
{
|
||||
soundSize = static_cast<size_t>(flacFile.m_length);
|
||||
|
||||
soundData = std::make_unique<char[]>(soundSize);
|
||||
flacFile.m_stream->read(soundData.get(), soundSize);
|
||||
|
||||
flac::FlacMetaData metaData;
|
||||
if (flac::GetFlacMetaData(soundData.get(), soundSize, metaData))
|
||||
{
|
||||
const auto frameRateIndex = INDEX_FOR_FRAMERATE[metaData.m_sample_rate];
|
||||
SoundAssetBankEntry entry{
|
||||
soundId,
|
||||
soundSize,
|
||||
static_cast<size_t>(m_current_offset),
|
||||
static_cast<unsigned>(metaData.m_total_samples),
|
||||
frameRateIndex,
|
||||
metaData.m_number_of_channels,
|
||||
sound.m_looping,
|
||||
8,
|
||||
};
|
||||
|
||||
m_entries.push_back(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unable to decode .flac file for sound " << soundFilePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unable to find a compatible file for sound " << soundFilePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto lastEntry = m_entries.rbegin();
|
||||
if (!sound.m_streamed && lastEntry->frameRateIndex != 6)
|
||||
{
|
||||
std::cout << "WARNING: Loaded sound \"" << soundFilePath
|
||||
<< "\" should have a framerate of 48000 but doesn't. This sound may not work on all games!" << std::endl;
|
||||
}
|
||||
|
||||
// calculate checksum
|
||||
SoundAssetBankChecksum checksum{};
|
||||
|
||||
const auto md5Crypt = Crypto::CreateMD5();
|
||||
md5Crypt->Process(soundData.get(), soundSize);
|
||||
md5Crypt->Finish(checksum.checksumBytes);
|
||||
|
||||
m_checksums.push_back(checksum);
|
||||
|
||||
// write data
|
||||
Write(soundData.get(), soundSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteEntryList()
|
||||
{
|
||||
AlignToChunk();
|
||||
|
||||
m_entry_section_offset = m_current_offset;
|
||||
|
||||
for (auto& entry : m_entries)
|
||||
{
|
||||
Write(&entry, sizeof(SoundAssetBankEntry));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteChecksumList()
|
||||
{
|
||||
m_checksum_section_offset = m_current_offset;
|
||||
|
||||
for (auto& checksum : m_checksums)
|
||||
{
|
||||
Write(&checksum, sizeof(SoundAssetBankChecksum));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteBranding()
|
||||
{
|
||||
AlignToChunk();
|
||||
Write(BRANDING, sizeof(BRANDING));
|
||||
AlignToChunk();
|
||||
}
|
||||
|
||||
bool Write(size_t& dataSize) override
|
||||
{
|
||||
if (!WriteEntries())
|
||||
{
|
||||
std::cerr << "An error occurred writing the sound bank entries. Please check output." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteEntryList();
|
||||
WriteChecksumList();
|
||||
WriteBranding();
|
||||
|
||||
m_total_size = m_current_offset;
|
||||
|
||||
WriteHeader();
|
||||
|
||||
if (m_current_offset > UINT32_MAX)
|
||||
{
|
||||
std::cerr << "Sound bank files must be under 4GB. Please reduce the number of sounds being written!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// output the total size for the sound asset data
|
||||
dataSize = static_cast<size_t>(m_entry_section_offset - DATA_OFFSET);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
class SoundBankEntryInfo
|
||||
{
|
||||
public:
|
||||
SoundBankEntryInfo()
|
||||
: m_sound_id(0u),
|
||||
m_looping(false),
|
||||
m_streamed(false)
|
||||
{
|
||||
}
|
||||
|
||||
SoundBankEntryInfo(std::string filePath, const unsigned int soundId, const bool looping, const bool streamed)
|
||||
: m_file_path(std::move(filePath)),
|
||||
m_sound_id(soundId),
|
||||
m_looping(looping),
|
||||
m_streamed(streamed)
|
||||
{
|
||||
}
|
||||
|
||||
std::string m_file_path;
|
||||
unsigned int m_sound_id;
|
||||
bool m_looping;
|
||||
bool m_streamed;
|
||||
};
|
||||
|
||||
std::string m_file_name;
|
||||
std::ostream& m_stream;
|
||||
ISearchPath* m_asset_search_path;
|
||||
std::vector<SoundBankEntryInfo> m_sounds;
|
||||
|
||||
int64_t m_current_offset;
|
||||
std::vector<SoundAssetBankEntry> m_entries;
|
||||
std::vector<SoundAssetBankChecksum> m_checksums;
|
||||
int64_t m_total_size;
|
||||
int64_t m_entry_section_offset;
|
||||
int64_t m_checksum_section_offset;
|
||||
};
|
||||
|
||||
std::filesystem::path SoundBankWriter::OutputPath;
|
||||
|
||||
std::unique_ptr<SoundBankWriter> SoundBankWriter::Create(const std::string& fileName, std::ostream& stream, ISearchPath* assetSearchPath)
|
||||
{
|
||||
return std::make_unique<SoundBankWriterImpl>(fileName, stream, assetSearchPath);
|
||||
}
|
25
src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.h
Normal file
25
src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
class SoundBankWriter
|
||||
{
|
||||
public:
|
||||
SoundBankWriter() = default;
|
||||
virtual ~SoundBankWriter() = default;
|
||||
|
||||
SoundBankWriter(const SoundBankWriter& other) = default;
|
||||
SoundBankWriter(SoundBankWriter&& other) noexcept = default;
|
||||
SoundBankWriter& operator=(const SoundBankWriter& other) = default;
|
||||
SoundBankWriter& operator=(SoundBankWriter&& other) noexcept = default;
|
||||
|
||||
virtual void AddSound(const std::string& soundFilePath, unsigned int soundId, bool looping = false, bool streamed = false) = 0;
|
||||
virtual bool Write(size_t& dataSize) = 0;
|
||||
|
||||
static std::unique_ptr<SoundBankWriter> Create(const std::string& fileName, std::ostream& stream, ISearchPath* assetSearchPath);
|
||||
|
||||
static std::filesystem::path OutputPath;
|
||||
};
|
@ -52,7 +52,8 @@ function ObjWriting:project()
|
||||
|
||||
self:include(includes)
|
||||
Utils:include(includes)
|
||||
json:include(includes)
|
||||
minilzo:include(includes)
|
||||
minizip:include(includes)
|
||||
json:include(includes)
|
||||
|
||||
end
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "Csv/CsvStream.h"
|
||||
#include "Game/T6/CommonT6.h"
|
||||
#include "Game/T6/SoundConstantsT6.h"
|
||||
#include "ObjContainer/SoundBank/SoundBank.h"
|
||||
#include "Sound/WavWriter.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
@ -51,6 +52,7 @@ namespace
|
||||
"start_delay",
|
||||
"reverb_send",
|
||||
"duck",
|
||||
"duck_group",
|
||||
"pan",
|
||||
"center_send",
|
||||
"envelop_min",
|
||||
@ -118,147 +120,15 @@ namespace
|
||||
192000,
|
||||
};
|
||||
|
||||
const std::string GROUPS_ENUM[]{
|
||||
"grp_reference",
|
||||
"grp_master",
|
||||
"grp_wpn_lfe",
|
||||
"grp_lfe",
|
||||
"grp_hdrfx",
|
||||
"grp_music",
|
||||
"grp_voice",
|
||||
"grp_set_piece",
|
||||
"grp_igc",
|
||||
"grp_mp_game",
|
||||
"grp_explosion",
|
||||
"grp_player_impacts",
|
||||
"grp_scripted_moment",
|
||||
"grp_menu",
|
||||
"grp_whizby",
|
||||
"grp_weapon",
|
||||
"grp_vehicle",
|
||||
"grp_impacts",
|
||||
"grp_foley",
|
||||
"grp_destructible",
|
||||
"grp_physics",
|
||||
"grp_ambience",
|
||||
"grp_alerts",
|
||||
"grp_air",
|
||||
"grp_bink",
|
||||
"grp_announcer",
|
||||
"",
|
||||
};
|
||||
|
||||
const std::string CURVES_ENUM[]{
|
||||
"default",
|
||||
"defaultmin",
|
||||
"allon",
|
||||
"alloff",
|
||||
"rcurve0",
|
||||
"rcurve1",
|
||||
"rcurve2",
|
||||
"rcurve3",
|
||||
"rcurve4",
|
||||
"rcurve5",
|
||||
"steep",
|
||||
"sindelay",
|
||||
"cosdelay",
|
||||
"sin",
|
||||
"cos",
|
||||
"rev60",
|
||||
"rev65",
|
||||
"",
|
||||
};
|
||||
|
||||
std::unordered_map<unsigned int, std::string> CreateCurvesMap()
|
||||
{
|
||||
std::unordered_map<unsigned int, std::string> result;
|
||||
for (auto i = 0u; i < std::extent_v<decltype(CURVES_ENUM)>; i++)
|
||||
result.emplace(T6::Common::SND_HashName(CURVES_ENUM[i].data()), CURVES_ENUM[i]);
|
||||
for (auto i = 0u; i < std::extent_v<decltype(SOUND_CURVES)>; i++)
|
||||
result.emplace(T6::Common::SND_HashName(SOUND_CURVES[i].data()), SOUND_CURVES[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::unordered_map<unsigned int, std::string> CURVES_MAP = CreateCurvesMap();
|
||||
|
||||
const std::string DUCK_GROUPS_ENUM[]{
|
||||
"snp_alerts_gameplay",
|
||||
"snp_ambience",
|
||||
"snp_claw",
|
||||
"snp_destructible",
|
||||
"snp_dying",
|
||||
"snp_dying_ice",
|
||||
"snp_evt_2d",
|
||||
"snp_explosion",
|
||||
"snp_foley",
|
||||
"snp_grenade",
|
||||
"snp_hdrfx",
|
||||
"snp_igc",
|
||||
"snp_impacts",
|
||||
"snp_menu",
|
||||
"snp_movie",
|
||||
"snp_music",
|
||||
"snp_never_duck",
|
||||
"snp_player_dead",
|
||||
"snp_player_impacts",
|
||||
"snp_scripted_moment",
|
||||
"snp_set_piece",
|
||||
"snp_special",
|
||||
"snp_vehicle",
|
||||
"snp_vehicle_interior",
|
||||
"snp_voice",
|
||||
"snp_weapon_decay_1p",
|
||||
"snp_whizby",
|
||||
"snp_wpn_1p",
|
||||
"snp_wpn_3p",
|
||||
"snp_wpn_turret",
|
||||
"snp_x2",
|
||||
"snp_x3",
|
||||
};
|
||||
|
||||
const std::string LIMIT_TYPES_ENUM[]{
|
||||
"none",
|
||||
"oldest",
|
||||
"reject",
|
||||
"priority",
|
||||
};
|
||||
|
||||
const std::string MOVE_TYPES_ENUM[]{
|
||||
"none",
|
||||
"left_player",
|
||||
"center_player",
|
||||
"right_player",
|
||||
"random",
|
||||
"left_shot",
|
||||
"center_shot",
|
||||
"right_shot",
|
||||
};
|
||||
|
||||
const std::string LOAD_TYPES_ENUM[]{
|
||||
"unknown",
|
||||
"loaded",
|
||||
"streamed",
|
||||
"primed",
|
||||
};
|
||||
|
||||
const std::string BUS_IDS_ENUM[]{
|
||||
"bus_reverb",
|
||||
"bus_fx",
|
||||
"bus_voice",
|
||||
"bus_pfutz",
|
||||
"bus_hdrfx",
|
||||
"bus_ui",
|
||||
"bus_reference",
|
||||
"bus_music",
|
||||
"bus_movie",
|
||||
"bus_reference",
|
||||
"",
|
||||
};
|
||||
|
||||
const std::string RANDOMIZE_TYPES_ENUM[]{
|
||||
"volume",
|
||||
"pitch",
|
||||
"variant",
|
||||
"",
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class AssetDumperSndBank::Internal
|
||||
@ -344,7 +214,7 @@ class AssetDumperSndBank::Internal
|
||||
stream.WriteColumn(alias->name);
|
||||
|
||||
// file
|
||||
stream.WriteColumn(alias->assetFileName);
|
||||
stream.WriteColumn(alias->assetFileName ? alias->assetFileName : "");
|
||||
|
||||
// template
|
||||
stream.WriteColumn("");
|
||||
@ -356,7 +226,7 @@ class AssetDumperSndBank::Internal
|
||||
stream.WriteColumn((alias->secondaryname && *alias->secondaryname) ? alias->secondaryname : "");
|
||||
|
||||
// group
|
||||
stream.WriteColumn(GROUPS_ENUM[alias->flags.volumeGroup]);
|
||||
stream.WriteColumn(SOUND_GROUPS[alias->flags.volumeGroup]);
|
||||
|
||||
// vol_min
|
||||
stream.WriteColumn(std::to_string(alias->volMin));
|
||||
@ -377,28 +247,28 @@ class AssetDumperSndBank::Internal
|
||||
stream.WriteColumn(std::to_string(alias->distReverbMax));
|
||||
|
||||
// volume_falloff_curve
|
||||
stream.WriteColumn(CURVES_ENUM[alias->flags.volumeFalloffCurve]);
|
||||
stream.WriteColumn(SOUND_CURVES[alias->flags.volumeFalloffCurve]);
|
||||
|
||||
// reverb_falloff_curve
|
||||
stream.WriteColumn(CURVES_ENUM[alias->flags.reverbFalloffCurve]);
|
||||
stream.WriteColumn(SOUND_CURVES[alias->flags.reverbFalloffCurve]);
|
||||
|
||||
// volume_min_falloff_curve
|
||||
stream.WriteColumn(CURVES_ENUM[alias->flags.volumeMinFalloffCurve]);
|
||||
stream.WriteColumn(SOUND_CURVES[alias->flags.volumeMinFalloffCurve]);
|
||||
|
||||
// reverb_min_falloff_curve"
|
||||
stream.WriteColumn(CURVES_ENUM[alias->flags.reverbMinFalloffCurve]);
|
||||
stream.WriteColumn(SOUND_CURVES[alias->flags.reverbMinFalloffCurve]);
|
||||
|
||||
// limit_count
|
||||
stream.WriteColumn(std::to_string(alias->limitCount));
|
||||
|
||||
// limit_type
|
||||
stream.WriteColumn(LIMIT_TYPES_ENUM[alias->flags.limitType]);
|
||||
stream.WriteColumn(SOUND_LIMIT_TYPES[alias->flags.limitType]);
|
||||
|
||||
// entity_limit_count
|
||||
stream.WriteColumn(std::to_string(alias->entityLimitCount));
|
||||
|
||||
// entity_limit_type
|
||||
stream.WriteColumn(LIMIT_TYPES_ENUM[alias->flags.entityLimitType]);
|
||||
stream.WriteColumn(SOUND_LIMIT_TYPES[alias->flags.entityLimitType]);
|
||||
|
||||
// pitch_min
|
||||
stream.WriteColumn(std::to_string(alias->pitchMin));
|
||||
@ -425,13 +295,13 @@ class AssetDumperSndBank::Internal
|
||||
stream.WriteColumn("");
|
||||
|
||||
// type
|
||||
stream.WriteColumn(LOAD_TYPES_ENUM[alias->flags.loadType]);
|
||||
stream.WriteColumn(SOUND_LOAD_TYPES[alias->flags.loadType]);
|
||||
|
||||
// loop
|
||||
stream.WriteColumn(alias->flags.looping == T6::SA_NON_LOOPING ? "nonlooping" : "looping");
|
||||
|
||||
// randomize_type
|
||||
stream.WriteColumn(RANDOMIZE_TYPES_ENUM[alias->flags.randomizeType]);
|
||||
stream.WriteColumn(SOUND_RANDOMIZE_TYPES[std::min(alias->flags.randomizeType, 3u)]);
|
||||
|
||||
// probability",
|
||||
stream.WriteColumn(std::to_string(alias->probability));
|
||||
@ -445,6 +315,9 @@ class AssetDumperSndBank::Internal
|
||||
// duck",
|
||||
stream.WriteColumn(FindNameForDuck(alias->duck, bank));
|
||||
|
||||
// duck_group",
|
||||
stream.WriteColumn(SOUND_DUCK_GROUPS[alias->duckGroup]);
|
||||
|
||||
// pan",
|
||||
stream.WriteColumn(alias->flags.panType == SA_PAN_2D ? "2d" : "3d");
|
||||
|
||||
@ -473,7 +346,7 @@ class AssetDumperSndBank::Internal
|
||||
stream.WriteColumn(alias->flags.distanceLpf ? "yes" : "no");
|
||||
|
||||
// move_type",
|
||||
stream.WriteColumn(MOVE_TYPES_ENUM[alias->flags.fluxType]);
|
||||
stream.WriteColumn(SOUND_MOVE_TYPES[alias->flags.fluxType]);
|
||||
|
||||
// move_time",
|
||||
stream.WriteColumn(std::to_string(alias->fluxTime));
|
||||
@ -524,7 +397,7 @@ class AssetDumperSndBank::Internal
|
||||
stream.WriteColumn(alias->flags.stopOnDeath ? "yes" : "no");
|
||||
|
||||
// bus",
|
||||
stream.WriteColumn(BUS_IDS_ENUM[alias->flags.busType]);
|
||||
stream.WriteColumn(SOUND_BUS_IDS[alias->flags.busType]);
|
||||
|
||||
// snapshot",
|
||||
stream.WriteColumn("");
|
||||
@ -647,13 +520,14 @@ class AssetDumperSndBank::Internal
|
||||
for (auto j = 0; j < aliasList.count; j++)
|
||||
{
|
||||
const auto& alias = aliasList.head[j];
|
||||
|
||||
WriteAliasToFile(csvStream, &alias, sndBank);
|
||||
csvStream.NextRow();
|
||||
|
||||
if (alias.assetId && alias.assetFileName && dumpedAssets.find(alias.assetId) == dumpedAssets.end())
|
||||
{
|
||||
DumpSndAlias(alias);
|
||||
dumpedAssets.emplace(alias.assetId);
|
||||
|
||||
WriteAliasToFile(csvStream, &alias, sndBank);
|
||||
csvStream.NextRow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -757,7 +631,7 @@ class AssetDumperSndBank::Internal
|
||||
for (auto i = 0u; i < 32u; i++)
|
||||
{
|
||||
values.push_back({
|
||||
{"duckGroup", DUCK_GROUPS_ENUM[i]},
|
||||
{"duckGroup", SOUND_DUCK_GROUPS[i]},
|
||||
{"attenuation", duck.attenuation[i] },
|
||||
{"filter", duck.filter[i] }
|
||||
});
|
||||
|
@ -1,7 +1,5 @@
|
||||
#include "WavWriter.h"
|
||||
|
||||
#include "Sound/WavTypes.h"
|
||||
|
||||
WavWriter::WavWriter(std::ostream& stream)
|
||||
: m_stream(stream)
|
||||
{
|
||||
|
@ -1,12 +1,7 @@
|
||||
#pragma once
|
||||
#include <ostream>
|
||||
#include "Sound/WavTypes.h"
|
||||
|
||||
struct WavMetaData
|
||||
{
|
||||
unsigned channelCount;
|
||||
unsigned samplesPerSec;
|
||||
unsigned bitsPerSample;
|
||||
};
|
||||
#include <ostream>
|
||||
|
||||
class WavWriter
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace utils
|
||||
@ -100,4 +101,18 @@ namespace utils
|
||||
for (auto& c : str)
|
||||
c = static_cast<char>(toupper(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
std::vector<std::string> StringSplit(const std::string& str, const char delim)
|
||||
{
|
||||
std::vector<std::string> strings;
|
||||
std::istringstream stream(str);
|
||||
|
||||
std::string s;
|
||||
while (std::getline(stream, s, delim))
|
||||
{
|
||||
strings.emplace_back(std::move(s));
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
} // namespace utils
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
@ -14,4 +15,6 @@ namespace utils
|
||||
|
||||
void MakeStringLowerCase(std::string& str);
|
||||
void MakeStringUpperCase(std::string& str);
|
||||
|
||||
std::vector<std::string> StringSplit(const std::string& str, const char delim);
|
||||
} // namespace utils
|
||||
|
39
test/ObjCommonTests/Sound/FlacDecoderTest.cpp
Normal file
39
test/ObjCommonTests/Sound/FlacDecoderTest.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include "Sound/FlacDecoder.h"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
namespace flac
|
||||
{
|
||||
TEST_CASE("FlacDecoder: Ensure properly decodes flac stream info", "[sound][flac]")
|
||||
{
|
||||
// clang-format off
|
||||
constexpr uint8_t testData[]
|
||||
{
|
||||
// Magic
|
||||
'f', 'L', 'a', 'C',
|
||||
|
||||
// Block header
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
|
||||
// StreamInfo block
|
||||
0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xB8, 0x02, 0xF0, 0x00,
|
||||
0x02, 0xF9, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
FlacMetaData metaData;
|
||||
const auto result = GetFlacMetaData(testData, sizeof(testData), metaData);
|
||||
|
||||
REQUIRE(result == true);
|
||||
REQUIRE(metaData.m_minimum_block_size == 1024);
|
||||
REQUIRE(metaData.m_maximum_block_size == 1024);
|
||||
REQUIRE(metaData.m_minimum_frame_size == 0);
|
||||
REQUIRE(metaData.m_maximum_frame_size == 0);
|
||||
REQUIRE(metaData.m_sample_rate == 48000);
|
||||
REQUIRE(metaData.m_number_of_channels == 2);
|
||||
REQUIRE(metaData.m_bits_per_sample == 16);
|
||||
REQUIRE(metaData.m_total_samples == 194870);
|
||||
}
|
||||
} // namespace flac
|
Loading…
x
Reference in New Issue
Block a user