diff --git a/src/ObjLoading/AssetLoading/SndCurve/SndCurveReader.cpp b/src/ObjLoading/AssetLoading/SndCurve/SndCurveReader.cpp new file mode 100644 index 00000000..eacfd680 --- /dev/null +++ b/src/ObjLoading/AssetLoading/SndCurve/SndCurveReader.cpp @@ -0,0 +1,212 @@ +#include "SndCurveReader.h" + +#include "Parsing/Impl/AbstractParser.h" +#include "Parsing/Impl/ParserSingleInputStream.h" +#include "Parsing/Simple/SimpleLexer.h" +#include "Parsing/Simple/SimpleParserValue.h" +#include "Parsing/Simple/Matcher/SimpleMatcherFactory.h" + +enum class SndCurveParserStatus +{ + EXPECT_MAGIC, + EXPECT_KNOT_COUNT, + KNOTS +}; + +class SndCurveParserState +{ +public: + SndCurveParserStatus m_status; + size_t m_knot_count; + SndCurveReader::Result m_result; + + SndCurveParserState() + : m_status(SndCurveParserStatus::EXPECT_MAGIC), + m_knot_count(0u) + { + } +}; + +using snd_sequence_t = AbstractSequence; + +class SndCurveMagicSequence final : public snd_sequence_t +{ +public: + SndCurveMagicSequence() + { + const SimpleMatcherFactory create(this); + AddMatchers({ + create.Keyword("SNDCURVE") + }); + } + +protected: + void ProcessMatch(SndCurveParserState* state, SequenceResult& result) const override + { + state->m_status = SndCurveParserStatus::EXPECT_KNOT_COUNT; + } +}; + +class SndCurveKnotCountSequence final : public snd_sequence_t +{ + static constexpr auto CAPTURE_KNOT_COUNT = 1; + +public: + SndCurveKnotCountSequence() + { + const SimpleMatcherFactory create(this); + AddMatchers({ + create.Integer().Capture(CAPTURE_KNOT_COUNT) + }); + } + +protected: + void ProcessMatch(SndCurveParserState* state, SequenceResult& result) const override + { + state->m_status = SndCurveParserStatus::KNOTS; + + const auto& knotCountToken = result.NextCapture(CAPTURE_KNOT_COUNT); + if (knotCountToken.IntegerValue() < 0) + throw ParsingException(knotCountToken.GetPos(), "Negative knot count is invalid"); + + state->m_knot_count = static_cast(knotCountToken.IntegerValue()); + state->m_result.m_knots.reserve(state->m_knot_count); + } +}; + +class SndCurveKnotSequence final : public snd_sequence_t +{ + static constexpr auto CAPTURE_X = 1; + static constexpr auto CAPTURE_Y = 2; + +public: + SndCurveKnotSequence() + { + const SimpleMatcherFactory create(this); + AddMatchers({ + create.Or({ + create.FloatingPoint(), + create.Integer() + }).Capture(CAPTURE_X), + + create.Or({ + create.FloatingPoint(), + create.Integer() + }).Capture(CAPTURE_Y), + }); + } + +private: + static double GetValue(const SimpleParserValue& token) + { + if (token.m_type == SimpleParserValueType::INTEGER) + return static_cast(token.IntegerValue()); + + return token.FloatingPointValue(); + } + +protected: + void ProcessMatch(SndCurveParserState* state, SequenceResult& result) const override + { + const auto& xToken = result.NextCapture(CAPTURE_X); + const auto& yToken = result.NextCapture(CAPTURE_Y); + + if (state->m_result.m_knots.size() >= state->m_knot_count) + throw ParsingException(xToken.GetPos(), "Too many knots"); + + const auto xValue = GetValue(xToken); + const auto yValue = GetValue(yToken); + + state->m_result.m_knots.push_back(SndCurveReader::Result::Knot{xValue, yValue}); + } +}; + +class SndCurveParser final : public AbstractParser +{ +public: + explicit SndCurveParser(ILexer* lexer) + : AbstractParser(lexer, std::make_unique()) + { + } + +protected: + const std::vector& GetTestsForState() override + { + switch (m_state->m_status) + { + case SndCurveParserStatus::EXPECT_MAGIC: + { + static std::vector expectMagicSequences + { + new SndCurveMagicSequence() + }; + return expectMagicSequences; + } + + case SndCurveParserStatus::EXPECT_KNOT_COUNT: + { + static std::vector expectKnotCountSequences + { + new SndCurveKnotCountSequence() + }; + return expectKnotCountSequences; + } + + case SndCurveParserStatus::KNOTS: + { + static std::vector knotsSequences + { + new SndCurveKnotSequence() + }; + return knotsSequences; + } + } + + assert(false); + throw std::runtime_error("Invalid parsing status"); + } + +public: + _NODISCARD SndCurveReader::Result& GetResult() const + { + return m_state->m_result; + } + + _NODISCARD bool HasExpectedKnotCount() const + { + return m_state->m_knot_count == m_state->m_result.m_knots.size(); + } +}; + +SndCurveReader::SndCurveReader(std::istream& stream, const std::string& filename) + : m_stream(stream), + m_filename(filename) +{ +} + +std::unique_ptr SndCurveReader::Read() const +{ + ParserSingleInputStream parserStream(m_stream, m_filename); + + SimpleLexer::Config lexerConfig; + lexerConfig.m_emit_new_line_tokens = false; + lexerConfig.m_read_strings = false; + lexerConfig.m_read_numbers = true; + + SimpleLexer lexer(&parserStream, std::move(lexerConfig)); + SndCurveParser parser(&lexer); + + if (!parser.Parse()) + { + std::cerr << "Failed to parse SndCurve \"" << m_filename << "\"\n"; + return nullptr; + } + + if(!parser.HasExpectedKnotCount()) + { + std::cerr << "Failed to load SndCurve: Actual knot count differs from expected: \"" << m_filename << "\"\n"; + return nullptr; + } + + return std::make_unique(std::move(parser.GetResult())); +} diff --git a/src/ObjLoading/AssetLoading/SndCurve/SndCurveReader.h b/src/ObjLoading/AssetLoading/SndCurve/SndCurveReader.h new file mode 100644 index 00000000..58ecec88 --- /dev/null +++ b/src/ObjLoading/AssetLoading/SndCurve/SndCurveReader.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "Utils/ClassUtils.h" + +class SndCurveReader +{ +public: + class Result + { + public: + struct Knot + { + double m_x; + double m_y; + }; + + std::vector m_knots; + }; + +private: + std::istream& m_stream; + const std::string& m_filename; + +public: + SndCurveReader(std::istream& stream, const std::string& filename); + + _NODISCARD std::unique_ptr Read() const; +}; diff --git a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.cpp b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.cpp index fb864e64..e2610840 100644 --- a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.cpp +++ b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.cpp @@ -1,13 +1,25 @@ #include "AssetLoaderSndCurve.h" #include +#include +#include #include "ObjLoading.h" +#include "AssetLoading/SndCurve/SndCurveReader.h" #include "Game/IW4/IW4.h" #include "Pool/GlobalAssetPool.h" using namespace IW4; +std::string AssetLoaderSndCurve::GetAssetFilename(const std::string& assetName) +{ + std::ostringstream ss; + + ss << "soundaliases/" << assetName << ".vfcurve"; + + return ss.str(); +} + void* AssetLoaderSndCurve::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) { auto* sndCurve = memory->Create(); @@ -15,3 +27,52 @@ void* AssetLoaderSndCurve::CreateEmptyAsset(const std::string& assetName, Memory sndCurve->filename = memory->Dup(assetName.c_str()); return sndCurve; } + +bool AssetLoaderSndCurve::CanLoadFromRaw() const +{ + return true; +} + +bool AssetLoaderSndCurve::LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto filename = GetAssetFilename(assetName); + const auto file = searchPath->Open(filename); + if (!file.IsOpen()) + return false; + + const SndCurveReader reader(*file.m_stream, filename); + + const auto sndCurveData = reader.Read(); + + if (!sndCurveData) + return false; + + if(sndCurveData->m_knots.size() > std::extent_v) + { + std::cerr << "Failed to load SndCurve \"" << assetName << "\": Too many knots (" << sndCurveData->m_knots.size() << ")\n"; + return false; + } + + auto* sndCurve = memory->Create(); + sndCurve->filename = memory->Dup(assetName.c_str()); + sndCurve->knotCount = static_cast(sndCurveData->m_knots.size()); + + for(auto i = 0u; i < std::extent_v; i++) + { + if(i < sndCurveData->m_knots.size()) + { + const auto& [x, y] = sndCurveData->m_knots[i]; + sndCurve->knots[i][0] = static_cast(x); + sndCurve->knots[i][1] = static_cast(y); + } + else + { + sndCurve->knots[i][0] = 0; + sndCurve->knots[i][1] = 0; + } + } + + manager->AddAsset(ASSET_TYPE_SOUND_CURVE, assetName, sndCurve); + + return true; +} diff --git a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.h b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.h index 505b18bf..4a55f9ca 100644 --- a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.h +++ b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderSndCurve.h @@ -8,7 +8,11 @@ namespace IW4 { class AssetLoaderSndCurve final : public BasicAssetLoader { + static std::string GetAssetFilename(const std::string& assetName); + 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; }; }