From 480f2ee6b965940b7f44fe9c0344d26270394f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=9D=CE=B1=CF=81=CE=BF=CF=85=CF=83=CE=AD=C2=B7=CE=BC?= =?UTF-8?q?=C2=B7=CE=B3=CE=B9=CE=BF=CF=85=CE=BC=CE=B5=CE=BC=CE=AF=C2=B7?= =?UTF-8?q?=CE=A7=CE=B9=CE=BD=CE=B1=CE=BA=CE=AC=CE=BD=CE=BD=CE=B1?= <40709280+NaruseMioShirakana@users.noreply.github.com> Date: Sat, 24 Feb 2024 02:02:24 +0800 Subject: [PATCH] Add files via upload --- libsvc/Modules/Lib/MJson/MJson.cpp | 59 + libsvc/Modules/Lib/MJson/MJson.h | 339 + libsvc/Modules/Lib/MJson/yyjson.c | 9201 +++++++++++++++++ libsvc/Modules/Lib/MJson/yyjson.h | 7688 ++++++++++++++ libsvc/Modules/Lib/World/LICENSE.txt | 39 + libsvc/Modules/Lib/World/src/cheaptrick.cpp | 239 + libsvc/Modules/Lib/World/src/codec.cpp | 324 + libsvc/Modules/Lib/World/src/common.cpp | 228 + libsvc/Modules/Lib/World/src/d4c.cpp | 401 + libsvc/Modules/Lib/World/src/dio.cpp | 666 ++ libsvc/Modules/Lib/World/src/fft.cpp | 2652 +++++ libsvc/Modules/Lib/World/src/harvest.cpp | 1262 +++ .../Modules/Lib/World/src/matlabfunctions.cpp | 320 + libsvc/Modules/Lib/World/src/stonemask.cpp | 218 + libsvc/Modules/Lib/World/src/synthesis.cpp | 397 + .../Lib/World/src/synthesisrealtime.cpp | 600 ++ .../Modules/Lib/World/src/world/cheaptrick.h | 84 + libsvc/Modules/Lib/World/src/world/codec.h | 92 + libsvc/Modules/Lib/World/src/world/common.h | 138 + .../Lib/World/src/world/constantnumbers.h | 51 + libsvc/Modules/Lib/World/src/world/d4c.h | 50 + libsvc/Modules/Lib/World/src/world/dio.h | 65 + libsvc/Modules/Lib/World/src/world/fft.h | 48 + libsvc/Modules/Lib/World/src/world/harvest.h | 63 + .../Lib/World/src/world/macrodefinitions.h | 144 + .../Lib/World/src/world/matlabfunctions.h | 175 + .../Modules/Lib/World/src/world/stonemask.h | 33 + .../Modules/Lib/World/src/world/synthesis.h | 36 + .../Lib/World/src/world/synthesisrealtime.h | 152 + libsvc/Modules/Lib/World/tools/audioio.cpp | 252 + libsvc/Modules/Lib/World/tools/audioio.h | 53 + .../Modules/Lib/World/tools/parameterio.cpp | 243 + libsvc/Modules/Lib/World/tools/parameterio.h | 120 + libsvc/Modules/README.md | 43 + libsvc/Modules/framework.h | 7 + .../InferTools/AvCodec/AvCodeResample.h | 45 + .../InferTools/Cluster/MoeVSBaseCluster.hpp | 46 + .../Cluster/MoeVSClusterManager.hpp | 75 + .../InferTools/Cluster/MoeVSIndexCluster.hpp | 71 + .../InferTools/Cluster/MoeVSKmeansCluster.hpp | 41 + .../header/InferTools/DataStruct/KDTree.hpp | 129 + .../header/InferTools/DataStruct/README.md | 1 + .../F0Extractor/BaseF0Extractor.hpp | 85 + .../InferTools/F0Extractor/DioF0Extractor.hpp | 41 + .../F0Extractor/F0ExtractorManager.hpp | 85 + .../F0Extractor/HarvestF0Extractor.hpp | 42 + .../F0Extractor/NetF0Predictors.hpp | 83 + .../InferTools/Sampler/MoeVSBaseSampler.hpp | 67 + .../Sampler/MoeVSSamplerManager.hpp | 86 + .../InferTools/Sampler/MoeVSSamplers.hpp | 53 + .../Modules/header/InferTools/Stft/stft.hpp | 36 + .../MoeVSCoreTensorExtractor.hpp | 143 + .../MoeVoiceStudioTensorExtractor.hpp | 192 + .../TensorExtractorManager.hpp | 86 + .../Modules/header/InferTools/inferTools.hpp | 311 + libsvc/Modules/header/Logger/MoeSSLogger.hpp | 28 + libsvc/Modules/header/Models/DiffSvc.hpp | 135 + libsvc/Modules/header/Models/EnvManager.hpp | 57 + libsvc/Modules/header/Models/ModelBase.hpp | 131 + libsvc/Modules/header/Models/MoeVSProject.hpp | 312 + libsvc/Modules/header/Models/SVC.hpp | 215 + libsvc/Modules/header/Models/VitsSvc.hpp | 103 + libsvc/Modules/header/Modules.hpp | 127 + libsvc/Modules/header/StringPreprocess.hpp | 37 + .../src/InferTools/AvCodec/AvCodeResample.cpp | 198 + .../InferTools/Cluster/MoeVSBaseCluster.cpp | 7 + .../Cluster/MoeVSClusterManager.cpp | 28 + .../InferTools/Cluster/MoeVSIndexCluster.cpp | 101 + .../InferTools/Cluster/MoeVSKmeansCluster.cpp | 42 + .../src/InferTools/DataStruct/KDTree.cpp | 323 + .../src/InferTools/DataStruct/README.md | 1 + .../F0Extractor/BaseF0Extractor.cpp | 47 + .../InferTools/F0Extractor/DioF0Extractor.cpp | 61 + .../F0Extractor/F0ExtractorManager.cpp | 41 + .../F0Extractor/HarvestF0Extractor.cpp | 61 + .../F0Extractor/NetF0Predictors.cpp | 328 + .../InferTools/Sampler/MoeVSBaseSampler.cpp | 17 + .../Sampler/MoeVSSamplerManager.cpp | 39 + .../src/InferTools/Sampler/MoeVSSamplers.cpp | 236 + libsvc/Modules/src/InferTools/Stft/stft.cpp | 184 + .../MoeVSCoreTensorExtractor.cpp | 588 ++ .../MoeVoiceStudioTensorExtractor.cpp | 289 + .../TensorExtractorManager.cpp | 26 + libsvc/Modules/src/InferTools/inferTools.cpp | 356 + libsvc/Modules/src/Logger/MoeSSLogger.cpp | 177 + libsvc/Modules/src/Models/DiffSvc.cpp | 1050 ++ libsvc/Modules/src/Models/EnvManager.cpp | 190 + libsvc/Modules/src/Models/ModelBase.cpp | 213 + libsvc/Modules/src/Models/MoeVSProject.cpp | 441 + libsvc/Modules/src/Models/SVC.cpp | 122 + libsvc/Modules/src/Models/VitsSvc.cpp | 1320 +++ libsvc/Modules/src/Modules.cpp | 359 + libsvc/Modules/src/StringPreprocess.cpp | 76 + libsvc/dllmain.cpp | 19 + libsvc/libsvc.vcxproj | 250 + libsvc/libsvc.vcxproj.filters | 363 + libsvc/libsvc.vcxproj.user | 6 + 97 files changed, 36904 insertions(+) create mode 100644 libsvc/Modules/Lib/MJson/MJson.cpp create mode 100644 libsvc/Modules/Lib/MJson/MJson.h create mode 100644 libsvc/Modules/Lib/MJson/yyjson.c create mode 100644 libsvc/Modules/Lib/MJson/yyjson.h create mode 100644 libsvc/Modules/Lib/World/LICENSE.txt create mode 100644 libsvc/Modules/Lib/World/src/cheaptrick.cpp create mode 100644 libsvc/Modules/Lib/World/src/codec.cpp create mode 100644 libsvc/Modules/Lib/World/src/common.cpp create mode 100644 libsvc/Modules/Lib/World/src/d4c.cpp create mode 100644 libsvc/Modules/Lib/World/src/dio.cpp create mode 100644 libsvc/Modules/Lib/World/src/fft.cpp create mode 100644 libsvc/Modules/Lib/World/src/harvest.cpp create mode 100644 libsvc/Modules/Lib/World/src/matlabfunctions.cpp create mode 100644 libsvc/Modules/Lib/World/src/stonemask.cpp create mode 100644 libsvc/Modules/Lib/World/src/synthesis.cpp create mode 100644 libsvc/Modules/Lib/World/src/synthesisrealtime.cpp create mode 100644 libsvc/Modules/Lib/World/src/world/cheaptrick.h create mode 100644 libsvc/Modules/Lib/World/src/world/codec.h create mode 100644 libsvc/Modules/Lib/World/src/world/common.h create mode 100644 libsvc/Modules/Lib/World/src/world/constantnumbers.h create mode 100644 libsvc/Modules/Lib/World/src/world/d4c.h create mode 100644 libsvc/Modules/Lib/World/src/world/dio.h create mode 100644 libsvc/Modules/Lib/World/src/world/fft.h create mode 100644 libsvc/Modules/Lib/World/src/world/harvest.h create mode 100644 libsvc/Modules/Lib/World/src/world/macrodefinitions.h create mode 100644 libsvc/Modules/Lib/World/src/world/matlabfunctions.h create mode 100644 libsvc/Modules/Lib/World/src/world/stonemask.h create mode 100644 libsvc/Modules/Lib/World/src/world/synthesis.h create mode 100644 libsvc/Modules/Lib/World/src/world/synthesisrealtime.h create mode 100644 libsvc/Modules/Lib/World/tools/audioio.cpp create mode 100644 libsvc/Modules/Lib/World/tools/audioio.h create mode 100644 libsvc/Modules/Lib/World/tools/parameterio.cpp create mode 100644 libsvc/Modules/Lib/World/tools/parameterio.h create mode 100644 libsvc/Modules/README.md create mode 100644 libsvc/Modules/framework.h create mode 100644 libsvc/Modules/header/InferTools/AvCodec/AvCodeResample.h create mode 100644 libsvc/Modules/header/InferTools/Cluster/MoeVSBaseCluster.hpp create mode 100644 libsvc/Modules/header/InferTools/Cluster/MoeVSClusterManager.hpp create mode 100644 libsvc/Modules/header/InferTools/Cluster/MoeVSIndexCluster.hpp create mode 100644 libsvc/Modules/header/InferTools/Cluster/MoeVSKmeansCluster.hpp create mode 100644 libsvc/Modules/header/InferTools/DataStruct/KDTree.hpp create mode 100644 libsvc/Modules/header/InferTools/DataStruct/README.md create mode 100644 libsvc/Modules/header/InferTools/F0Extractor/BaseF0Extractor.hpp create mode 100644 libsvc/Modules/header/InferTools/F0Extractor/DioF0Extractor.hpp create mode 100644 libsvc/Modules/header/InferTools/F0Extractor/F0ExtractorManager.hpp create mode 100644 libsvc/Modules/header/InferTools/F0Extractor/HarvestF0Extractor.hpp create mode 100644 libsvc/Modules/header/InferTools/F0Extractor/NetF0Predictors.hpp create mode 100644 libsvc/Modules/header/InferTools/Sampler/MoeVSBaseSampler.hpp create mode 100644 libsvc/Modules/header/InferTools/Sampler/MoeVSSamplerManager.hpp create mode 100644 libsvc/Modules/header/InferTools/Sampler/MoeVSSamplers.hpp create mode 100644 libsvc/Modules/header/InferTools/Stft/stft.hpp create mode 100644 libsvc/Modules/header/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.hpp create mode 100644 libsvc/Modules/header/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.hpp create mode 100644 libsvc/Modules/header/InferTools/TensorExtractor/TensorExtractorManager.hpp create mode 100644 libsvc/Modules/header/InferTools/inferTools.hpp create mode 100644 libsvc/Modules/header/Logger/MoeSSLogger.hpp create mode 100644 libsvc/Modules/header/Models/DiffSvc.hpp create mode 100644 libsvc/Modules/header/Models/EnvManager.hpp create mode 100644 libsvc/Modules/header/Models/ModelBase.hpp create mode 100644 libsvc/Modules/header/Models/MoeVSProject.hpp create mode 100644 libsvc/Modules/header/Models/SVC.hpp create mode 100644 libsvc/Modules/header/Models/VitsSvc.hpp create mode 100644 libsvc/Modules/header/Modules.hpp create mode 100644 libsvc/Modules/header/StringPreprocess.hpp create mode 100644 libsvc/Modules/src/InferTools/AvCodec/AvCodeResample.cpp create mode 100644 libsvc/Modules/src/InferTools/Cluster/MoeVSBaseCluster.cpp create mode 100644 libsvc/Modules/src/InferTools/Cluster/MoeVSClusterManager.cpp create mode 100644 libsvc/Modules/src/InferTools/Cluster/MoeVSIndexCluster.cpp create mode 100644 libsvc/Modules/src/InferTools/Cluster/MoeVSKmeansCluster.cpp create mode 100644 libsvc/Modules/src/InferTools/DataStruct/KDTree.cpp create mode 100644 libsvc/Modules/src/InferTools/DataStruct/README.md create mode 100644 libsvc/Modules/src/InferTools/F0Extractor/BaseF0Extractor.cpp create mode 100644 libsvc/Modules/src/InferTools/F0Extractor/DioF0Extractor.cpp create mode 100644 libsvc/Modules/src/InferTools/F0Extractor/F0ExtractorManager.cpp create mode 100644 libsvc/Modules/src/InferTools/F0Extractor/HarvestF0Extractor.cpp create mode 100644 libsvc/Modules/src/InferTools/F0Extractor/NetF0Predictors.cpp create mode 100644 libsvc/Modules/src/InferTools/Sampler/MoeVSBaseSampler.cpp create mode 100644 libsvc/Modules/src/InferTools/Sampler/MoeVSSamplerManager.cpp create mode 100644 libsvc/Modules/src/InferTools/Sampler/MoeVSSamplers.cpp create mode 100644 libsvc/Modules/src/InferTools/Stft/stft.cpp create mode 100644 libsvc/Modules/src/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.cpp create mode 100644 libsvc/Modules/src/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.cpp create mode 100644 libsvc/Modules/src/InferTools/TensorExtractor/TensorExtractorManager.cpp create mode 100644 libsvc/Modules/src/InferTools/inferTools.cpp create mode 100644 libsvc/Modules/src/Logger/MoeSSLogger.cpp create mode 100644 libsvc/Modules/src/Models/DiffSvc.cpp create mode 100644 libsvc/Modules/src/Models/EnvManager.cpp create mode 100644 libsvc/Modules/src/Models/ModelBase.cpp create mode 100644 libsvc/Modules/src/Models/MoeVSProject.cpp create mode 100644 libsvc/Modules/src/Models/SVC.cpp create mode 100644 libsvc/Modules/src/Models/VitsSvc.cpp create mode 100644 libsvc/Modules/src/Modules.cpp create mode 100644 libsvc/Modules/src/StringPreprocess.cpp create mode 100644 libsvc/dllmain.cpp create mode 100644 libsvc/libsvc.vcxproj create mode 100644 libsvc/libsvc.vcxproj.filters create mode 100644 libsvc/libsvc.vcxproj.user diff --git a/libsvc/Modules/Lib/MJson/MJson.cpp b/libsvc/Modules/Lib/MJson/MJson.cpp new file mode 100644 index 0000000..5aa8cf5 --- /dev/null +++ b/libsvc/Modules/Lib/MJson/MJson.cpp @@ -0,0 +1,59 @@ +#include "MJson.h" + +class FileGuard +{ +public: + FileGuard() = delete; + ~FileGuard() + { + if (_fp) fclose(_fp); + _fp = nullptr; + } + FileGuard(const char* _path) + { + if (_fp) fclose(_fp); + _wfopen_s(&_fp, to_wide_string(_path).c_str(), L"rb"); + } + operator FILE* () const + { + return _fp; + } +private: + FILE* _fp = nullptr; + static std::wstring to_wide_string(const std::string& input) + { + std::vector WideString(input.length() * 2); + MultiByteToWideChar( + CP_UTF8, + 0, + input.c_str(), + int(input.length()), + WideString.data(), + int(WideString.size()) + ); + return WideString.data(); + } +}; + +MJson::MJson(const char* _path) +{ + const auto file = FileGuard(_path); + _document = yyjson_read_file(_path, YYJSON_READ_NOFLAG, nullptr, nullptr); + if (!_document) + throw std::exception("Json Parse Error !"); + root = yyjson_doc_get_root(_document); +} + +MJson::MJson(const std::string& _data, bool _read_from_string) +{ + if (_read_from_string) + _document = yyjson_read(_data.c_str(), _data.length(), YYJSON_READ_NOFLAG); + else + { + const auto file = FileGuard(_data.c_str()); + _document = yyjson_read_fp(file, YYJSON_READ_NOFLAG, nullptr, nullptr); + } + if (!_document) + throw std::exception("Json Parse Error !"); + root = yyjson_doc_get_root(_document); +} diff --git a/libsvc/Modules/Lib/MJson/MJson.h b/libsvc/Modules/Lib/MJson/MJson.h new file mode 100644 index 0000000..4c37f09 --- /dev/null +++ b/libsvc/Modules/Lib/MJson/MJson.h @@ -0,0 +1,339 @@ +#pragma once +#include "../../framework.h" +#include +#include "yyjson.h" +#include +#ifdef _WIN32 +#include +#else +#error +#endif + +class MJsonValue +{ +public: + MJsonValue() = default; + MJsonValue(yyjson_val* _val) : _Ptr(_val) {} + ~MJsonValue() = default; + MJsonValue(MJsonValue&& _val) noexcept + { + _Ptr = _val._Ptr; + _val._Ptr = nullptr; + } + MJsonValue(const MJsonValue& _val) = delete; + MJsonValue& operator=(MJsonValue&& _val) noexcept + { + _Ptr = _val._Ptr; + _val._Ptr = nullptr; + return *this; + } + MJsonValue& operator=(const MJsonValue& _val) = delete; + [[nodiscard]] bool IsNull() const + { + return yyjson_is_null(_Ptr); + } + [[nodiscard]] bool IsBoolean() const + { + return yyjson_is_bool(_Ptr); + } + [[nodiscard]] bool IsBool() const + { + return yyjson_is_bool(_Ptr); + } + [[nodiscard]] bool IsInt() const + { + return yyjson_is_num(_Ptr); + } + [[nodiscard]] bool IsFloat() const + { + return yyjson_is_num(_Ptr); + } + [[nodiscard]] bool IsInt64() const + { + return yyjson_is_num(_Ptr); + } + [[nodiscard]] bool IsDouble() const + { + return yyjson_is_num(_Ptr); + } + [[nodiscard]] bool IsString() const + { + return yyjson_is_str(_Ptr); + } + [[nodiscard]] bool IsArray() const + { + return yyjson_is_arr(_Ptr); + } + [[nodiscard]] bool GetBool() const + { + return yyjson_get_bool(_Ptr); + } + [[nodiscard]] bool GetBoolean() const + { + return yyjson_get_bool(_Ptr); + } + [[nodiscard]] int GetInt() const + { + return int(yyjson_get_num(_Ptr)); + } + [[nodiscard]] int64_t GetInt64() const + { + return int64_t(yyjson_get_num(_Ptr)); + } + [[nodiscard]] float GetFloat() const + { + return float(yyjson_get_num(_Ptr)); + } + [[nodiscard]] double GetDouble() const + { + return yyjson_get_num(_Ptr); + } + [[nodiscard]] std::string GetString() const + { + if (const auto _str = yyjson_get_str(_Ptr)) + return _str; + return ""; + } + [[nodiscard]] std::vector GetArray() const + { + std::vector _ret; + if (!IsArray()) + return {}; + const auto _PArray = _Ptr; + size_t idx, max; + yyjson_val* _Object; + yyjson_arr_foreach(_PArray, idx, max, _Object) + _ret.emplace_back(_Object); + return _ret; + } + [[nodiscard]] size_t GetSize() const + { + return yyjson_get_len(_Ptr); + } + [[nodiscard]] size_t Size() const + { + return yyjson_get_len(_Ptr); + } + [[nodiscard]] size_t GetStringLength() const + { + return yyjson_get_len(_Ptr); + } + [[nodiscard]] MJsonValue Get(const std::string& _key) const + { + return yyjson_obj_get(_Ptr, _key.c_str()); + } + [[nodiscard]] MJsonValue operator[](const std::string& _key) const + { + return yyjson_obj_get(_Ptr, _key.c_str()); + } + [[nodiscard]] MJsonValue operator[](size_t _idx) const + { + if (!IsArray()) + return _Ptr; + const auto _max = yyjson_arr_size(_Ptr); + const auto _val = yyjson_arr_get_first(_Ptr); + return _idx < _max ? _val + _idx : _val + _max - 1; + } + [[nodiscard]] bool Empty() const + { + if (!IsArray() && !IsString()) + return true; + auto _max = yyjson_arr_size(_Ptr); + if (IsString()) _max = yyjson_get_len(_Ptr); + return !_max; + } + [[nodiscard]] size_t GetMemberCount() const + { + return yyjson_obj_size(_Ptr); + } + [[nodiscard]] std::vector> GetMemberArray() const + { + std::vector> ret; + yyjson_val* key; + yyjson_obj_iter iter = yyjson_obj_iter_with(_Ptr); + while ((key = yyjson_obj_iter_next(&iter))) { + const auto val = yyjson_obj_iter_get_val(key); + ret.emplace_back(MJsonValue(key).GetString(), val); + } + return ret; + } + [[nodiscard]] bool HasMember(const std::string& _key) const + { + return yyjson_obj_get(_Ptr, _key.c_str()); + } +private: + yyjson_val* _Ptr = nullptr; +}; + +class MJson +{ +public: + MJson() = default; + LibSvcApi MJson(const char* _path); + LibSvcApi MJson(const std::string& _data, bool _read_from_string); + ~MJson() + { + if(_document) + { + yyjson_doc_free(_document); + _document = nullptr; + root = nullptr; + } + } + MJson(MJson&& _Right) noexcept + { + _document = _Right._document; + _Right._document = nullptr; + root = yyjson_doc_get_root(_document); + } + MJson(const MJson& _Right) = delete; + MJson& operator=(MJson&& _Right) noexcept + { + if (_document) + yyjson_doc_free(_document); + _document = _Right._document; + _Right._document = nullptr; + root = yyjson_doc_get_root(_document); + return *this; + } + MJson& operator=(const MJson& _Right) = delete; + void Parse(const std::string& _str) + { + _document = yyjson_read(_str.c_str(), _str.length(), YYJSON_READ_NOFLAG); + if (!_document) + throw std::exception("Json Parse Error !"); + root = yyjson_doc_get_root(_document); + } + [[nodiscard]] bool HasMember(const std::string& _key) const + { + return yyjson_obj_get(root, _key.c_str()); + } + [[nodiscard]] MJsonValue Get(const std::string& _key) const + { + return yyjson_obj_get(root, _key.c_str()); + } + [[nodiscard]] MJsonValue operator[](const std::string& _key) const + { + return yyjson_obj_get(root, _key.c_str()); + } + [[nodiscard]] MJsonValue operator[](size_t _idx) const + { + if (MJsonValue(root).IsArray()) + return root; + const auto _max = yyjson_arr_size(root); + const auto _val = yyjson_arr_get_first(root); + return _idx < _max ? _val + _idx : _val + _max - 1; + } + [[nodiscard]] bool HasParseError() const + { + return _document == nullptr; + } + [[nodiscard]] bool IsNull() const + { + return yyjson_is_null(root); + } + [[nodiscard]] bool IsBoolean() const + { + return yyjson_is_bool(root); + } + [[nodiscard]] bool IsBool() const + { + return yyjson_is_bool(root); + } + [[nodiscard]] bool IsInt() const + { + return yyjson_is_num(root); + } + [[nodiscard]] bool IsFloat() const + { + return yyjson_is_num(root); + } + [[nodiscard]] bool IsInt64() const + { + return yyjson_is_num(root); + } + [[nodiscard]] bool IsDouble() const + { + return yyjson_is_num(root); + } + [[nodiscard]] bool IsString() const + { + return yyjson_is_str(root); + } + [[nodiscard]] bool IsArray() const + { + return yyjson_is_arr(root); + } + [[nodiscard]] bool GetBool() const + { + return yyjson_get_bool(root); + } + [[nodiscard]] bool GetBoolean() const + { + return yyjson_get_bool(root); + } + [[nodiscard]] int GetInt() const + { + return int(yyjson_get_num(root)); + } + [[nodiscard]] int64_t GetInt64() const + { + return int64_t(yyjson_get_num(root)); + } + [[nodiscard]] float GetFloat() const + { + return float(yyjson_get_num(root)); + } + [[nodiscard]] double GetDouble() const + { + return yyjson_get_num(root); + } + [[nodiscard]] std::string GetString() const + { + if (const auto _str = yyjson_get_str(root)) + return _str; + return ""; + } + [[nodiscard]] std::vector GetArray() const + { + std::vector _ret; + if (!IsArray()) + return {}; + const auto _PArray = root; + size_t idx, max; + yyjson_val* _Object; + yyjson_arr_foreach(_PArray, idx, max, _Object) + _ret.emplace_back(_Object); + return _ret; + } + [[nodiscard]] size_t GetSize() const + { + return yyjson_get_len(root); + } + [[nodiscard]] size_t Size() const + { + return yyjson_get_len(root); + } + [[nodiscard]] size_t GetStringLength() const + { + return yyjson_get_len(root); + } + [[nodiscard]] size_t GetMemberCount() const + { + return yyjson_obj_size(root); + } + [[nodiscard]] std::vector> GetMemberArray() const + { + std::vector> ret; + yyjson_val* key; + yyjson_obj_iter iter = yyjson_obj_iter_with(root); + while ((key = yyjson_obj_iter_next(&iter))) { + const auto val = yyjson_obj_iter_get_val(key); + ret.emplace_back(MJsonValue(key).GetString(), val); + } + return ret; + } +private: + yyjson_doc* _document = nullptr; + yyjson_val* root = nullptr; +}; \ No newline at end of file diff --git a/libsvc/Modules/Lib/MJson/yyjson.c b/libsvc/Modules/Lib/MJson/yyjson.c new file mode 100644 index 0000000..d04930d --- /dev/null +++ b/libsvc/Modules/Lib/MJson/yyjson.c @@ -0,0 +1,9201 @@ +/*============================================================================== + * Created by Yaoyuan on 2019/3/9. + * Copyright (C) 2019 Yaoyuan . + * + * Released under the MIT License: + * https://github.com/ibireme/yyjson/blob/master/LICENSE + *============================================================================*/ + +#include "yyjson.h" +#include + + + +/*============================================================================== + * Compile Hint Begin + *============================================================================*/ + +/* warning suppress begin */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +# pragma clang diagnostic ignored "-Wunused-label" +# pragma clang diagnostic ignored "-Wunused-macros" +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wunused-label" +# pragma GCC diagnostic ignored "-Wunused-macros" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4100) /* unreferenced formal parameter */ +# pragma warning(disable:4102) /* unreferenced label */ +# pragma warning(disable:4127) /* conditional expression is constant */ +# pragma warning(disable:4706) /* assignment within conditional expression */ +#endif + + + +/*============================================================================== + * Version + *============================================================================*/ + +uint32_t yyjson_version(void) { + return YYJSON_VERSION_HEX; +} + + + +/*============================================================================== + * Flags + *============================================================================*/ + +/* gcc version check */ +#if defined(__GNUC__) +# if defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ + >= (major * 10000 + minor * 100 + patch)) +# elif defined(__GNUC_MINOR__) +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) \ + >= (major * 10000 + minor * 100 + patch)) +# else +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000) >= (major * 10000 + minor * 100 + patch)) +# endif +#else +# define yyjson_gcc_available(major, minor, patch) 0 +#endif + +/* real gcc check */ +#if !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__ICC) && \ + defined(__GNUC__) && defined(__GNUC_MINOR__) +# define YYJSON_IS_REAL_GCC 1 +#else +# define YYJSON_IS_REAL_GCC 0 +#endif + +/* msvc intrinsic */ +#if YYJSON_MSC_VER >= 1400 +# include +# if defined(_M_AMD64) || defined(_M_ARM64) +# define MSC_HAS_BIT_SCAN_64 1 +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# else +# define MSC_HAS_BIT_SCAN_64 0 +# endif +# if defined(_M_AMD64) || defined(_M_ARM64) || \ + defined(_M_IX86) || defined(_M_ARM) +# define MSC_HAS_BIT_SCAN 1 +# pragma intrinsic(_BitScanForward) +# pragma intrinsic(_BitScanReverse) +# else +# define MSC_HAS_BIT_SCAN 0 +# endif +# if defined(_M_AMD64) +# define MSC_HAS_UMUL128 1 +# pragma intrinsic(_umul128) +# else +# define MSC_HAS_UMUL128 0 +# endif +#else +# define MSC_HAS_BIT_SCAN_64 0 +# define MSC_HAS_BIT_SCAN 0 +# define MSC_HAS_UMUL128 0 +#endif + +/* gcc builtin */ +#if yyjson_has_builtin(__builtin_clzll) || yyjson_gcc_available(3, 4, 0) +# define GCC_HAS_CLZLL 1 +#else +# define GCC_HAS_CLZLL 0 +#endif + +#if yyjson_has_builtin(__builtin_ctzll) || yyjson_gcc_available(3, 4, 0) +# define GCC_HAS_CTZLL 1 +#else +# define GCC_HAS_CTZLL 0 +#endif + +/* int128 type */ +#if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16) && \ + (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) +# define YYJSON_HAS_INT128 1 +#else +# define YYJSON_HAS_INT128 0 +#endif + +/* IEEE 754 floating-point binary representation */ +#if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__) +# define YYJSON_HAS_IEEE_754 1 +#elif (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_DIG == 15) && \ + (DBL_MIN_EXP == -1021) && (DBL_MAX_EXP == 1024) && \ + (DBL_MIN_10_EXP == -307) && (DBL_MAX_10_EXP == 308) +# define YYJSON_HAS_IEEE_754 1 +#else +# define YYJSON_HAS_IEEE_754 0 +#endif + +/* + Correct rounding in double number computations. + + On the x86 architecture, some compilers may use x87 FPU instructions for + floating-point arithmetic. The x87 FPU loads all floating point number as + 80-bit double-extended precision internally, then rounds the result to original + precision, which may produce inaccurate results. For a more detailed + explanation, see the paper: https://arxiv.org/abs/cs/0701192 + + Here are some examples of double precision calculation error: + + 2877.0 / 1e6 == 0.002877, but x87 returns 0.0028770000000000002 + 43683.0 * 1e21 == 4.3683e25, but x87 returns 4.3683000000000004e25 + + Here are some examples of compiler flags to generate x87 instructions on x86: + + clang -m32 -mno-sse + gcc/icc -m32 -mfpmath=387 + msvc /arch:SSE or /arch:IA32 + + If we are sure that there's no similar error described above, we can define the + YYJSON_DOUBLE_MATH_CORRECT as 1 to enable the fast path calculation. This is + not an accurate detection, it's just try to avoid the error at compile-time. + An accurate detection can be done at run-time: + + bool is_double_math_correct(void) { + volatile double r = 43683.0; + r *= 1e21; + return r == 4.3683e25; + } + + See also: utils.h in https://github.com/google/double-conversion/ + */ +#if !defined(FLT_EVAL_METHOD) && defined(__FLT_EVAL_METHOD__) +# define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ +#endif + +#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1 +# define YYJSON_DOUBLE_MATH_CORRECT 0 +#elif defined(i386) || defined(__i386) || defined(__i386__) || \ + defined(_X86_) || defined(__X86__) || defined(_M_IX86) || \ + defined(__I86__) || defined(__IA32__) || defined(__THW_INTEL) +# if (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 2) || \ + (defined(__SSE2_MATH__) && __SSE2_MATH__) +# define YYJSON_DOUBLE_MATH_CORRECT 1 +# else +# define YYJSON_DOUBLE_MATH_CORRECT 0 +# endif +#elif defined(__mc68000__) || defined(__pnacl__) || defined(__native_client__) +# define YYJSON_DOUBLE_MATH_CORRECT 0 +#else +# define YYJSON_DOUBLE_MATH_CORRECT 1 +#endif + +/* endian */ +#if yyjson_has_include() +# include +#endif + +#if yyjson_has_include() +# include +#elif yyjson_has_include() +# include +#elif yyjson_has_include() +# include +#endif + +#define YYJSON_BIG_ENDIAN 4321 +#define YYJSON_LITTLE_ENDIAN 1234 + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif + +#elif defined(__BYTE_ORDER) && __BYTE_ORDER +# if __BYTE_ORDER == __BIG_ENDIAN +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif __BYTE_ORDER == __LITTLE_ENDIAN +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif + +#elif defined(BYTE_ORDER) && BYTE_ORDER +# if BYTE_ORDER == BIG_ENDIAN +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif BYTE_ORDER == LITTLE_ENDIAN +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif + +#elif (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \ + defined(__i386) || defined(__i386__) || \ + defined(_X86_) || defined(__X86__) || \ + defined(_M_IX86) || defined(__THW_INTEL__) || \ + defined(__x86_64) || defined(__x86_64__) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(_M_AMD64) || defined(_M_X64) || \ + defined(__ia64) || defined(_IA64) || defined(__IA64__) || \ + defined(__ia64__) || defined(_M_IA64) || defined(__itanium__) || \ + defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \ + defined(__alpha) || defined(__alpha__) || defined(_M_ALPHA) || \ + defined(__riscv) || defined(__riscv__) || \ + defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ + defined(__EMSCRIPTEN__) || defined(__wasm__) || \ + defined(__loongarch__) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN + +#elif (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) || \ + defined(_ARCH_PPC) || defined(_ARCH_PPC64) || \ + defined(__ppc) || defined(__ppc__) || \ + defined(__sparc) || defined(__sparc__) || defined(__sparc64__) || \ + defined(__or1k__) || defined(__OR1K__) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN + +#else +# define YYJSON_ENDIAN 0 /* unknown endian, detect at run-time */ +#endif + +/* + Unaligned memory access detection. + + Some architectures cannot perform unaligned memory access, or unaligned memory + accesses can have a large performance penalty. Modern compilers can make some + optimizations for unaligned access. For example: https://godbolt.org/z/Ejo3Pa + + typedef struct { char c[2] } vec2; + void copy_vec2(vec2 *dst, vec2 *src) { + *dst = *src; + } + + Compiler may generate `load/store` or `move` instruction if target architecture + supports unaligned access, otherwise it may generate `call memcpy` instruction. + + We want to avoid `memcpy` calls, so we should disable unaligned access by + define `YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS` as 1 on these architectures. + */ +#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS +# if defined(i386) || defined(__i386) || defined(__i386__) || \ + defined(__i486__) || defined(__i586__) || defined(__i686__) || \ + defined(_X86_) || defined(__X86__) || defined(_M_IX86) || \ + defined(__I86__) || defined(__IA32__) || \ + defined(__THW_INTEL) || defined(__THW_INTEL__) || \ + defined(__x86_64) || defined(__x86_64__) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(_M_AMD64) || defined(_M_X64) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 /* x86 */ + +# elif defined(__ia64) || defined(_IA64) || defined(__IA64__) || \ + defined(__ia64__) || defined(_M_IA64) || defined(__itanium__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* Itanium */ + +# elif defined(__arm64) || defined(__arm64__) || \ + defined(__AARCH64EL__) || defined(__AARCH64EB__) || \ + defined(__aarch64__) || defined(_M_ARM64) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 /* ARM64 */ + +# elif defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \ + defined(__ARM_ARCH_5TEJ__) || defined(__ARM_ARCH_5TE__) || \ + defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6KZ__) || \ + defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6K__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* ARM */ + +# elif defined(__ppc64__) || defined(__PPC64__) || \ + defined(__powerpc64__) || defined(_ARCH_PPC64) || \ + defined(__ppc) || defined(__ppc__) || defined(__PPC__) || \ + defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) || \ + defined(_ARCH_PPC) || defined(_M_PPC) || \ + defined(__PPCGECKO__) || defined(__PPCBROADWAY__) || defined(_XENON) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 /* PowerPC */ + +# elif defined(__loongarch__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 /* loongarch */ + +# else +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 /* Unknown */ +# endif + +#endif + +/* + Estimated initial ratio of the JSON data (data_size / value_count). + For example: + + data: {"id":12345678,"name":"Harry"} + data_size: 30 + value_count: 5 + ratio: 6 + + yyjson uses dynamic memory with a growth factor of 1.5 when reading and writing + JSON, the ratios below are used to determine the initial memory size. + + A too large ratio will waste memory, and a too small ratio will cause multiple + memory growths and degrade performance. Currently, these ratios are generated + with some commonly used JSON datasets. + */ +#define YYJSON_READER_ESTIMATED_PRETTY_RATIO 16 +#define YYJSON_READER_ESTIMATED_MINIFY_RATIO 6 +#define YYJSON_WRITER_ESTIMATED_PRETTY_RATIO 32 +#define YYJSON_WRITER_ESTIMATED_MINIFY_RATIO 18 + +/* The initial and maximum size of the memory pool's chunk in yyjson_mut_doc. */ +#define YYJSON_MUT_DOC_STR_POOL_INIT_SIZE 0x100 +#define YYJSON_MUT_DOC_STR_POOL_MAX_SIZE 0x10000000 +#define YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE (0x10 * sizeof(yyjson_mut_val)) +#define YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE (0x1000000 * sizeof(yyjson_mut_val)) + +/* Default value for compile-time options. */ +#ifndef YYJSON_DISABLE_READER +#define YYJSON_DISABLE_READER 0 +#endif +#ifndef YYJSON_DISABLE_WRITER +#define YYJSON_DISABLE_WRITER 0 +#endif +#ifndef YYJSON_DISABLE_UTILS +#define YYJSON_DISABLE_UTILS 0 +#endif +#ifndef YYJSON_DISABLE_FAST_FP_CONV +#define YYJSON_DISABLE_FAST_FP_CONV 0 +#endif +#ifndef YYJSON_DISABLE_NON_STANDARD +#define YYJSON_DISABLE_NON_STANDARD 0 +#endif + + + +/*============================================================================== + * Macros + *============================================================================*/ + +/* Macros used for loop unrolling and other purpose. */ +#define repeat2(x) { x x } +#define repeat3(x) { x x x } +#define repeat4(x) { x x x x } +#define repeat8(x) { x x x x x x x x } +#define repeat16(x) { x x x x x x x x x x x x x x x x } + +#define repeat2_incr(x) { x(0) x(1) } +#define repeat4_incr(x) { x(0) x(1) x(2) x(3) } +#define repeat8_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) } +#define repeat16_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) } +#define repeat_in_1_18(x) { x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) \ + x(16) x(17) x(18) } + +/* Macros used to provide branch prediction information for compiler. */ +#undef likely +#define likely(x) yyjson_likely(x) +#undef unlikely +#define unlikely(x) yyjson_unlikely(x) + +/* Macros used to provide inline information for compiler. */ +#undef static_inline +#define static_inline static yyjson_inline +#undef static_noinline +#define static_noinline static yyjson_noinline + +/* Macros for min and max. */ +#undef yyjson_min +#define yyjson_min(x, y) ((x) < (y) ? (x) : (y)) +#undef yyjson_max +#define yyjson_max(x, y) ((x) > (y) ? (x) : (y)) + +/* Used to write u64 literal for C89 which doesn't support "ULL" suffix. */ +#undef U64 +#define U64(hi, lo) ((((u64)hi##UL) << 32U) + lo##UL) + +/* Used to cast away (remove) const qualifier. */ +#define constcast(type) (type)(void *)(size_t)(const void *) + + + +/*============================================================================== + * Integer Constants + *============================================================================*/ + +/* U64 constant values */ +#undef U64_MAX +#define U64_MAX U64(0xFFFFFFFF, 0xFFFFFFFF) +#undef I64_MAX +#define I64_MAX U64(0x7FFFFFFF, 0xFFFFFFFF) +#undef USIZE_MAX +#define USIZE_MAX ((usize)(~(usize)0)) + +/* Maximum number of digits for reading u32/u64/usize safety (not overflow). */ +#undef U32_SAFE_DIG +#define U32_SAFE_DIG 9 /* u32 max is 4294967295, 10 digits */ +#undef U64_SAFE_DIG +#define U64_SAFE_DIG 19 /* u64 max is 18446744073709551615, 20 digits */ +#undef USIZE_SAFE_DIG +#define USIZE_SAFE_DIG (sizeof(usize) == 64 ? U64_SAFE_DIG : U32_SAFE_DIG) + + + +/*============================================================================== + * IEEE-754 Double Number Constants + *============================================================================*/ + +/* Inf raw value (positive) */ +#define F64_RAW_INF U64(0x7FF00000, 0x00000000) + +/* NaN raw value (quiet NaN, no payload, no sign) */ +#if defined(__hppa__) || (defined(__mips__) && !defined(__mips_nan2008)) +#define F64_RAW_NAN U64(0x7FF7FFFF, 0xFFFFFFFF) +#else +#define F64_RAW_NAN U64(0x7FF80000, 0x00000000) +#endif + +/* double number bits */ +#define F64_BITS 64 + +/* double number exponent part bits */ +#define F64_EXP_BITS 11 + +/* double number significand part bits */ +#define F64_SIG_BITS 52 + +/* double number significand part bits (with 1 hidden bit) */ +#define F64_SIG_FULL_BITS 53 + +/* double number significand bit mask */ +#define F64_SIG_MASK U64(0x000FFFFF, 0xFFFFFFFF) + +/* double number exponent bit mask */ +#define F64_EXP_MASK U64(0x7FF00000, 0x00000000) + +/* double number exponent bias */ +#define F64_EXP_BIAS 1023 + +/* double number significant digits count in decimal */ +#define F64_DEC_DIG 17 + +/* max significant digits count in decimal when reading double number */ +#define F64_MAX_DEC_DIG 768 + +/* maximum decimal power of double number (1.7976931348623157e308) */ +#define F64_MAX_DEC_EXP 308 + +/* minimum decimal power of double number (4.9406564584124654e-324) */ +#define F64_MIN_DEC_EXP (-324) + +/* maximum binary power of double number */ +#define F64_MAX_BIN_EXP 1024 + +/* minimum binary power of double number */ +#define F64_MIN_BIN_EXP (-1021) + + + +/*============================================================================== + * Types + *============================================================================*/ + +/** Type define for primitive types. */ +typedef float f32; +typedef double f64; +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; +typedef size_t usize; + +/** 128-bit integer, used by floating-point number reader and writer. */ +#if YYJSON_HAS_INT128 +__extension__ typedef __int128 i128; +__extension__ typedef unsigned __int128 u128; +#endif + +/** 16/32/64-bit vector */ +typedef struct v16 { char c1, c2; } v16; +typedef struct v32 { char c1, c2, c3, c4; } v32; +typedef struct v64 { char c1, c2, c3, c4, c5, c6, c7, c8; } v64; + +/** 16/32/64-bit vector union, used for unaligned memory access on modern CPU */ +typedef union v16_uni { v16 v; u16 u; } v16_uni; +typedef union v32_uni { v32 v; u32 u; } v32_uni; +typedef union v64_uni { v64 v; u64 u; } v64_uni; + + + +/*============================================================================== + * Load/Store Utils + *============================================================================*/ + +#define byte_move_idx(x) ((u8 *)dst)[x] = ((u8 *)src)[x]; + +static_inline void byte_move_2(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat2_incr(byte_move_idx) +#else + memmove(dst, src, 2); +#endif +} + +static_inline void byte_move_4(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat4_incr(byte_move_idx) +#else + memmove(dst, src, 4); +#endif +} + +static_inline void byte_move_8(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat8_incr(byte_move_idx) +#else + memmove(dst, src, 8); +#endif +} + +static_inline void byte_move_16(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat16_incr(byte_move_idx) +#else + memmove(dst, src, 16); +#endif +} + +static_inline void byte_copy_2(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat2_incr(byte_move_idx) +#else + memcpy(dst, src, 2); +#endif +} + +static_inline void byte_copy_4(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat4_incr(byte_move_idx) +#else + memcpy(dst, src, 4); +#endif +} + +static_inline void byte_copy_8(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat8_incr(byte_move_idx) +#else + memcpy(dst, src, 8); +#endif +} + +static_inline void byte_copy_16(void *dst, const void *src) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + repeat16_incr(byte_move_idx) +#else + memcpy(dst, src, 16); +#endif +} + +static_inline bool byte_match_2(void *buf, const char *pat) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + return + ((u8 *)buf)[0] == ((const u8 *)pat)[0] && + ((u8 *)buf)[1] == ((const u8 *)pat)[1]; +#else + v16_uni u1, u2; + u1.v = *(const v16 *)pat; + u2.v = *(const v16 *)buf; + return u1.u == u2.u; +#endif +} + +static_inline bool byte_match_4(void *buf, const char *pat) { +#if YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + return + ((u8 *)buf)[0] == ((const u8 *)pat)[0] && + ((u8 *)buf)[1] == ((const u8 *)pat)[1] && + ((u8 *)buf)[2] == ((const u8 *)pat)[2] && + ((u8 *)buf)[3] == ((const u8 *)pat)[3]; +#else + v32_uni u1, u2; + u1.v = *(const v32 *)pat; + u2.v = *(const v32 *)buf; + return u1.u == u2.u; +#endif +} + +static_inline u16 byte_load_2(const void *src) { + v16_uni uni; + uni.v = *(const v16 *)src; + return uni.u; +} + +static_inline u32 byte_load_3(const void *src) { + v32_uni uni; + ((v16_uni *)&uni)->v = *(const v16 *)src; + uni.v.c3 = ((const char *)src)[2]; + uni.v.c4 = 0; + return uni.u; +} + +static_inline u32 byte_load_4(const void *src) { + v32_uni uni; + uni.v = *(const v32 *)src; + return uni.u; +} + +#undef byte_move_expr + + + +/*============================================================================== + * Number Utils + * These functions are used to detect and convert NaN and Inf numbers. + *============================================================================*/ + +/** + This union is used to avoid violating the strict aliasing rule in C. + `memcpy` can be used in both C and C++, but it may reduce performance without + compiler optimization. + */ +typedef union { u64 u; f64 f; } f64_uni; + +/** Convert raw binary to double. */ +static_inline f64 f64_from_raw(u64 u) { +#ifndef __cplusplus + f64_uni uni; + uni.u = u; + return uni.f; +#else + f64 f; + memcpy(&f, &u, 8); + return f; +#endif +} + +/** Convert double to raw binary. */ +static_inline u64 f64_to_raw(f64 f) { +#ifndef __cplusplus + f64_uni uni; + uni.f = f; + return uni.u; +#else + u64 u; + memcpy(&u, &f, 8); + return u; +#endif +} + +/** Get raw 'infinity' with sign. */ +static_inline u64 f64_raw_get_inf(bool sign) { +#if YYJSON_HAS_IEEE_754 + return F64_RAW_INF | ((u64)sign << 63); +#elif defined(INFINITY) + return f64_to_raw(sign ? -INFINITY : INFINITY); +#else + return f64_to_raw(sign ? -HUGE_VAL : HUGE_VAL); +#endif +} + +/** Get raw 'nan' with sign. */ +static_inline u64 f64_raw_get_nan(bool sign) { +#if YYJSON_HAS_IEEE_754 + return F64_RAW_NAN | ((u64)sign << 63); +#elif defined(NAN) + return f64_to_raw(sign ? (f64)-NAN : (f64)NAN); +#else + return f64_to_raw((sign ? -0.0 : 0.0) / 0.0); +#endif +} + +/** + Convert normalized u64 (highest bit is 1) to f64. + + Some compiler (such as Microsoft Visual C++ 6.0) do not support converting + number from u64 to f64. This function will first convert u64 to i64 and then + to f64, with `to nearest` rounding mode. + */ +static_inline f64 normalized_u64_to_f64(u64 val) { +#if YYJSON_U64_TO_F64_NO_IMPL + i64 sig = (i64)((val >> 1) | (val & 1)); + return ((f64)sig) * (f64)2.0; +#else + return (f64)val; +#endif +} + + + +/*============================================================================== + * Size Utils + * These functions are used for memory allocation. + *============================================================================*/ + +/** Returns whether the size is overflow after increment. */ +static_inline bool size_add_is_overflow(usize size, usize add) { + return size > (size + add); +} + +/** Returns whether the size is power of 2 (size should not be 0). */ +static_inline bool size_is_pow2(usize size) { + return (size & (size - 1)) == 0; +} + +/** Align size upwards (may overflow). */ +static_inline usize size_align_up(usize size, usize align) { + if (size_is_pow2(align)) { + return (size + (align - 1)) & ~(align - 1); + } else { + return size + align - (size + align - 1) % align - 1; + } +} + +/** Align size downwards. */ +static_inline usize size_align_down(usize size, usize align) { + if (size_is_pow2(align)) { + return size & ~(align - 1); + } else { + return size - (size % align); + } +} + +/** Align address upwards (may overflow). */ +static_inline void *mem_align_up(void *mem, usize align) { + usize size; + memcpy(&size, &mem, sizeof(usize)); + size = size_align_up(size, align); + memcpy(&mem, &size, sizeof(usize)); + return mem; +} + + + +/*============================================================================== + * Bits Utils + * These functions are used by the floating-point number reader and writer. + *============================================================================*/ + +/** Returns the number of leading 0-bits in value (input should not be 0). */ +static_inline u32 u64_lz_bits(u64 v) { +#if GCC_HAS_CLZLL + return (u32)__builtin_clzll(v); +#elif MSC_HAS_BIT_SCAN_64 + unsigned long r; + _BitScanReverse64(&r, v); + return (u32)63 - (u32)r; +#elif MSC_HAS_BIT_SCAN + unsigned long hi, lo; + bool hi_set = _BitScanReverse(&hi, (u32)(v >> 32)) != 0; + _BitScanReverse(&lo, (u32)v); + hi |= 32; + return (u32)63 - (u32)(hi_set ? hi : lo); +#else + /* + branchless, use de Bruijn sequences + see: https://www.chessprogramming.org/BitScan + */ + const u8 table[64] = { + 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, + 9, 5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59, 1, + 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18, + 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0 + }; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + return table[(v * U64(0x03F79D71, 0xB4CB0A89)) >> 58]; +#endif +} + +/** Returns the number of trailing 0-bits in value (input should not be 0). */ +static_inline u32 u64_tz_bits(u64 v) { +#if GCC_HAS_CTZLL + return (u32)__builtin_ctzll(v); +#elif MSC_HAS_BIT_SCAN_64 + unsigned long r; + _BitScanForward64(&r, v); + return (u32)r; +#elif MSC_HAS_BIT_SCAN + unsigned long lo, hi; + bool lo_set = _BitScanForward(&lo, (u32)(v)) != 0; + _BitScanForward(&hi, (u32)(v >> 32)); + hi += 32; + return lo_set ? lo : hi; +#else + /* + branchless, use de Bruijn sequences + see: https://www.chessprogramming.org/BitScan + */ + const u8 table[64] = { + 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 + }; + return table[((v & (~v + 1)) * U64(0x022FDD63, 0xCC95386D)) >> 58]; +#endif +} + + + +/*============================================================================== + * 128-bit Integer Utils + * These functions are used by the floating-point number reader and writer. + *============================================================================*/ + +/** Multiplies two 64-bit unsigned integers (a * b), + returns the 128-bit result as 'hi' and 'lo'. */ +static_inline void u128_mul(u64 a, u64 b, u64 *hi, u64 *lo) { +#if YYJSON_HAS_INT128 + u128 m = (u128)a * b; + *hi = (u64)(m >> 64); + *lo = (u64)(m); +#elif MSC_HAS_UMUL128 + *lo = _umul128(a, b, hi); +#else + u32 a0 = (u32)(a), a1 = (u32)(a >> 32); + u32 b0 = (u32)(b), b1 = (u32)(b >> 32); + u64 p00 = (u64)a0 * b0, p01 = (u64)a0 * b1; + u64 p10 = (u64)a1 * b0, p11 = (u64)a1 * b1; + u64 m0 = p01 + (p00 >> 32); + u32 m00 = (u32)(m0), m01 = (u32)(m0 >> 32); + u64 m1 = p10 + m00; + u32 m10 = (u32)(m1), m11 = (u32)(m1 >> 32); + *hi = p11 + m01 + m11; + *lo = ((u64)m10 << 32) | (u32)p00; +#endif +} + +/** Multiplies two 64-bit unsigned integers and add a value (a * b + c), + returns the 128-bit result as 'hi' and 'lo'. */ +static_inline void u128_mul_add(u64 a, u64 b, u64 c, u64 *hi, u64 *lo) { +#if YYJSON_HAS_INT128 + u128 m = (u128)a * b + c; + *hi = (u64)(m >> 64); + *lo = (u64)(m); +#else + u64 h, l, t; + u128_mul(a, b, &h, &l); + t = l + c; + h += (u64)(((t < l) | (t < c))); + *hi = h; + *lo = t; +#endif +} + + + +/*============================================================================== + * File Utils + * These functions are used to read and write JSON files. + *============================================================================*/ + +#define YYJSON_FOPEN_EXT +#if !defined(_MSC_VER) && defined(__GLIBC__) && defined(__GLIBC_PREREQ) +# if __GLIBC_PREREQ(2, 7) +# undef YYJSON_FOPEN_EXT +# define YYJSON_FOPEN_EXT "e" /* glibc extension to enable O_CLOEXEC */ +# endif +#endif + +static_inline FILE *fopen_safe(const char *path, const char *mode) { +#if YYJSON_MSC_VER >= 1400 + FILE *file = NULL; + if (fopen_s(&file, path, mode) != 0) return NULL; + return file; +#else + return fopen(path, mode); +#endif +} + +static_inline FILE *fopen_readonly(const char *path) { + return fopen_safe(path, "rb" YYJSON_FOPEN_EXT); +} + +static_inline FILE *fopen_writeonly(const char *path) { + return fopen_safe(path, "wb" YYJSON_FOPEN_EXT); +} + +static_inline usize fread_safe(void *buf, usize size, FILE *file) { +#if YYJSON_MSC_VER >= 1400 + return fread_s(buf, size, 1, size, file); +#else + return fread(buf, 1, size, file); +#endif +} + + + +/*============================================================================== + * Default Memory Allocator + * This is a simple libc memory allocator wrapper. + *============================================================================*/ + +static void *default_malloc(void *ctx, usize size) { + return malloc(size); +} + +static void *default_realloc(void *ctx, void *ptr, usize old_size, usize size) { + return realloc(ptr, size); +} + +static void default_free(void *ctx, void *ptr) { + free(ptr); +} + +static const yyjson_alc YYJSON_DEFAULT_ALC = { + default_malloc, + default_realloc, + default_free, + NULL +}; + +static void *null_malloc(void *ctx, usize size) { + return NULL; +} + +static void *null_realloc(void *ctx, void *ptr, usize old_size, usize size) { + return NULL; +} + +static void null_free(void *ctx, void *ptr) { + return; +} + +static const yyjson_alc YYJSON_NULL_ALC = { + null_malloc, + null_realloc, + null_free, + NULL +}; + + + +/*============================================================================== + * Pool Memory Allocator + * This is a simple memory allocator that uses linked list memory chunk. + * The following code will be executed only when the library user creates + * this allocator manually. + *============================================================================*/ + +/** chunk header */ +typedef struct pool_chunk { + usize size; /* chunk memory size (include chunk header) */ + struct pool_chunk *next; +} pool_chunk; + +/** ctx header */ +typedef struct pool_ctx { + usize size; /* total memory size (include ctx header) */ + pool_chunk *free_list; +} pool_ctx; + +static void *pool_malloc(void *ctx_ptr, usize size) { + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *next, *prev = NULL, *cur = ctx->free_list; + + if (unlikely(size == 0 || size >= ctx->size)) return NULL; + size = size_align_up(size, sizeof(pool_chunk)) + sizeof(pool_chunk); + + while (cur) { + if (cur->size < size) { + /* not enough space, try next chunk */ + prev = cur; + cur = cur->next; + continue; + } + if (cur->size >= size + sizeof(pool_chunk) * 2) { + /* too much space, split this chunk */ + next = (pool_chunk *)(void *)((u8 *)cur + size); + next->size = cur->size - size; + next->next = cur->next; + cur->size = size; + } else { + /* just enough space, use whole chunk */ + next = cur->next; + } + if (prev) prev->next = next; + else ctx->free_list = next; + return (void *)(cur + 1); + } + return NULL; +} + +static void pool_free(void *ctx_ptr, void *ptr) { + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *cur = ((pool_chunk *)ptr) - 1; + pool_chunk *prev = NULL, *next = ctx->free_list; + + while (next && next < cur) { + prev = next; + next = next->next; + } + if (prev) prev->next = cur; + else ctx->free_list = cur; + cur->next = next; + + if (next && ((u8 *)cur + cur->size) == (u8 *)next) { + /* merge cur to higher chunk */ + cur->size += next->size; + cur->next = next->next; + } + if (prev && ((u8 *)prev + prev->size) == (u8 *)cur) { + /* merge cur to lower chunk */ + prev->size += cur->size; + prev->next = cur->next; + } +} + +static void *pool_realloc(void *ctx_ptr, void *ptr, + usize old_size, usize size) { + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *cur = ((pool_chunk *)ptr) - 1, *prev, *next, *tmp; + usize free_size; + void *new_ptr; + + if (unlikely(size == 0 || size >= ctx->size)) return NULL; + size = size_align_up(size, sizeof(pool_chunk)) + sizeof(pool_chunk); + + /* reduce size */ + if (unlikely(size <= cur->size)) { + free_size = cur->size - size; + if (free_size >= sizeof(pool_chunk) * 2) { + tmp = (pool_chunk *)(void *)((u8 *)cur + cur->size - free_size); + tmp->size = free_size; + pool_free(ctx_ptr, (void *)(tmp + 1)); + cur->size -= free_size; + } + return ptr; + } + + /* find next and prev chunk */ + prev = NULL; + next = ctx->free_list; + while (next && next < cur) { + prev = next; + next = next->next; + } + + /* merge to higher chunk if they are contiguous */ + if ((u8 *)cur + cur->size == (u8 *)next && + cur->size + next->size >= size) { + free_size = cur->size + next->size - size; + if (free_size > sizeof(pool_chunk) * 2) { + tmp = (pool_chunk *)(void *)((u8 *)cur + size); + if (prev) prev->next = tmp; + else ctx->free_list = tmp; + tmp->next = next->next; + tmp->size = free_size; + cur->size = size; + } else { + if (prev) prev->next = next->next; + else ctx->free_list = next->next; + cur->size += next->size; + } + return ptr; + } + + /* fallback to malloc and memcpy */ + new_ptr = pool_malloc(ctx_ptr, size - sizeof(pool_chunk)); + if (new_ptr) { + memcpy(new_ptr, ptr, cur->size - sizeof(pool_chunk)); + pool_free(ctx_ptr, ptr); + } + return new_ptr; +} + +bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, usize size) { + pool_chunk *chunk; + pool_ctx *ctx; + + if (unlikely(!alc)) return false; + *alc = YYJSON_NULL_ALC; + if (size < sizeof(pool_ctx) * 4) return false; + ctx = (pool_ctx *)mem_align_up(buf, sizeof(pool_ctx)); + if (unlikely(!ctx)) return false; + size -= (usize)((u8 *)ctx - (u8 *)buf); + size = size_align_down(size, sizeof(pool_ctx)); + + chunk = (pool_chunk *)(ctx + 1); + chunk->size = size - sizeof(pool_ctx); + chunk->next = NULL; + ctx->size = size; + ctx->free_list = chunk; + + alc->malloc = pool_malloc; + alc->realloc = pool_realloc; + alc->free = pool_free; + alc->ctx = (void *)ctx; + return true; +} + + + +/*============================================================================== + * JSON document and value + *============================================================================*/ + +static_inline void unsafe_yyjson_str_pool_release(yyjson_str_pool *pool, + yyjson_alc *alc) { + yyjson_str_chunk *chunk = pool->chunks, *next; + while (chunk) { + next = chunk->next; + alc->free(alc->ctx, chunk); + chunk = next; + } +} + +static_inline void unsafe_yyjson_val_pool_release(yyjson_val_pool *pool, + yyjson_alc *alc) { + yyjson_val_chunk *chunk = pool->chunks, *next; + while (chunk) { + next = chunk->next; + alc->free(alc->ctx, chunk); + chunk = next; + } +} + +bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, + const yyjson_alc *alc, usize len) { + yyjson_str_chunk *chunk; + usize size, max_len; + + /* create a new chunk */ + max_len = USIZE_MAX - sizeof(yyjson_str_chunk); + if (unlikely(len > max_len)) return false; + size = len + sizeof(yyjson_str_chunk); + size = yyjson_max(pool->chunk_size, size); + chunk = (yyjson_str_chunk *)alc->malloc(alc->ctx, size); + if (unlikely(!chunk)) return false; + + /* insert the new chunk as the head of the linked list */ + chunk->next = pool->chunks; + chunk->chunk_size = size; + pool->chunks = chunk; + pool->cur = (char *)chunk + sizeof(yyjson_str_chunk); + pool->end = (char *)chunk + size; + + /* the next chunk is twice the size of the current one */ + size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); + if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ + pool->chunk_size = size; + return true; +} + +bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, + const yyjson_alc *alc, usize count) { + yyjson_val_chunk *chunk; + usize size, max_count; + + /* create a new chunk */ + max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; + if (unlikely(count > max_count)) return false; + size = (count + 1) * sizeof(yyjson_mut_val); + size = yyjson_max(pool->chunk_size, size); + chunk = (yyjson_val_chunk *)alc->malloc(alc->ctx, size); + if (unlikely(!chunk)) return false; + + /* insert the new chunk as the head of the linked list */ + chunk->next = pool->chunks; + chunk->chunk_size = size; + pool->chunks = chunk; + pool->cur = (yyjson_mut_val *)(void *)((u8 *)chunk) + 1; + pool->end = (yyjson_mut_val *)(void *)((u8 *)chunk + size); + + /* the next chunk is twice the size of the current one */ + size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); + if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ + pool->chunk_size = size; + return true; +} + +bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, size_t len) { + usize max_size = USIZE_MAX - sizeof(yyjson_str_chunk); + if (!doc || !len || len > max_size) return false; + doc->str_pool.chunk_size = len + sizeof(yyjson_str_chunk); + return true; +} + +bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, size_t count) { + usize max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; + if (!doc || !count || count > max_count) return false; + doc->val_pool.chunk_size = (count + 1) * sizeof(yyjson_mut_val); + return true; +} + +void yyjson_mut_doc_free(yyjson_mut_doc *doc) { + if (doc) { + yyjson_alc alc = doc->alc; + unsafe_yyjson_str_pool_release(&doc->str_pool, &alc); + unsafe_yyjson_val_pool_release(&doc->val_pool, &alc); + alc.free(alc.ctx, doc); + } +} + +yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc) { + yyjson_mut_doc *doc; + if (!alc) alc = &YYJSON_DEFAULT_ALC; + doc = (yyjson_mut_doc *)alc->malloc(alc->ctx, sizeof(yyjson_mut_doc)); + if (!doc) return NULL; + memset(doc, 0, sizeof(yyjson_mut_doc)); + + doc->alc = *alc; + doc->str_pool.chunk_size = YYJSON_MUT_DOC_STR_POOL_INIT_SIZE; + doc->str_pool.chunk_size_max = YYJSON_MUT_DOC_STR_POOL_MAX_SIZE; + doc->val_pool.chunk_size = YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE; + doc->val_pool.chunk_size_max = YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE; + return doc; +} + +yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, const yyjson_alc *alc) { + yyjson_mut_doc *m_doc; + yyjson_mut_val *m_val; + + if (!doc || !doc->root) return NULL; + m_doc = yyjson_mut_doc_new(alc); + if (!m_doc) return NULL; + m_val = yyjson_val_mut_copy(m_doc, doc->root); + if (!m_val) { + yyjson_mut_doc_free(m_doc); + return NULL; + } + yyjson_mut_doc_set_root(m_doc, m_val); + return m_doc; +} + +yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc) { + yyjson_mut_doc *m_doc; + yyjson_mut_val *m_val; + + if (!doc) return NULL; + if (!doc->root) return yyjson_mut_doc_new(alc); + + m_doc = yyjson_mut_doc_new(alc); + if (!m_doc) return NULL; + m_val = yyjson_mut_val_mut_copy(m_doc, doc->root); + if (!m_val) { + yyjson_mut_doc_free(m_doc); + return NULL; + } + yyjson_mut_doc_set_root(m_doc, m_val); + return m_doc; +} + +yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *m_doc, + yyjson_val *i_vals) { + /* + The immutable object or array stores all sub-values in a contiguous memory, + We copy them to another contiguous memory as mutable values, + then reconnect the mutable values with the original relationship. + */ + + usize i_vals_len; + yyjson_mut_val *m_vals, *m_val; + yyjson_val *i_val, *i_end; + + if (!m_doc || !i_vals) return NULL; + i_end = unsafe_yyjson_get_next(i_vals); + i_vals_len = (usize)(unsafe_yyjson_get_next(i_vals) - i_vals); + m_vals = unsafe_yyjson_mut_val(m_doc, i_vals_len); + if (!m_vals) return NULL; + i_val = i_vals; + m_val = m_vals; + + for (; i_val < i_end; i_val++, m_val++) { + yyjson_type type = unsafe_yyjson_get_type(i_val); + m_val->tag = i_val->tag; + m_val->uni.u64 = i_val->uni.u64; + if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + const char *str = i_val->uni.str; + usize str_len = unsafe_yyjson_get_len(i_val); + m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); + if (!m_val->uni.str) return NULL; + } else if (type == YYJSON_TYPE_ARR) { + usize len = unsafe_yyjson_get_len(i_val); + if (len > 0) { + yyjson_val *ii_val = i_val + 1, *ii_next; + yyjson_mut_val *mm_val = m_val + 1, *mm_ctn = m_val, *mm_next; + while (len-- > 1) { + ii_next = unsafe_yyjson_get_next(ii_val); + mm_next = mm_val + (ii_next - ii_val); + mm_val->next = mm_next; + ii_val = ii_next; + mm_val = mm_next; + } + mm_val->next = mm_ctn + 1; + mm_ctn->uni.ptr = mm_val; + } + } else if (type == YYJSON_TYPE_OBJ) { + usize len = unsafe_yyjson_get_len(i_val); + if (len > 0) { + yyjson_val *ii_key = i_val + 1, *ii_nextkey; + yyjson_mut_val *mm_key = m_val + 1, *mm_ctn = m_val; + yyjson_mut_val *mm_nextkey; + while (len-- > 1) { + ii_nextkey = unsafe_yyjson_get_next(ii_key + 1); + mm_nextkey = mm_key + (ii_nextkey - ii_key); + mm_key->next = mm_key + 1; + mm_key->next->next = mm_nextkey; + ii_key = ii_nextkey; + mm_key = mm_nextkey; + } + mm_key->next = mm_key + 1; + mm_key->next->next = mm_ctn + 1; + mm_ctn->uni.ptr = mm_key; + } + } + } + + return m_vals; +} + +static yyjson_mut_val *unsafe_yyjson_mut_val_mut_copy(yyjson_mut_doc *m_doc, + yyjson_mut_val *m_vals) { + /* + The mutable object or array stores all sub-values in a circular linked + list, so we can traverse them in the same loop. The traversal starts from + the last item, continues with the first item in a list, and ends with the + second to last item, which needs to be linked to the last item to close the + circle. + */ + + yyjson_mut_val *m_val = unsafe_yyjson_mut_val(m_doc, 1); + if (unlikely(!m_val)) return NULL; + m_val->tag = m_vals->tag; + + switch (unsafe_yyjson_get_type(m_vals)) { + case YYJSON_TYPE_OBJ: + case YYJSON_TYPE_ARR: + if (unsafe_yyjson_get_len(m_vals) > 0) { + yyjson_mut_val *last = (yyjson_mut_val *)m_vals->uni.ptr; + yyjson_mut_val *next = last->next, *prev; + prev = unsafe_yyjson_mut_val_mut_copy(m_doc, last); + if (!prev) return NULL; + m_val->uni.ptr = (void *)prev; + while (next != last) { + prev->next = unsafe_yyjson_mut_val_mut_copy(m_doc, next); + if (!prev->next) return NULL; + prev = prev->next; + next = next->next; + } + prev->next = (yyjson_mut_val *)m_val->uni.ptr; + } + break; + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: { + const char *str = m_vals->uni.str; + usize str_len = unsafe_yyjson_get_len(m_vals); + m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); + if (!m_val->uni.str) return NULL; + break; + } + + default: + m_val->uni = m_vals->uni; + break; + } + + return m_val; +} + +yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, + yyjson_mut_val *val) { + if (doc && val) return unsafe_yyjson_mut_val_mut_copy(doc, val); + return NULL; +} + +/* Count the number of values and the total length of the strings. */ +static void yyjson_mut_stat(yyjson_mut_val *val, + usize *val_sum, usize *str_sum) { + yyjson_type type = unsafe_yyjson_get_type(val); + *val_sum += 1; + if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { + yyjson_mut_val *child = (yyjson_mut_val *)val->uni.ptr; + usize len = unsafe_yyjson_get_len(val), i; + len <<= (u8)(type == YYJSON_TYPE_OBJ); + *val_sum += len; + for (i = 0; i < len; i++) { + yyjson_type stype = unsafe_yyjson_get_type(child); + if (stype == YYJSON_TYPE_STR || stype == YYJSON_TYPE_RAW) { + *str_sum += unsafe_yyjson_get_len(child) + 1; + } else if (stype == YYJSON_TYPE_ARR || stype == YYJSON_TYPE_OBJ) { + yyjson_mut_stat(child, val_sum, str_sum); + *val_sum -= 1; + } + child = child->next; + } + } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + *str_sum += unsafe_yyjson_get_len(val) + 1; + } +} + +/* Copy mutable values to immutable value pool. */ +static usize yyjson_imut_copy(yyjson_val **val_ptr, char **buf_ptr, + yyjson_mut_val *mval) { + yyjson_val *val = *val_ptr; + yyjson_type type = unsafe_yyjson_get_type(mval); + if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { + yyjson_mut_val *child = (yyjson_mut_val *)mval->uni.ptr; + usize len = unsafe_yyjson_get_len(mval), i; + usize val_sum = 1; + if (type == YYJSON_TYPE_OBJ) { + if (len) child = child->next->next; + len <<= 1; + } else { + if (len) child = child->next; + } + *val_ptr = val + 1; + for (i = 0; i < len; i++) { + val_sum += yyjson_imut_copy(val_ptr, buf_ptr, child); + child = child->next; + } + val->tag = mval->tag; + val->uni.ofs = val_sum * sizeof(yyjson_val); + return val_sum; + } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + char *buf = *buf_ptr; + usize len = unsafe_yyjson_get_len(mval); + memcpy((void *)buf, (const void *)mval->uni.str, len); + buf[len] = '\0'; + val->tag = mval->tag; + val->uni.str = buf; + *val_ptr = val + 1; + *buf_ptr = buf + len + 1; + return 1; + } else { + val->tag = mval->tag; + val->uni = mval->uni; + *val_ptr = val + 1; + return 1; + } +} + +yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *mdoc, + const yyjson_alc *alc) { + if (!mdoc) return NULL; + return yyjson_mut_val_imut_copy(mdoc->root, alc); +} + +yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *mval, + const yyjson_alc *alc) { + usize val_num = 0, str_sum = 0, hdr_size, buf_size; + yyjson_doc *doc = NULL; + yyjson_val *val_hdr = NULL; + + /* This value should be NULL here. Setting a non-null value suppresses + warning from the clang analyzer. */ + char *str_hdr = (char *)(void *)&str_sum; + if (!mval) return NULL; + if (!alc) alc = &YYJSON_DEFAULT_ALC; + + /* traverse the input value to get pool size */ + yyjson_mut_stat(mval, &val_num, &str_sum); + + /* create doc and val pool */ + hdr_size = size_align_up(sizeof(yyjson_doc), sizeof(yyjson_val)); + buf_size = hdr_size + val_num * sizeof(yyjson_val); + doc = (yyjson_doc *)alc->malloc(alc->ctx, buf_size); + if (!doc) return NULL; + memset(doc, 0, sizeof(yyjson_doc)); + val_hdr = (yyjson_val *)(void *)((char *)(void *)doc + hdr_size); + doc->root = val_hdr; + doc->alc = *alc; + + /* create str pool */ + if (str_sum > 0) { + str_hdr = (char *)alc->malloc(alc->ctx, str_sum); + doc->str_pool = str_hdr; + if (!str_hdr) { + alc->free(alc->ctx, (void *)doc); + return NULL; + } + } + + /* copy vals and strs */ + doc->val_read = yyjson_imut_copy(&val_hdr, &str_hdr, mval); + doc->dat_read = str_sum + 1; + return doc; +} + +static_inline bool unsafe_yyjson_num_equals(void *lhs, void *rhs) { + yyjson_val_uni *luni = &((yyjson_val *)lhs)->uni; + yyjson_val_uni *runi = &((yyjson_val *)rhs)->uni; + yyjson_subtype lt = unsafe_yyjson_get_subtype(lhs); + yyjson_subtype rt = unsafe_yyjson_get_subtype(rhs); + if (lt == rt) + return luni->u64 == runi->u64; + if (lt == YYJSON_SUBTYPE_SINT && rt == YYJSON_SUBTYPE_UINT) + return luni->i64 >= 0 && luni->u64 == runi->u64; + if (lt == YYJSON_SUBTYPE_UINT && rt == YYJSON_SUBTYPE_SINT) + return runi->i64 >= 0 && luni->u64 == runi->u64; + return false; +} + +static_inline bool unsafe_yyjson_str_equals(void *lhs, void *rhs) { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + return !memcmp(unsafe_yyjson_get_str(lhs), + unsafe_yyjson_get_str(rhs), len); +} + +bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { + yyjson_type type = unsafe_yyjson_get_type(lhs); + if (type != unsafe_yyjson_get_type(rhs)) return false; + + switch (type) { + case YYJSON_TYPE_OBJ: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + yyjson_obj_iter iter; + yyjson_obj_iter_init(rhs, &iter); + lhs = unsafe_yyjson_get_first(lhs); + while (len-- > 0) { + rhs = yyjson_obj_iter_getn(&iter, lhs->uni.str, + unsafe_yyjson_get_len(lhs)); + if (!rhs || !unsafe_yyjson_equals(lhs + 1, rhs)) + return false; + lhs = unsafe_yyjson_get_next(lhs + 1); + } + } + /* yyjson allows duplicate keys, so the check may be inaccurate */ + return true; + } + + case YYJSON_TYPE_ARR: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + lhs = unsafe_yyjson_get_first(lhs); + rhs = unsafe_yyjson_get_first(rhs); + while (len-- > 0) { + if (!unsafe_yyjson_equals(lhs, rhs)) return false; + lhs = unsafe_yyjson_get_next(lhs); + rhs = unsafe_yyjson_get_next(rhs); + } + } + return true; + } + + case YYJSON_TYPE_NUM: + return unsafe_yyjson_num_equals(lhs, rhs); + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + return unsafe_yyjson_str_equals(lhs, rhs); + + case YYJSON_TYPE_NULL: + case YYJSON_TYPE_BOOL: + return lhs->tag == rhs->tag; + + default: + return false; + } +} + +bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs) { + yyjson_type type = unsafe_yyjson_get_type(lhs); + if (type != unsafe_yyjson_get_type(rhs)) return false; + + switch (type) { + case YYJSON_TYPE_OBJ: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(rhs, &iter); + lhs = (yyjson_mut_val *)lhs->uni.ptr; + while (len-- > 0) { + rhs = yyjson_mut_obj_iter_getn(&iter, lhs->uni.str, + unsafe_yyjson_get_len(lhs)); + if (!rhs || !unsafe_yyjson_mut_equals(lhs->next, rhs)) + return false; + lhs = lhs->next->next; + } + } + /* yyjson allows duplicate keys, so the check may be inaccurate */ + return true; + } + + case YYJSON_TYPE_ARR: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + lhs = (yyjson_mut_val *)lhs->uni.ptr; + rhs = (yyjson_mut_val *)rhs->uni.ptr; + while (len-- > 0) { + if (!unsafe_yyjson_mut_equals(lhs, rhs)) return false; + lhs = lhs->next; + rhs = rhs->next; + } + } + return true; + } + + case YYJSON_TYPE_NUM: + return unsafe_yyjson_num_equals(lhs, rhs); + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + return unsafe_yyjson_str_equals(lhs, rhs); + + case YYJSON_TYPE_NULL: + case YYJSON_TYPE_BOOL: + return lhs->tag == rhs->tag; + + default: + return false; + } +} + + + +#if !YYJSON_DISABLE_UTILS + +/*============================================================================== + * JSON Pointer API (RFC 6901) + *============================================================================*/ + +/** + Get a token from JSON pointer string. + @param ptr [in,out] + in: string that points to current token prefix `/` + out: string that points to next token prefix `/`, or string end + @param end [in] end of the entire JSON Pointer string + @param len [out] unescaped token length + @param esc [out] number of escaped characters in this token + @return head of the token, or NULL if syntax error + */ +static_inline const char *ptr_next_token(const char **ptr, const char *end, + usize *len, usize *esc) { + const char *hdr = *ptr + 1; + const char *cur = hdr; + /* skip unescaped characters */ + while (cur < end && *cur != '/' && *cur != '~') cur++; + if (likely(cur == end || *cur != '~')) { + /* no escaped characters, return */ + *ptr = cur; + *len = (usize)(cur - hdr); + *esc = 0; + return hdr; + } else { + /* handle escaped characters */ + usize esc_num = 0; + while (cur < end && *cur != '/') { + if (*cur++ == '~') { + if (cur == end || (*cur != '0' && *cur != '1')) { + *ptr = cur - 1; + return NULL; + } + esc_num++; + } + } + *ptr = cur; + *len = (usize)(cur - hdr) - esc_num; + *esc = esc_num; + return hdr; + } +} + +/** + Convert token string to index. + @param cur [in] token head + @param len [in] token length + @param idx [out] the index number, or USIZE_MAX if token is '-' + @return true if token is a valid array index + */ +static_inline bool ptr_token_to_idx(const char *cur, usize len, usize *idx) { + const char *end = cur + len; + usize num = 0, add; + if (unlikely(len == 0 || len > USIZE_SAFE_DIG)) return false; + if (*cur == '0') { + if (unlikely(len > 1)) return false; + *idx = 0; + return true; + } + if (*cur == '-') { + if (unlikely(len > 1)) return false; + *idx = USIZE_MAX; + return true; + } + for (; cur < end && (add = (usize)((u8)*cur - (u8)'0')) <= 9; cur++) { + num = num * 10 + add; + } + if (unlikely(num == 0 || cur < end)) return false; + *idx = num; + return true; +} + +/** + Compare JSON key with token. + @param key a string key (yyjson_val or yyjson_mut_val) + @param tag the expected string key tag + @param token a JSON pointer token + @param len unescaped token length + @param esc number of escaped characters in this token + @return true if `str` is equals to `token` + */ +static_inline bool ptr_token_eq(void *key, u64 tag, + const char *token, usize len, usize esc) { + yyjson_val *val = (yyjson_val *)key; + if (val->tag != tag) return false; + if (likely(!esc)) { + return memcmp(val->uni.str, token, len) == 0; + } else { + const char *str = val->uni.str; + for (; len-- > 0; token++, str++) { + if (*token == '~') { + if (*str != (*++token == '0' ? '~' : '/')) return false; + } else { + if (*str != *token) return false; + } + } + return true; + } +} + +/** + Get a value from array by token. + @param arr an array, should not be NULL or non-array type + @param token a JSON pointer token + @param len unescaped token length + @param esc number of escaped characters in this token + @return value at index, or NULL if token is not index or index is out of range + */ +static_inline yyjson_val *ptr_arr_get(yyjson_val *arr, const char *token, + usize len, usize esc) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + usize num = unsafe_yyjson_get_len(arr), idx; + if (unlikely(num == 0)) return NULL; + if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; + if (unlikely(idx >= num)) return NULL; + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + idx; + } else { + while (idx-- > 0) val = unsafe_yyjson_get_next(val); + return val; + } +} + +/** + Get a value from object by token. + @param obj [in] an object, should not be NULL or non-object type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @return value associated with the token, or NULL if no value + */ +static_inline yyjson_val *ptr_obj_get(yyjson_val *obj, const char *token, + usize len, usize esc) { + u64 tag = (((u64)len) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + yyjson_val *key = unsafe_yyjson_get_first(obj); + usize num = unsafe_yyjson_get_len(obj); + if (unlikely(num == 0)) return NULL; + for (; num > 0; num--, key = unsafe_yyjson_get_next(key + 1)) { + if (ptr_token_eq(key, tag, token, len, esc)) return key + 1; + } + return NULL; +} + +/** + Get a value from array by token. + @param arr [in] an array, should not be NULL or non-array type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param pre [out] previous (sibling) value of the returned value + @param last [out] whether index is last + @return value at index, or NULL if token is not index or index is out of range + */ +static_inline yyjson_mut_val *ptr_mut_arr_get(yyjson_mut_val *arr, + const char *token, + usize len, usize esc, + yyjson_mut_val **pre, + bool *last) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; /* last (tail) */ + usize num = unsafe_yyjson_get_len(arr), idx; + if (last) *last = false; + if (pre) *pre = NULL; + if (unlikely(num == 0)) { + if (last && len == 1 && (*token == '0' || *token == '-')) *last = true; + return NULL; + } + if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; + if (last) *last = (idx == num || idx == USIZE_MAX); + if (unlikely(idx >= num)) return NULL; + while (idx-- > 0) val = val->next; + *pre = val; + return val->next; +} + +/** + Get a value from object by token. + @param obj [in] an object, should not be NULL or non-object type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param pre [out] previous (sibling) key of the returned value's key + @return value associated with the token, or NULL if no value + */ +static_inline yyjson_mut_val *ptr_mut_obj_get(yyjson_mut_val *obj, + const char *token, + usize len, usize esc, + yyjson_mut_val **pre) { + u64 tag = (((u64)len) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr, *key; + usize num = unsafe_yyjson_get_len(obj); + if (pre) *pre = NULL; + if (unlikely(num == 0)) return NULL; + for (; num > 0; num--, pre_key = key) { + key = pre_key->next->next; + if (ptr_token_eq(key, tag, token, len, esc)) { + *pre = pre_key; + return key->next; + } + } + return NULL; +} + +/** + Create a string value with JSON pointer token. + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param doc [in] used for memory allocation when creating value + @return new string value, or NULL if memory allocation failed + */ +static_inline yyjson_mut_val *ptr_new_key(const char *token, + usize len, usize esc, + yyjson_mut_doc *doc) { + const char *src = token; + if (likely(!esc)) { + return yyjson_mut_strncpy(doc, src, len); + } else { + const char *end = src + len + esc; + char *dst = unsafe_yyjson_mut_str_alc(doc, len + esc); + char *str = dst; + if (unlikely(!dst)) return NULL; + for (; src < end; src++, dst++) { + if (*src != '~') *dst = *src; + else *dst = (*++src == '0' ? '~' : '/'); + } + *dst = '\0'; + return yyjson_mut_strn(doc, str, len); + } +} + +/* macros for yyjson_ptr */ +#define return_err(_ret, _code, _pos, _msg) do { \ + if (err) { \ + err->code = YYJSON_PTR_ERR_##_code; \ + err->msg = _msg; \ + err->pos = (usize)(_pos); \ + } \ + return _ret; \ +} while (false) + +#define return_err_resolve(_ret, _pos) \ + return_err(_ret, RESOLVE, _pos, "JSON pointer cannot be resolved") +#define return_err_syntax(_ret, _pos) \ + return_err(_ret, SYNTAX, _pos, "invalid escaped character") +#define return_err_alloc(_ret) \ + return_err(_ret, MEMORY_ALLOCATION, 0, "failed to create value") + +yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t ptr_len, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize len, esc; + yyjson_type type; + + while (true) { + token = ptr_next_token(&ptr, end, &len, &esc); + if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); + type = unsafe_yyjson_get_type(val); + if (type == YYJSON_TYPE_OBJ) { + val = ptr_obj_get(val, token, len, esc); + } else if (type == YYJSON_TYPE_ARR) { + val = ptr_arr_get(val, token, len, esc); + } else { + val = NULL; + } + if (!val) return_err_resolve(NULL, token - hdr); + if (ptr == end) return val; + } +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t ptr_len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize len, esc; + yyjson_mut_val *ctn, *pre = NULL; + yyjson_type type; + bool idx_is_last = false; + + while (true) { + token = ptr_next_token(&ptr, end, &len, &esc); + if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); + ctn = val; + type = unsafe_yyjson_get_type(val); + if (type == YYJSON_TYPE_OBJ) { + val = ptr_mut_obj_get(val, token, len, esc, &pre); + } else if (type == YYJSON_TYPE_ARR) { + val = ptr_mut_arr_get(val, token, len, esc, &pre, &idx_is_last); + } else { + val = NULL; + } + if (ctx && (ptr == end)) { + if (type == YYJSON_TYPE_OBJ || + (type == YYJSON_TYPE_ARR && (val || idx_is_last))) { + ctx->ctn = ctn; + ctx->pre = pre; + } + } + if (!val) return_err_resolve(NULL, token - hdr); + if (ptr == end) return val; + } +} + +bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val, + const char *ptr, size_t ptr_len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, bool insert_new, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize token_len, esc, ctn_len; + yyjson_mut_val *ctn, *key, *pre = NULL; + yyjson_mut_val *sep_ctn = NULL, *sep_key = NULL, *sep_val = NULL; + yyjson_type ctn_type; + bool idx_is_last = false; + + /* skip exist parent nodes */ + while (true) { + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_syntax(false, ptr - hdr); + ctn = val; + ctn_type = unsafe_yyjson_get_type(ctn); + if (ctn_type == YYJSON_TYPE_OBJ) { + val = ptr_mut_obj_get(ctn, token, token_len, esc, &pre); + } else if (ctn_type == YYJSON_TYPE_ARR) { + val = ptr_mut_arr_get(ctn, token, token_len, esc, &pre, + &idx_is_last); + } else return_err_resolve(false, token - hdr); + if (!val) break; + if (ptr == end) break; /* is last token */ + } + + /* create parent nodes if not exist */ + if (unlikely(ptr != end)) { /* not last token */ + if (!create_parent) return_err_resolve(false, token - hdr); + + /* add value at last index if container is array */ + if (ctn_type == YYJSON_TYPE_ARR) { + if (!idx_is_last || !insert_new) { + return_err_resolve(false, token - hdr); + } + val = yyjson_mut_obj(doc); + if (!val) return_err_alloc(false); + + /* delay attaching until all operations are completed */ + sep_ctn = ctn; + sep_key = NULL; + sep_val = val; + + /* move to next token */ + ctn = val; + val = NULL; + ctn_type = YYJSON_TYPE_OBJ; + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_resolve(false, token - hdr); + } + + /* container is object, create parent nodes */ + while (ptr != end) { /* not last token */ + key = ptr_new_key(token, token_len, esc, doc); + if (!key) return_err_alloc(false); + val = yyjson_mut_obj(doc); + if (!val) return_err_alloc(false); + + /* delay attaching until all operations are completed */ + if (!sep_ctn) { + sep_ctn = ctn; + sep_key = key; + sep_val = val; + } else { + yyjson_mut_obj_add(ctn, key, val); + } + + /* move to next token */ + ctn = val; + val = NULL; + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_syntax(false, ptr - hdr); + } + } + + /* JSON pointer is resolved, insert or replace target value */ + ctn_len = unsafe_yyjson_get_len(ctn); + if (ctn_type == YYJSON_TYPE_OBJ) { + if (ctx) ctx->ctn = ctn; + if (!val || insert_new) { + /* insert new key-value pair */ + key = ptr_new_key(token, token_len, esc, doc); + if (unlikely(!key)) return_err_alloc(false); + if (ctx) ctx->pre = ctn_len ? (yyjson_mut_val *)ctn->uni.ptr : key; + unsafe_yyjson_mut_obj_add(ctn, key, new_val, ctn_len); + } else { + /* replace exist value */ + key = pre->next->next; + if (ctx) ctx->pre = pre; + if (ctx) ctx->old = val; + yyjson_mut_obj_put(ctn, key, new_val); + } + } else { + /* array */ + if (ctx && (val || idx_is_last)) ctx->ctn = ctn; + if (insert_new) { + /* append new value */ + if (val) { + pre->next = new_val; + new_val->next = val; + if (ctx) ctx->pre = pre; + unsafe_yyjson_set_len(ctn, ctn_len + 1); + } else if (idx_is_last) { + if (ctx) ctx->pre = ctn_len ? + (yyjson_mut_val *)ctn->uni.ptr : new_val; + yyjson_mut_arr_append(ctn, new_val); + } else { + return_err_resolve(false, token - hdr); + } + } else { + /* replace exist value */ + if (!val) return_err_resolve(false, token - hdr); + if (ctn_len > 1) { + new_val->next = val->next; + pre->next = new_val; + if (ctn->uni.ptr == val) ctn->uni.ptr = new_val; + } else { + new_val->next = new_val; + ctn->uni.ptr = new_val; + pre = new_val; + } + if (ctx) ctx->pre = pre; + if (ctx) ctx->old = val; + } + } + + /* all operations are completed, attach the new components to the target */ + if (unlikely(sep_ctn)) { + if (sep_key) yyjson_mut_obj_add(sep_ctn, sep_key, sep_val); + else yyjson_mut_arr_append(sep_ctn, sep_val); + } + return true; +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_mut_val *cur_val; + yyjson_ptr_ctx cur_ctx; + memset(&cur_ctx, 0, sizeof(cur_ctx)); + if (!ctx) ctx = &cur_ctx; + cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); + if (!cur_val) return NULL; + + if (yyjson_mut_is_obj(ctx->ctn)) { + yyjson_mut_val *key = ctx->pre->next->next; + yyjson_mut_obj_put(ctx->ctn, key, new_val); + } else { + yyjson_ptr_ctx_replace(ctx, new_val); + } + ctx->old = cur_val; + return cur_val; +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_mut_val *cur_val; + yyjson_ptr_ctx cur_ctx; + memset(&cur_ctx, 0, sizeof(cur_ctx)); + if (!ctx) ctx = &cur_ctx; + cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); + if (cur_val) { + if (yyjson_mut_is_obj(ctx->ctn)) { + yyjson_mut_val *key = ctx->pre->next->next; + yyjson_mut_obj_put(ctx->ctn, key, NULL); + } else { + yyjson_ptr_ctx_remove(ctx); + } + ctx->pre = NULL; + ctx->old = cur_val; + } + return cur_val; +} + +/* macros for yyjson_ptr */ +#undef return_err +#undef return_err_resolve +#undef return_err_syntax +#undef return_err_alloc + + + +/*============================================================================== + * JSON Patch API (RFC 6902) + *============================================================================*/ + +/* JSON Patch operation */ +typedef enum patch_op { + PATCH_OP_ADD, /* path, value */ + PATCH_OP_REMOVE, /* path */ + PATCH_OP_REPLACE, /* path, value */ + PATCH_OP_MOVE, /* from, path */ + PATCH_OP_COPY, /* from, path */ + PATCH_OP_TEST, /* path, value */ + PATCH_OP_NONE /* invalid */ +} patch_op; + +static patch_op patch_op_get(yyjson_val *op) { + const char *str = op->uni.str; + switch (unsafe_yyjson_get_len(op)) { + case 3: + if (!memcmp(str, "add", 3)) return PATCH_OP_ADD; + return PATCH_OP_NONE; + case 4: + if (!memcmp(str, "move", 4)) return PATCH_OP_MOVE; + if (!memcmp(str, "copy", 4)) return PATCH_OP_COPY; + if (!memcmp(str, "test", 4)) return PATCH_OP_TEST; + return PATCH_OP_NONE; + case 6: + if (!memcmp(str, "remove", 6)) return PATCH_OP_REMOVE; + return PATCH_OP_NONE; + case 7: + if (!memcmp(str, "replace", 7)) return PATCH_OP_REPLACE; + return PATCH_OP_NONE; + default: + return PATCH_OP_NONE; + } +} + +/* macros for yyjson_patch */ +#define return_err(_code, _msg) do { \ + if (err->ptr.code == YYJSON_PTR_ERR_MEMORY_ALLOCATION) { \ + err->code = YYJSON_PATCH_ERROR_MEMORY_ALLOCATION; \ + err->msg = _msg; \ + memset(&err->ptr, 0, sizeof(yyjson_ptr_err)); \ + } else { \ + err->code = YYJSON_PATCH_ERROR_##_code; \ + err->msg = _msg; \ + err->idx = iter.idx ? iter.idx - 1 : 0; \ + } \ + return NULL; \ +} while (false) + +#define return_err_copy() \ + return_err(MEMORY_ALLOCATION, "failed to copy value") +#define return_err_key(_key) \ + return_err(MISSING_KEY, "missing key " _key) +#define return_err_val(_key) \ + return_err(INVALID_MEMBER, "invalid member " _key) + +#define ptr_get(_ptr) yyjson_mut_ptr_getx( \ + root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) +#define ptr_add(_ptr, _val) yyjson_mut_ptr_addx( \ + root, _ptr->uni.str, _ptr##_len, _val, doc, false, NULL, &err->ptr) +#define ptr_remove(_ptr) yyjson_mut_ptr_removex( \ + root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) +#define ptr_replace(_ptr, _val)yyjson_mut_ptr_replacex( \ + root, _ptr->uni.str, _ptr##_len, _val, NULL, &err->ptr) + +yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch, + yyjson_patch_err *err) { + + yyjson_mut_val *root; + yyjson_val *obj; + yyjson_arr_iter iter; + yyjson_patch_err err_tmp; + if (!err) err = &err_tmp; + memset(err, 0, sizeof(*err)); + memset(&iter, 0, sizeof(iter)); + + if (unlikely(!doc || !orig || !patch)) { + return_err(INVALID_PARAMETER, "input parameter is NULL"); + } + if (unlikely(!yyjson_is_arr(patch))) { + return_err(INVALID_PARAMETER, "input patch is not array"); + } + root = yyjson_val_mut_copy(doc, orig); + if (unlikely(!root)) return_err_copy(); + + /* iterate through the patch array */ + yyjson_arr_iter_init(patch, &iter); + while ((obj = yyjson_arr_iter_next(&iter))) { + patch_op op_enum; + yyjson_val *op, *path, *from = NULL, *value; + yyjson_mut_val *val = NULL, *test; + usize path_len, from_len = 0; + if (unlikely(!unsafe_yyjson_is_obj(obj))) { + return_err(INVALID_OPERATION, "JSON patch operation is not object"); + } + + /* get required member: op */ + op = yyjson_obj_get(obj, "op"); + if (unlikely(!op)) return_err_key("`op`"); + if (unlikely(!yyjson_is_str(op))) return_err_val("`op`"); + op_enum = patch_op_get(op); + + /* get required member: path */ + path = yyjson_obj_get(obj, "path"); + if (unlikely(!path)) return_err_key("`path`"); + if (unlikely(!yyjson_is_str(path))) return_err_val("`path`"); + path_len = unsafe_yyjson_get_len(path); + + /* get required member: value, from */ + switch (op_enum) { + case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: + value = yyjson_obj_get(obj, "value"); + if (unlikely(!value)) return_err_key("`value`"); + val = yyjson_val_mut_copy(doc, value); + if (unlikely(!val)) return_err_copy(); + break; + case PATCH_OP_MOVE: case PATCH_OP_COPY: + from = yyjson_obj_get(obj, "from"); + if (unlikely(!from)) return_err_key("`from`"); + if (unlikely(!yyjson_is_str(from))) return_err_val("`from`"); + from_len = unsafe_yyjson_get_len(from); + break; + default: + break; + } + + /* perform an operation */ + switch (op_enum) { + case PATCH_OP_ADD: /* add(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_REMOVE: /* remove(path) */ + if (unlikely(!ptr_remove(path))) { + return_err(POINTER, "failed to remove `path`"); + } + break; + case PATCH_OP_REPLACE: /* replace(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_replace(path, val))) { + return_err(POINTER, "failed to replace `path`"); + } + break; + case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ + if (unlikely(from_len == 0 && path_len == 0)) break; + val = ptr_remove(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to remove `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ + val = ptr_get(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to get `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + val = yyjson_mut_val_mut_copy(doc, val); + if (unlikely(!val)) return_err_copy(); + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ + test = ptr_get(path); + if (unlikely(!test)) { + return_err(POINTER, "failed to get `path`"); + } + if (unlikely(!yyjson_mut_equals(val, test))) { + return_err(EQUAL, "failed to test equal"); + } + break; + default: + return_err(INVALID_MEMBER, "unsupported `op`"); + } + } + return root; +} + +yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch, + yyjson_patch_err *err) { + yyjson_mut_val *root, *obj; + yyjson_mut_arr_iter iter; + yyjson_patch_err err_tmp; + if (!err) err = &err_tmp; + memset(err, 0, sizeof(*err)); + memset(&iter, 0, sizeof(iter)); + + if (unlikely(!doc || !orig || !patch)) { + return_err(INVALID_PARAMETER, "input parameter is NULL"); + } + if (unlikely(!yyjson_mut_is_arr(patch))) { + return_err(INVALID_PARAMETER, "input patch is not array"); + } + root = yyjson_mut_val_mut_copy(doc, orig); + if (unlikely(!root)) return_err_copy(); + + /* iterate through the patch array */ + yyjson_mut_arr_iter_init(patch, &iter); + while ((obj = yyjson_mut_arr_iter_next(&iter))) { + patch_op op_enum; + yyjson_mut_val *op, *path, *from = NULL, *value; + yyjson_mut_val *val = NULL, *test; + usize path_len, from_len = 0; + if (!unsafe_yyjson_is_obj(obj)) { + return_err(INVALID_OPERATION, "JSON patch operation is not object"); + } + + /* get required member: op */ + op = yyjson_mut_obj_get(obj, "op"); + if (unlikely(!op)) return_err_key("`op`"); + if (unlikely(!yyjson_mut_is_str(op))) return_err_val("`op`"); + op_enum = patch_op_get((yyjson_val *)(void *)op); + + /* get required member: path */ + path = yyjson_mut_obj_get(obj, "path"); + if (unlikely(!path)) return_err_key("`path`"); + if (unlikely(!yyjson_mut_is_str(path))) return_err_val("`path`"); + path_len = unsafe_yyjson_get_len(path); + + /* get required member: value, from */ + switch (op_enum) { + case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: + value = yyjson_mut_obj_get(obj, "value"); + if (unlikely(!value)) return_err_key("`value`"); + val = yyjson_mut_val_mut_copy(doc, value); + if (unlikely(!val)) return_err_copy(); + break; + case PATCH_OP_MOVE: case PATCH_OP_COPY: + from = yyjson_mut_obj_get(obj, "from"); + if (unlikely(!from)) return_err_key("`from`"); + if (unlikely(!yyjson_mut_is_str(from))) { + return_err_val("`from`"); + } + from_len = unsafe_yyjson_get_len(from); + break; + default: + break; + } + + /* perform an operation */ + switch (op_enum) { + case PATCH_OP_ADD: /* add(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_REMOVE: /* remove(path) */ + if (unlikely(!ptr_remove(path))) { + return_err(POINTER, "failed to remove `path`"); + } + break; + case PATCH_OP_REPLACE: /* replace(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_replace(path, val))) { + return_err(POINTER, "failed to replace `path`"); + } + break; + case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ + if (unlikely(from_len == 0 && path_len == 0)) break; + val = ptr_remove(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to remove `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ + val = ptr_get(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to get `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + val = yyjson_mut_val_mut_copy(doc, val); + if (unlikely(!val)) return_err_copy(); + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ + test = ptr_get(path); + if (unlikely(!test)) { + return_err(POINTER, "failed to get `path`"); + } + if (unlikely(!yyjson_mut_equals(val, test))) { + return_err(EQUAL, "failed to test equal"); + } + break; + default: + return_err(INVALID_MEMBER, "unsupported `op`"); + } + } + return root; +} + +/* macros for yyjson_patch */ +#undef return_err +#undef return_err_copy +#undef return_err_key +#undef return_err_val +#undef ptr_get +#undef ptr_add +#undef ptr_remove +#undef ptr_replace + + + +/*============================================================================== + * JSON Merge-Patch API (RFC 7386) + *============================================================================*/ + +yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch) { + usize idx, max; + yyjson_val *key, *orig_val, *patch_val, local_orig; + yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; + + if (unlikely(!yyjson_is_obj(patch))) { + return yyjson_val_mut_copy(doc, patch); + } + + builder = yyjson_mut_obj(doc); + if (unlikely(!builder)) return NULL; + + if (!yyjson_is_obj(orig)) { + orig = &local_orig; + orig->tag = builder->tag; + orig->uni = builder->uni; + } + + /* If orig is contributing, copy any items not modified by the patch */ + if (orig != &local_orig) + { + yyjson_obj_foreach(orig, idx, max, key, orig_val) { + patch_val = yyjson_obj_getn(patch, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + if (!patch_val) { + mut_key = yyjson_val_mut_copy(doc, key); + mut_val = yyjson_val_mut_copy(doc, orig_val); + if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; + } + } + } + + /* Merge items modified by the patch. */ + yyjson_obj_foreach(patch, idx, max, key, patch_val) { + /* null indicates the field is removed. */ + if (unsafe_yyjson_is_null(patch_val)) { + continue; + } + mut_key = yyjson_val_mut_copy(doc, key); + orig_val = yyjson_obj_getn(orig, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + merged_val = yyjson_merge_patch(doc, orig_val, patch_val); + if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; + } + + return builder; +} + +yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch) { + usize idx, max; + yyjson_mut_val *key, *orig_val, *patch_val, local_orig; + yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; + + if (unlikely(!yyjson_mut_is_obj(patch))) { + return yyjson_mut_val_mut_copy(doc, patch); + } + + builder = yyjson_mut_obj(doc); + if (unlikely(!builder)) return NULL; + + if (!yyjson_mut_is_obj(orig)) { + orig = &local_orig; + orig->tag = builder->tag; + orig->uni = builder->uni; + } + + /* If orig is contributing, copy any items not modified by the patch */ + if (orig != &local_orig) + { + yyjson_mut_obj_foreach(orig, idx, max, key, orig_val) { + patch_val = yyjson_mut_obj_getn(patch, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + if (!patch_val) { + mut_key = yyjson_mut_val_mut_copy(doc, key); + mut_val = yyjson_mut_val_mut_copy(doc, orig_val); + if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; + } + } + } + + /* Merge items modified by the patch. */ + yyjson_mut_obj_foreach(patch, idx, max, key, patch_val) { + /* null indicates the field is removed. */ + if (unsafe_yyjson_is_null(patch_val)) { + continue; + } + mut_key = yyjson_mut_val_mut_copy(doc, key); + orig_val = yyjson_mut_obj_getn(orig, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + merged_val = yyjson_mut_merge_patch(doc, orig_val, patch_val); + if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; + } + + return builder; +} + +#endif /* YYJSON_DISABLE_UTILS */ + + + +/*============================================================================== + * Power10 Lookup Table + * These data are used by the floating-point number reader and writer. + *============================================================================*/ + +#if (!YYJSON_DISABLE_READER || !YYJSON_DISABLE_WRITER) && \ + (!YYJSON_DISABLE_FAST_FP_CONV) + +/** Minimum decimal exponent in pow10_sig_table. */ +#define POW10_SIG_TABLE_MIN_EXP -343 + +/** Maximum decimal exponent in pow10_sig_table. */ +#define POW10_SIG_TABLE_MAX_EXP 324 + +/** Minimum exact decimal exponent in pow10_sig_table */ +#define POW10_SIG_TABLE_MIN_EXACT_EXP 0 + +/** Maximum exact decimal exponent in pow10_sig_table */ +#define POW10_SIG_TABLE_MAX_EXACT_EXP 55 + +/** Normalized significant 128 bits of pow10, no rounded up (size: 10.4KB). + This lookup table is used by both the double number reader and writer. + (generate with misc/make_tables.c) */ +static const u64 pow10_sig_table[] = { + U64(0xBF29DCAB, 0xA82FDEAE), U64(0x7432EE87, 0x3880FC33), /* ~= 10^-343 */ + U64(0xEEF453D6, 0x923BD65A), U64(0x113FAA29, 0x06A13B3F), /* ~= 10^-342 */ + U64(0x9558B466, 0x1B6565F8), U64(0x4AC7CA59, 0xA424C507), /* ~= 10^-341 */ + U64(0xBAAEE17F, 0xA23EBF76), U64(0x5D79BCF0, 0x0D2DF649), /* ~= 10^-340 */ + U64(0xE95A99DF, 0x8ACE6F53), U64(0xF4D82C2C, 0x107973DC), /* ~= 10^-339 */ + U64(0x91D8A02B, 0xB6C10594), U64(0x79071B9B, 0x8A4BE869), /* ~= 10^-338 */ + U64(0xB64EC836, 0xA47146F9), U64(0x9748E282, 0x6CDEE284), /* ~= 10^-337 */ + U64(0xE3E27A44, 0x4D8D98B7), U64(0xFD1B1B23, 0x08169B25), /* ~= 10^-336 */ + U64(0x8E6D8C6A, 0xB0787F72), U64(0xFE30F0F5, 0xE50E20F7), /* ~= 10^-335 */ + U64(0xB208EF85, 0x5C969F4F), U64(0xBDBD2D33, 0x5E51A935), /* ~= 10^-334 */ + U64(0xDE8B2B66, 0xB3BC4723), U64(0xAD2C7880, 0x35E61382), /* ~= 10^-333 */ + U64(0x8B16FB20, 0x3055AC76), U64(0x4C3BCB50, 0x21AFCC31), /* ~= 10^-332 */ + U64(0xADDCB9E8, 0x3C6B1793), U64(0xDF4ABE24, 0x2A1BBF3D), /* ~= 10^-331 */ + U64(0xD953E862, 0x4B85DD78), U64(0xD71D6DAD, 0x34A2AF0D), /* ~= 10^-330 */ + U64(0x87D4713D, 0x6F33AA6B), U64(0x8672648C, 0x40E5AD68), /* ~= 10^-329 */ + U64(0xA9C98D8C, 0xCB009506), U64(0x680EFDAF, 0x511F18C2), /* ~= 10^-328 */ + U64(0xD43BF0EF, 0xFDC0BA48), U64(0x0212BD1B, 0x2566DEF2), /* ~= 10^-327 */ + U64(0x84A57695, 0xFE98746D), U64(0x014BB630, 0xF7604B57), /* ~= 10^-326 */ + U64(0xA5CED43B, 0x7E3E9188), U64(0x419EA3BD, 0x35385E2D), /* ~= 10^-325 */ + U64(0xCF42894A, 0x5DCE35EA), U64(0x52064CAC, 0x828675B9), /* ~= 10^-324 */ + U64(0x818995CE, 0x7AA0E1B2), U64(0x7343EFEB, 0xD1940993), /* ~= 10^-323 */ + U64(0xA1EBFB42, 0x19491A1F), U64(0x1014EBE6, 0xC5F90BF8), /* ~= 10^-322 */ + U64(0xCA66FA12, 0x9F9B60A6), U64(0xD41A26E0, 0x77774EF6), /* ~= 10^-321 */ + U64(0xFD00B897, 0x478238D0), U64(0x8920B098, 0x955522B4), /* ~= 10^-320 */ + U64(0x9E20735E, 0x8CB16382), U64(0x55B46E5F, 0x5D5535B0), /* ~= 10^-319 */ + U64(0xC5A89036, 0x2FDDBC62), U64(0xEB2189F7, 0x34AA831D), /* ~= 10^-318 */ + U64(0xF712B443, 0xBBD52B7B), U64(0xA5E9EC75, 0x01D523E4), /* ~= 10^-317 */ + U64(0x9A6BB0AA, 0x55653B2D), U64(0x47B233C9, 0x2125366E), /* ~= 10^-316 */ + U64(0xC1069CD4, 0xEABE89F8), U64(0x999EC0BB, 0x696E840A), /* ~= 10^-315 */ + U64(0xF148440A, 0x256E2C76), U64(0xC00670EA, 0x43CA250D), /* ~= 10^-314 */ + U64(0x96CD2A86, 0x5764DBCA), U64(0x38040692, 0x6A5E5728), /* ~= 10^-313 */ + U64(0xBC807527, 0xED3E12BC), U64(0xC6050837, 0x04F5ECF2), /* ~= 10^-312 */ + U64(0xEBA09271, 0xE88D976B), U64(0xF7864A44, 0xC633682E), /* ~= 10^-311 */ + U64(0x93445B87, 0x31587EA3), U64(0x7AB3EE6A, 0xFBE0211D), /* ~= 10^-310 */ + U64(0xB8157268, 0xFDAE9E4C), U64(0x5960EA05, 0xBAD82964), /* ~= 10^-309 */ + U64(0xE61ACF03, 0x3D1A45DF), U64(0x6FB92487, 0x298E33BD), /* ~= 10^-308 */ + U64(0x8FD0C162, 0x06306BAB), U64(0xA5D3B6D4, 0x79F8E056), /* ~= 10^-307 */ + U64(0xB3C4F1BA, 0x87BC8696), U64(0x8F48A489, 0x9877186C), /* ~= 10^-306 */ + U64(0xE0B62E29, 0x29ABA83C), U64(0x331ACDAB, 0xFE94DE87), /* ~= 10^-305 */ + U64(0x8C71DCD9, 0xBA0B4925), U64(0x9FF0C08B, 0x7F1D0B14), /* ~= 10^-304 */ + U64(0xAF8E5410, 0x288E1B6F), U64(0x07ECF0AE, 0x5EE44DD9), /* ~= 10^-303 */ + U64(0xDB71E914, 0x32B1A24A), U64(0xC9E82CD9, 0xF69D6150), /* ~= 10^-302 */ + U64(0x892731AC, 0x9FAF056E), U64(0xBE311C08, 0x3A225CD2), /* ~= 10^-301 */ + U64(0xAB70FE17, 0xC79AC6CA), U64(0x6DBD630A, 0x48AAF406), /* ~= 10^-300 */ + U64(0xD64D3D9D, 0xB981787D), U64(0x092CBBCC, 0xDAD5B108), /* ~= 10^-299 */ + U64(0x85F04682, 0x93F0EB4E), U64(0x25BBF560, 0x08C58EA5), /* ~= 10^-298 */ + U64(0xA76C5823, 0x38ED2621), U64(0xAF2AF2B8, 0x0AF6F24E), /* ~= 10^-297 */ + U64(0xD1476E2C, 0x07286FAA), U64(0x1AF5AF66, 0x0DB4AEE1), /* ~= 10^-296 */ + U64(0x82CCA4DB, 0x847945CA), U64(0x50D98D9F, 0xC890ED4D), /* ~= 10^-295 */ + U64(0xA37FCE12, 0x6597973C), U64(0xE50FF107, 0xBAB528A0), /* ~= 10^-294 */ + U64(0xCC5FC196, 0xFEFD7D0C), U64(0x1E53ED49, 0xA96272C8), /* ~= 10^-293 */ + U64(0xFF77B1FC, 0xBEBCDC4F), U64(0x25E8E89C, 0x13BB0F7A), /* ~= 10^-292 */ + U64(0x9FAACF3D, 0xF73609B1), U64(0x77B19161, 0x8C54E9AC), /* ~= 10^-291 */ + U64(0xC795830D, 0x75038C1D), U64(0xD59DF5B9, 0xEF6A2417), /* ~= 10^-290 */ + U64(0xF97AE3D0, 0xD2446F25), U64(0x4B057328, 0x6B44AD1D), /* ~= 10^-289 */ + U64(0x9BECCE62, 0x836AC577), U64(0x4EE367F9, 0x430AEC32), /* ~= 10^-288 */ + U64(0xC2E801FB, 0x244576D5), U64(0x229C41F7, 0x93CDA73F), /* ~= 10^-287 */ + U64(0xF3A20279, 0xED56D48A), U64(0x6B435275, 0x78C1110F), /* ~= 10^-286 */ + U64(0x9845418C, 0x345644D6), U64(0x830A1389, 0x6B78AAA9), /* ~= 10^-285 */ + U64(0xBE5691EF, 0x416BD60C), U64(0x23CC986B, 0xC656D553), /* ~= 10^-284 */ + U64(0xEDEC366B, 0x11C6CB8F), U64(0x2CBFBE86, 0xB7EC8AA8), /* ~= 10^-283 */ + U64(0x94B3A202, 0xEB1C3F39), U64(0x7BF7D714, 0x32F3D6A9), /* ~= 10^-282 */ + U64(0xB9E08A83, 0xA5E34F07), U64(0xDAF5CCD9, 0x3FB0CC53), /* ~= 10^-281 */ + U64(0xE858AD24, 0x8F5C22C9), U64(0xD1B3400F, 0x8F9CFF68), /* ~= 10^-280 */ + U64(0x91376C36, 0xD99995BE), U64(0x23100809, 0xB9C21FA1), /* ~= 10^-279 */ + U64(0xB5854744, 0x8FFFFB2D), U64(0xABD40A0C, 0x2832A78A), /* ~= 10^-278 */ + U64(0xE2E69915, 0xB3FFF9F9), U64(0x16C90C8F, 0x323F516C), /* ~= 10^-277 */ + U64(0x8DD01FAD, 0x907FFC3B), U64(0xAE3DA7D9, 0x7F6792E3), /* ~= 10^-276 */ + U64(0xB1442798, 0xF49FFB4A), U64(0x99CD11CF, 0xDF41779C), /* ~= 10^-275 */ + U64(0xDD95317F, 0x31C7FA1D), U64(0x40405643, 0xD711D583), /* ~= 10^-274 */ + U64(0x8A7D3EEF, 0x7F1CFC52), U64(0x482835EA, 0x666B2572), /* ~= 10^-273 */ + U64(0xAD1C8EAB, 0x5EE43B66), U64(0xDA324365, 0x0005EECF), /* ~= 10^-272 */ + U64(0xD863B256, 0x369D4A40), U64(0x90BED43E, 0x40076A82), /* ~= 10^-271 */ + U64(0x873E4F75, 0xE2224E68), U64(0x5A7744A6, 0xE804A291), /* ~= 10^-270 */ + U64(0xA90DE353, 0x5AAAE202), U64(0x711515D0, 0xA205CB36), /* ~= 10^-269 */ + U64(0xD3515C28, 0x31559A83), U64(0x0D5A5B44, 0xCA873E03), /* ~= 10^-268 */ + U64(0x8412D999, 0x1ED58091), U64(0xE858790A, 0xFE9486C2), /* ~= 10^-267 */ + U64(0xA5178FFF, 0x668AE0B6), U64(0x626E974D, 0xBE39A872), /* ~= 10^-266 */ + U64(0xCE5D73FF, 0x402D98E3), U64(0xFB0A3D21, 0x2DC8128F), /* ~= 10^-265 */ + U64(0x80FA687F, 0x881C7F8E), U64(0x7CE66634, 0xBC9D0B99), /* ~= 10^-264 */ + U64(0xA139029F, 0x6A239F72), U64(0x1C1FFFC1, 0xEBC44E80), /* ~= 10^-263 */ + U64(0xC9874347, 0x44AC874E), U64(0xA327FFB2, 0x66B56220), /* ~= 10^-262 */ + U64(0xFBE91419, 0x15D7A922), U64(0x4BF1FF9F, 0x0062BAA8), /* ~= 10^-261 */ + U64(0x9D71AC8F, 0xADA6C9B5), U64(0x6F773FC3, 0x603DB4A9), /* ~= 10^-260 */ + U64(0xC4CE17B3, 0x99107C22), U64(0xCB550FB4, 0x384D21D3), /* ~= 10^-259 */ + U64(0xF6019DA0, 0x7F549B2B), U64(0x7E2A53A1, 0x46606A48), /* ~= 10^-258 */ + U64(0x99C10284, 0x4F94E0FB), U64(0x2EDA7444, 0xCBFC426D), /* ~= 10^-257 */ + U64(0xC0314325, 0x637A1939), U64(0xFA911155, 0xFEFB5308), /* ~= 10^-256 */ + U64(0xF03D93EE, 0xBC589F88), U64(0x793555AB, 0x7EBA27CA), /* ~= 10^-255 */ + U64(0x96267C75, 0x35B763B5), U64(0x4BC1558B, 0x2F3458DE), /* ~= 10^-254 */ + U64(0xBBB01B92, 0x83253CA2), U64(0x9EB1AAED, 0xFB016F16), /* ~= 10^-253 */ + U64(0xEA9C2277, 0x23EE8BCB), U64(0x465E15A9, 0x79C1CADC), /* ~= 10^-252 */ + U64(0x92A1958A, 0x7675175F), U64(0x0BFACD89, 0xEC191EC9), /* ~= 10^-251 */ + U64(0xB749FAED, 0x14125D36), U64(0xCEF980EC, 0x671F667B), /* ~= 10^-250 */ + U64(0xE51C79A8, 0x5916F484), U64(0x82B7E127, 0x80E7401A), /* ~= 10^-249 */ + U64(0x8F31CC09, 0x37AE58D2), U64(0xD1B2ECB8, 0xB0908810), /* ~= 10^-248 */ + U64(0xB2FE3F0B, 0x8599EF07), U64(0x861FA7E6, 0xDCB4AA15), /* ~= 10^-247 */ + U64(0xDFBDCECE, 0x67006AC9), U64(0x67A791E0, 0x93E1D49A), /* ~= 10^-246 */ + U64(0x8BD6A141, 0x006042BD), U64(0xE0C8BB2C, 0x5C6D24E0), /* ~= 10^-245 */ + U64(0xAECC4991, 0x4078536D), U64(0x58FAE9F7, 0x73886E18), /* ~= 10^-244 */ + U64(0xDA7F5BF5, 0x90966848), U64(0xAF39A475, 0x506A899E), /* ~= 10^-243 */ + U64(0x888F9979, 0x7A5E012D), U64(0x6D8406C9, 0x52429603), /* ~= 10^-242 */ + U64(0xAAB37FD7, 0xD8F58178), U64(0xC8E5087B, 0xA6D33B83), /* ~= 10^-241 */ + U64(0xD5605FCD, 0xCF32E1D6), U64(0xFB1E4A9A, 0x90880A64), /* ~= 10^-240 */ + U64(0x855C3BE0, 0xA17FCD26), U64(0x5CF2EEA0, 0x9A55067F), /* ~= 10^-239 */ + U64(0xA6B34AD8, 0xC9DFC06F), U64(0xF42FAA48, 0xC0EA481E), /* ~= 10^-238 */ + U64(0xD0601D8E, 0xFC57B08B), U64(0xF13B94DA, 0xF124DA26), /* ~= 10^-237 */ + U64(0x823C1279, 0x5DB6CE57), U64(0x76C53D08, 0xD6B70858), /* ~= 10^-236 */ + U64(0xA2CB1717, 0xB52481ED), U64(0x54768C4B, 0x0C64CA6E), /* ~= 10^-235 */ + U64(0xCB7DDCDD, 0xA26DA268), U64(0xA9942F5D, 0xCF7DFD09), /* ~= 10^-234 */ + U64(0xFE5D5415, 0x0B090B02), U64(0xD3F93B35, 0x435D7C4C), /* ~= 10^-233 */ + U64(0x9EFA548D, 0x26E5A6E1), U64(0xC47BC501, 0x4A1A6DAF), /* ~= 10^-232 */ + U64(0xC6B8E9B0, 0x709F109A), U64(0x359AB641, 0x9CA1091B), /* ~= 10^-231 */ + U64(0xF867241C, 0x8CC6D4C0), U64(0xC30163D2, 0x03C94B62), /* ~= 10^-230 */ + U64(0x9B407691, 0xD7FC44F8), U64(0x79E0DE63, 0x425DCF1D), /* ~= 10^-229 */ + U64(0xC2109436, 0x4DFB5636), U64(0x985915FC, 0x12F542E4), /* ~= 10^-228 */ + U64(0xF294B943, 0xE17A2BC4), U64(0x3E6F5B7B, 0x17B2939D), /* ~= 10^-227 */ + U64(0x979CF3CA, 0x6CEC5B5A), U64(0xA705992C, 0xEECF9C42), /* ~= 10^-226 */ + U64(0xBD8430BD, 0x08277231), U64(0x50C6FF78, 0x2A838353), /* ~= 10^-225 */ + U64(0xECE53CEC, 0x4A314EBD), U64(0xA4F8BF56, 0x35246428), /* ~= 10^-224 */ + U64(0x940F4613, 0xAE5ED136), U64(0x871B7795, 0xE136BE99), /* ~= 10^-223 */ + U64(0xB9131798, 0x99F68584), U64(0x28E2557B, 0x59846E3F), /* ~= 10^-222 */ + U64(0xE757DD7E, 0xC07426E5), U64(0x331AEADA, 0x2FE589CF), /* ~= 10^-221 */ + U64(0x9096EA6F, 0x3848984F), U64(0x3FF0D2C8, 0x5DEF7621), /* ~= 10^-220 */ + U64(0xB4BCA50B, 0x065ABE63), U64(0x0FED077A, 0x756B53A9), /* ~= 10^-219 */ + U64(0xE1EBCE4D, 0xC7F16DFB), U64(0xD3E84959, 0x12C62894), /* ~= 10^-218 */ + U64(0x8D3360F0, 0x9CF6E4BD), U64(0x64712DD7, 0xABBBD95C), /* ~= 10^-217 */ + U64(0xB080392C, 0xC4349DEC), U64(0xBD8D794D, 0x96AACFB3), /* ~= 10^-216 */ + U64(0xDCA04777, 0xF541C567), U64(0xECF0D7A0, 0xFC5583A0), /* ~= 10^-215 */ + U64(0x89E42CAA, 0xF9491B60), U64(0xF41686C4, 0x9DB57244), /* ~= 10^-214 */ + U64(0xAC5D37D5, 0xB79B6239), U64(0x311C2875, 0xC522CED5), /* ~= 10^-213 */ + U64(0xD77485CB, 0x25823AC7), U64(0x7D633293, 0x366B828B), /* ~= 10^-212 */ + U64(0x86A8D39E, 0xF77164BC), U64(0xAE5DFF9C, 0x02033197), /* ~= 10^-211 */ + U64(0xA8530886, 0xB54DBDEB), U64(0xD9F57F83, 0x0283FDFC), /* ~= 10^-210 */ + U64(0xD267CAA8, 0x62A12D66), U64(0xD072DF63, 0xC324FD7B), /* ~= 10^-209 */ + U64(0x8380DEA9, 0x3DA4BC60), U64(0x4247CB9E, 0x59F71E6D), /* ~= 10^-208 */ + U64(0xA4611653, 0x8D0DEB78), U64(0x52D9BE85, 0xF074E608), /* ~= 10^-207 */ + U64(0xCD795BE8, 0x70516656), U64(0x67902E27, 0x6C921F8B), /* ~= 10^-206 */ + U64(0x806BD971, 0x4632DFF6), U64(0x00BA1CD8, 0xA3DB53B6), /* ~= 10^-205 */ + U64(0xA086CFCD, 0x97BF97F3), U64(0x80E8A40E, 0xCCD228A4), /* ~= 10^-204 */ + U64(0xC8A883C0, 0xFDAF7DF0), U64(0x6122CD12, 0x8006B2CD), /* ~= 10^-203 */ + U64(0xFAD2A4B1, 0x3D1B5D6C), U64(0x796B8057, 0x20085F81), /* ~= 10^-202 */ + U64(0x9CC3A6EE, 0xC6311A63), U64(0xCBE33036, 0x74053BB0), /* ~= 10^-201 */ + U64(0xC3F490AA, 0x77BD60FC), U64(0xBEDBFC44, 0x11068A9C), /* ~= 10^-200 */ + U64(0xF4F1B4D5, 0x15ACB93B), U64(0xEE92FB55, 0x15482D44), /* ~= 10^-199 */ + U64(0x99171105, 0x2D8BF3C5), U64(0x751BDD15, 0x2D4D1C4A), /* ~= 10^-198 */ + U64(0xBF5CD546, 0x78EEF0B6), U64(0xD262D45A, 0x78A0635D), /* ~= 10^-197 */ + U64(0xEF340A98, 0x172AACE4), U64(0x86FB8971, 0x16C87C34), /* ~= 10^-196 */ + U64(0x9580869F, 0x0E7AAC0E), U64(0xD45D35E6, 0xAE3D4DA0), /* ~= 10^-195 */ + U64(0xBAE0A846, 0xD2195712), U64(0x89748360, 0x59CCA109), /* ~= 10^-194 */ + U64(0xE998D258, 0x869FACD7), U64(0x2BD1A438, 0x703FC94B), /* ~= 10^-193 */ + U64(0x91FF8377, 0x5423CC06), U64(0x7B6306A3, 0x4627DDCF), /* ~= 10^-192 */ + U64(0xB67F6455, 0x292CBF08), U64(0x1A3BC84C, 0x17B1D542), /* ~= 10^-191 */ + U64(0xE41F3D6A, 0x7377EECA), U64(0x20CABA5F, 0x1D9E4A93), /* ~= 10^-190 */ + U64(0x8E938662, 0x882AF53E), U64(0x547EB47B, 0x7282EE9C), /* ~= 10^-189 */ + U64(0xB23867FB, 0x2A35B28D), U64(0xE99E619A, 0x4F23AA43), /* ~= 10^-188 */ + U64(0xDEC681F9, 0xF4C31F31), U64(0x6405FA00, 0xE2EC94D4), /* ~= 10^-187 */ + U64(0x8B3C113C, 0x38F9F37E), U64(0xDE83BC40, 0x8DD3DD04), /* ~= 10^-186 */ + U64(0xAE0B158B, 0x4738705E), U64(0x9624AB50, 0xB148D445), /* ~= 10^-185 */ + U64(0xD98DDAEE, 0x19068C76), U64(0x3BADD624, 0xDD9B0957), /* ~= 10^-184 */ + U64(0x87F8A8D4, 0xCFA417C9), U64(0xE54CA5D7, 0x0A80E5D6), /* ~= 10^-183 */ + U64(0xA9F6D30A, 0x038D1DBC), U64(0x5E9FCF4C, 0xCD211F4C), /* ~= 10^-182 */ + U64(0xD47487CC, 0x8470652B), U64(0x7647C320, 0x0069671F), /* ~= 10^-181 */ + U64(0x84C8D4DF, 0xD2C63F3B), U64(0x29ECD9F4, 0x0041E073), /* ~= 10^-180 */ + U64(0xA5FB0A17, 0xC777CF09), U64(0xF4681071, 0x00525890), /* ~= 10^-179 */ + U64(0xCF79CC9D, 0xB955C2CC), U64(0x7182148D, 0x4066EEB4), /* ~= 10^-178 */ + U64(0x81AC1FE2, 0x93D599BF), U64(0xC6F14CD8, 0x48405530), /* ~= 10^-177 */ + U64(0xA21727DB, 0x38CB002F), U64(0xB8ADA00E, 0x5A506A7C), /* ~= 10^-176 */ + U64(0xCA9CF1D2, 0x06FDC03B), U64(0xA6D90811, 0xF0E4851C), /* ~= 10^-175 */ + U64(0xFD442E46, 0x88BD304A), U64(0x908F4A16, 0x6D1DA663), /* ~= 10^-174 */ + U64(0x9E4A9CEC, 0x15763E2E), U64(0x9A598E4E, 0x043287FE), /* ~= 10^-173 */ + U64(0xC5DD4427, 0x1AD3CDBA), U64(0x40EFF1E1, 0x853F29FD), /* ~= 10^-172 */ + U64(0xF7549530, 0xE188C128), U64(0xD12BEE59, 0xE68EF47C), /* ~= 10^-171 */ + U64(0x9A94DD3E, 0x8CF578B9), U64(0x82BB74F8, 0x301958CE), /* ~= 10^-170 */ + U64(0xC13A148E, 0x3032D6E7), U64(0xE36A5236, 0x3C1FAF01), /* ~= 10^-169 */ + U64(0xF18899B1, 0xBC3F8CA1), U64(0xDC44E6C3, 0xCB279AC1), /* ~= 10^-168 */ + U64(0x96F5600F, 0x15A7B7E5), U64(0x29AB103A, 0x5EF8C0B9), /* ~= 10^-167 */ + U64(0xBCB2B812, 0xDB11A5DE), U64(0x7415D448, 0xF6B6F0E7), /* ~= 10^-166 */ + U64(0xEBDF6617, 0x91D60F56), U64(0x111B495B, 0x3464AD21), /* ~= 10^-165 */ + U64(0x936B9FCE, 0xBB25C995), U64(0xCAB10DD9, 0x00BEEC34), /* ~= 10^-164 */ + U64(0xB84687C2, 0x69EF3BFB), U64(0x3D5D514F, 0x40EEA742), /* ~= 10^-163 */ + U64(0xE65829B3, 0x046B0AFA), U64(0x0CB4A5A3, 0x112A5112), /* ~= 10^-162 */ + U64(0x8FF71A0F, 0xE2C2E6DC), U64(0x47F0E785, 0xEABA72AB), /* ~= 10^-161 */ + U64(0xB3F4E093, 0xDB73A093), U64(0x59ED2167, 0x65690F56), /* ~= 10^-160 */ + U64(0xE0F218B8, 0xD25088B8), U64(0x306869C1, 0x3EC3532C), /* ~= 10^-159 */ + U64(0x8C974F73, 0x83725573), U64(0x1E414218, 0xC73A13FB), /* ~= 10^-158 */ + U64(0xAFBD2350, 0x644EEACF), U64(0xE5D1929E, 0xF90898FA), /* ~= 10^-157 */ + U64(0xDBAC6C24, 0x7D62A583), U64(0xDF45F746, 0xB74ABF39), /* ~= 10^-156 */ + U64(0x894BC396, 0xCE5DA772), U64(0x6B8BBA8C, 0x328EB783), /* ~= 10^-155 */ + U64(0xAB9EB47C, 0x81F5114F), U64(0x066EA92F, 0x3F326564), /* ~= 10^-154 */ + U64(0xD686619B, 0xA27255A2), U64(0xC80A537B, 0x0EFEFEBD), /* ~= 10^-153 */ + U64(0x8613FD01, 0x45877585), U64(0xBD06742C, 0xE95F5F36), /* ~= 10^-152 */ + U64(0xA798FC41, 0x96E952E7), U64(0x2C481138, 0x23B73704), /* ~= 10^-151 */ + U64(0xD17F3B51, 0xFCA3A7A0), U64(0xF75A1586, 0x2CA504C5), /* ~= 10^-150 */ + U64(0x82EF8513, 0x3DE648C4), U64(0x9A984D73, 0xDBE722FB), /* ~= 10^-149 */ + U64(0xA3AB6658, 0x0D5FDAF5), U64(0xC13E60D0, 0xD2E0EBBA), /* ~= 10^-148 */ + U64(0xCC963FEE, 0x10B7D1B3), U64(0x318DF905, 0x079926A8), /* ~= 10^-147 */ + U64(0xFFBBCFE9, 0x94E5C61F), U64(0xFDF17746, 0x497F7052), /* ~= 10^-146 */ + U64(0x9FD561F1, 0xFD0F9BD3), U64(0xFEB6EA8B, 0xEDEFA633), /* ~= 10^-145 */ + U64(0xC7CABA6E, 0x7C5382C8), U64(0xFE64A52E, 0xE96B8FC0), /* ~= 10^-144 */ + U64(0xF9BD690A, 0x1B68637B), U64(0x3DFDCE7A, 0xA3C673B0), /* ~= 10^-143 */ + U64(0x9C1661A6, 0x51213E2D), U64(0x06BEA10C, 0xA65C084E), /* ~= 10^-142 */ + U64(0xC31BFA0F, 0xE5698DB8), U64(0x486E494F, 0xCFF30A62), /* ~= 10^-141 */ + U64(0xF3E2F893, 0xDEC3F126), U64(0x5A89DBA3, 0xC3EFCCFA), /* ~= 10^-140 */ + U64(0x986DDB5C, 0x6B3A76B7), U64(0xF8962946, 0x5A75E01C), /* ~= 10^-139 */ + U64(0xBE895233, 0x86091465), U64(0xF6BBB397, 0xF1135823), /* ~= 10^-138 */ + U64(0xEE2BA6C0, 0x678B597F), U64(0x746AA07D, 0xED582E2C), /* ~= 10^-137 */ + U64(0x94DB4838, 0x40B717EF), U64(0xA8C2A44E, 0xB4571CDC), /* ~= 10^-136 */ + U64(0xBA121A46, 0x50E4DDEB), U64(0x92F34D62, 0x616CE413), /* ~= 10^-135 */ + U64(0xE896A0D7, 0xE51E1566), U64(0x77B020BA, 0xF9C81D17), /* ~= 10^-134 */ + U64(0x915E2486, 0xEF32CD60), U64(0x0ACE1474, 0xDC1D122E), /* ~= 10^-133 */ + U64(0xB5B5ADA8, 0xAAFF80B8), U64(0x0D819992, 0x132456BA), /* ~= 10^-132 */ + U64(0xE3231912, 0xD5BF60E6), U64(0x10E1FFF6, 0x97ED6C69), /* ~= 10^-131 */ + U64(0x8DF5EFAB, 0xC5979C8F), U64(0xCA8D3FFA, 0x1EF463C1), /* ~= 10^-130 */ + U64(0xB1736B96, 0xB6FD83B3), U64(0xBD308FF8, 0xA6B17CB2), /* ~= 10^-129 */ + U64(0xDDD0467C, 0x64BCE4A0), U64(0xAC7CB3F6, 0xD05DDBDE), /* ~= 10^-128 */ + U64(0x8AA22C0D, 0xBEF60EE4), U64(0x6BCDF07A, 0x423AA96B), /* ~= 10^-127 */ + U64(0xAD4AB711, 0x2EB3929D), U64(0x86C16C98, 0xD2C953C6), /* ~= 10^-126 */ + U64(0xD89D64D5, 0x7A607744), U64(0xE871C7BF, 0x077BA8B7), /* ~= 10^-125 */ + U64(0x87625F05, 0x6C7C4A8B), U64(0x11471CD7, 0x64AD4972), /* ~= 10^-124 */ + U64(0xA93AF6C6, 0xC79B5D2D), U64(0xD598E40D, 0x3DD89BCF), /* ~= 10^-123 */ + U64(0xD389B478, 0x79823479), U64(0x4AFF1D10, 0x8D4EC2C3), /* ~= 10^-122 */ + U64(0x843610CB, 0x4BF160CB), U64(0xCEDF722A, 0x585139BA), /* ~= 10^-121 */ + U64(0xA54394FE, 0x1EEDB8FE), U64(0xC2974EB4, 0xEE658828), /* ~= 10^-120 */ + U64(0xCE947A3D, 0xA6A9273E), U64(0x733D2262, 0x29FEEA32), /* ~= 10^-119 */ + U64(0x811CCC66, 0x8829B887), U64(0x0806357D, 0x5A3F525F), /* ~= 10^-118 */ + U64(0xA163FF80, 0x2A3426A8), U64(0xCA07C2DC, 0xB0CF26F7), /* ~= 10^-117 */ + U64(0xC9BCFF60, 0x34C13052), U64(0xFC89B393, 0xDD02F0B5), /* ~= 10^-116 */ + U64(0xFC2C3F38, 0x41F17C67), U64(0xBBAC2078, 0xD443ACE2), /* ~= 10^-115 */ + U64(0x9D9BA783, 0x2936EDC0), U64(0xD54B944B, 0x84AA4C0D), /* ~= 10^-114 */ + U64(0xC5029163, 0xF384A931), U64(0x0A9E795E, 0x65D4DF11), /* ~= 10^-113 */ + U64(0xF64335BC, 0xF065D37D), U64(0x4D4617B5, 0xFF4A16D5), /* ~= 10^-112 */ + U64(0x99EA0196, 0x163FA42E), U64(0x504BCED1, 0xBF8E4E45), /* ~= 10^-111 */ + U64(0xC06481FB, 0x9BCF8D39), U64(0xE45EC286, 0x2F71E1D6), /* ~= 10^-110 */ + U64(0xF07DA27A, 0x82C37088), U64(0x5D767327, 0xBB4E5A4C), /* ~= 10^-109 */ + U64(0x964E858C, 0x91BA2655), U64(0x3A6A07F8, 0xD510F86F), /* ~= 10^-108 */ + U64(0xBBE226EF, 0xB628AFEA), U64(0x890489F7, 0x0A55368B), /* ~= 10^-107 */ + U64(0xEADAB0AB, 0xA3B2DBE5), U64(0x2B45AC74, 0xCCEA842E), /* ~= 10^-106 */ + U64(0x92C8AE6B, 0x464FC96F), U64(0x3B0B8BC9, 0x0012929D), /* ~= 10^-105 */ + U64(0xB77ADA06, 0x17E3BBCB), U64(0x09CE6EBB, 0x40173744), /* ~= 10^-104 */ + U64(0xE5599087, 0x9DDCAABD), U64(0xCC420A6A, 0x101D0515), /* ~= 10^-103 */ + U64(0x8F57FA54, 0xC2A9EAB6), U64(0x9FA94682, 0x4A12232D), /* ~= 10^-102 */ + U64(0xB32DF8E9, 0xF3546564), U64(0x47939822, 0xDC96ABF9), /* ~= 10^-101 */ + U64(0xDFF97724, 0x70297EBD), U64(0x59787E2B, 0x93BC56F7), /* ~= 10^-100 */ + U64(0x8BFBEA76, 0xC619EF36), U64(0x57EB4EDB, 0x3C55B65A), /* ~= 10^-99 */ + U64(0xAEFAE514, 0x77A06B03), U64(0xEDE62292, 0x0B6B23F1), /* ~= 10^-98 */ + U64(0xDAB99E59, 0x958885C4), U64(0xE95FAB36, 0x8E45ECED), /* ~= 10^-97 */ + U64(0x88B402F7, 0xFD75539B), U64(0x11DBCB02, 0x18EBB414), /* ~= 10^-96 */ + U64(0xAAE103B5, 0xFCD2A881), U64(0xD652BDC2, 0x9F26A119), /* ~= 10^-95 */ + U64(0xD59944A3, 0x7C0752A2), U64(0x4BE76D33, 0x46F0495F), /* ~= 10^-94 */ + U64(0x857FCAE6, 0x2D8493A5), U64(0x6F70A440, 0x0C562DDB), /* ~= 10^-93 */ + U64(0xA6DFBD9F, 0xB8E5B88E), U64(0xCB4CCD50, 0x0F6BB952), /* ~= 10^-92 */ + U64(0xD097AD07, 0xA71F26B2), U64(0x7E2000A4, 0x1346A7A7), /* ~= 10^-91 */ + U64(0x825ECC24, 0xC873782F), U64(0x8ED40066, 0x8C0C28C8), /* ~= 10^-90 */ + U64(0xA2F67F2D, 0xFA90563B), U64(0x72890080, 0x2F0F32FA), /* ~= 10^-89 */ + U64(0xCBB41EF9, 0x79346BCA), U64(0x4F2B40A0, 0x3AD2FFB9), /* ~= 10^-88 */ + U64(0xFEA126B7, 0xD78186BC), U64(0xE2F610C8, 0x4987BFA8), /* ~= 10^-87 */ + U64(0x9F24B832, 0xE6B0F436), U64(0x0DD9CA7D, 0x2DF4D7C9), /* ~= 10^-86 */ + U64(0xC6EDE63F, 0xA05D3143), U64(0x91503D1C, 0x79720DBB), /* ~= 10^-85 */ + U64(0xF8A95FCF, 0x88747D94), U64(0x75A44C63, 0x97CE912A), /* ~= 10^-84 */ + U64(0x9B69DBE1, 0xB548CE7C), U64(0xC986AFBE, 0x3EE11ABA), /* ~= 10^-83 */ + U64(0xC24452DA, 0x229B021B), U64(0xFBE85BAD, 0xCE996168), /* ~= 10^-82 */ + U64(0xF2D56790, 0xAB41C2A2), U64(0xFAE27299, 0x423FB9C3), /* ~= 10^-81 */ + U64(0x97C560BA, 0x6B0919A5), U64(0xDCCD879F, 0xC967D41A), /* ~= 10^-80 */ + U64(0xBDB6B8E9, 0x05CB600F), U64(0x5400E987, 0xBBC1C920), /* ~= 10^-79 */ + U64(0xED246723, 0x473E3813), U64(0x290123E9, 0xAAB23B68), /* ~= 10^-78 */ + U64(0x9436C076, 0x0C86E30B), U64(0xF9A0B672, 0x0AAF6521), /* ~= 10^-77 */ + U64(0xB9447093, 0x8FA89BCE), U64(0xF808E40E, 0x8D5B3E69), /* ~= 10^-76 */ + U64(0xE7958CB8, 0x7392C2C2), U64(0xB60B1D12, 0x30B20E04), /* ~= 10^-75 */ + U64(0x90BD77F3, 0x483BB9B9), U64(0xB1C6F22B, 0x5E6F48C2), /* ~= 10^-74 */ + U64(0xB4ECD5F0, 0x1A4AA828), U64(0x1E38AEB6, 0x360B1AF3), /* ~= 10^-73 */ + U64(0xE2280B6C, 0x20DD5232), U64(0x25C6DA63, 0xC38DE1B0), /* ~= 10^-72 */ + U64(0x8D590723, 0x948A535F), U64(0x579C487E, 0x5A38AD0E), /* ~= 10^-71 */ + U64(0xB0AF48EC, 0x79ACE837), U64(0x2D835A9D, 0xF0C6D851), /* ~= 10^-70 */ + U64(0xDCDB1B27, 0x98182244), U64(0xF8E43145, 0x6CF88E65), /* ~= 10^-69 */ + U64(0x8A08F0F8, 0xBF0F156B), U64(0x1B8E9ECB, 0x641B58FF), /* ~= 10^-68 */ + U64(0xAC8B2D36, 0xEED2DAC5), U64(0xE272467E, 0x3D222F3F), /* ~= 10^-67 */ + U64(0xD7ADF884, 0xAA879177), U64(0x5B0ED81D, 0xCC6ABB0F), /* ~= 10^-66 */ + U64(0x86CCBB52, 0xEA94BAEA), U64(0x98E94712, 0x9FC2B4E9), /* ~= 10^-65 */ + U64(0xA87FEA27, 0xA539E9A5), U64(0x3F2398D7, 0x47B36224), /* ~= 10^-64 */ + U64(0xD29FE4B1, 0x8E88640E), U64(0x8EEC7F0D, 0x19A03AAD), /* ~= 10^-63 */ + U64(0x83A3EEEE, 0xF9153E89), U64(0x1953CF68, 0x300424AC), /* ~= 10^-62 */ + U64(0xA48CEAAA, 0xB75A8E2B), U64(0x5FA8C342, 0x3C052DD7), /* ~= 10^-61 */ + U64(0xCDB02555, 0x653131B6), U64(0x3792F412, 0xCB06794D), /* ~= 10^-60 */ + U64(0x808E1755, 0x5F3EBF11), U64(0xE2BBD88B, 0xBEE40BD0), /* ~= 10^-59 */ + U64(0xA0B19D2A, 0xB70E6ED6), U64(0x5B6ACEAE, 0xAE9D0EC4), /* ~= 10^-58 */ + U64(0xC8DE0475, 0x64D20A8B), U64(0xF245825A, 0x5A445275), /* ~= 10^-57 */ + U64(0xFB158592, 0xBE068D2E), U64(0xEED6E2F0, 0xF0D56712), /* ~= 10^-56 */ + U64(0x9CED737B, 0xB6C4183D), U64(0x55464DD6, 0x9685606B), /* ~= 10^-55 */ + U64(0xC428D05A, 0xA4751E4C), U64(0xAA97E14C, 0x3C26B886), /* ~= 10^-54 */ + U64(0xF5330471, 0x4D9265DF), U64(0xD53DD99F, 0x4B3066A8), /* ~= 10^-53 */ + U64(0x993FE2C6, 0xD07B7FAB), U64(0xE546A803, 0x8EFE4029), /* ~= 10^-52 */ + U64(0xBF8FDB78, 0x849A5F96), U64(0xDE985204, 0x72BDD033), /* ~= 10^-51 */ + U64(0xEF73D256, 0xA5C0F77C), U64(0x963E6685, 0x8F6D4440), /* ~= 10^-50 */ + U64(0x95A86376, 0x27989AAD), U64(0xDDE70013, 0x79A44AA8), /* ~= 10^-49 */ + U64(0xBB127C53, 0xB17EC159), U64(0x5560C018, 0x580D5D52), /* ~= 10^-48 */ + U64(0xE9D71B68, 0x9DDE71AF), U64(0xAAB8F01E, 0x6E10B4A6), /* ~= 10^-47 */ + U64(0x92267121, 0x62AB070D), U64(0xCAB39613, 0x04CA70E8), /* ~= 10^-46 */ + U64(0xB6B00D69, 0xBB55C8D1), U64(0x3D607B97, 0xC5FD0D22), /* ~= 10^-45 */ + U64(0xE45C10C4, 0x2A2B3B05), U64(0x8CB89A7D, 0xB77C506A), /* ~= 10^-44 */ + U64(0x8EB98A7A, 0x9A5B04E3), U64(0x77F3608E, 0x92ADB242), /* ~= 10^-43 */ + U64(0xB267ED19, 0x40F1C61C), U64(0x55F038B2, 0x37591ED3), /* ~= 10^-42 */ + U64(0xDF01E85F, 0x912E37A3), U64(0x6B6C46DE, 0xC52F6688), /* ~= 10^-41 */ + U64(0x8B61313B, 0xBABCE2C6), U64(0x2323AC4B, 0x3B3DA015), /* ~= 10^-40 */ + U64(0xAE397D8A, 0xA96C1B77), U64(0xABEC975E, 0x0A0D081A), /* ~= 10^-39 */ + U64(0xD9C7DCED, 0x53C72255), U64(0x96E7BD35, 0x8C904A21), /* ~= 10^-38 */ + U64(0x881CEA14, 0x545C7575), U64(0x7E50D641, 0x77DA2E54), /* ~= 10^-37 */ + U64(0xAA242499, 0x697392D2), U64(0xDDE50BD1, 0xD5D0B9E9), /* ~= 10^-36 */ + U64(0xD4AD2DBF, 0xC3D07787), U64(0x955E4EC6, 0x4B44E864), /* ~= 10^-35 */ + U64(0x84EC3C97, 0xDA624AB4), U64(0xBD5AF13B, 0xEF0B113E), /* ~= 10^-34 */ + U64(0xA6274BBD, 0xD0FADD61), U64(0xECB1AD8A, 0xEACDD58E), /* ~= 10^-33 */ + U64(0xCFB11EAD, 0x453994BA), U64(0x67DE18ED, 0xA5814AF2), /* ~= 10^-32 */ + U64(0x81CEB32C, 0x4B43FCF4), U64(0x80EACF94, 0x8770CED7), /* ~= 10^-31 */ + U64(0xA2425FF7, 0x5E14FC31), U64(0xA1258379, 0xA94D028D), /* ~= 10^-30 */ + U64(0xCAD2F7F5, 0x359A3B3E), U64(0x096EE458, 0x13A04330), /* ~= 10^-29 */ + U64(0xFD87B5F2, 0x8300CA0D), U64(0x8BCA9D6E, 0x188853FC), /* ~= 10^-28 */ + U64(0x9E74D1B7, 0x91E07E48), U64(0x775EA264, 0xCF55347D), /* ~= 10^-27 */ + U64(0xC6120625, 0x76589DDA), U64(0x95364AFE, 0x032A819D), /* ~= 10^-26 */ + U64(0xF79687AE, 0xD3EEC551), U64(0x3A83DDBD, 0x83F52204), /* ~= 10^-25 */ + U64(0x9ABE14CD, 0x44753B52), U64(0xC4926A96, 0x72793542), /* ~= 10^-24 */ + U64(0xC16D9A00, 0x95928A27), U64(0x75B7053C, 0x0F178293), /* ~= 10^-23 */ + U64(0xF1C90080, 0xBAF72CB1), U64(0x5324C68B, 0x12DD6338), /* ~= 10^-22 */ + U64(0x971DA050, 0x74DA7BEE), U64(0xD3F6FC16, 0xEBCA5E03), /* ~= 10^-21 */ + U64(0xBCE50864, 0x92111AEA), U64(0x88F4BB1C, 0xA6BCF584), /* ~= 10^-20 */ + U64(0xEC1E4A7D, 0xB69561A5), U64(0x2B31E9E3, 0xD06C32E5), /* ~= 10^-19 */ + U64(0x9392EE8E, 0x921D5D07), U64(0x3AFF322E, 0x62439FCF), /* ~= 10^-18 */ + U64(0xB877AA32, 0x36A4B449), U64(0x09BEFEB9, 0xFAD487C2), /* ~= 10^-17 */ + U64(0xE69594BE, 0xC44DE15B), U64(0x4C2EBE68, 0x7989A9B3), /* ~= 10^-16 */ + U64(0x901D7CF7, 0x3AB0ACD9), U64(0x0F9D3701, 0x4BF60A10), /* ~= 10^-15 */ + U64(0xB424DC35, 0x095CD80F), U64(0x538484C1, 0x9EF38C94), /* ~= 10^-14 */ + U64(0xE12E1342, 0x4BB40E13), U64(0x2865A5F2, 0x06B06FB9), /* ~= 10^-13 */ + U64(0x8CBCCC09, 0x6F5088CB), U64(0xF93F87B7, 0x442E45D3), /* ~= 10^-12 */ + U64(0xAFEBFF0B, 0xCB24AAFE), U64(0xF78F69A5, 0x1539D748), /* ~= 10^-11 */ + U64(0xDBE6FECE, 0xBDEDD5BE), U64(0xB573440E, 0x5A884D1B), /* ~= 10^-10 */ + U64(0x89705F41, 0x36B4A597), U64(0x31680A88, 0xF8953030), /* ~= 10^-9 */ + U64(0xABCC7711, 0x8461CEFC), U64(0xFDC20D2B, 0x36BA7C3D), /* ~= 10^-8 */ + U64(0xD6BF94D5, 0xE57A42BC), U64(0x3D329076, 0x04691B4C), /* ~= 10^-7 */ + U64(0x8637BD05, 0xAF6C69B5), U64(0xA63F9A49, 0xC2C1B10F), /* ~= 10^-6 */ + U64(0xA7C5AC47, 0x1B478423), U64(0x0FCF80DC, 0x33721D53), /* ~= 10^-5 */ + U64(0xD1B71758, 0xE219652B), U64(0xD3C36113, 0x404EA4A8), /* ~= 10^-4 */ + U64(0x83126E97, 0x8D4FDF3B), U64(0x645A1CAC, 0x083126E9), /* ~= 10^-3 */ + U64(0xA3D70A3D, 0x70A3D70A), U64(0x3D70A3D7, 0x0A3D70A3), /* ~= 10^-2 */ + U64(0xCCCCCCCC, 0xCCCCCCCC), U64(0xCCCCCCCC, 0xCCCCCCCC), /* ~= 10^-1 */ + U64(0x80000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^0 */ + U64(0xA0000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^1 */ + U64(0xC8000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^2 */ + U64(0xFA000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^3 */ + U64(0x9C400000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^4 */ + U64(0xC3500000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^5 */ + U64(0xF4240000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^6 */ + U64(0x98968000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^7 */ + U64(0xBEBC2000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^8 */ + U64(0xEE6B2800, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^9 */ + U64(0x9502F900, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^10 */ + U64(0xBA43B740, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^11 */ + U64(0xE8D4A510, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^12 */ + U64(0x9184E72A, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^13 */ + U64(0xB5E620F4, 0x80000000), U64(0x00000000, 0x00000000), /* == 10^14 */ + U64(0xE35FA931, 0xA0000000), U64(0x00000000, 0x00000000), /* == 10^15 */ + U64(0x8E1BC9BF, 0x04000000), U64(0x00000000, 0x00000000), /* == 10^16 */ + U64(0xB1A2BC2E, 0xC5000000), U64(0x00000000, 0x00000000), /* == 10^17 */ + U64(0xDE0B6B3A, 0x76400000), U64(0x00000000, 0x00000000), /* == 10^18 */ + U64(0x8AC72304, 0x89E80000), U64(0x00000000, 0x00000000), /* == 10^19 */ + U64(0xAD78EBC5, 0xAC620000), U64(0x00000000, 0x00000000), /* == 10^20 */ + U64(0xD8D726B7, 0x177A8000), U64(0x00000000, 0x00000000), /* == 10^21 */ + U64(0x87867832, 0x6EAC9000), U64(0x00000000, 0x00000000), /* == 10^22 */ + U64(0xA968163F, 0x0A57B400), U64(0x00000000, 0x00000000), /* == 10^23 */ + U64(0xD3C21BCE, 0xCCEDA100), U64(0x00000000, 0x00000000), /* == 10^24 */ + U64(0x84595161, 0x401484A0), U64(0x00000000, 0x00000000), /* == 10^25 */ + U64(0xA56FA5B9, 0x9019A5C8), U64(0x00000000, 0x00000000), /* == 10^26 */ + U64(0xCECB8F27, 0xF4200F3A), U64(0x00000000, 0x00000000), /* == 10^27 */ + U64(0x813F3978, 0xF8940984), U64(0x40000000, 0x00000000), /* == 10^28 */ + U64(0xA18F07D7, 0x36B90BE5), U64(0x50000000, 0x00000000), /* == 10^29 */ + U64(0xC9F2C9CD, 0x04674EDE), U64(0xA4000000, 0x00000000), /* == 10^30 */ + U64(0xFC6F7C40, 0x45812296), U64(0x4D000000, 0x00000000), /* == 10^31 */ + U64(0x9DC5ADA8, 0x2B70B59D), U64(0xF0200000, 0x00000000), /* == 10^32 */ + U64(0xC5371912, 0x364CE305), U64(0x6C280000, 0x00000000), /* == 10^33 */ + U64(0xF684DF56, 0xC3E01BC6), U64(0xC7320000, 0x00000000), /* == 10^34 */ + U64(0x9A130B96, 0x3A6C115C), U64(0x3C7F4000, 0x00000000), /* == 10^35 */ + U64(0xC097CE7B, 0xC90715B3), U64(0x4B9F1000, 0x00000000), /* == 10^36 */ + U64(0xF0BDC21A, 0xBB48DB20), U64(0x1E86D400, 0x00000000), /* == 10^37 */ + U64(0x96769950, 0xB50D88F4), U64(0x13144480, 0x00000000), /* == 10^38 */ + U64(0xBC143FA4, 0xE250EB31), U64(0x17D955A0, 0x00000000), /* == 10^39 */ + U64(0xEB194F8E, 0x1AE525FD), U64(0x5DCFAB08, 0x00000000), /* == 10^40 */ + U64(0x92EFD1B8, 0xD0CF37BE), U64(0x5AA1CAE5, 0x00000000), /* == 10^41 */ + U64(0xB7ABC627, 0x050305AD), U64(0xF14A3D9E, 0x40000000), /* == 10^42 */ + U64(0xE596B7B0, 0xC643C719), U64(0x6D9CCD05, 0xD0000000), /* == 10^43 */ + U64(0x8F7E32CE, 0x7BEA5C6F), U64(0xE4820023, 0xA2000000), /* == 10^44 */ + U64(0xB35DBF82, 0x1AE4F38B), U64(0xDDA2802C, 0x8A800000), /* == 10^45 */ + U64(0xE0352F62, 0xA19E306E), U64(0xD50B2037, 0xAD200000), /* == 10^46 */ + U64(0x8C213D9D, 0xA502DE45), U64(0x4526F422, 0xCC340000), /* == 10^47 */ + U64(0xAF298D05, 0x0E4395D6), U64(0x9670B12B, 0x7F410000), /* == 10^48 */ + U64(0xDAF3F046, 0x51D47B4C), U64(0x3C0CDD76, 0x5F114000), /* == 10^49 */ + U64(0x88D8762B, 0xF324CD0F), U64(0xA5880A69, 0xFB6AC800), /* == 10^50 */ + U64(0xAB0E93B6, 0xEFEE0053), U64(0x8EEA0D04, 0x7A457A00), /* == 10^51 */ + U64(0xD5D238A4, 0xABE98068), U64(0x72A49045, 0x98D6D880), /* == 10^52 */ + U64(0x85A36366, 0xEB71F041), U64(0x47A6DA2B, 0x7F864750), /* == 10^53 */ + U64(0xA70C3C40, 0xA64E6C51), U64(0x999090B6, 0x5F67D924), /* == 10^54 */ + U64(0xD0CF4B50, 0xCFE20765), U64(0xFFF4B4E3, 0xF741CF6D), /* == 10^55 */ + U64(0x82818F12, 0x81ED449F), U64(0xBFF8F10E, 0x7A8921A4), /* ~= 10^56 */ + U64(0xA321F2D7, 0x226895C7), U64(0xAFF72D52, 0x192B6A0D), /* ~= 10^57 */ + U64(0xCBEA6F8C, 0xEB02BB39), U64(0x9BF4F8A6, 0x9F764490), /* ~= 10^58 */ + U64(0xFEE50B70, 0x25C36A08), U64(0x02F236D0, 0x4753D5B4), /* ~= 10^59 */ + U64(0x9F4F2726, 0x179A2245), U64(0x01D76242, 0x2C946590), /* ~= 10^60 */ + U64(0xC722F0EF, 0x9D80AAD6), U64(0x424D3AD2, 0xB7B97EF5), /* ~= 10^61 */ + U64(0xF8EBAD2B, 0x84E0D58B), U64(0xD2E08987, 0x65A7DEB2), /* ~= 10^62 */ + U64(0x9B934C3B, 0x330C8577), U64(0x63CC55F4, 0x9F88EB2F), /* ~= 10^63 */ + U64(0xC2781F49, 0xFFCFA6D5), U64(0x3CBF6B71, 0xC76B25FB), /* ~= 10^64 */ + U64(0xF316271C, 0x7FC3908A), U64(0x8BEF464E, 0x3945EF7A), /* ~= 10^65 */ + U64(0x97EDD871, 0xCFDA3A56), U64(0x97758BF0, 0xE3CBB5AC), /* ~= 10^66 */ + U64(0xBDE94E8E, 0x43D0C8EC), U64(0x3D52EEED, 0x1CBEA317), /* ~= 10^67 */ + U64(0xED63A231, 0xD4C4FB27), U64(0x4CA7AAA8, 0x63EE4BDD), /* ~= 10^68 */ + U64(0x945E455F, 0x24FB1CF8), U64(0x8FE8CAA9, 0x3E74EF6A), /* ~= 10^69 */ + U64(0xB975D6B6, 0xEE39E436), U64(0xB3E2FD53, 0x8E122B44), /* ~= 10^70 */ + U64(0xE7D34C64, 0xA9C85D44), U64(0x60DBBCA8, 0x7196B616), /* ~= 10^71 */ + U64(0x90E40FBE, 0xEA1D3A4A), U64(0xBC8955E9, 0x46FE31CD), /* ~= 10^72 */ + U64(0xB51D13AE, 0xA4A488DD), U64(0x6BABAB63, 0x98BDBE41), /* ~= 10^73 */ + U64(0xE264589A, 0x4DCDAB14), U64(0xC696963C, 0x7EED2DD1), /* ~= 10^74 */ + U64(0x8D7EB760, 0x70A08AEC), U64(0xFC1E1DE5, 0xCF543CA2), /* ~= 10^75 */ + U64(0xB0DE6538, 0x8CC8ADA8), U64(0x3B25A55F, 0x43294BCB), /* ~= 10^76 */ + U64(0xDD15FE86, 0xAFFAD912), U64(0x49EF0EB7, 0x13F39EBE), /* ~= 10^77 */ + U64(0x8A2DBF14, 0x2DFCC7AB), U64(0x6E356932, 0x6C784337), /* ~= 10^78 */ + U64(0xACB92ED9, 0x397BF996), U64(0x49C2C37F, 0x07965404), /* ~= 10^79 */ + U64(0xD7E77A8F, 0x87DAF7FB), U64(0xDC33745E, 0xC97BE906), /* ~= 10^80 */ + U64(0x86F0AC99, 0xB4E8DAFD), U64(0x69A028BB, 0x3DED71A3), /* ~= 10^81 */ + U64(0xA8ACD7C0, 0x222311BC), U64(0xC40832EA, 0x0D68CE0C), /* ~= 10^82 */ + U64(0xD2D80DB0, 0x2AABD62B), U64(0xF50A3FA4, 0x90C30190), /* ~= 10^83 */ + U64(0x83C7088E, 0x1AAB65DB), U64(0x792667C6, 0xDA79E0FA), /* ~= 10^84 */ + U64(0xA4B8CAB1, 0xA1563F52), U64(0x577001B8, 0x91185938), /* ~= 10^85 */ + U64(0xCDE6FD5E, 0x09ABCF26), U64(0xED4C0226, 0xB55E6F86), /* ~= 10^86 */ + U64(0x80B05E5A, 0xC60B6178), U64(0x544F8158, 0x315B05B4), /* ~= 10^87 */ + U64(0xA0DC75F1, 0x778E39D6), U64(0x696361AE, 0x3DB1C721), /* ~= 10^88 */ + U64(0xC913936D, 0xD571C84C), U64(0x03BC3A19, 0xCD1E38E9), /* ~= 10^89 */ + U64(0xFB587849, 0x4ACE3A5F), U64(0x04AB48A0, 0x4065C723), /* ~= 10^90 */ + U64(0x9D174B2D, 0xCEC0E47B), U64(0x62EB0D64, 0x283F9C76), /* ~= 10^91 */ + U64(0xC45D1DF9, 0x42711D9A), U64(0x3BA5D0BD, 0x324F8394), /* ~= 10^92 */ + U64(0xF5746577, 0x930D6500), U64(0xCA8F44EC, 0x7EE36479), /* ~= 10^93 */ + U64(0x9968BF6A, 0xBBE85F20), U64(0x7E998B13, 0xCF4E1ECB), /* ~= 10^94 */ + U64(0xBFC2EF45, 0x6AE276E8), U64(0x9E3FEDD8, 0xC321A67E), /* ~= 10^95 */ + U64(0xEFB3AB16, 0xC59B14A2), U64(0xC5CFE94E, 0xF3EA101E), /* ~= 10^96 */ + U64(0x95D04AEE, 0x3B80ECE5), U64(0xBBA1F1D1, 0x58724A12), /* ~= 10^97 */ + U64(0xBB445DA9, 0xCA61281F), U64(0x2A8A6E45, 0xAE8EDC97), /* ~= 10^98 */ + U64(0xEA157514, 0x3CF97226), U64(0xF52D09D7, 0x1A3293BD), /* ~= 10^99 */ + U64(0x924D692C, 0xA61BE758), U64(0x593C2626, 0x705F9C56), /* ~= 10^100 */ + U64(0xB6E0C377, 0xCFA2E12E), U64(0x6F8B2FB0, 0x0C77836C), /* ~= 10^101 */ + U64(0xE498F455, 0xC38B997A), U64(0x0B6DFB9C, 0x0F956447), /* ~= 10^102 */ + U64(0x8EDF98B5, 0x9A373FEC), U64(0x4724BD41, 0x89BD5EAC), /* ~= 10^103 */ + U64(0xB2977EE3, 0x00C50FE7), U64(0x58EDEC91, 0xEC2CB657), /* ~= 10^104 */ + U64(0xDF3D5E9B, 0xC0F653E1), U64(0x2F2967B6, 0x6737E3ED), /* ~= 10^105 */ + U64(0x8B865B21, 0x5899F46C), U64(0xBD79E0D2, 0x0082EE74), /* ~= 10^106 */ + U64(0xAE67F1E9, 0xAEC07187), U64(0xECD85906, 0x80A3AA11), /* ~= 10^107 */ + U64(0xDA01EE64, 0x1A708DE9), U64(0xE80E6F48, 0x20CC9495), /* ~= 10^108 */ + U64(0x884134FE, 0x908658B2), U64(0x3109058D, 0x147FDCDD), /* ~= 10^109 */ + U64(0xAA51823E, 0x34A7EEDE), U64(0xBD4B46F0, 0x599FD415), /* ~= 10^110 */ + U64(0xD4E5E2CD, 0xC1D1EA96), U64(0x6C9E18AC, 0x7007C91A), /* ~= 10^111 */ + U64(0x850FADC0, 0x9923329E), U64(0x03E2CF6B, 0xC604DDB0), /* ~= 10^112 */ + U64(0xA6539930, 0xBF6BFF45), U64(0x84DB8346, 0xB786151C), /* ~= 10^113 */ + U64(0xCFE87F7C, 0xEF46FF16), U64(0xE6126418, 0x65679A63), /* ~= 10^114 */ + U64(0x81F14FAE, 0x158C5F6E), U64(0x4FCB7E8F, 0x3F60C07E), /* ~= 10^115 */ + U64(0xA26DA399, 0x9AEF7749), U64(0xE3BE5E33, 0x0F38F09D), /* ~= 10^116 */ + U64(0xCB090C80, 0x01AB551C), U64(0x5CADF5BF, 0xD3072CC5), /* ~= 10^117 */ + U64(0xFDCB4FA0, 0x02162A63), U64(0x73D9732F, 0xC7C8F7F6), /* ~= 10^118 */ + U64(0x9E9F11C4, 0x014DDA7E), U64(0x2867E7FD, 0xDCDD9AFA), /* ~= 10^119 */ + U64(0xC646D635, 0x01A1511D), U64(0xB281E1FD, 0x541501B8), /* ~= 10^120 */ + U64(0xF7D88BC2, 0x4209A565), U64(0x1F225A7C, 0xA91A4226), /* ~= 10^121 */ + U64(0x9AE75759, 0x6946075F), U64(0x3375788D, 0xE9B06958), /* ~= 10^122 */ + U64(0xC1A12D2F, 0xC3978937), U64(0x0052D6B1, 0x641C83AE), /* ~= 10^123 */ + U64(0xF209787B, 0xB47D6B84), U64(0xC0678C5D, 0xBD23A49A), /* ~= 10^124 */ + U64(0x9745EB4D, 0x50CE6332), U64(0xF840B7BA, 0x963646E0), /* ~= 10^125 */ + U64(0xBD176620, 0xA501FBFF), U64(0xB650E5A9, 0x3BC3D898), /* ~= 10^126 */ + U64(0xEC5D3FA8, 0xCE427AFF), U64(0xA3E51F13, 0x8AB4CEBE), /* ~= 10^127 */ + U64(0x93BA47C9, 0x80E98CDF), U64(0xC66F336C, 0x36B10137), /* ~= 10^128 */ + U64(0xB8A8D9BB, 0xE123F017), U64(0xB80B0047, 0x445D4184), /* ~= 10^129 */ + U64(0xE6D3102A, 0xD96CEC1D), U64(0xA60DC059, 0x157491E5), /* ~= 10^130 */ + U64(0x9043EA1A, 0xC7E41392), U64(0x87C89837, 0xAD68DB2F), /* ~= 10^131 */ + U64(0xB454E4A1, 0x79DD1877), U64(0x29BABE45, 0x98C311FB), /* ~= 10^132 */ + U64(0xE16A1DC9, 0xD8545E94), U64(0xF4296DD6, 0xFEF3D67A), /* ~= 10^133 */ + U64(0x8CE2529E, 0x2734BB1D), U64(0x1899E4A6, 0x5F58660C), /* ~= 10^134 */ + U64(0xB01AE745, 0xB101E9E4), U64(0x5EC05DCF, 0xF72E7F8F), /* ~= 10^135 */ + U64(0xDC21A117, 0x1D42645D), U64(0x76707543, 0xF4FA1F73), /* ~= 10^136 */ + U64(0x899504AE, 0x72497EBA), U64(0x6A06494A, 0x791C53A8), /* ~= 10^137 */ + U64(0xABFA45DA, 0x0EDBDE69), U64(0x0487DB9D, 0x17636892), /* ~= 10^138 */ + U64(0xD6F8D750, 0x9292D603), U64(0x45A9D284, 0x5D3C42B6), /* ~= 10^139 */ + U64(0x865B8692, 0x5B9BC5C2), U64(0x0B8A2392, 0xBA45A9B2), /* ~= 10^140 */ + U64(0xA7F26836, 0xF282B732), U64(0x8E6CAC77, 0x68D7141E), /* ~= 10^141 */ + U64(0xD1EF0244, 0xAF2364FF), U64(0x3207D795, 0x430CD926), /* ~= 10^142 */ + U64(0x8335616A, 0xED761F1F), U64(0x7F44E6BD, 0x49E807B8), /* ~= 10^143 */ + U64(0xA402B9C5, 0xA8D3A6E7), U64(0x5F16206C, 0x9C6209A6), /* ~= 10^144 */ + U64(0xCD036837, 0x130890A1), U64(0x36DBA887, 0xC37A8C0F), /* ~= 10^145 */ + U64(0x80222122, 0x6BE55A64), U64(0xC2494954, 0xDA2C9789), /* ~= 10^146 */ + U64(0xA02AA96B, 0x06DEB0FD), U64(0xF2DB9BAA, 0x10B7BD6C), /* ~= 10^147 */ + U64(0xC83553C5, 0xC8965D3D), U64(0x6F928294, 0x94E5ACC7), /* ~= 10^148 */ + U64(0xFA42A8B7, 0x3ABBF48C), U64(0xCB772339, 0xBA1F17F9), /* ~= 10^149 */ + U64(0x9C69A972, 0x84B578D7), U64(0xFF2A7604, 0x14536EFB), /* ~= 10^150 */ + U64(0xC38413CF, 0x25E2D70D), U64(0xFEF51385, 0x19684ABA), /* ~= 10^151 */ + U64(0xF46518C2, 0xEF5B8CD1), U64(0x7EB25866, 0x5FC25D69), /* ~= 10^152 */ + U64(0x98BF2F79, 0xD5993802), U64(0xEF2F773F, 0xFBD97A61), /* ~= 10^153 */ + U64(0xBEEEFB58, 0x4AFF8603), U64(0xAAFB550F, 0xFACFD8FA), /* ~= 10^154 */ + U64(0xEEAABA2E, 0x5DBF6784), U64(0x95BA2A53, 0xF983CF38), /* ~= 10^155 */ + U64(0x952AB45C, 0xFA97A0B2), U64(0xDD945A74, 0x7BF26183), /* ~= 10^156 */ + U64(0xBA756174, 0x393D88DF), U64(0x94F97111, 0x9AEEF9E4), /* ~= 10^157 */ + U64(0xE912B9D1, 0x478CEB17), U64(0x7A37CD56, 0x01AAB85D), /* ~= 10^158 */ + U64(0x91ABB422, 0xCCB812EE), U64(0xAC62E055, 0xC10AB33A), /* ~= 10^159 */ + U64(0xB616A12B, 0x7FE617AA), U64(0x577B986B, 0x314D6009), /* ~= 10^160 */ + U64(0xE39C4976, 0x5FDF9D94), U64(0xED5A7E85, 0xFDA0B80B), /* ~= 10^161 */ + U64(0x8E41ADE9, 0xFBEBC27D), U64(0x14588F13, 0xBE847307), /* ~= 10^162 */ + U64(0xB1D21964, 0x7AE6B31C), U64(0x596EB2D8, 0xAE258FC8), /* ~= 10^163 */ + U64(0xDE469FBD, 0x99A05FE3), U64(0x6FCA5F8E, 0xD9AEF3BB), /* ~= 10^164 */ + U64(0x8AEC23D6, 0x80043BEE), U64(0x25DE7BB9, 0x480D5854), /* ~= 10^165 */ + U64(0xADA72CCC, 0x20054AE9), U64(0xAF561AA7, 0x9A10AE6A), /* ~= 10^166 */ + U64(0xD910F7FF, 0x28069DA4), U64(0x1B2BA151, 0x8094DA04), /* ~= 10^167 */ + U64(0x87AA9AFF, 0x79042286), U64(0x90FB44D2, 0xF05D0842), /* ~= 10^168 */ + U64(0xA99541BF, 0x57452B28), U64(0x353A1607, 0xAC744A53), /* ~= 10^169 */ + U64(0xD3FA922F, 0x2D1675F2), U64(0x42889B89, 0x97915CE8), /* ~= 10^170 */ + U64(0x847C9B5D, 0x7C2E09B7), U64(0x69956135, 0xFEBADA11), /* ~= 10^171 */ + U64(0xA59BC234, 0xDB398C25), U64(0x43FAB983, 0x7E699095), /* ~= 10^172 */ + U64(0xCF02B2C2, 0x1207EF2E), U64(0x94F967E4, 0x5E03F4BB), /* ~= 10^173 */ + U64(0x8161AFB9, 0x4B44F57D), U64(0x1D1BE0EE, 0xBAC278F5), /* ~= 10^174 */ + U64(0xA1BA1BA7, 0x9E1632DC), U64(0x6462D92A, 0x69731732), /* ~= 10^175 */ + U64(0xCA28A291, 0x859BBF93), U64(0x7D7B8F75, 0x03CFDCFE), /* ~= 10^176 */ + U64(0xFCB2CB35, 0xE702AF78), U64(0x5CDA7352, 0x44C3D43E), /* ~= 10^177 */ + U64(0x9DEFBF01, 0xB061ADAB), U64(0x3A088813, 0x6AFA64A7), /* ~= 10^178 */ + U64(0xC56BAEC2, 0x1C7A1916), U64(0x088AAA18, 0x45B8FDD0), /* ~= 10^179 */ + U64(0xF6C69A72, 0xA3989F5B), U64(0x8AAD549E, 0x57273D45), /* ~= 10^180 */ + U64(0x9A3C2087, 0xA63F6399), U64(0x36AC54E2, 0xF678864B), /* ~= 10^181 */ + U64(0xC0CB28A9, 0x8FCF3C7F), U64(0x84576A1B, 0xB416A7DD), /* ~= 10^182 */ + U64(0xF0FDF2D3, 0xF3C30B9F), U64(0x656D44A2, 0xA11C51D5), /* ~= 10^183 */ + U64(0x969EB7C4, 0x7859E743), U64(0x9F644AE5, 0xA4B1B325), /* ~= 10^184 */ + U64(0xBC4665B5, 0x96706114), U64(0x873D5D9F, 0x0DDE1FEE), /* ~= 10^185 */ + U64(0xEB57FF22, 0xFC0C7959), U64(0xA90CB506, 0xD155A7EA), /* ~= 10^186 */ + U64(0x9316FF75, 0xDD87CBD8), U64(0x09A7F124, 0x42D588F2), /* ~= 10^187 */ + U64(0xB7DCBF53, 0x54E9BECE), U64(0x0C11ED6D, 0x538AEB2F), /* ~= 10^188 */ + U64(0xE5D3EF28, 0x2A242E81), U64(0x8F1668C8, 0xA86DA5FA), /* ~= 10^189 */ + U64(0x8FA47579, 0x1A569D10), U64(0xF96E017D, 0x694487BC), /* ~= 10^190 */ + U64(0xB38D92D7, 0x60EC4455), U64(0x37C981DC, 0xC395A9AC), /* ~= 10^191 */ + U64(0xE070F78D, 0x3927556A), U64(0x85BBE253, 0xF47B1417), /* ~= 10^192 */ + U64(0x8C469AB8, 0x43B89562), U64(0x93956D74, 0x78CCEC8E), /* ~= 10^193 */ + U64(0xAF584166, 0x54A6BABB), U64(0x387AC8D1, 0x970027B2), /* ~= 10^194 */ + U64(0xDB2E51BF, 0xE9D0696A), U64(0x06997B05, 0xFCC0319E), /* ~= 10^195 */ + U64(0x88FCF317, 0xF22241E2), U64(0x441FECE3, 0xBDF81F03), /* ~= 10^196 */ + U64(0xAB3C2FDD, 0xEEAAD25A), U64(0xD527E81C, 0xAD7626C3), /* ~= 10^197 */ + U64(0xD60B3BD5, 0x6A5586F1), U64(0x8A71E223, 0xD8D3B074), /* ~= 10^198 */ + U64(0x85C70565, 0x62757456), U64(0xF6872D56, 0x67844E49), /* ~= 10^199 */ + U64(0xA738C6BE, 0xBB12D16C), U64(0xB428F8AC, 0x016561DB), /* ~= 10^200 */ + U64(0xD106F86E, 0x69D785C7), U64(0xE13336D7, 0x01BEBA52), /* ~= 10^201 */ + U64(0x82A45B45, 0x0226B39C), U64(0xECC00246, 0x61173473), /* ~= 10^202 */ + U64(0xA34D7216, 0x42B06084), U64(0x27F002D7, 0xF95D0190), /* ~= 10^203 */ + U64(0xCC20CE9B, 0xD35C78A5), U64(0x31EC038D, 0xF7B441F4), /* ~= 10^204 */ + U64(0xFF290242, 0xC83396CE), U64(0x7E670471, 0x75A15271), /* ~= 10^205 */ + U64(0x9F79A169, 0xBD203E41), U64(0x0F0062C6, 0xE984D386), /* ~= 10^206 */ + U64(0xC75809C4, 0x2C684DD1), U64(0x52C07B78, 0xA3E60868), /* ~= 10^207 */ + U64(0xF92E0C35, 0x37826145), U64(0xA7709A56, 0xCCDF8A82), /* ~= 10^208 */ + U64(0x9BBCC7A1, 0x42B17CCB), U64(0x88A66076, 0x400BB691), /* ~= 10^209 */ + U64(0xC2ABF989, 0x935DDBFE), U64(0x6ACFF893, 0xD00EA435), /* ~= 10^210 */ + U64(0xF356F7EB, 0xF83552FE), U64(0x0583F6B8, 0xC4124D43), /* ~= 10^211 */ + U64(0x98165AF3, 0x7B2153DE), U64(0xC3727A33, 0x7A8B704A), /* ~= 10^212 */ + U64(0xBE1BF1B0, 0x59E9A8D6), U64(0x744F18C0, 0x592E4C5C), /* ~= 10^213 */ + U64(0xEDA2EE1C, 0x7064130C), U64(0x1162DEF0, 0x6F79DF73), /* ~= 10^214 */ + U64(0x9485D4D1, 0xC63E8BE7), U64(0x8ADDCB56, 0x45AC2BA8), /* ~= 10^215 */ + U64(0xB9A74A06, 0x37CE2EE1), U64(0x6D953E2B, 0xD7173692), /* ~= 10^216 */ + U64(0xE8111C87, 0xC5C1BA99), U64(0xC8FA8DB6, 0xCCDD0437), /* ~= 10^217 */ + U64(0x910AB1D4, 0xDB9914A0), U64(0x1D9C9892, 0x400A22A2), /* ~= 10^218 */ + U64(0xB54D5E4A, 0x127F59C8), U64(0x2503BEB6, 0xD00CAB4B), /* ~= 10^219 */ + U64(0xE2A0B5DC, 0x971F303A), U64(0x2E44AE64, 0x840FD61D), /* ~= 10^220 */ + U64(0x8DA471A9, 0xDE737E24), U64(0x5CEAECFE, 0xD289E5D2), /* ~= 10^221 */ + U64(0xB10D8E14, 0x56105DAD), U64(0x7425A83E, 0x872C5F47), /* ~= 10^222 */ + U64(0xDD50F199, 0x6B947518), U64(0xD12F124E, 0x28F77719), /* ~= 10^223 */ + U64(0x8A5296FF, 0xE33CC92F), U64(0x82BD6B70, 0xD99AAA6F), /* ~= 10^224 */ + U64(0xACE73CBF, 0xDC0BFB7B), U64(0x636CC64D, 0x1001550B), /* ~= 10^225 */ + U64(0xD8210BEF, 0xD30EFA5A), U64(0x3C47F7E0, 0x5401AA4E), /* ~= 10^226 */ + U64(0x8714A775, 0xE3E95C78), U64(0x65ACFAEC, 0x34810A71), /* ~= 10^227 */ + U64(0xA8D9D153, 0x5CE3B396), U64(0x7F1839A7, 0x41A14D0D), /* ~= 10^228 */ + U64(0xD31045A8, 0x341CA07C), U64(0x1EDE4811, 0x1209A050), /* ~= 10^229 */ + U64(0x83EA2B89, 0x2091E44D), U64(0x934AED0A, 0xAB460432), /* ~= 10^230 */ + U64(0xA4E4B66B, 0x68B65D60), U64(0xF81DA84D, 0x5617853F), /* ~= 10^231 */ + U64(0xCE1DE406, 0x42E3F4B9), U64(0x36251260, 0xAB9D668E), /* ~= 10^232 */ + U64(0x80D2AE83, 0xE9CE78F3), U64(0xC1D72B7C, 0x6B426019), /* ~= 10^233 */ + U64(0xA1075A24, 0xE4421730), U64(0xB24CF65B, 0x8612F81F), /* ~= 10^234 */ + U64(0xC94930AE, 0x1D529CFC), U64(0xDEE033F2, 0x6797B627), /* ~= 10^235 */ + U64(0xFB9B7CD9, 0xA4A7443C), U64(0x169840EF, 0x017DA3B1), /* ~= 10^236 */ + U64(0x9D412E08, 0x06E88AA5), U64(0x8E1F2895, 0x60EE864E), /* ~= 10^237 */ + U64(0xC491798A, 0x08A2AD4E), U64(0xF1A6F2BA, 0xB92A27E2), /* ~= 10^238 */ + U64(0xF5B5D7EC, 0x8ACB58A2), U64(0xAE10AF69, 0x6774B1DB), /* ~= 10^239 */ + U64(0x9991A6F3, 0xD6BF1765), U64(0xACCA6DA1, 0xE0A8EF29), /* ~= 10^240 */ + U64(0xBFF610B0, 0xCC6EDD3F), U64(0x17FD090A, 0x58D32AF3), /* ~= 10^241 */ + U64(0xEFF394DC, 0xFF8A948E), U64(0xDDFC4B4C, 0xEF07F5B0), /* ~= 10^242 */ + U64(0x95F83D0A, 0x1FB69CD9), U64(0x4ABDAF10, 0x1564F98E), /* ~= 10^243 */ + U64(0xBB764C4C, 0xA7A4440F), U64(0x9D6D1AD4, 0x1ABE37F1), /* ~= 10^244 */ + U64(0xEA53DF5F, 0xD18D5513), U64(0x84C86189, 0x216DC5ED), /* ~= 10^245 */ + U64(0x92746B9B, 0xE2F8552C), U64(0x32FD3CF5, 0xB4E49BB4), /* ~= 10^246 */ + U64(0xB7118682, 0xDBB66A77), U64(0x3FBC8C33, 0x221DC2A1), /* ~= 10^247 */ + U64(0xE4D5E823, 0x92A40515), U64(0x0FABAF3F, 0xEAA5334A), /* ~= 10^248 */ + U64(0x8F05B116, 0x3BA6832D), U64(0x29CB4D87, 0xF2A7400E), /* ~= 10^249 */ + U64(0xB2C71D5B, 0xCA9023F8), U64(0x743E20E9, 0xEF511012), /* ~= 10^250 */ + U64(0xDF78E4B2, 0xBD342CF6), U64(0x914DA924, 0x6B255416), /* ~= 10^251 */ + U64(0x8BAB8EEF, 0xB6409C1A), U64(0x1AD089B6, 0xC2F7548E), /* ~= 10^252 */ + U64(0xAE9672AB, 0xA3D0C320), U64(0xA184AC24, 0x73B529B1), /* ~= 10^253 */ + U64(0xDA3C0F56, 0x8CC4F3E8), U64(0xC9E5D72D, 0x90A2741E), /* ~= 10^254 */ + U64(0x88658996, 0x17FB1871), U64(0x7E2FA67C, 0x7A658892), /* ~= 10^255 */ + U64(0xAA7EEBFB, 0x9DF9DE8D), U64(0xDDBB901B, 0x98FEEAB7), /* ~= 10^256 */ + U64(0xD51EA6FA, 0x85785631), U64(0x552A7422, 0x7F3EA565), /* ~= 10^257 */ + U64(0x8533285C, 0x936B35DE), U64(0xD53A8895, 0x8F87275F), /* ~= 10^258 */ + U64(0xA67FF273, 0xB8460356), U64(0x8A892ABA, 0xF368F137), /* ~= 10^259 */ + U64(0xD01FEF10, 0xA657842C), U64(0x2D2B7569, 0xB0432D85), /* ~= 10^260 */ + U64(0x8213F56A, 0x67F6B29B), U64(0x9C3B2962, 0x0E29FC73), /* ~= 10^261 */ + U64(0xA298F2C5, 0x01F45F42), U64(0x8349F3BA, 0x91B47B8F), /* ~= 10^262 */ + U64(0xCB3F2F76, 0x42717713), U64(0x241C70A9, 0x36219A73), /* ~= 10^263 */ + U64(0xFE0EFB53, 0xD30DD4D7), U64(0xED238CD3, 0x83AA0110), /* ~= 10^264 */ + U64(0x9EC95D14, 0x63E8A506), U64(0xF4363804, 0x324A40AA), /* ~= 10^265 */ + U64(0xC67BB459, 0x7CE2CE48), U64(0xB143C605, 0x3EDCD0D5), /* ~= 10^266 */ + U64(0xF81AA16F, 0xDC1B81DA), U64(0xDD94B786, 0x8E94050A), /* ~= 10^267 */ + U64(0x9B10A4E5, 0xE9913128), U64(0xCA7CF2B4, 0x191C8326), /* ~= 10^268 */ + U64(0xC1D4CE1F, 0x63F57D72), U64(0xFD1C2F61, 0x1F63A3F0), /* ~= 10^269 */ + U64(0xF24A01A7, 0x3CF2DCCF), U64(0xBC633B39, 0x673C8CEC), /* ~= 10^270 */ + U64(0x976E4108, 0x8617CA01), U64(0xD5BE0503, 0xE085D813), /* ~= 10^271 */ + U64(0xBD49D14A, 0xA79DBC82), U64(0x4B2D8644, 0xD8A74E18), /* ~= 10^272 */ + U64(0xEC9C459D, 0x51852BA2), U64(0xDDF8E7D6, 0x0ED1219E), /* ~= 10^273 */ + U64(0x93E1AB82, 0x52F33B45), U64(0xCABB90E5, 0xC942B503), /* ~= 10^274 */ + U64(0xB8DA1662, 0xE7B00A17), U64(0x3D6A751F, 0x3B936243), /* ~= 10^275 */ + U64(0xE7109BFB, 0xA19C0C9D), U64(0x0CC51267, 0x0A783AD4), /* ~= 10^276 */ + U64(0x906A617D, 0x450187E2), U64(0x27FB2B80, 0x668B24C5), /* ~= 10^277 */ + U64(0xB484F9DC, 0x9641E9DA), U64(0xB1F9F660, 0x802DEDF6), /* ~= 10^278 */ + U64(0xE1A63853, 0xBBD26451), U64(0x5E7873F8, 0xA0396973), /* ~= 10^279 */ + U64(0x8D07E334, 0x55637EB2), U64(0xDB0B487B, 0x6423E1E8), /* ~= 10^280 */ + U64(0xB049DC01, 0x6ABC5E5F), U64(0x91CE1A9A, 0x3D2CDA62), /* ~= 10^281 */ + U64(0xDC5C5301, 0xC56B75F7), U64(0x7641A140, 0xCC7810FB), /* ~= 10^282 */ + U64(0x89B9B3E1, 0x1B6329BA), U64(0xA9E904C8, 0x7FCB0A9D), /* ~= 10^283 */ + U64(0xAC2820D9, 0x623BF429), U64(0x546345FA, 0x9FBDCD44), /* ~= 10^284 */ + U64(0xD732290F, 0xBACAF133), U64(0xA97C1779, 0x47AD4095), /* ~= 10^285 */ + U64(0x867F59A9, 0xD4BED6C0), U64(0x49ED8EAB, 0xCCCC485D), /* ~= 10^286 */ + U64(0xA81F3014, 0x49EE8C70), U64(0x5C68F256, 0xBFFF5A74), /* ~= 10^287 */ + U64(0xD226FC19, 0x5C6A2F8C), U64(0x73832EEC, 0x6FFF3111), /* ~= 10^288 */ + U64(0x83585D8F, 0xD9C25DB7), U64(0xC831FD53, 0xC5FF7EAB), /* ~= 10^289 */ + U64(0xA42E74F3, 0xD032F525), U64(0xBA3E7CA8, 0xB77F5E55), /* ~= 10^290 */ + U64(0xCD3A1230, 0xC43FB26F), U64(0x28CE1BD2, 0xE55F35EB), /* ~= 10^291 */ + U64(0x80444B5E, 0x7AA7CF85), U64(0x7980D163, 0xCF5B81B3), /* ~= 10^292 */ + U64(0xA0555E36, 0x1951C366), U64(0xD7E105BC, 0xC332621F), /* ~= 10^293 */ + U64(0xC86AB5C3, 0x9FA63440), U64(0x8DD9472B, 0xF3FEFAA7), /* ~= 10^294 */ + U64(0xFA856334, 0x878FC150), U64(0xB14F98F6, 0xF0FEB951), /* ~= 10^295 */ + U64(0x9C935E00, 0xD4B9D8D2), U64(0x6ED1BF9A, 0x569F33D3), /* ~= 10^296 */ + U64(0xC3B83581, 0x09E84F07), U64(0x0A862F80, 0xEC4700C8), /* ~= 10^297 */ + U64(0xF4A642E1, 0x4C6262C8), U64(0xCD27BB61, 0x2758C0FA), /* ~= 10^298 */ + U64(0x98E7E9CC, 0xCFBD7DBD), U64(0x8038D51C, 0xB897789C), /* ~= 10^299 */ + U64(0xBF21E440, 0x03ACDD2C), U64(0xE0470A63, 0xE6BD56C3), /* ~= 10^300 */ + U64(0xEEEA5D50, 0x04981478), U64(0x1858CCFC, 0xE06CAC74), /* ~= 10^301 */ + U64(0x95527A52, 0x02DF0CCB), U64(0x0F37801E, 0x0C43EBC8), /* ~= 10^302 */ + U64(0xBAA718E6, 0x8396CFFD), U64(0xD3056025, 0x8F54E6BA), /* ~= 10^303 */ + U64(0xE950DF20, 0x247C83FD), U64(0x47C6B82E, 0xF32A2069), /* ~= 10^304 */ + U64(0x91D28B74, 0x16CDD27E), U64(0x4CDC331D, 0x57FA5441), /* ~= 10^305 */ + U64(0xB6472E51, 0x1C81471D), U64(0xE0133FE4, 0xADF8E952), /* ~= 10^306 */ + U64(0xE3D8F9E5, 0x63A198E5), U64(0x58180FDD, 0xD97723A6), /* ~= 10^307 */ + U64(0x8E679C2F, 0x5E44FF8F), U64(0x570F09EA, 0xA7EA7648), /* ~= 10^308 */ + U64(0xB201833B, 0x35D63F73), U64(0x2CD2CC65, 0x51E513DA), /* ~= 10^309 */ + U64(0xDE81E40A, 0x034BCF4F), U64(0xF8077F7E, 0xA65E58D1), /* ~= 10^310 */ + U64(0x8B112E86, 0x420F6191), U64(0xFB04AFAF, 0x27FAF782), /* ~= 10^311 */ + U64(0xADD57A27, 0xD29339F6), U64(0x79C5DB9A, 0xF1F9B563), /* ~= 10^312 */ + U64(0xD94AD8B1, 0xC7380874), U64(0x18375281, 0xAE7822BC), /* ~= 10^313 */ + U64(0x87CEC76F, 0x1C830548), U64(0x8F229391, 0x0D0B15B5), /* ~= 10^314 */ + U64(0xA9C2794A, 0xE3A3C69A), U64(0xB2EB3875, 0x504DDB22), /* ~= 10^315 */ + U64(0xD433179D, 0x9C8CB841), U64(0x5FA60692, 0xA46151EB), /* ~= 10^316 */ + U64(0x849FEEC2, 0x81D7F328), U64(0xDBC7C41B, 0xA6BCD333), /* ~= 10^317 */ + U64(0xA5C7EA73, 0x224DEFF3), U64(0x12B9B522, 0x906C0800), /* ~= 10^318 */ + U64(0xCF39E50F, 0xEAE16BEF), U64(0xD768226B, 0x34870A00), /* ~= 10^319 */ + U64(0x81842F29, 0xF2CCE375), U64(0xE6A11583, 0x00D46640), /* ~= 10^320 */ + U64(0xA1E53AF4, 0x6F801C53), U64(0x60495AE3, 0xC1097FD0), /* ~= 10^321 */ + U64(0xCA5E89B1, 0x8B602368), U64(0x385BB19C, 0xB14BDFC4), /* ~= 10^322 */ + U64(0xFCF62C1D, 0xEE382C42), U64(0x46729E03, 0xDD9ED7B5), /* ~= 10^323 */ + U64(0x9E19DB92, 0xB4E31BA9), U64(0x6C07A2C2, 0x6A8346D1) /* ~= 10^324 */ +}; + +/** + Get the cached pow10 value from pow10_sig_table. + @param exp10 The exponent of pow(10, e). This value must in range + POW10_SIG_TABLE_MIN_EXP to POW10_SIG_TABLE_MAX_EXP. + @param hi The highest 64 bits of pow(10, e). + @param lo The lower 64 bits after `hi`. + */ +static_inline void pow10_table_get_sig(i32 exp10, u64 *hi, u64 *lo) { + i32 idx = exp10 - (POW10_SIG_TABLE_MIN_EXP); + *hi = pow10_sig_table[idx * 2]; + *lo = pow10_sig_table[idx * 2 + 1]; +} + +/** + Get the exponent (base 2) for highest 64 bits significand in pow10_sig_table. + */ +static_inline void pow10_table_get_exp(i32 exp10, i32 *exp2) { + /* e2 = floor(log2(pow(10, e))) - 64 + 1 */ + /* = floor(e * log2(10) - 63) */ + *exp2 = (exp10 * 217706 - 4128768) >> 16; +} + +#endif + + + +#if !YYJSON_DISABLE_READER + +/*============================================================================== + * JSON Character Matcher + *============================================================================*/ + +/** Character type */ +typedef u8 char_type; + +/** Whitespace character: ' ', '\\t', '\\n', '\\r'. */ +static const char_type CHAR_TYPE_SPACE = 1 << 0; + +/** Number character: '-', [0-9]. */ +static const char_type CHAR_TYPE_NUMBER = 1 << 1; + +/** JSON Escaped character: '"', '\', [0x00-0x1F]. */ +static const char_type CHAR_TYPE_ESC_ASCII = 1 << 2; + +/** Non-ASCII character: [0x80-0xFF]. */ +static const char_type CHAR_TYPE_NON_ASCII = 1 << 3; + +/** JSON container character: '{', '['. */ +static const char_type CHAR_TYPE_CONTAINER = 1 << 4; + +/** Comment character: '/'. */ +static const char_type CHAR_TYPE_COMMENT = 1 << 5; + +/** Line end character: '\\n', '\\r', '\0'. */ +static const char_type CHAR_TYPE_LINE_END = 1 << 6; + +/** Hexadecimal numeric character: [0-9a-fA-F]. */ +static const char_type CHAR_TYPE_HEX = 1 << 7; + +/** Character type table (generate with misc/make_tables.c) */ +static const char_type char_table[256] = { + 0x44, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x05, 0x45, 0x04, 0x04, 0x45, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x20, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 +}; + +/** Match a character with specified type. */ +static_inline bool char_is_type(u8 c, char_type type) { + return (char_table[c] & type) != 0; +} + +/** Match a whitespace: ' ', '\\t', '\\n', '\\r'. */ +static_inline bool char_is_space(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_SPACE); +} + +/** Match a whitespace or comment: ' ', '\\t', '\\n', '\\r', '/'. */ +static_inline bool char_is_space_or_comment(u8 c) { + return char_is_type(c, (char_type)(CHAR_TYPE_SPACE | CHAR_TYPE_COMMENT)); +} + +/** Match a JSON number: '-', [0-9]. */ +static_inline bool char_is_number(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_NUMBER); +} + +/** Match a JSON container: '{', '['. */ +static_inline bool char_is_container(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_CONTAINER); +} + +/** Match a stop character in ASCII string: '"', '\', [0x00-0x1F,0x80-0xFF]. */ +static_inline bool char_is_ascii_stop(u8 c) { + return char_is_type(c, (char_type)(CHAR_TYPE_ESC_ASCII | + CHAR_TYPE_NON_ASCII)); +} + +/** Match a line end character: '\\n', '\\r', '\0'. */ +static_inline bool char_is_line_end(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_LINE_END); +} + +/** Match a hexadecimal numeric character: [0-9a-fA-F]. */ +static_inline bool char_is_hex(u8 c) { + return char_is_type(c, (char_type)CHAR_TYPE_HEX); +} + + + +/*============================================================================== + * Digit Character Matcher + *============================================================================*/ + +/** Digit type */ +typedef u8 digi_type; + +/** Digit: '0'. */ +static const digi_type DIGI_TYPE_ZERO = 1 << 0; + +/** Digit: [1-9]. */ +static const digi_type DIGI_TYPE_NONZERO = 1 << 1; + +/** Plus sign (positive): '+'. */ +static const digi_type DIGI_TYPE_POS = 1 << 2; + +/** Minus sign (negative): '-'. */ +static const digi_type DIGI_TYPE_NEG = 1 << 3; + +/** Decimal point: '.' */ +static const digi_type DIGI_TYPE_DOT = 1 << 4; + +/** Exponent sign: 'e, 'E'. */ +static const digi_type DIGI_TYPE_EXP = 1 << 5; + +/** Digit type table (generate with misc/make_tables.c) */ +static const digi_type digi_table[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x10, 0x00, + 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** Match a character with specified type. */ +static_inline bool digi_is_type(u8 d, digi_type type) { + return (digi_table[d] & type) != 0; +} + +/** Match a sign: '+', '-' */ +static_inline bool digi_is_sign(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_POS | DIGI_TYPE_NEG)); +} + +/** Match a none zero digit: [1-9] */ +static_inline bool digi_is_nonzero(u8 d) { + return digi_is_type(d, (digi_type)DIGI_TYPE_NONZERO); +} + +/** Match a digit: [0-9] */ +static_inline bool digi_is_digit(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_ZERO | DIGI_TYPE_NONZERO)); +} + +/** Match an exponent sign: 'e', 'E'. */ +static_inline bool digi_is_exp(u8 d) { + return digi_is_type(d, (digi_type)DIGI_TYPE_EXP); +} + +/** Match a floating point indicator: '.', 'e', 'E'. */ +static_inline bool digi_is_fp(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_DOT | DIGI_TYPE_EXP)); +} + +/** Match a digit or floating point indicator: [0-9], '.', 'e', 'E'. */ +static_inline bool digi_is_digit_or_fp(u8 d) { + return digi_is_type(d, (digi_type)(DIGI_TYPE_ZERO | DIGI_TYPE_NONZERO | + DIGI_TYPE_DOT | DIGI_TYPE_EXP)); +} + + + +/*============================================================================== + * Hex Character Reader + * This function is used by JSON reader to read escaped characters. + *============================================================================*/ + +/** + This table is used to convert 4 hex character sequence to a number. + A valid hex character [0-9A-Fa-f] will mapped to it's raw number [0x00, 0x0F], + an invalid hex character will mapped to [0xF0]. + (generate with misc/make_tables.c) + */ +static const u8 hex_conv_table[256] = { + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 +}; + +/** + Scans an escaped character sequence as a UTF-16 code unit (branchless). + e.g. "\\u005C" should pass "005C" as `cur`. + + This requires the string has 4-byte zero padding. + */ +static_inline bool read_hex_u16(const u8 *cur, u16 *val) { + u16 c0, c1, c2, c3, t0, t1; + c0 = hex_conv_table[cur[0]]; + c1 = hex_conv_table[cur[1]]; + c2 = hex_conv_table[cur[2]]; + c3 = hex_conv_table[cur[3]]; + t0 = (u16)((c0 << 8) | c2); + t1 = (u16)((c1 << 8) | c3); + *val = (u16)((t0 << 4) | t1); + return ((t0 | t1) & (u16)0xF0F0) == 0; +} + + + +/*============================================================================== + * JSON Reader Utils + * These functions are used by JSON reader to read literals and comments. + *============================================================================*/ + +/** Read 'true' literal, '*cur' should be 't'. */ +static_inline bool read_true(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + u8 **end = ptr; + if (likely(byte_match_4(cur, "true"))) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + *end = cur + 4; + return true; + } + return false; +} + +/** Read 'false' literal, '*cur' should be 'f'. */ +static_inline bool read_false(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + u8 **end = ptr; + if (likely(byte_match_4(cur + 1, "alse"))) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + *end = cur + 5; + return true; + } + return false; +} + +/** Read 'null' literal, '*cur' should be 'n'. */ +static_inline bool read_null(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + u8 **end = ptr; + if (likely(byte_match_4(cur, "null"))) { + val->tag = YYJSON_TYPE_NULL; + *end = cur + 4; + return true; + } + return false; +} + +/** Read 'Inf' or 'Infinity' literal (ignoring case). */ +static_inline bool read_inf(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { +#if !YYJSON_DISABLE_NON_STANDARD + u8 *hdr = *ptr - sign; + u8 *cur = *ptr; + u8 **end = ptr; + if ((cur[0] == 'I' || cur[0] == 'i') && + (cur[1] == 'N' || cur[1] == 'n') && + (cur[2] == 'F' || cur[2] == 'f')) { + if ((cur[3] == 'I' || cur[3] == 'i') && + (cur[4] == 'N' || cur[4] == 'n') && + (cur[5] == 'I' || cur[5] == 'i') && + (cur[6] == 'T' || cur[6] == 't') && + (cur[7] == 'Y' || cur[7] == 'y')) { + cur += 8; + } else { + cur += 3; + } + *end = cur; + if (pre) { + /* add null-terminator for previous raw string */ + if (*pre) **pre = '\0'; + *pre = cur; + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + } else { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.u64 = f64_raw_get_inf(sign); + } + return true; + } +#endif + return false; +} + +/** Read 'NaN' literal (ignoring case). */ +static_inline bool read_nan(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { +#if !YYJSON_DISABLE_NON_STANDARD + u8 *hdr = *ptr - sign; + u8 *cur = *ptr; + u8 **end = ptr; + if ((cur[0] == 'N' || cur[0] == 'n') && + (cur[1] == 'A' || cur[1] == 'a') && + (cur[2] == 'N' || cur[2] == 'n')) { + cur += 3; + *end = cur; + if (pre) { + /* add null-terminator for previous raw string */ + if (*pre) **pre = '\0'; + *pre = cur; + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + } else { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.u64 = f64_raw_get_nan(sign); + } + return true; + } +#endif + return false; +} + +/** Read 'Inf', 'Infinity' or 'NaN' literal (ignoring case). */ +static_inline bool read_inf_or_nan(bool sign, u8 **ptr, u8 **pre, + yyjson_val *val) { + if (read_inf(sign, ptr, pre, val)) return true; + if (read_nan(sign, ptr, pre, val)) return true; + return false; +} + +/** Read a JSON number as raw string. */ +static_noinline bool read_number_raw(u8 **ptr, + u8 **pre, + yyjson_read_flag flg, + yyjson_val *val, + const char **msg) { + +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_raw() do { \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + + /* add null-terminator for previous raw string */ + if (*pre) **pre = '\0'; + + /* skip sign */ + cur += (*cur == '-'); + + /* read first digit, check leading zero */ + if (unlikely(!digi_is_digit(*cur))) { + if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) { + if (read_inf_or_nan(*hdr == '-', &cur, pre, val)) return_raw(); + } + return_err(cur, "no digit after minus sign"); + } + + /* read integral part */ + if (*cur == '0') { + cur++; + if (unlikely(digi_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (!digi_is_fp(*cur)) return_raw(); + } else { + while (digi_is_digit(*cur)) cur++; + if (!digi_is_fp(*cur)) return_raw(); + } + + /* read fraction part */ + if (*cur == '.') { + cur++; + if (!digi_is_digit(*cur++)) { + return_err(cur, "no digit after decimal point"); + } + while (digi_is_digit(*cur)) cur++; + } + + /* read exponent part */ + if (digi_is_exp(*cur)) { + cur += 1 + digi_is_sign(cur[1]); + if (!digi_is_digit(*cur++)) { + return_err(cur, "no digit after exponent sign"); + } + while (digi_is_digit(*cur)) cur++; + } + + return_raw(); + +#undef return_err +#undef return_raw +} + +/** + Skips spaces and comments as many as possible. + + It will return false in these cases: + 1. No character is skipped. The 'end' pointer is set as input cursor. + 2. A multiline comment is not closed. The 'end' pointer is set as the head + of this comment block. + */ +static_noinline bool skip_spaces_and_comments(u8 **ptr) { + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + while (true) { + if (byte_match_2(cur, "/*")) { + hdr = cur; + cur += 2; + while (true) { + if (byte_match_2(cur, "*/")) { + cur += 2; + break; + } + if (*cur == 0) { + *end = hdr; + return false; + } + cur++; + } + continue; + } + if (byte_match_2(cur, "//")) { + cur += 2; + while (!char_is_line_end(*cur)) cur++; + continue; + } + if (char_is_space(*cur)) { + cur += 1; + while (char_is_space(*cur)) cur++; + continue; + } + break; + } + *end = cur; + return hdr != cur; +} + +/** + Check truncated string. + Returns true if `cur` match `str` but is truncated. + */ +static_inline bool is_truncated_str(u8 *cur, u8 *end, + const char *str, + bool case_sensitive) { + usize len = strlen(str); + if (cur + len <= end || end <= cur) return false; + if (case_sensitive) { + return memcmp(cur, str, (usize)(end - cur)) == 0; + } + for (; cur < end; cur++, str++) { + if ((*cur != (u8)*str) && (*cur != (u8)*str - 'a' + 'A')) { + return false; + } + } + return true; +} + +/** + Check truncated JSON on parsing errors. + Returns true if the input is valid but truncated. + */ +static_noinline bool is_truncated_end(u8 *hdr, u8 *cur, u8 *end, + yyjson_read_code code, + yyjson_read_flag flg) { + if (cur >= end) return true; + if (code == YYJSON_READ_ERROR_LITERAL) { + if (is_truncated_str(cur, end, "true", true) || + is_truncated_str(cur, end, "false", true) || + is_truncated_str(cur, end, "null", true)) { + return true; + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER || + code == YYJSON_READ_ERROR_INVALID_NUMBER || + code == YYJSON_READ_ERROR_LITERAL) { + if ((flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (*cur == '-') cur++; + if (is_truncated_str(cur, end, "infinity", false) || + is_truncated_str(cur, end, "nan", false)) { + return true; + } + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CONTENT) { + if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) { + if (hdr + 3 <= cur && + is_truncated_str(cur - 3, end, "infinity", false)) { + return true; /* e.g. infin would be read as inf + in */ + } + } + } + if (code == YYJSON_READ_ERROR_INVALID_STRING) { + usize len = (usize)(end - cur); + + /* unicode escape sequence */ + if (*cur == '\\') { + if (len == 1) return true; + if (len <= 5) { + if (*++cur != 'u') return false; + for (++cur; cur < end; cur++) { + if (!char_is_hex(*cur)) return false; + } + return true; + } + return false; + } + + /* 2 to 4 bytes UTF-8, see `read_string()` for details. */ + if (*cur & 0x80) { + u8 c0 = cur[0], c1 = cur[1], c2 = cur[2]; + if (len == 1) { + /* 2 bytes UTF-8, truncated */ + if ((c0 & 0xE0) == 0xC0 && (c0 & 0x1E) != 0x00) return true; + /* 3 bytes UTF-8, truncated */ + if ((c0 & 0xF0) == 0xE0) return true; + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && (c0 & 0x07) <= 0x04) return true; + } + if (len == 2) { + /* 3 bytes UTF-8, truncated */ + if ((c0 & 0xF0) == 0xE0 && + (c1 & 0xC0) == 0x80) { + u8 pat = (u8)(((c0 & 0x0F) << 1) | ((c1 & 0x20) >> 5)); + return 0x01 <= pat && pat != 0x1B; + } + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && + (c1 & 0xC0) == 0x80) { + u8 pat = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); + return 0x01 <= pat && pat <= 0x10; + } + } + if (len == 3) { + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && + (c1 & 0xC0) == 0x80 && + (c2 & 0xC0) == 0x80) { + u8 pat = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); + return 0x01 <= pat && pat <= 0x10; + } + } + } + } + return false; +} + + + +#if YYJSON_HAS_IEEE_754 && !YYJSON_DISABLE_FAST_FP_CONV /* FP_READER */ + +/*============================================================================== + * BigInt For Floating Point Number Reader + * + * The bigint algorithm is used by floating-point number reader to get correctly + * rounded result for numbers with lots of digits. This part of code is rarely + * used for common numbers. + *============================================================================*/ + +/** Maximum exponent of exact pow10 */ +#define U64_POW10_MAX_EXP 19 + +/** Table: [ 10^0, ..., 10^19 ] (generate with misc/make_tables.c) */ +static const u64 u64_pow10_table[U64_POW10_MAX_EXP + 1] = { + U64(0x00000000, 0x00000001), U64(0x00000000, 0x0000000A), + U64(0x00000000, 0x00000064), U64(0x00000000, 0x000003E8), + U64(0x00000000, 0x00002710), U64(0x00000000, 0x000186A0), + U64(0x00000000, 0x000F4240), U64(0x00000000, 0x00989680), + U64(0x00000000, 0x05F5E100), U64(0x00000000, 0x3B9ACA00), + U64(0x00000002, 0x540BE400), U64(0x00000017, 0x4876E800), + U64(0x000000E8, 0xD4A51000), U64(0x00000918, 0x4E72A000), + U64(0x00005AF3, 0x107A4000), U64(0x00038D7E, 0xA4C68000), + U64(0x002386F2, 0x6FC10000), U64(0x01634578, 0x5D8A0000), + U64(0x0DE0B6B3, 0xA7640000), U64(0x8AC72304, 0x89E80000) +}; + +/** Maximum numbers of chunks used by a bigint (58 is enough here). */ +#define BIGINT_MAX_CHUNKS 64 + +/** Unsigned arbitrarily large integer */ +typedef struct bigint { + u32 used; /* used chunks count, should not be 0 */ + u64 bits[BIGINT_MAX_CHUNKS]; /* chunks */ +} bigint; + +/** + Evaluate 'big += val'. + @param big A big number (can be 0). + @param val An unsigned integer (can be 0). + */ +static_inline void bigint_add_u64(bigint *big, u64 val) { + u32 idx, max; + u64 num = big->bits[0]; + u64 add = num + val; + big->bits[0] = add; + if (likely((add >= num) || (add >= val))) return; + for ((void)(idx = 1), max = big->used; idx < max; idx++) { + if (likely(big->bits[idx] != U64_MAX)) { + big->bits[idx] += 1; + return; + } + big->bits[idx] = 0; + } + big->bits[big->used++] = 1; +} + +/** + Evaluate 'big *= val'. + @param big A big number (can be 0). + @param val An unsigned integer (cannot be 0). + */ +static_inline void bigint_mul_u64(bigint *big, u64 val) { + u32 idx = 0, max = big->used; + u64 hi, lo, carry = 0; + for (; idx < max; idx++) { + if (big->bits[idx]) break; + } + for (; idx < max; idx++) { + u128_mul_add(big->bits[idx], val, carry, &hi, &lo); + big->bits[idx] = lo; + carry = hi; + } + if (carry) big->bits[big->used++] = carry; +} + +/** + Evaluate 'big *= 2^exp'. + @param big A big number (can be 0). + @param exp An exponent integer (can be 0). + */ +static_inline void bigint_mul_pow2(bigint *big, u32 exp) { + u32 shft = exp % 64; + u32 move = exp / 64; + u32 idx = big->used; + if (unlikely(shft == 0)) { + for (; idx > 0; idx--) { + big->bits[idx + move - 1] = big->bits[idx - 1]; + } + big->used += move; + while (move) big->bits[--move] = 0; + } else { + big->bits[idx] = 0; + for (; idx > 0; idx--) { + u64 num = big->bits[idx] << shft; + num |= big->bits[idx - 1] >> (64 - shft); + big->bits[idx + move] = num; + } + big->bits[move] = big->bits[0] << shft; + big->used += move + (big->bits[big->used + move] > 0); + while (move) big->bits[--move] = 0; + } +} + +/** + Evaluate 'big *= 10^exp'. + @param big A big number (can be 0). + @param exp An exponent integer (cannot be 0). + */ +static_inline void bigint_mul_pow10(bigint *big, i32 exp) { + for (; exp >= U64_POW10_MAX_EXP; exp -= U64_POW10_MAX_EXP) { + bigint_mul_u64(big, u64_pow10_table[U64_POW10_MAX_EXP]); + } + if (exp) { + bigint_mul_u64(big, u64_pow10_table[exp]); + } +} + +/** + Compare two bigint. + @return -1 if 'a < b', +1 if 'a > b', 0 if 'a == b'. + */ +static_inline i32 bigint_cmp(bigint *a, bigint *b) { + u32 idx = a->used; + if (a->used < b->used) return -1; + if (a->used > b->used) return +1; + while (idx-- > 0) { + u64 av = a->bits[idx]; + u64 bv = b->bits[idx]; + if (av < bv) return -1; + if (av > bv) return +1; + } + return 0; +} + +/** + Evaluate 'big = val'. + @param big A big number (can be 0). + @param val An unsigned integer (can be 0). + */ +static_inline void bigint_set_u64(bigint *big, u64 val) { + big->used = 1; + big->bits[0] = val; +} + +/** Set a bigint with floating point number string. */ +static_noinline void bigint_set_buf(bigint *big, u64 sig, i32 *exp, + u8 *sig_cut, u8 *sig_end, u8 *dot_pos) { + + if (unlikely(!sig_cut)) { + /* no digit cut, set significant part only */ + bigint_set_u64(big, sig); + return; + + } else { + /* some digits were cut, read them from 'sig_cut' to 'sig_end' */ + u8 *hdr = sig_cut; + u8 *cur = hdr; + u32 len = 0; + u64 val = 0; + bool dig_big_cut = false; + bool has_dot = (hdr < dot_pos) & (dot_pos < sig_end); + u32 dig_len_total = U64_SAFE_DIG + (u32)(sig_end - hdr) - has_dot; + + sig -= (*sig_cut >= '5'); /* sig was rounded before */ + if (dig_len_total > F64_MAX_DEC_DIG) { + dig_big_cut = true; + sig_end -= dig_len_total - (F64_MAX_DEC_DIG + 1); + sig_end -= (dot_pos + 1 == sig_end); + dig_len_total = (F64_MAX_DEC_DIG + 1); + } + *exp -= (i32)dig_len_total - U64_SAFE_DIG; + + big->used = 1; + big->bits[0] = sig; + while (cur < sig_end) { + if (likely(cur != dot_pos)) { + val = val * 10 + (u8)(*cur++ - '0'); + len++; + if (unlikely(cur == sig_end && dig_big_cut)) { + /* The last digit must be non-zero, */ + /* set it to '1' for correct rounding. */ + val = val - (val % 10) + 1; + } + if (len == U64_SAFE_DIG || cur == sig_end) { + bigint_mul_pow10(big, (i32)len); + bigint_add_u64(big, val); + val = 0; + len = 0; + } + } else { + cur++; + } + } + } +} + + + +/*============================================================================== + * Diy Floating Point + *============================================================================*/ + +/** "Do It Yourself Floating Point" struct. */ +typedef struct diy_fp { + u64 sig; /* significand */ + i32 exp; /* exponent, base 2 */ + i32 pad; /* padding, useless */ +} diy_fp; + +/** Get cached rounded diy_fp with pow(10, e) The input value must in range + [POW10_SIG_TABLE_MIN_EXP, POW10_SIG_TABLE_MAX_EXP]. */ +static_inline diy_fp diy_fp_get_cached_pow10(i32 exp10) { + diy_fp fp; + u64 sig_ext; + pow10_table_get_sig(exp10, &fp.sig, &sig_ext); + pow10_table_get_exp(exp10, &fp.exp); + fp.sig += (sig_ext >> 63); + return fp; +} + +/** Returns fp * fp2. */ +static_inline diy_fp diy_fp_mul(diy_fp fp, diy_fp fp2) { + u64 hi, lo; + u128_mul(fp.sig, fp2.sig, &hi, &lo); + fp.sig = hi + (lo >> 63); + fp.exp += fp2.exp + 64; + return fp; +} + +/** Convert diy_fp to IEEE-754 raw value. */ +static_inline u64 diy_fp_to_ieee_raw(diy_fp fp) { + u64 sig = fp.sig; + i32 exp = fp.exp; + u32 lz_bits; + if (unlikely(fp.sig == 0)) return 0; + + lz_bits = u64_lz_bits(sig); + sig <<= lz_bits; + sig >>= F64_BITS - F64_SIG_FULL_BITS; + exp -= (i32)lz_bits; + exp += F64_BITS - F64_SIG_FULL_BITS; + exp += F64_SIG_BITS; + + if (unlikely(exp >= F64_MAX_BIN_EXP)) { + /* overflow */ + return F64_RAW_INF; + } else if (likely(exp >= F64_MIN_BIN_EXP - 1)) { + /* normal */ + exp += F64_EXP_BIAS; + return ((u64)exp << F64_SIG_BITS) | (sig & F64_SIG_MASK); + } else if (likely(exp >= F64_MIN_BIN_EXP - F64_SIG_FULL_BITS)) { + /* subnormal */ + return sig >> (F64_MIN_BIN_EXP - exp - 1); + } else { + /* underflow */ + return 0; + } +} + + + +/*============================================================================== + * JSON Number Reader (IEEE-754) + *============================================================================*/ + +/** Maximum exact pow10 exponent for double value. */ +#define F64_POW10_EXP_MAX_EXACT 22 + +/** Cached pow10 table. */ +static const f64 f64_pow10_table[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, + 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 +}; + +/** + Read a JSON number. + + 1. This function assume that the floating-point number is in IEEE-754 format. + 2. This function support uint64/int64/double number. If an integer number + cannot fit in uint64/int64, it will returns as a double number. If a double + number is infinite, the return value is based on flag. + 3. This function (with inline attribute) may generate a lot of instructions. + */ +static_inline bool read_number(u8 **ptr, + u8 **pre, + yyjson_read_flag flg, + yyjson_val *val, + const char **msg) { + +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_0() do { \ + val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ + val->uni.u64 = 0; \ + *end = cur; return true; \ +} while (false) + +#define return_i64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ + val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ + *end = cur; return true; \ +} while (false) + +#define return_f64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_f64_bin(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_inf() do { \ + if (flg & YYJSON_READ_BIGNUM_AS_RAW) return_raw(); \ + if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) return_f64_bin(F64_RAW_INF); \ + else return_err(hdr, "number is infinity when parsed as double"); \ +} while (false) + +#define return_raw() do { \ + if (*pre) **pre = '\0'; /* add null-terminator for previous raw string */ \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u8 *sig_cut = NULL; /* significant part cutting position for long number */ + u8 *sig_end = NULL; /* significant part ending position */ + u8 *dot_pos = NULL; /* decimal point position */ + + u64 sig = 0; /* significant part of the number */ + i32 exp = 0; /* exponent part of the number */ + + bool exp_sign; /* temporary exponent sign from literal part */ + i64 exp_sig = 0; /* temporary exponent number from significant part */ + i64 exp_lit = 0; /* temporary exponent number from exponent literal part */ + u64 num; /* temporary number for reading */ + u8 *tmp; /* temporary cursor for reading */ + + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + bool sign; + + /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ + if (unlikely(pre && !(flg & YYJSON_READ_BIGNUM_AS_RAW))) { + return read_number_raw(ptr, pre, flg, val, msg); + } + + sign = (*hdr == '-'); + cur += sign; + + /* begin with a leading zero or non-digit */ + if (unlikely(!digi_is_nonzero(*cur))) { /* 0 or non-digit char */ + if (unlikely(*cur != '0')) { /* non-digit char */ + if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) { + if (read_inf_or_nan(sign, &cur, pre, val)) { + *end = cur; + return true; + } + } + return_err(cur, "no digit after minus sign"); + } + /* begin with 0 */ + if (likely(!digi_is_digit_or_fp(*++cur))) return_0(); + if (likely(*cur == '.')) { + dot_pos = cur++; + if (unlikely(!digi_is_digit(*cur))) { + return_err(cur, "no digit after decimal point"); + } + while (unlikely(*cur == '0')) cur++; + if (likely(digi_is_digit(*cur))) { + /* first non-zero digit after decimal point */ + sig = (u64)(*cur - '0'); /* read first digit */ + cur--; + goto digi_frac_1; /* continue read fraction part */ + } + } + if (unlikely(digi_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (unlikely(digi_is_exp(*cur))) { /* 0 with any exponent is still 0 */ + cur += (usize)1 + digi_is_sign(cur[1]); + if (unlikely(!digi_is_digit(*cur))) { + return_err(cur, "no digit after exponent sign"); + } + while (digi_is_digit(*++cur)); + } + return_f64_bin(0); + } + + /* begin with non-zero digit */ + sig = (u64)(*cur - '0'); + + /* + Read integral part, same as the following code. + + for (int i = 1; i <= 18; i++) { + num = cur[i] - '0'; + if (num <= 9) sig = num + sig * 10; + else goto digi_sepr_i; + } + */ +#define expr_intg(i) \ + if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ + else { goto digi_sepr_##i; } + repeat_in_1_18(expr_intg) +#undef expr_intg + + + cur += 19; /* skip continuous 19 digits */ + if (!digi_is_digit_or_fp(*cur)) { + /* this number is an integer consisting of 19 digits */ + if (sign && (sig > ((u64)1 << 63))) { /* overflow */ + if (flg & YYJSON_READ_BIGNUM_AS_RAW) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + goto digi_intg_more; /* read more digits in integral part */ + + + /* process first non-digit character */ +#define expr_sepr(i) \ + digi_sepr_##i: \ + if (likely(!digi_is_fp(cur[i]))) { cur += i; return_i64(sig); } \ + dot_pos = cur + i; \ + if (likely(cur[i] == '.')) goto digi_frac_##i; \ + cur += i; sig_end = cur; goto digi_exp_more; + repeat_in_1_18(expr_sepr) +#undef expr_sepr + + + /* read fraction part */ +#define expr_frac(i) \ + digi_frac_##i: \ + if (likely((num = (u64)(cur[i + 1] - (u8)'0')) <= 9)) \ + sig = num + sig * 10; \ + else { goto digi_stop_##i; } + repeat_in_1_18(expr_frac) +#undef expr_frac + + cur += 20; /* skip 19 digits and 1 decimal point */ + if (!digi_is_digit(*cur)) goto digi_frac_end; /* fraction part end */ + goto digi_frac_more; /* read more digits in fraction part */ + + + /* significant part end */ +#define expr_stop(i) \ + digi_stop_##i: \ + cur += i + 1; \ + goto digi_frac_end; + repeat_in_1_18(expr_stop) +#undef expr_stop + + + /* read more digits in integral part */ +digi_intg_more: + if (digi_is_digit(*cur)) { + if (!digi_is_digit_or_fp(cur[1])) { + /* this number is an integer consisting of 20 digits */ + num = (u64)(*cur - '0'); + if ((sig < (U64_MAX / 10)) || + (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { + sig = num + sig * 10; + cur++; + /* convert to double if overflow */ + if (sign) { + if (flg & YYJSON_READ_BIGNUM_AS_RAW) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + } + } + + if (digi_is_exp(*cur)) { + dot_pos = cur; + goto digi_exp_more; + } + + if (*cur == '.') { + dot_pos = cur++; + if (!digi_is_digit(*cur)) { + return_err(cur, "no digit after decimal point"); + } + } + + + /* read more digits in fraction part */ +digi_frac_more: + sig_cut = cur; /* too large to fit in u64, excess digits need to be cut */ + sig += (*cur >= '5'); /* round */ + while (digi_is_digit(*++cur)); + if (!dot_pos) { + if (!digi_is_fp(*cur) && (flg & YYJSON_READ_BIGNUM_AS_RAW)) { + return_raw(); /* it's a large integer */ + } + dot_pos = cur; + if (*cur == '.') { + if (!digi_is_digit(*++cur)) { + return_err(cur, "no digit after decimal point"); + } + while (digi_is_digit(*cur)) cur++; + } + } + exp_sig = (i64)(dot_pos - sig_cut); + exp_sig += (dot_pos < sig_cut); + + /* ignore trailing zeros */ + tmp = cur - 1; + while (*tmp == '0' || *tmp == '.') tmp--; + if (tmp < sig_cut) { + sig_cut = NULL; + } else { + sig_end = cur; + } + + if (digi_is_exp(*cur)) goto digi_exp_more; + goto digi_exp_finish; + + + /* fraction part end */ +digi_frac_end: + if (unlikely(dot_pos + 1 == cur)) { + return_err(cur, "no digit after decimal point"); + } + sig_end = cur; + exp_sig = -(i64)((u64)(cur - dot_pos) - 1); + if (likely(!digi_is_exp(*cur))) { + if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { + return_f64_bin(0); /* underflow */ + } + exp = (i32)exp_sig; + goto digi_finish; + } else { + goto digi_exp_more; + } + + + /* read exponent part */ +digi_exp_more: + exp_sign = (*++cur == '-'); + cur += digi_is_sign(*cur); + if (unlikely(!digi_is_digit(*cur))) { + return_err(cur, "no digit after exponent sign"); + } + while (*cur == '0') cur++; + + /* read exponent literal */ + tmp = cur; + while (digi_is_digit(*cur)) { + exp_lit = (i64)((u8)(*cur++ - '0') + (u64)exp_lit * 10); + } + if (unlikely(cur - tmp >= U64_SAFE_DIG)) { + if (exp_sign) { + return_f64_bin(0); /* underflow */ + } else { + return_inf(); /* overflow */ + } + } + exp_sig += exp_sign ? -exp_lit : exp_lit; + + + /* validate exponent value */ +digi_exp_finish: + if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { + return_f64_bin(0); /* underflow */ + } + if (unlikely(exp_sig > F64_MAX_DEC_EXP)) { + return_inf(); /* overflow */ + } + exp = (i32)exp_sig; + + + /* all digit read finished */ +digi_finish: + + /* + Fast path 1: + + 1. The floating-point number calculation should be accurate, see the + comments of macro `YYJSON_DOUBLE_MATH_CORRECT`. + 2. Correct rounding should be performed (fegetround() == FE_TONEAREST). + 3. The input of floating point number calculation does not lose precision, + which means: 64 - leading_zero(input) - trailing_zero(input) < 53. + + We don't check all available inputs here, because that would make the code + more complicated, and not friendly to branch predictor. + */ +#if YYJSON_DOUBLE_MATH_CORRECT + if (sig < ((u64)1 << 53) && + exp >= -F64_POW10_EXP_MAX_EXACT && + exp <= +F64_POW10_EXP_MAX_EXACT) { + f64 dbl = (f64)sig; + if (exp < 0) { + dbl /= f64_pow10_table[-exp]; + } else { + dbl *= f64_pow10_table[+exp]; + } + return_f64(dbl); + } +#endif + + /* + Fast path 2: + + To keep it simple, we only accept normal number here, + let the slow path to handle subnormal and infinity number. + */ + if (likely(!sig_cut && + exp > -F64_MAX_DEC_EXP + 1 && + exp < +F64_MAX_DEC_EXP - 20)) { + /* + The result value is exactly equal to (sig * 10^exp), + the exponent part (10^exp) can be converted to (sig2 * 2^exp2). + + The sig2 can be an infinite length number, only the highest 128 bits + is cached in the pow10_sig_table. + + Now we have these bits: + sig1 (normalized 64bit) : aaaaaaaa + sig2 (higher 64bit) : bbbbbbbb + sig2_ext (lower 64bit) : cccccccc + sig2_cut (extra unknown bits) : dddddddddddd.... + + And the calculation process is: + ---------------------------------------- + aaaaaaaa * + bbbbbbbbccccccccdddddddddddd.... + ---------------------------------------- + abababababababab + + acacacacacacacac + + adadadadadadadadadad.... + ---------------------------------------- + [hi____][lo____] + + [hi2___][lo2___] + + [unknown___________....] + ---------------------------------------- + + The addition with carry may affect higher bits, but if there is a 0 + in higher bits, the bits higher than 0 will not be affected. + + `lo2` + `unknown` may get a carry bit and may affect `hi2`, the max + value of `hi2` is 0xFFFFFFFFFFFFFFFE, so `hi2` will not overflow. + + `lo` + `hi2` may also get a carry bit and may affect `hi`, but only + the highest significant 53 bits of `hi` is needed. If there is a 0 + in the lower bits of `hi`, then all the following bits can be dropped. + + To convert the result to IEEE-754 double number, we need to perform + correct rounding: + 1. if bit 54 is 0, round down, + 2. if bit 54 is 1 and any bit beyond bit 54 is 1, round up, + 3. if bit 54 is 1 and all bits beyond bit 54 are 0, round to even, + as the extra bits is unknown, this case will not be handled here. + */ + + u64 raw; + u64 sig1, sig2, sig2_ext, hi, lo, hi2, lo2, add, bits; + i32 exp2; + u32 lz; + bool exact = false, carry, round_up; + + /* convert (10^exp) to (sig2 * 2^exp2) */ + pow10_table_get_sig(exp, &sig2, &sig2_ext); + pow10_table_get_exp(exp, &exp2); + + /* normalize and multiply */ + lz = u64_lz_bits(sig); + sig1 = sig << lz; + exp2 -= (i32)lz; + u128_mul(sig1, sig2, &hi, &lo); + + /* + The `hi` is in range [0x4000000000000000, 0xFFFFFFFFFFFFFFFE], + To get normalized value, `hi` should be shifted to the left by 0 or 1. + + The highest significant 53 bits is used by IEEE-754 double number, + and the bit 54 is used to detect rounding direction. + + The lowest (64 - 54 - 1) bits is used to check whether it contains 0. + */ + bits = hi & (((u64)1 << (64 - 54 - 1)) - 1); + if (bits - 1 < (((u64)1 << (64 - 54 - 1)) - 2)) { + /* + (bits != 0 && bits != 0x1FF) => (bits - 1 < 0x1FF - 1) + The `bits` is not zero, so we don't need to check `round to even` + case. The `bits` contains bit `0`, so we can drop the extra bits + after `0`. + */ + exact = true; + + } else { + /* + (bits == 0 || bits == 0x1FF) + The `bits` is filled with all `0` or all `1`, so we need to check + lower bits with another 64-bit multiplication. + */ + u128_mul(sig1, sig2_ext, &hi2, &lo2); + + add = lo + hi2; + if (add + 1 > (u64)1) { + /* + (add != 0 && add != U64_MAX) => (add + 1 > 1) + The `add` is not zero, so we don't need to check `round to + even` case. The `add` contains bit `0`, so we can drop the + extra bits after `0`. The `hi` cannot be U64_MAX, so it will + not overflow. + */ + carry = add < lo || add < hi2; + hi += carry; + exact = true; + } + } + + if (exact) { + /* normalize */ + lz = hi < ((u64)1 << 63); + hi <<= lz; + exp2 -= (i32)lz; + exp2 += 64; + + /* test the bit 54 and get rounding direction */ + round_up = (hi & ((u64)1 << (64 - 54))) > (u64)0; + hi += (round_up ? ((u64)1 << (64 - 54)) : (u64)0); + + /* test overflow */ + if (hi < ((u64)1 << (64 - 54))) { + hi = ((u64)1 << 63); + exp2 += 1; + } + + /* This is a normal number, convert it to IEEE-754 format. */ + hi >>= F64_BITS - F64_SIG_FULL_BITS; + exp2 += F64_BITS - F64_SIG_FULL_BITS + F64_SIG_BITS; + exp2 += F64_EXP_BIAS; + raw = ((u64)exp2 << F64_SIG_BITS) | (hi & F64_SIG_MASK); + return_f64_bin(raw); + } + } + + /* + Slow path: read double number exactly with diyfp. + 1. Use cached diyfp to get an approximation value. + 2. Use bigcomp to check the approximation value if needed. + + This algorithm refers to google's double-conversion project: + https://github.com/google/double-conversion + */ + { + const i32 ERR_ULP_LOG = 3; + const i32 ERR_ULP = 1 << ERR_ULP_LOG; + const i32 ERR_CACHED_POW = ERR_ULP / 2; + const i32 ERR_MUL_FIXED = ERR_ULP / 2; + const i32 DIY_SIG_BITS = 64; + const i32 EXP_BIAS = F64_EXP_BIAS + F64_SIG_BITS; + const i32 EXP_SUBNORMAL = -EXP_BIAS + 1; + + u64 fp_err; + u32 bits; + i32 order_of_magnitude; + i32 effective_significand_size; + i32 precision_digits_count; + u64 precision_bits; + u64 half_way; + + u64 raw; + diy_fp fp, fp_upper; + bigint big_full, big_comp; + i32 cmp; + + fp.sig = sig; + fp.exp = 0; + fp_err = sig_cut ? (u64)(ERR_ULP / 2) : (u64)0; + + /* normalize */ + bits = u64_lz_bits(fp.sig); + fp.sig <<= bits; + fp.exp -= (i32)bits; + fp_err <<= bits; + + /* multiply and add error */ + fp = diy_fp_mul(fp, diy_fp_get_cached_pow10(exp)); + fp_err += (u64)ERR_CACHED_POW + (fp_err != 0) + (u64)ERR_MUL_FIXED; + + /* normalize */ + bits = u64_lz_bits(fp.sig); + fp.sig <<= bits; + fp.exp -= (i32)bits; + fp_err <<= bits; + + /* effective significand */ + order_of_magnitude = DIY_SIG_BITS + fp.exp; + if (likely(order_of_magnitude >= EXP_SUBNORMAL + F64_SIG_FULL_BITS)) { + effective_significand_size = F64_SIG_FULL_BITS; + } else if (order_of_magnitude <= EXP_SUBNORMAL) { + effective_significand_size = 0; + } else { + effective_significand_size = order_of_magnitude - EXP_SUBNORMAL; + } + + /* precision digits count */ + precision_digits_count = DIY_SIG_BITS - effective_significand_size; + if (unlikely(precision_digits_count + ERR_ULP_LOG >= DIY_SIG_BITS)) { + i32 shr = (precision_digits_count + ERR_ULP_LOG) - DIY_SIG_BITS + 1; + fp.sig >>= shr; + fp.exp += shr; + fp_err = (fp_err >> shr) + 1 + (u32)ERR_ULP; + precision_digits_count -= shr; + } + + /* half way */ + precision_bits = fp.sig & (((u64)1 << precision_digits_count) - 1); + precision_bits *= (u32)ERR_ULP; + half_way = (u64)1 << (precision_digits_count - 1); + half_way *= (u32)ERR_ULP; + + /* rounding */ + fp.sig >>= precision_digits_count; + fp.sig += (precision_bits >= half_way + fp_err); + fp.exp += precision_digits_count; + + /* get IEEE double raw value */ + raw = diy_fp_to_ieee_raw(fp); + if (unlikely(raw == F64_RAW_INF)) return_inf(); + if (likely(precision_bits <= half_way - fp_err || + precision_bits >= half_way + fp_err)) { + return_f64_bin(raw); /* number is accurate */ + } + /* now the number is the correct value, or the next lower value */ + + /* upper boundary */ + if (raw & F64_EXP_MASK) { + fp_upper.sig = (raw & F64_SIG_MASK) + ((u64)1 << F64_SIG_BITS); + fp_upper.exp = (i32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + } else { + fp_upper.sig = (raw & F64_SIG_MASK); + fp_upper.exp = 1; + } + fp_upper.exp -= F64_EXP_BIAS + F64_SIG_BITS; + fp_upper.sig <<= 1; + fp_upper.exp -= 1; + fp_upper.sig += 1; /* add half ulp */ + + /* compare with bigint */ + bigint_set_buf(&big_full, sig, &exp, sig_cut, sig_end, dot_pos); + bigint_set_u64(&big_comp, fp_upper.sig); + if (exp >= 0) { + bigint_mul_pow10(&big_full, +exp); + } else { + bigint_mul_pow10(&big_comp, -exp); + } + if (fp_upper.exp > 0) { + bigint_mul_pow2(&big_comp, (u32)+fp_upper.exp); + } else { + bigint_mul_pow2(&big_full, (u32)-fp_upper.exp); + } + cmp = bigint_cmp(&big_full, &big_comp); + if (likely(cmp != 0)) { + /* round down or round up */ + raw += (cmp > 0); + } else { + /* falls midway, round to even */ + raw += (raw & 1); + } + + if (unlikely(raw == F64_RAW_INF)) return_inf(); + return_f64_bin(raw); + } + +#undef return_err +#undef return_inf +#undef return_0 +#undef return_i64 +#undef return_f64 +#undef return_f64_bin +#undef return_raw +} + + + +#else /* FP_READER */ + +/** + Read a JSON number. + This is a fallback function if the custom number reader is disabled. + This function use libc's strtod() to read floating-point number. + */ +static_noinline bool read_number(u8 **ptr, + u8 **pre, + yyjson_read_flag flg, + yyjson_val *val, + const char **msg) { + +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_0() do { \ + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ + val->uni.u64 = 0; \ + *end = cur; return true; \ +} while (false) + +#define return_i64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ + val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ + *end = cur; return true; \ +} while (false) + +#define return_f64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_f64_bin(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_inf() do { \ + if (flg & YYJSON_READ_BIGNUM_AS_RAW) return_raw(); \ + if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) return_f64_bin(F64_RAW_INF); \ + else return_err(hdr, "number is infinity when parsed as double"); \ +} while (false) + +#define return_raw() do { \ + if (*pre) **pre = '\0'; /* add null-terminator for previous raw string */ \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u64 sig, num; + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + u8 *dot = NULL; + u8 *f64_end = NULL; + bool sign; + + /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ + if (unlikely(pre && !(flg & YYJSON_READ_BIGNUM_AS_RAW))) { + return read_number_raw(ptr, pre, flg, val, msg); + } + + sign = (*hdr == '-'); + cur += sign; + sig = (u8)(*cur - '0'); + + /* read first digit, check leading zero */ + if (unlikely(!digi_is_digit(*cur))) { + if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) { + if (read_inf_or_nan(sign, &cur, pre, val)) { + *end = cur; + return true; + } + } + return_err(cur, "no digit after minus sign"); + } + if (*cur == '0') { + cur++; + if (unlikely(digi_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (!digi_is_fp(*cur)) return_0(); + goto read_double; + } + + /* read continuous digits, up to 19 characters */ +#define expr_intg(i) \ + if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ + else { cur += i; goto intg_end; } + repeat_in_1_18(expr_intg) +#undef expr_intg + + /* here are 19 continuous digits, skip them */ + cur += 19; + if (digi_is_digit(cur[0]) && !digi_is_digit_or_fp(cur[1])) { + /* this number is an integer consisting of 20 digits */ + num = (u8)(*cur - '0'); + if ((sig < (U64_MAX / 10)) || + (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { + sig = num + sig * 10; + cur++; + if (sign) { + if (flg & YYJSON_READ_BIGNUM_AS_RAW) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + } + +intg_end: + /* continuous digits ended */ + if (!digi_is_digit_or_fp(*cur)) { + /* this number is an integer consisting of 1 to 19 digits */ + if (sign && (sig > ((u64)1 << 63))) { + if (flg & YYJSON_READ_BIGNUM_AS_RAW) return_raw(); + return_f64(normalized_u64_to_f64(sig)); + } + return_i64(sig); + } + +read_double: + /* this number should be read as double */ + while (digi_is_digit(*cur)) cur++; + if (!digi_is_fp(*cur) && (flg & YYJSON_READ_BIGNUM_AS_RAW)) { + return_raw(); /* it's a large integer */ + } + if (*cur == '.') { + /* skip fraction part */ + dot = cur; + cur++; + if (!digi_is_digit(*cur)) { + return_err(cur, "no digit after decimal point"); + } + cur++; + while (digi_is_digit(*cur)) cur++; + } + if (digi_is_exp(*cur)) { + /* skip exponent part */ + cur += 1 + digi_is_sign(cur[1]); + if (!digi_is_digit(*cur)) { + return_err(cur, "no digit after exponent sign"); + } + cur++; + while (digi_is_digit(*cur)) cur++; + } + + /* + libc's strtod() is used to parse the floating-point number. + + Note that the decimal point character used by strtod() is locale-dependent, + and the rounding direction may affected by fesetround(). + + For currently known locales, (en, zh, ja, ko, am, he, hi) use '.' as the + decimal point, while other locales use ',' as the decimal point. + + Here strtod() is called twice for different locales, but if another thread + happens calls setlocale() between two strtod(), parsing may still fail. + */ + val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); + if (unlikely(f64_end != cur)) { + /* replace '.' with ',' for locale */ + bool cut = (*cur == ','); + if (cut) *cur = ' '; + if (dot) *dot = ','; + val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); + /* restore ',' to '.' */ + if (cut) *cur = ','; + if (dot) *dot = '.'; + if (unlikely(f64_end != cur)) { + return_err(hdr, "strtod() failed to parse the number"); + } + } + if (unlikely(val->uni.f64 >= HUGE_VAL || val->uni.f64 <= -HUGE_VAL)) { + return_inf(); + } + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + *end = cur; + return true; + +#undef return_err +#undef return_0 +#undef return_i64 +#undef return_f64 +#undef return_f64_bin +#undef return_inf +#undef return_raw +} + +#endif /* FP_READER */ + + + +/*============================================================================== + * JSON String Reader + *============================================================================*/ + +/** + Read a JSON string. + @param ptr The head pointer of string before '"' prefix (inout). + @param lst JSON last position. + @param inv Allow invalid unicode. + @param val The string value to be written. + @param msg The error message pointer. + @return Whether success. + */ +static_inline bool read_string(u8 **ptr, + u8 *lst, + bool inv, + yyjson_val *val, + const char **msg) { + /* + Each unicode code point is encoded as 1 to 4 bytes in UTF-8 encoding, + we use 4-byte mask and pattern value to validate UTF-8 byte sequence, + this requires the input data to have 4-byte zero padding. + --------------------------------------------------- + 1 byte + unicode range [U+0000, U+007F] + unicode min [.......0] + unicode max [.1111111] + bit pattern [0.......] + --------------------------------------------------- + 2 byte + unicode range [U+0080, U+07FF] + unicode min [......10 ..000000] + unicode max [...11111 ..111111] + bit require [...xxxx. ........] (1E 00) + bit mask [xxx..... xx......] (E0 C0) + bit pattern [110..... 10......] (C0 80) + --------------------------------------------------- + 3 byte + unicode range [U+0800, U+FFFF] + unicode min [........ ..100000 ..000000] + unicode max [....1111 ..111111 ..111111] + bit require [....xxxx ..x..... ........] (0F 20 00) + bit mask [xxxx.... xx...... xx......] (F0 C0 C0) + bit pattern [1110.... 10...... 10......] (E0 80 80) + --------------------------------------------------- + 3 byte invalid (reserved for surrogate halves) + unicode range [U+D800, U+DFFF] + unicode min [....1101 ..100000 ..000000] + unicode max [....1101 ..111111 ..111111] + bit mask [....xxxx ..x..... ........] (0F 20 00) + bit pattern [....1101 ..1..... ........] (0D 20 00) + --------------------------------------------------- + 4 byte + unicode range [U+10000, U+10FFFF] + unicode min [........ ...10000 ..000000 ..000000] + unicode max [.....100 ..001111 ..111111 ..111111] + bit require [.....xxx ..xx.... ........ ........] (07 30 00 00) + bit mask [xxxxx... xx...... xx...... xx......] (F8 C0 C0 C0) + bit pattern [11110... 10...... 10...... 10......] (F0 80 80 80) + --------------------------------------------------- + */ +#if YYJSON_ENDIAN == YYJSON_BIG_ENDIAN + const u32 b1_mask = 0x80000000UL; + const u32 b1_patt = 0x00000000UL; + const u32 b2_mask = 0xE0C00000UL; + const u32 b2_patt = 0xC0800000UL; + const u32 b2_requ = 0x1E000000UL; + const u32 b3_mask = 0xF0C0C000UL; + const u32 b3_patt = 0xE0808000UL; + const u32 b3_requ = 0x0F200000UL; + const u32 b3_erro = 0x0D200000UL; + const u32 b4_mask = 0xF8C0C0C0UL; + const u32 b4_patt = 0xF0808080UL; + const u32 b4_requ = 0x07300000UL; + const u32 b4_err0 = 0x04000000UL; + const u32 b4_err1 = 0x03300000UL; +#elif YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN + const u32 b1_mask = 0x00000080UL; + const u32 b1_patt = 0x00000000UL; + const u32 b2_mask = 0x0000C0E0UL; + const u32 b2_patt = 0x000080C0UL; + const u32 b2_requ = 0x0000001EUL; + const u32 b3_mask = 0x00C0C0F0UL; + const u32 b3_patt = 0x008080E0UL; + const u32 b3_requ = 0x0000200FUL; + const u32 b3_erro = 0x0000200DUL; + const u32 b4_mask = 0xC0C0C0F8UL; + const u32 b4_patt = 0x808080F0UL; + const u32 b4_requ = 0x00003007UL; + const u32 b4_err0 = 0x00000004UL; + const u32 b4_err1 = 0x00003003UL; +#else + v32_uni b1_mask_uni = {{ 0x80, 0x00, 0x00, 0x00 }}; + v32_uni b1_patt_uni = {{ 0x00, 0x00, 0x00, 0x00 }}; + v32_uni b2_mask_uni = {{ 0xE0, 0xC0, 0x00, 0x00 }}; + v32_uni b2_patt_uni = {{ 0xC0, 0x80, 0x00, 0x00 }}; + v32_uni b2_requ_uni = {{ 0x1E, 0x00, 0x00, 0x00 }}; + v32_uni b3_mask_uni = {{ 0xF0, 0xC0, 0xC0, 0x00 }}; + v32_uni b3_patt_uni = {{ 0xE0, 0x80, 0x80, 0x00 }}; + v32_uni b3_requ_uni = {{ 0x0F, 0x20, 0x00, 0x00 }}; + v32_uni b3_erro_uni = {{ 0x0D, 0x20, 0x00, 0x00 }}; + v32_uni b4_mask_uni = {{ 0xF8, 0xC0, 0xC0, 0xC0 }}; + v32_uni b4_patt_uni = {{ 0xF0, 0x80, 0x80, 0x80 }}; + v32_uni b4_requ_uni = {{ 0x07, 0x30, 0x00, 0x00 }}; + v32_uni b4_err0_uni = {{ 0x04, 0x00, 0x00, 0x00 }}; + v32_uni b4_err1_uni = {{ 0x03, 0x30, 0x00, 0x00 }}; + u32 b1_mask = b1_mask_uni.u; + u32 b1_patt = b1_patt_uni.u; + u32 b2_mask = b2_mask_uni.u; + u32 b2_patt = b2_patt_uni.u; + u32 b2_requ = b2_requ_uni.u; + u32 b3_mask = b3_mask_uni.u; + u32 b3_patt = b3_patt_uni.u; + u32 b3_requ = b3_requ_uni.u; + u32 b3_erro = b3_erro_uni.u; + u32 b4_mask = b4_mask_uni.u; + u32 b4_patt = b4_patt_uni.u; + u32 b4_requ = b4_requ_uni.u; + u32 b4_err0 = b4_err0_uni.u; + u32 b4_err1 = b4_err1_uni.u; +#endif + +#define is_valid_seq_1(uni) ( \ + ((uni & b1_mask) == b1_patt) \ +) + +#define is_valid_seq_2(uni) ( \ + ((uni & b2_mask) == b2_patt) && \ + ((uni & b2_requ)) \ +) + +#define is_valid_seq_3(uni) ( \ + ((uni & b3_mask) == b3_patt) && \ + ((tmp = (uni & b3_requ))) && \ + ((tmp != b3_erro)) \ +) + +#define is_valid_seq_4(uni) ( \ + ((uni & b4_mask) == b4_patt) && \ + ((tmp = (uni & b4_requ))) && \ + ((tmp & b4_err0) == 0 || (tmp & b4_err1) == 0) \ +) + +#define return_err(_end, _msg) do { \ + *msg = _msg; \ + *end = _end; \ + return false; \ +} while (false) + + u8 *cur = *ptr; + u8 **end = ptr; + u8 *src = ++cur, *dst, *pos; + u16 hi, lo; + u32 uni, tmp; + +skip_ascii: + /* Most strings have no escaped characters, so we can jump them quickly. */ + +skip_ascii_begin: + /* + We want to make loop unrolling, as shown in the following code. Some + compiler may not generate instructions as expected, so we rewrite it with + explicit goto statements. We hope the compiler can generate instructions + like this: https://godbolt.org/z/8vjsYq + + while (true) repeat16({ + if (likely(!(char_is_ascii_stop(*src)))) src++; + else break; + }) + */ +#define expr_jump(i) \ + if (likely(!char_is_ascii_stop(src[i]))) {} \ + else goto skip_ascii_stop##i; + +#define expr_stop(i) \ + skip_ascii_stop##i: \ + src += i; \ + goto skip_ascii_end; + + repeat16_incr(expr_jump) + src += 16; + goto skip_ascii_begin; + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + +skip_ascii_end: + + /* + GCC may store src[i] in a register at each line of expr_jump(i) above. + These instructions are useless and will degrade performance. + This inline asm is a hint for gcc: "the memory has been modified, + do not cache it". + + MSVC, Clang, ICC can generate expected instructions without this hint. + */ +#if YYJSON_IS_REAL_GCC + __asm__ volatile("":"=m"(*src)); +#endif + if (likely(*src == '"')) { + val->tag = ((u64)(src - cur) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = (const char *)cur; + *src = '\0'; + *end = src + 1; + return true; + } + +skip_utf8: + if (*src & 0x80) { /* non-ASCII character */ + /* + Non-ASCII character appears here, which means that the text is likely + to be written in non-English or emoticons. According to some common + data set statistics, byte sequences of the same length may appear + consecutively. We process the byte sequences of the same length in each + loop, which is more friendly to branch prediction. + */ + pos = src; + uni = byte_load_4(src); + while (is_valid_seq_3(uni)) { + src += 3; + uni = byte_load_4(src); + } + if (is_valid_seq_1(uni)) goto skip_ascii; + while (is_valid_seq_2(uni)) { + src += 2; + uni = byte_load_4(src); + } + while (is_valid_seq_4(uni)) { + src += 4; + uni = byte_load_4(src); + } + if (unlikely(pos == src)) { + if (!inv) return_err(src, "invalid UTF-8 encoding in string"); + ++src; + } + goto skip_ascii; + } + + /* The escape character appears, we need to copy it. */ + dst = src; +copy_escape: + if (likely(*src == '\\')) { + switch (*++src) { + case '"': *dst++ = '"'; src++; break; + case '\\': *dst++ = '\\'; src++; break; + case '/': *dst++ = '/'; src++; break; + case 'b': *dst++ = '\b'; src++; break; + case 'f': *dst++ = '\f'; src++; break; + case 'n': *dst++ = '\n'; src++; break; + case 'r': *dst++ = '\r'; src++; break; + case 't': *dst++ = '\t'; src++; break; + case 'u': + if (unlikely(!read_hex_u16(++src, &hi))) { + return_err(src - 2, "invalid escaped sequence in string"); + } + src += 4; + if (likely((hi & 0xF800) != 0xD800)) { + /* a BMP character */ + if (hi >= 0x800) { + *dst++ = (u8)(0xE0 | (hi >> 12)); + *dst++ = (u8)(0x80 | ((hi >> 6) & 0x3F)); + *dst++ = (u8)(0x80 | (hi & 0x3F)); + } else if (hi >= 0x80) { + *dst++ = (u8)(0xC0 | (hi >> 6)); + *dst++ = (u8)(0x80 | (hi & 0x3F)); + } else { + *dst++ = (u8)hi; + } + } else { + /* a non-BMP character, represented as a surrogate pair */ + if (unlikely((hi & 0xFC00) != 0xD800)) { + return_err(src - 6, "invalid high surrogate in string"); + } + if (unlikely(!byte_match_2(src, "\\u"))) { + return_err(src, "no low surrogate in string"); + } + if (unlikely(!read_hex_u16(src + 2, &lo))) { + return_err(src, "invalid escaped sequence in string"); + } + if (unlikely((lo & 0xFC00) != 0xDC00)) { + return_err(src, "invalid low surrogate in string"); + } + uni = ((((u32)hi - 0xD800) << 10) | + ((u32)lo - 0xDC00)) + 0x10000; + *dst++ = (u8)(0xF0 | (uni >> 18)); + *dst++ = (u8)(0x80 | ((uni >> 12) & 0x3F)); + *dst++ = (u8)(0x80 | ((uni >> 6) & 0x3F)); + *dst++ = (u8)(0x80 | (uni & 0x3F)); + src += 6; + } + break; + default: return_err(src, "invalid escaped character in string"); + } + } else if (likely(*src == '"')) { + val->tag = ((u64)(dst - cur) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = (const char *)cur; + *dst = '\0'; + *end = src + 1; + return true; + } else { + if (!inv) return_err(src, "unexpected control character in string"); + if (src >= lst) return_err(src, "unclosed string"); + *dst++ = *src++; + } + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (true) repeat16({ + if (unlikely(char_is_ascii_stop(*src))) break; + *dst++ = *src++; + }) + */ +#if YYJSON_IS_REAL_GCC +# define expr_jump(i) \ + if (likely(!(char_is_ascii_stop(src[i])))) {} \ + else { __asm__ volatile("":"=m"(src[i])); goto copy_ascii_stop_##i; } +#else +# define expr_jump(i) \ + if (likely(!(char_is_ascii_stop(src[i])))) {} \ + else { goto copy_ascii_stop_##i; } +#endif + repeat16_incr(expr_jump) +#undef expr_jump + + byte_move_16(dst, src); + src += 16; + dst += 16; + goto copy_ascii; + +copy_ascii_stop_0: + goto copy_utf8; +copy_ascii_stop_1: + byte_move_2(dst, src); + src += 1; + dst += 1; + goto copy_utf8; +copy_ascii_stop_2: + byte_move_2(dst, src); + src += 2; + dst += 2; + goto copy_utf8; +copy_ascii_stop_3: + byte_move_4(dst, src); + src += 3; + dst += 3; + goto copy_utf8; +copy_ascii_stop_4: + byte_move_4(dst, src); + src += 4; + dst += 4; + goto copy_utf8; +copy_ascii_stop_5: + byte_move_4(dst, src); + byte_move_2(dst + 4, src + 4); + src += 5; + dst += 5; + goto copy_utf8; +copy_ascii_stop_6: + byte_move_4(dst, src); + byte_move_2(dst + 4, src + 4); + src += 6; + dst += 6; + goto copy_utf8; +copy_ascii_stop_7: + byte_move_8(dst, src); + src += 7; + dst += 7; + goto copy_utf8; +copy_ascii_stop_8: + byte_move_8(dst, src); + src += 8; + dst += 8; + goto copy_utf8; +copy_ascii_stop_9: + byte_move_8(dst, src); + byte_move_2(dst + 8, src + 8); + src += 9; + dst += 9; + goto copy_utf8; +copy_ascii_stop_10: + byte_move_8(dst, src); + byte_move_2(dst + 8, src + 8); + src += 10; + dst += 10; + goto copy_utf8; +copy_ascii_stop_11: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + src += 11; + dst += 11; + goto copy_utf8; +copy_ascii_stop_12: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + src += 12; + dst += 12; + goto copy_utf8; +copy_ascii_stop_13: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + byte_move_2(dst + 12, src + 12); + src += 13; + dst += 13; + goto copy_utf8; +copy_ascii_stop_14: + byte_move_8(dst, src); + byte_move_4(dst + 8, src + 8); + byte_move_2(dst + 12, src + 12); + src += 14; + dst += 14; + goto copy_utf8; +copy_ascii_stop_15: + byte_move_16(dst, src); + src += 15; + dst += 15; + goto copy_utf8; + +copy_utf8: + if (*src & 0x80) { /* non-ASCII character */ + pos = src; + uni = byte_load_4(src); + while (is_valid_seq_3(uni)) { + byte_move_4(dst, &uni); + dst += 3; + src += 3; + uni = byte_load_4(src); + } + if (is_valid_seq_1(uni)) goto copy_ascii; + while (is_valid_seq_2(uni)) { + byte_move_2(dst, &uni); + dst += 2; + src += 2; + uni = byte_load_4(src); + } + while (is_valid_seq_4(uni)) { + byte_move_4(dst, &uni); + dst += 4; + src += 4; + uni = byte_load_4(src); + } + if (unlikely(pos == src)) { + if (!inv) return_err(src, "invalid UTF-8 encoding in string"); + goto copy_ascii_stop_1; + } + goto copy_ascii; + } + goto copy_escape; + +#undef return_err +#undef is_valid_seq_1 +#undef is_valid_seq_2 +#undef is_valid_seq_3 +#undef is_valid_seq_4 +} + + + +/*============================================================================== + * JSON Reader Implementation + * + * We use goto statements to build the finite state machine (FSM). + * The FSM's state was held by program counter (PC) and the 'goto' make the + * state transitions. + *============================================================================*/ + +/** Read single value JSON document. */ +static_noinline yyjson_doc *read_root_single(u8 *hdr, + u8 *cur, + u8 *end, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { + +#define has_flag(_flag) unlikely((flg & YYJSON_READ_##_flag) != 0) + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(end - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = "unexpected end of data"; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, (void *)val_hdr); \ + return NULL; \ +} while (false) + + usize hdr_len; /* value count used by doc */ + usize alc_num; /* value count capacity */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val; /* current value */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + bool raw; /* read number as raw */ + bool inv; /* allow invalid unicode */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_num = hdr_len + 1; /* single value */ + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_num * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val = val_hdr + hdr_len; + raw = (flg & (YYJSON_READ_NUMBER_AS_RAW | YYJSON_READ_BIGNUM_AS_RAW)) != 0; + inv = (flg & YYJSON_READ_ALLOW_INVALID_UNICODE) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + + if (char_is_number(*cur)) { + if (likely(read_number(&cur, pre, flg, val, &msg))) goto doc_end; + goto fail_number; + } + if (*cur == '"') { + if (likely(read_string(&cur, end, inv, val, &msg))) goto doc_end; + goto fail_string; + } + if (*cur == 't') { + if (likely(read_true(&cur, val))) goto doc_end; + goto fail_literal; + } + if (*cur == 'f') { + if (likely(read_false(&cur, val))) goto doc_end; + goto fail_literal; + } + if (*cur == 'n') { + if (likely(read_null(&cur, val))) goto doc_end; + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto doc_end; + } + goto fail_literal; + } + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (read_inf_or_nan(false, &cur, pre, val)) goto doc_end; + } + goto fail_character; + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_flag(STOP_WHEN_DONE)) { + if (has_flag(ALLOW_COMMENTS)) { + if (!skip_spaces_and_comments(&cur)) { + if (byte_match_2(cur, "/*")) goto fail_comment; + } + } else { + while (char_is_space(*cur)) cur++; + } + if (unlikely(cur < end)) goto fail_garbage; + } + + if (pre && *pre) **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = 1; + doc->str_pool = has_flag(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: + return_err(cur, INVALID_STRING, msg); +fail_number: + return_err(cur, INVALID_NUMBER, msg); +fail_alloc: + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); +fail_literal: + return_err(cur, LITERAL, "invalid literal"); +fail_comment: + return_err(cur, INVALID_COMMENT, "unclosed multiline comment"); +fail_character: + return_err(cur, UNEXPECTED_CHARACTER, "unexpected character"); +fail_garbage: + return_err(cur, UNEXPECTED_CONTENT, "unexpected content after document"); + +#undef has_flag +#undef return_err +} + +/** Read JSON document (accept all style, but optimized for minify). */ +static_inline yyjson_doc *read_root_minify(u8 *hdr, + u8 *cur, + u8 *end, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { + +#define has_flag(_flag) unlikely((flg & YYJSON_READ_##_flag) != 0) + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(end - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = "unexpected end of data"; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, (void *)val_hdr); \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + alc_len += alc_len / 2; \ + if ((alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + (usize)(val - val_hdr); \ + ctn = val_tmp + (usize)(ctn - val_hdr); \ + val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + } \ +} while (false) + + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + bool raw; /* read number as raw */ + bool inv; /* allow invalid unicode */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + + dat_len = has_flag(STOP_WHEN_DONE) ? 256 : (usize)(end - cur); + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_max = USIZE_MAX / sizeof(yyjson_val); + alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + raw = (flg & (YYJSON_READ_NUMBER_AS_RAW | YYJSON_READ_BIGNUM_AS_RAW)) != 0; + inv = (flg & YYJSON_READ_ALLOW_INVALID_UNICODE) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + + if (*cur++ == '{') { + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + goto obj_key_begin; + } else { + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + goto arr_val_begin; + } + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + +arr_val_begin: + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_number(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto arr_val_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto arr_val_end; + } + goto fail_literal; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + if (has_flag(ALLOW_TRAILING_COMMAS)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_begin; + } + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val_incr(); + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto arr_val_end; + goto fail_character; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_val_end: + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + +obj_key_begin: + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + if (has_flag(ALLOW_TRAILING_COMMAS)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_begin; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_key_end: + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_begin: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (char_is_number(*cur)) { + val++; + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto obj_val_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto obj_val_end; + } + goto fail_literal; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_begin; + } + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val++; + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto obj_val_end; + goto fail_character; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_end: + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_flag(STOP_WHEN_DONE)) { + if (has_flag(ALLOW_COMMENTS)) { + skip_spaces_and_comments(&cur); + if (byte_match_2(cur, "/*")) goto fail_comment; + } + else while (char_is_space(*cur)) cur++; + if (unlikely(cur < end)) goto fail_garbage; + } + + if (pre && *pre) **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - doc->root) + 1); + doc->str_pool = has_flag(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: + return_err(cur, INVALID_STRING, msg); +fail_number: + return_err(cur, INVALID_NUMBER, msg); +fail_alloc: + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); +fail_trailing_comma: + return_err(cur, JSON_STRUCTURE, "trailing comma is not allowed"); +fail_literal: + return_err(cur, LITERAL, "invalid literal"); +fail_comment: + return_err(cur, INVALID_COMMENT, "unclosed multiline comment"); +fail_character: + return_err(cur, UNEXPECTED_CHARACTER, "unexpected character"); +fail_garbage: + return_err(cur, UNEXPECTED_CONTENT, "unexpected content after document"); + +#undef has_flag +#undef val_incr +#undef return_err +} + +/** Read JSON document (accept all style, but optimized for pretty). */ +static_inline yyjson_doc *read_root_pretty(u8 *hdr, + u8 *cur, + u8 *end, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { + +#define has_flag(_flag) unlikely((flg & YYJSON_READ_##_flag) != 0) + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(end - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = "unexpected end of data"; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, (void *)val_hdr); \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + alc_len += alc_len / 2; \ + if ((alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + (usize)(val - val_hdr); \ + ctn = val_tmp + (usize)(ctn - val_hdr); \ + val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + } \ +} while (false) + + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + bool raw; /* read number as raw */ + bool inv; /* allow invalid unicode */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + + dat_len = has_flag(STOP_WHEN_DONE) ? 256 : (usize)(end - cur); + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_max = USIZE_MAX / sizeof(yyjson_val); + alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_PRETTY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + raw = (flg & (YYJSON_READ_NUMBER_AS_RAW | YYJSON_READ_BIGNUM_AS_RAW)) != 0; + inv = (flg & YYJSON_READ_ALLOW_INVALID_UNICODE) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + + if (*cur++ == '{') { + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + if (*cur == '\n') cur++; + goto obj_key_begin; + } else { + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + if (*cur == '\n') cur++; + goto arr_val_begin; + } + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + if (*cur == '\n') cur++; + +arr_val_begin: +#if YYJSON_IS_REAL_GCC + while (true) repeat16({ + if (byte_match_2(cur, " ")) cur += 2; + else break; + }) +#else + while (true) repeat16({ + if (likely(byte_match_2(cur, " "))) cur += 2; + else break; + }) +#endif + + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_number(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto arr_val_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto arr_val_end; + } + goto fail_literal; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + if (has_flag(ALLOW_TRAILING_COMMAS)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_begin; + } + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val_incr(); + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto arr_val_end; + goto fail_character; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_val_end: + if (byte_match_2(cur, ",\n")) { + cur += 2; + goto arr_val_begin; + } + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto arr_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if (*cur == '\n') cur++; + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + if (*cur == '\n') cur++; + +obj_key_begin: +#if YYJSON_IS_REAL_GCC + while (true) repeat16({ + if (byte_match_2(cur, " ")) cur += 2; + else break; + }) +#else + while (true) repeat16({ + if (likely(byte_match_2(cur, " "))) cur += 2; + else break; + }) +#endif + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + if (has_flag(ALLOW_TRAILING_COMMAS)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_begin; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_key_end: + if (byte_match_2(cur, ": ")) { + cur += 2; + goto obj_val_begin; + } + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_key_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_begin: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_string(&cur, end, inv, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (char_is_number(*cur)) { + val++; + ctn_len++; + if (likely(read_number(&cur, pre, flg, val, &msg))) goto obj_val_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + if (read_nan(false, &cur, pre, val)) goto obj_val_end; + } + goto fail_literal; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_begin; + } + if (unlikely(flg & YYJSON_READ_ALLOW_INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val++; + ctn_len++; + if (read_inf_or_nan(false, &cur, pre, val)) goto obj_val_end; + goto fail_character; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_begin; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_val_end: + if (byte_match_2(cur, ",\n")) { + cur += 2; + goto obj_key_begin; + } + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + if (has_flag(ALLOW_COMMENTS)) { + if (skip_spaces_and_comments(&cur)) goto obj_val_end; + if (byte_match_2(cur, "/*")) goto fail_comment; + } + goto fail_character; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if (*cur == '\n') cur++; + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_flag(STOP_WHEN_DONE)) { + if (has_flag(ALLOW_COMMENTS)) { + skip_spaces_and_comments(&cur); + if (byte_match_2(cur, "/*")) goto fail_comment; + } + else while (char_is_space(*cur)) cur++; + if (unlikely(cur < end)) goto fail_garbage; + } + + if (pre && *pre) **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - val_hdr)) - hdr_len + 1; + doc->str_pool = has_flag(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: + return_err(cur, INVALID_STRING, msg); +fail_number: + return_err(cur, INVALID_NUMBER, msg); +fail_alloc: + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); +fail_trailing_comma: + return_err(cur, JSON_STRUCTURE, "trailing comma is not allowed"); +fail_literal: + return_err(cur, LITERAL, "invalid literal"); +fail_comment: + return_err(cur, INVALID_COMMENT, "unclosed multiline comment"); +fail_character: + return_err(cur, UNEXPECTED_CHARACTER, "unexpected character"); +fail_garbage: + return_err(cur, UNEXPECTED_CONTENT, "unexpected content after document"); + +#undef has_flag +#undef val_incr +#undef return_err +} + + + +/*============================================================================== + * JSON Reader Entrance + *============================================================================*/ + +yyjson_doc *yyjson_read_opts(char *dat, + usize len, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { + +#define has_flag(_flag) unlikely((flg & YYJSON_READ_##_flag) != 0) + +#define return_err(_pos, _code, _msg) do { \ + err->pos = (usize)(_pos); \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + if (!has_flag(INSITU) && hdr) alc.free(alc.ctx, (void *)hdr); \ + return NULL; \ +} while (false) + + yyjson_read_err dummy_err; + yyjson_alc alc; + yyjson_doc *doc; + u8 *hdr = NULL, *end, *cur; + +#if YYJSON_DISABLE_NON_STANDARD + flg &= ~YYJSON_READ_ALLOW_TRAILING_COMMAS; + flg &= ~YYJSON_READ_ALLOW_COMMENTS; + flg &= ~YYJSON_READ_ALLOW_INF_AND_NAN; + flg &= ~YYJSON_READ_ALLOW_INVALID_UNICODE; +#endif + + /* validate input parameters */ + if (!err) err = &dummy_err; + if (likely(!alc_ptr)) { + alc = YYJSON_DEFAULT_ALC; + } else { + alc = *alc_ptr; + } + if (unlikely(!dat)) { + return_err(0, INVALID_PARAMETER, "input data is NULL"); + } + if (unlikely(!len)) { + return_err(0, INVALID_PARAMETER, "input length is 0"); + } + + /* add 4-byte zero padding for input data if necessary */ + if (has_flag(INSITU)) { + hdr = (u8 *)dat; + end = (u8 *)dat + len; + cur = (u8 *)dat; + } else { + if (unlikely(len >= USIZE_MAX - YYJSON_PADDING_SIZE)) { + return_err(0, MEMORY_ALLOCATION, "memory allocation failed"); + } + hdr = (u8 *)alc.malloc(alc.ctx, len + YYJSON_PADDING_SIZE); + if (unlikely(!hdr)) { + return_err(0, MEMORY_ALLOCATION, "memory allocation failed"); + } + end = hdr + len; + cur = hdr; + memcpy(hdr, dat, len); + memset(end, 0, YYJSON_PADDING_SIZE); + } + + /* skip empty contents before json document */ + if (unlikely(char_is_space_or_comment(*cur))) { + if (has_flag(ALLOW_COMMENTS)) { + if (!skip_spaces_and_comments(&cur)) { + return_err(cur - hdr, INVALID_COMMENT, + "unclosed multiline comment"); + } + } else { + if (likely(char_is_space(*cur))) { + while (char_is_space(*++cur)); + } + } + if (unlikely(cur >= end)) { + return_err(0, EMPTY_CONTENT, "input data is empty"); + } + } + + /* read json document */ + if (likely(char_is_container(*cur))) { + if (char_is_space(cur[1]) && char_is_space(cur[2])) { + doc = read_root_pretty(hdr, cur, end, alc, flg, err); + } else { + doc = read_root_minify(hdr, cur, end, alc, flg, err); + } + } else { + doc = read_root_single(hdr, cur, end, alc, flg, err); + } + + /* check result */ + if (likely(doc)) { + memset(err, 0, sizeof(yyjson_read_err)); + } else { + /* RFC 8259: JSON text MUST be encoded using UTF-8 */ + if (err->pos == 0 && err->code != YYJSON_READ_ERROR_MEMORY_ALLOCATION) { + if ((hdr[0] == 0xEF && hdr[1] == 0xBB && hdr[2] == 0xBF)) { + err->msg = "byte order mark (BOM) is not supported"; + } else if (len >= 4 && + ((hdr[0] == 0x00 && hdr[1] == 0x00 && + hdr[2] == 0xFE && hdr[3] == 0xFF) || + (hdr[0] == 0xFF && hdr[1] == 0xFE && + hdr[2] == 0x00 && hdr[3] == 0x00))) { + err->msg = "UTF-32 encoding is not supported"; + } else if (len >= 2 && + ((hdr[0] == 0xFE && hdr[1] == 0xFF) || + (hdr[0] == 0xFF && hdr[1] == 0xFE))) { + err->msg = "UTF-16 encoding is not supported"; + } + } + if (!has_flag(INSITU)) alc.free(alc.ctx, (void *)hdr); + } + return doc; + +#undef has_flag +#undef return_err +} + +yyjson_doc *yyjson_read_file(const char *path, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_code, _msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + return NULL; \ +} while (false) + + yyjson_read_err dummy_err; + yyjson_doc *doc; + FILE *file; + + if (!err) err = &dummy_err; + if (unlikely(!path)) return_err(INVALID_PARAMETER, "input path is NULL"); + + file = fopen_readonly(path); + if (unlikely(!file)) return_err(FILE_OPEN, "file opening failed"); + + doc = yyjson_read_fp(file, flg, alc_ptr, err); + fclose(file); + return doc; + +#undef return_err +} + +yyjson_doc *yyjson_read_fp(FILE *file, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_code, _msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + if (buf) alc.free(alc.ctx, buf); \ + return NULL; \ +} while (false) + + yyjson_read_err dummy_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_doc *doc; + + long file_size = 0, file_pos; + void *buf = NULL; + usize buf_size = 0; + + /* validate input parameters */ + if (!err) err = &dummy_err; + if (unlikely(!file)) return_err(INVALID_PARAMETER, "input file is NULL"); + + /* get current position */ + file_pos = ftell(file); + if (file_pos != -1) { + /* get total file size, may fail */ + if (fseek(file, 0, SEEK_END) == 0) file_size = ftell(file); + /* reset to original position, may fail */ + if (fseek(file, file_pos, SEEK_SET) != 0) file_size = 0; + /* get file size from current postion to end */ + if (file_size > 0) file_size -= file_pos; + } + + /* read file */ + if (file_size > 0) { + /* read the entire file in one call */ + buf_size = (usize)file_size + YYJSON_PADDING_SIZE; + buf = alc.malloc(alc.ctx, buf_size); + if (buf == NULL) { + return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + } + if (fread_safe(buf, (usize)file_size, file) != (usize)file_size) { + return_err(FILE_READ, "file reading failed"); + } + } else { + /* failed to get file size, read it as a stream */ + usize chunk_min = (usize)64; + usize chunk_max = (usize)512 * 1024 * 1024; + usize chunk_now = chunk_min; + usize read_size; + void *tmp; + + buf_size = YYJSON_PADDING_SIZE; + while (true) { + if (buf_size + chunk_now < buf_size) { /* overflow */ + return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + } + buf_size += chunk_now; + if (!buf) { + buf = alc.malloc(alc.ctx, buf_size); + if (!buf) return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + } else { + tmp = alc.realloc(alc.ctx, buf, buf_size - chunk_now, buf_size); + if (!tmp) return_err(MEMORY_ALLOCATION, "fail to alloc memory"); + buf = tmp; + } + tmp = ((u8 *)buf) + buf_size - YYJSON_PADDING_SIZE - chunk_now; + read_size = fread_safe(tmp, chunk_now, file); + file_size += (long)read_size; + if (read_size != chunk_now) break; + + chunk_now *= 2; + if (chunk_now > chunk_max) chunk_now = chunk_max; + } + } + + /* read JSON */ + memset((u8 *)buf + file_size, 0, YYJSON_PADDING_SIZE); + flg |= YYJSON_READ_INSITU; + doc = yyjson_read_opts((char *)buf, (usize)file_size, flg, &alc, err); + if (doc) { + doc->str_pool = (char *)buf; + return doc; + } else { + alc.free(alc.ctx, buf); + return NULL; + } + +#undef return_err +} + + + +const char *yyjson_read_number(const char *dat, + yyjson_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + err->pos = _pos > hdr ? (usize)(_pos - hdr) : 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + return NULL; \ +} while (false) + + u8 *hdr = constcast(u8 *)dat, *cur = hdr; + bool raw; /* read number as raw */ + u8 *raw_end; /* raw end for null-terminator */ + u8 **pre; /* previous raw end pointer */ + const char *msg; + yyjson_read_err dummy_err; + +#if !YYJSON_HAS_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV + u8 buf[128]; + usize dat_len; +#endif + + if (!err) err = &dummy_err; + if (unlikely(!dat)) { + return_err(cur, INVALID_PARAMETER, "input data is NULL"); + } + if (unlikely(!val)) { + return_err(cur, INVALID_PARAMETER, "output value is NULL"); + } + +#if !YYJSON_HAS_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV + if (!alc) alc = &YYJSON_DEFAULT_ALC; + dat_len = strlen(dat); + if (dat_len < sizeof(buf)) { + memcpy(buf, dat, dat_len + 1); + hdr = buf; + cur = hdr; + } else { + hdr = (u8 *)alc->malloc(alc->ctx, dat_len + 1); + cur = hdr; + if (unlikely(!hdr)) { + return_err(cur, MEMORY_ALLOCATION, "memory allocation failed"); + } + memcpy(hdr, dat, dat_len + 1); + } + hdr[dat_len] = 0; +#endif + +#if YYJSON_DISABLE_NON_STANDARD + flg &= ~YYJSON_READ_ALLOW_INF_AND_NAN; +#endif + + raw = (flg & (YYJSON_READ_NUMBER_AS_RAW | YYJSON_READ_BIGNUM_AS_RAW)) != 0; + raw_end = NULL; + pre = raw ? &raw_end : NULL; + +#if !YYJSON_HAS_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV + if (!read_number(&cur, pre, flg, val, &msg)) { + if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); + return_err(cur, INVALID_NUMBER, msg); + } + if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); + if (yyjson_is_raw(val)) val->uni.str = dat; + return dat + (cur - hdr); +#else + if (!read_number(&cur, pre, flg, val, &msg)) { + return_err(cur, INVALID_NUMBER, msg); + } + return (const char *)cur; +#endif + +#undef return_err +} + +#endif /* YYJSON_DISABLE_READER */ + + + +#if !YYJSON_DISABLE_WRITER + +/*============================================================================== + * Integer Writer + * + * The maximum value of uint32_t is 4294967295 (10 digits), + * these digits are named as 'aabbccddee' here. + * + * Although most compilers may convert the "division by constant value" into + * "multiply and shift", manual conversion can still help some compilers + * generate fewer and better instructions. + * + * Reference: + * Division by Invariant Integers using Multiplication, 1994. + * https://gmplib.org/~tege/divcnst-pldi94.pdf + * Improved division by invariant integers, 2011. + * https://gmplib.org/~tege/division-paper.pdf + *============================================================================*/ + +/** Digit table from 00 to 99. */ +yyjson_align(2) +static const char digit_table[200] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', + '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', + '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', + '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', + '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', + '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', + '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', + '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', + '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', + '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', + '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' +}; + +static_inline u8 *write_u32_len_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, ccdd; /* 8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + ((v16 *)buf)[0] = ((const v16 *)digit_table)[aa]; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + ((v16 *)buf)[2] = ((const v16 *)digit_table)[cc]; + ((v16 *)buf)[3] = ((const v16 *)digit_table)[dd]; + return buf + 8; +} + +static_inline u8 *write_u32_len_4(u32 val, u8 *buf) { + u32 aa, bb; /* 4 digits: aabb */ + aa = (val * 5243) >> 19; /* (val / 100) */ + bb = val - aa * 100; /* (val % 100) */ + ((v16 *)buf)[0] = ((const v16 *)digit_table)[aa]; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + return buf + 4; +} + +static_inline u8 *write_u32_len_1_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; + + if (val < 100) { /* 1-2 digits: aa */ + lz = val < 10; /* leading zero: 0 or 1 */ + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (val * 2 + lz)); + buf -= lz; + return buf + 2; + + } else if (val < 10000) { /* 3-4 digits: aabb */ + aa = (val * 5243) >> 19; /* (val / 100) */ + bb = val - aa * 100; /* (val % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (aa * 2 + lz)); + buf -= lz; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + return buf + 4; + + } else if (val < 1000000) { /* 5-6 digits: aabbcc */ + aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ + bbcc = val - aa * 10000; /* (val % 10000) */ + bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ + cc = bbcc - bb * 100; /* (bbcc % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (aa * 2 + lz)); + buf -= lz; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + ((v16 *)buf)[2] = ((const v16 *)digit_table)[cc]; + return buf + 6; + + } else { /* 7-8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (aa * 2 + lz)); + buf -= lz; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + ((v16 *)buf)[2] = ((const v16 *)digit_table)[cc]; + ((v16 *)buf)[3] = ((const v16 *)digit_table)[dd]; + return buf + 8; + } +} + +static_inline u8 *write_u64_len_5_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; + + if (val < 1000000) { /* 5-6 digits: aabbcc */ + aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ + bbcc = val - aa * 10000; /* (val % 10000) */ + bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ + cc = bbcc - bb * 100; /* (bbcc % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (aa * 2 + lz)); + buf -= lz; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + ((v16 *)buf)[2] = ((const v16 *)digit_table)[cc]; + return buf + 6; + + } else { /* 7-8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (aa * 2 + lz)); + buf -= lz; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[bb]; + ((v16 *)buf)[2] = ((const v16 *)digit_table)[cc]; + ((v16 *)buf)[3] = ((const v16 *)digit_table)[dd]; + return buf + 8; + } +} + +static_inline u8 *write_u64(u64 val, u8 *buf) { + u64 tmp, hgh; + u32 mid, low; + + if (val < 100000000) { /* 1-8 digits */ + buf = write_u32_len_1_8((u32)val, buf); + return buf; + + } else if (val < (u64)100000000 * 100000000) { /* 9-16 digits */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + + } else { /* 17-20 digits */ + tmp = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - tmp * 100000000); /* (val % 100000000) */ + hgh = (u32)(tmp / 10000); /* (tmp / 10000) */ + mid = (u32)(tmp - hgh * 10000); /* (tmp % 10000) */ + buf = write_u64_len_5_8((u32)hgh, buf); + buf = write_u32_len_4(mid, buf); + buf = write_u32_len_8(low, buf); + return buf; + } +} + + + +/*============================================================================== + * Number Writer + *============================================================================*/ + +#if YYJSON_HAS_IEEE_754 && !YYJSON_DISABLE_FAST_FP_CONV /* FP_WRITER */ + +/** Trailing zero count table for number 0 to 99. + (generate with misc/make_tables.c) */ +static const u8 dec_trailing_zero_table[] = { + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/** Write an unsigned integer with a length of 1 to 16. */ +static_inline u8 *write_u64_len_1_to_16(u64 val, u8 *buf) { + u64 hgh; + u32 low; + if (val < 100000000) { /* 1-8 digits */ + buf = write_u32_len_1_8((u32)val, buf); + return buf; + } else { /* 9-16 digits */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + } +} + +/** Write an unsigned integer with a length of 1 to 17. */ +static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { + u64 hgh; + u32 mid, low, one; + if (val >= (u64)100000000 * 10000000) { /* len: 16 to 17 */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + one = (u32)(hgh / 100000000); /* (hgh / 100000000) */ + mid = (u32)(hgh - (u64)one * 100000000); /* (hgh % 100000000) */ + *buf = (u8)((u8)one + (u8)'0'); + buf += one > 0; + buf = write_u32_len_8(mid, buf); + buf = write_u32_len_8(low, buf); + return buf; + } else if (val >= (u64)100000000){ /* len: 9 to 15 */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + } else { /* len: 1 to 8 */ + buf = write_u32_len_1_8((u32)val, buf); + return buf; + } +} + +/** + Write an unsigned integer with a length of 15 to 17 with trailing zero trimmed. + These digits are named as "aabbccddeeffgghhii" here. + For example, input 1234567890123000, output "1234567890123". + */ +static_inline u8 *write_u64_len_15_to_17_trim(u8 *buf, u64 sig) { + bool lz; /* leading zero */ + u32 tz1, tz2, tz; /* trailing zero */ + + u32 abbccddee = (u32)(sig / 100000000); + u32 ffgghhii = (u32)(sig - (u64)abbccddee * 100000000); + u32 abbcc = abbccddee / 10000; /* (abbccddee / 10000) */ + u32 ddee = abbccddee - abbcc * 10000; /* (abbccddee % 10000) */ + u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ + u32 a = (abb * 41) >> 12; /* (abb / 100) */ + u32 bb = abb - a * 100; /* (abb % 100) */ + u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ + + /* write abbcc */ + buf[0] = (u8)(a + '0'); + buf += a > 0; + lz = bb < 10 && a == 0; + ((v16 *)buf)[0] = *(const v16 *)(digit_table + (bb * 2 + lz)); + buf -= lz; + ((v16 *)buf)[1] = ((const v16 *)digit_table)[cc]; + + if (ffgghhii) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + u32 ffgg = (u32)(((u64)ffgghhii * 109951163) >> 40); /* (val / 10000) */ + u32 hhii = ffgghhii - ffgg * 10000; /* (val % 10000) */ + u32 ff = (ffgg * 5243) >> 19; /* (aabb / 100) */ + u32 gg = ffgg - ff * 100; /* (aabb % 100) */ + ((v16 *)buf)[2] = ((const v16 *)digit_table)[dd]; + ((v16 *)buf)[3] = ((const v16 *)digit_table)[ee]; + ((v16 *)buf)[4] = ((const v16 *)digit_table)[ff]; + ((v16 *)buf)[5] = ((const v16 *)digit_table)[gg]; + if (hhii) { + u32 hh = (hhii * 5243) >> 19; /* (ccdd / 100) */ + u32 ii = hhii - hh * 100; /* (ccdd % 100) */ + ((v16 *)buf)[6] = ((const v16 *)digit_table)[hh]; + ((v16 *)buf)[7] = ((const v16 *)digit_table)[ii]; + tz1 = dec_trailing_zero_table[hh]; + tz2 = dec_trailing_zero_table[ii]; + tz = ii ? tz2 : (tz1 + 2); + buf += 16 - tz; + return buf; + } else { + tz1 = dec_trailing_zero_table[ff]; + tz2 = dec_trailing_zero_table[gg]; + tz = gg ? tz2 : (tz1 + 2); + buf += 12 - tz; + return buf; + } + } else { + if (ddee) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + ((v16 *)buf)[2] = ((const v16 *)digit_table)[dd]; + ((v16 *)buf)[3] = ((const v16 *)digit_table)[ee]; + tz1 = dec_trailing_zero_table[dd]; + tz2 = dec_trailing_zero_table[ee]; + tz = ee ? tz2 : (tz1 + 2); + buf += 8 - tz; + return buf; + } else { + tz1 = dec_trailing_zero_table[bb]; + tz2 = dec_trailing_zero_table[cc]; + tz = cc ? tz2 : (tz1 + tz2); + buf += 4 - tz; + return buf; + } + } +} + +/** Write a signed integer in the range -324 to 308. */ +static_inline u8 *write_f64_exp(i32 exp, u8 *buf) { + buf[0] = '-'; + buf += exp < 0; + exp = exp < 0 ? -exp : exp; + if (exp < 100) { + u32 lz = exp < 10; + *(v16 *)&buf[0] = *(const v16 *)(digit_table + ((u32)exp * 2 + lz)); + return buf + 2 - lz; + } else { + u32 hi = ((u32)exp * 656) >> 16; /* exp / 100 */ + u32 lo = (u32)exp - hi * 100; /* exp % 100 */ + buf[0] = (u8)((u8)hi + (u8)'0'); + *(v16 *)&buf[1] = *(const v16 *)(digit_table + (lo * 2)); + return buf + 3; + } +} + +/** Multiplies 128-bit integer and returns highest 64-bit rounded value. */ +static_inline u64 round_to_odd(u64 hi, u64 lo, u64 cp) { + u64 x_hi, x_lo, y_hi, y_lo; + u128_mul(cp, lo, &x_hi, &x_lo); + u128_mul_add(cp, hi, x_hi, &y_hi, &y_lo); + return y_hi | (y_lo > 1); +} + +/** + Convert double number from binary to decimal. + The output significand is shortest decimal but may have trailing zeros. + + This function use the Schubfach algorithm: + Raffaello Giulietti, The Schubfach way to render doubles (5th version), 2022. + https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb + https://mail.openjdk.java.net/pipermail/core-libs-dev/2021-November/083536.html + https://github.com/openjdk/jdk/pull/3402 (Java implementation) + https://github.com/abolz/Drachennest (C++ implementation) + + See also: + Dragonbox: A New Floating-Point Binary-to-Decimal Conversion Algorithm, 2022. + https://github.com/jk-jeon/dragonbox/blob/master/other_files/Dragonbox.pdf + https://github.com/jk-jeon/dragonbox + + @param sig_raw The raw value of significand in IEEE 754 format. + @param exp_raw The raw value of exponent in IEEE 754 format. + @param sig_bin The decoded value of significand in binary. + @param exp_bin The decoded value of exponent in binary. + @param sig_dec The output value of significand in decimal. + @param exp_dec The output value of exponent in decimal. + @warning The input double number should not be 0, inf, nan. + */ +static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, + u64 sig_bin, i32 exp_bin, + u64 *sig_dec, i32 *exp_dec) { + + bool is_even, regular_spacing, u_inside, w_inside, round_up; + u64 s, sp, cb, cbl, cbr, vb, vbl, vbr, pow10hi, pow10lo, upper, lower, mid; + i32 k, h, exp10; + + is_even = !(sig_bin & 1); + regular_spacing = (sig_raw == 0 && exp_raw > 1); + + cbl = 4 * sig_bin - 2 + regular_spacing; + cb = 4 * sig_bin; + cbr = 4 * sig_bin + 2; + + /* exp_bin: [-1074, 971] */ + /* k = regular_spacing ? floor(log10(pow(2, exp_bin))) */ + /* : floor(log10(pow(2, exp_bin) * 3.0 / 4.0)) */ + /* = regular_spacing ? floor(exp_bin * log10(2)) */ + /* : floor(exp_bin * log10(2) + log10(3.0 / 4.0)) */ + k = (i32)(exp_bin * 315653 - (regular_spacing ? 131237 : 0)) >> 20; + + /* k: [-324, 292] */ + /* h = exp_bin + floor(log2(pow(10, e))) */ + /* = exp_bin + floor(log2(10) * e) */ + exp10 = -k; + h = exp_bin + ((exp10 * 217707) >> 16) + 1; + + pow10_table_get_sig(exp10, &pow10hi, &pow10lo); + pow10lo += (exp10 < POW10_SIG_TABLE_MIN_EXACT_EXP || + exp10 > POW10_SIG_TABLE_MAX_EXACT_EXP); + vbl = round_to_odd(pow10hi, pow10lo, cbl << h); + vb = round_to_odd(pow10hi, pow10lo, cb << h); + vbr = round_to_odd(pow10hi, pow10lo, cbr << h); + + lower = vbl + !is_even; + upper = vbr - !is_even; + + s = vb / 4; + if (s >= 10) { + sp = s / 10; + u_inside = (lower <= 40 * sp); + w_inside = (upper >= 40 * sp + 40); + if (u_inside != w_inside) { + *sig_dec = sp + w_inside; + *exp_dec = k + 1; + return; + } + } + + u_inside = (lower <= 4 * s); + w_inside = (upper >= 4 * s + 4); + + mid = 4 * s + 2; + round_up = (vb > mid) || (vb == mid && (s & 1) != 0); + + *sig_dec = s + ((u_inside != w_inside) ? w_inside : round_up); + *exp_dec = k; +} + +/** + Write a double number (requires 32 bytes buffer). + + We follows the ECMAScript specification to print floating point numbers, + but with the following changes: + 1. Keep the negative sign of 0.0 to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign of exponent part. + */ +static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { + u64 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_pos, i, max; + u32 exp_raw, hi, lo; + u8 *hdr, *num_hdr, *num_end, *dot_end; + bool sign; + + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F64_BITS - 1)); + sig_raw = raw & F64_SIG_MASK; + exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + + /* return inf and nan */ + if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { + if (flg & YYJSON_WRITE_INF_AND_NAN_AS_NULL) { + byte_copy_4(buf, "null"); + return buf + 4; + } else if (flg & YYJSON_WRITE_ALLOW_INF_AND_NAN) { + if (sig_raw == 0) { + buf[0] = '-'; + buf += sign; + byte_copy_8(buf, "Infinity"); + buf += 8; + return buf; + } else { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } else { + return NULL; + } + } + + /* add sign for all finite double value, including 0.0 and inf */ + buf[0] = '-'; + buf += sign; + hdr = buf; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + buf += 3; + return buf; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); + exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; + + /* fast path for small integer number without fraction */ + if (-F64_SIG_BITS <= exp_bin && exp_bin <= 0) { + if (u64_tz_bits(sig_bin) >= (u32)-exp_bin) { + /* number is integer in range 1 to 0x1FFFFFFFFFFFFF */ + sig_dec = sig_bin >> -exp_bin; + buf = write_u64_len_1_to_16(sig_dec, buf); + byte_copy_2(buf, ".0"); + buf += 2; + return buf; + } + } + + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* the sig length is 15 to 17 */ + sig_len = 17; + sig_len -= (sig_dec < (u64)100000000 * 100000000); + sig_len -= (sig_dec < (u64)100000000 * 10000000); + + /* the decimal point position relative to the first digit */ + dot_pos = sig_len + exp_dec; + + if (-6 < dot_pos && dot_pos <= 21) { + /* no need to write exponent part */ + if (dot_pos <= 0) { + /* dot before first digit */ + /* such as 0.1234, 0.000001234 */ + num_hdr = hdr + (2 - dot_pos); + num_end = write_u64_len_15_to_17_trim(num_hdr, sig_dec); + hdr[0] = '0'; + hdr[1] = '.'; + hdr += 2; + max = -dot_pos; + for (i = 0; i < max; i++) hdr[i] = '0'; + return num_end; + } else { + /* dot after first digit */ + /* such as 1.234, 1234.0, 123400000000000000000.0 */ + memset(hdr + 0, '0', 8); + memset(hdr + 8, '0', 8); + memset(hdr + 16, '0', 8); + num_hdr = hdr + 1; + num_end = write_u64_len_15_to_17_trim(num_hdr, sig_dec); + for (i = 0; i < dot_pos; i++) hdr[i] = hdr[i + 1]; + hdr[dot_pos] = '.'; + dot_end = hdr + dot_pos + 2; + return dot_end < num_end ? num_end : dot_end; + } + } else { + /* write with scientific notation */ + /* such as 1.234e56 */ + u8 *end = write_u64_len_15_to_17_trim(buf + 1, sig_dec); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + hdr[0] = hdr[1]; + hdr[1] = '.'; + end[0] = 'e'; + buf = write_f64_exp(exp_dec, end + 1); + return buf; + } + + } else { + /* subnormal number */ + sig_bin = sig_raw; + exp_bin = 1 - F64_EXP_BIAS - F64_SIG_BITS; + + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* write significand part */ + buf = write_u64_len_1_to_17(sig_dec, buf + 1); + hdr[0] = hdr[1]; + hdr[1] = '.'; + do { + buf--; + exp_dec++; + } while (*buf == '0'); + exp_dec += (i32)(buf - hdr - 2); + buf += (*buf != '.'); + buf[0] = 'e'; + buf++; + + /* write exponent part */ + buf[0] = '-'; + buf++; + exp_dec = -exp_dec; + hi = ((u32)exp_dec * 656) >> 16; /* exp / 100 */ + lo = (u32)exp_dec - hi * 100; /* exp % 100 */ + buf[0] = (u8)((u8)hi + (u8)'0'); + *(v16 *)&buf[1] = *(const v16 *)(digit_table + (lo * 2)); + buf += 3; + return buf; + } +} + +#else /* FP_WRITER */ + +/** Write a double number (requires 32 bytes buffer). */ +static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { + /* + For IEEE 754, `DBL_DECIMAL_DIG` is 17 for round-trip. + For non-IEEE formats, 17 is used to avoid buffer overflow, + round-trip is not guaranteed. + */ +#if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG != 17 + int dig = DBL_DECIMAL_DIG > 17 ? 17 : DBL_DECIMAL_DIG; +#else + int dig = 17; +#endif + + /* + The snprintf() function is locale-dependent. For currently known locales, + (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, while other + locales use ',' as the decimal point. we need to replace ',' with '.' + to avoid the locale setting. + */ + f64 val = f64_from_raw(raw); +#if YYJSON_MSC_VER >= 1400 + int len = sprintf_s((char *)buf, 32, "%.*g", dig, val); +#elif defined(snprintf) || (YYJSON_STDC_VER >= 199901L) + int len = snprintf((char *)buf, 32, "%.*g", dig, val); +#else + int len = sprintf((char *)buf, "%.*g", dig, val); +#endif + + u8 *cur = buf; + if (unlikely(len < 1)) return NULL; + cur += (*cur == '-'); + if (unlikely(!digi_is_digit(*cur))) { + /* nan, inf, or bad output */ + if (flg & YYJSON_WRITE_INF_AND_NAN_AS_NULL) { + byte_copy_4(buf, "null"); + return buf + 4; + } else if (flg & YYJSON_WRITE_ALLOW_INF_AND_NAN) { + if (*cur == 'i') { + byte_copy_8(cur, "Infinity"); + cur += 8; + return cur; + } else if (*cur == 'n') { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } + return NULL; + } else { + /* finite number */ + int i = 0; + bool fp = false; + for (; i < len; i++) { + if (buf[i] == ',') buf[i] = '.'; + if (digi_is_fp((u8)buf[i])) fp = true; + } + if (!fp) { + buf[len++] = '.'; + buf[len++] = '0'; + } + } + return buf + len; +} + +#endif /* FP_WRITER */ + +/** Write a JSON number (requires 32 bytes buffer). */ +static_inline u8 *write_number(u8 *cur, yyjson_val *val, + yyjson_write_flag flg) { + if (val->tag & YYJSON_SUBTYPE_REAL) { + u64 raw = val->uni.u64; + return write_f64_raw(cur, raw, flg); + } else { + u64 pos = val->uni.u64; + u64 neg = ~pos + 1; + usize sgn = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0); + *cur = '-'; + return write_u64(sgn ? neg : pos, cur + sgn); + } +} + + + +/*============================================================================== + * String Writer + *============================================================================*/ + +/** Character encode type, if (type > CHAR_ENC_ERR_1) bytes = type / 2; */ +typedef u8 char_enc_type; +#define CHAR_ENC_CPY_1 0 /* 1-byte UTF-8, copy. */ +#define CHAR_ENC_ERR_1 1 /* 1-byte UTF-8, error. */ +#define CHAR_ENC_ESC_A 2 /* 1-byte ASCII, escaped as '\x'. */ +#define CHAR_ENC_ESC_1 3 /* 1-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_2 4 /* 2-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_2 5 /* 2-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_3 6 /* 3-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_3 7 /* 3-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_4 8 /* 4-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_4 9 /* 4-byte UTF-8, escaped as '\uXXXX\uXXXX'. */ + +/** Character encode type table: don't escape unicode, don't escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_cpy[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: don't escape unicode, escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_cpy_slash[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: escape unicode, don't escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_esc[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: escape unicode, escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_esc_slash[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Escaped hex character table: ["00" "01" "02" ... "FD" "FE" "FF"]. + (generate with misc/make_tables.c) */ +yyjson_align(2) +static const u8 esc_hex_char_table[512] = { + '0', '0', '0', '1', '0', '2', '0', '3', + '0', '4', '0', '5', '0', '6', '0', '7', + '0', '8', '0', '9', '0', 'A', '0', 'B', + '0', 'C', '0', 'D', '0', 'E', '0', 'F', + '1', '0', '1', '1', '1', '2', '1', '3', + '1', '4', '1', '5', '1', '6', '1', '7', + '1', '8', '1', '9', '1', 'A', '1', 'B', + '1', 'C', '1', 'D', '1', 'E', '1', 'F', + '2', '0', '2', '1', '2', '2', '2', '3', + '2', '4', '2', '5', '2', '6', '2', '7', + '2', '8', '2', '9', '2', 'A', '2', 'B', + '2', 'C', '2', 'D', '2', 'E', '2', 'F', + '3', '0', '3', '1', '3', '2', '3', '3', + '3', '4', '3', '5', '3', '6', '3', '7', + '3', '8', '3', '9', '3', 'A', '3', 'B', + '3', 'C', '3', 'D', '3', 'E', '3', 'F', + '4', '0', '4', '1', '4', '2', '4', '3', + '4', '4', '4', '5', '4', '6', '4', '7', + '4', '8', '4', '9', '4', 'A', '4', 'B', + '4', 'C', '4', 'D', '4', 'E', '4', 'F', + '5', '0', '5', '1', '5', '2', '5', '3', + '5', '4', '5', '5', '5', '6', '5', '7', + '5', '8', '5', '9', '5', 'A', '5', 'B', + '5', 'C', '5', 'D', '5', 'E', '5', 'F', + '6', '0', '6', '1', '6', '2', '6', '3', + '6', '4', '6', '5', '6', '6', '6', '7', + '6', '8', '6', '9', '6', 'A', '6', 'B', + '6', 'C', '6', 'D', '6', 'E', '6', 'F', + '7', '0', '7', '1', '7', '2', '7', '3', + '7', '4', '7', '5', '7', '6', '7', '7', + '7', '8', '7', '9', '7', 'A', '7', 'B', + '7', 'C', '7', 'D', '7', 'E', '7', 'F', + '8', '0', '8', '1', '8', '2', '8', '3', + '8', '4', '8', '5', '8', '6', '8', '7', + '8', '8', '8', '9', '8', 'A', '8', 'B', + '8', 'C', '8', 'D', '8', 'E', '8', 'F', + '9', '0', '9', '1', '9', '2', '9', '3', + '9', '4', '9', '5', '9', '6', '9', '7', + '9', '8', '9', '9', '9', 'A', '9', 'B', + '9', 'C', '9', 'D', '9', 'E', '9', 'F', + 'A', '0', 'A', '1', 'A', '2', 'A', '3', + 'A', '4', 'A', '5', 'A', '6', 'A', '7', + 'A', '8', 'A', '9', 'A', 'A', 'A', 'B', + 'A', 'C', 'A', 'D', 'A', 'E', 'A', 'F', + 'B', '0', 'B', '1', 'B', '2', 'B', '3', + 'B', '4', 'B', '5', 'B', '6', 'B', '7', + 'B', '8', 'B', '9', 'B', 'A', 'B', 'B', + 'B', 'C', 'B', 'D', 'B', 'E', 'B', 'F', + 'C', '0', 'C', '1', 'C', '2', 'C', '3', + 'C', '4', 'C', '5', 'C', '6', 'C', '7', + 'C', '8', 'C', '9', 'C', 'A', 'C', 'B', + 'C', 'C', 'C', 'D', 'C', 'E', 'C', 'F', + 'D', '0', 'D', '1', 'D', '2', 'D', '3', + 'D', '4', 'D', '5', 'D', '6', 'D', '7', + 'D', '8', 'D', '9', 'D', 'A', 'D', 'B', + 'D', 'C', 'D', 'D', 'D', 'E', 'D', 'F', + 'E', '0', 'E', '1', 'E', '2', 'E', '3', + 'E', '4', 'E', '5', 'E', '6', 'E', '7', + 'E', '8', 'E', '9', 'E', 'A', 'E', 'B', + 'E', 'C', 'E', 'D', 'E', 'E', 'E', 'F', + 'F', '0', 'F', '1', 'F', '2', 'F', '3', + 'F', '4', 'F', '5', 'F', '6', 'F', '7', + 'F', '8', 'F', '9', 'F', 'A', 'F', 'B', + 'F', 'C', 'F', 'D', 'F', 'E', 'F', 'F' +}; + +/** Escaped single character table. (generate with misc/make_tables.c) */ +yyjson_align(2) +static const u8 esc_single_char_table[512] = { + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '\\', 'b', '\\', 't', '\\', 'n', ' ', ' ', + '\\', 'f', '\\', 'r', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', '\\', '"', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', '\\', '/', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '\\', '\\', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' +}; + +/** Returns the encode table with options. */ +static_inline const char_enc_type *get_enc_table_with_flag( + yyjson_read_flag flg) { + if (unlikely(flg & YYJSON_WRITE_ESCAPE_UNICODE)) { + if (unlikely(flg & YYJSON_WRITE_ESCAPE_SLASHES)) { + return enc_table_esc_slash; + } else { + return enc_table_esc; + } + } else { + if (unlikely(flg & YYJSON_WRITE_ESCAPE_SLASHES)) { + return enc_table_cpy_slash; + } else { + return enc_table_cpy; + } + } +} + +/** Write raw string. */ +static_inline u8 *write_raw(u8 *cur, const u8 *raw, usize raw_len) { + memcpy(cur, raw, raw_len); + return cur + raw_len; +} + +/** + Write UTF-8 string (requires len * 6 + 2 bytes buffer). + @param cur Buffer cursor. + @param esc Escape unicode. + @param inv Allow invalid unicode. + @param str A UTF-8 string, null-terminator is not required. + @param str_len Length of string in bytes. + @param enc_table Encode type table for character. + @return The buffer cursor after string, or NULL on invalid unicode. + */ +static_inline u8 *write_string(u8 *cur, bool esc, bool inv, + const u8 *str, usize str_len, + const char_enc_type *enc_table) { + + /* UTF-8 character mask and pattern, see `read_string()` for details. */ +#if YYJSON_ENDIAN == YYJSON_BIG_ENDIAN + const u16 b2_mask = 0xE0C0UL; + const u16 b2_patt = 0xC080UL; + const u16 b2_requ = 0x1E00UL; + const u32 b3_mask = 0xF0C0C000UL; + const u32 b3_patt = 0xE0808000UL; + const u32 b3_requ = 0x0F200000UL; + const u32 b3_erro = 0x0D200000UL; + const u32 b4_mask = 0xF8C0C0C0UL; + const u32 b4_patt = 0xF0808080UL; + const u32 b4_requ = 0x07300000UL; + const u32 b4_err0 = 0x04000000UL; + const u32 b4_err1 = 0x03300000UL; +#elif YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN + const u16 b2_mask = 0xC0E0UL; + const u16 b2_patt = 0x80C0UL; + const u16 b2_requ = 0x001EUL; + const u32 b3_mask = 0x00C0C0F0UL; + const u32 b3_patt = 0x008080E0UL; + const u32 b3_requ = 0x0000200FUL; + const u32 b3_erro = 0x0000200DUL; + const u32 b4_mask = 0xC0C0C0F8UL; + const u32 b4_patt = 0x808080F0UL; + const u32 b4_requ = 0x00003007UL; + const u32 b4_err0 = 0x00000004UL; + const u32 b4_err1 = 0x00003003UL; +#else + v16_uni b2_mask_uni = {{ 0xE0, 0xC0 }}; + v16_uni b2_patt_uni = {{ 0xC0, 0x80 }}; + v16_uni b2_requ_uni = {{ 0x1E, 0x00 }}; + v32_uni b3_mask_uni = {{ 0xF0, 0xC0, 0xC0, 0x00 }}; + v32_uni b3_patt_uni = {{ 0xE0, 0x80, 0x80, 0x00 }}; + v32_uni b3_requ_uni = {{ 0x0F, 0x20, 0x00, 0x00 }}; + v32_uni b3_erro_uni = {{ 0x0D, 0x20, 0x00, 0x00 }}; + v32_uni b4_mask_uni = {{ 0xF8, 0xC0, 0xC0, 0xC0 }}; + v32_uni b4_patt_uni = {{ 0xF0, 0x80, 0x80, 0x80 }}; + v32_uni b4_requ_uni = {{ 0x07, 0x30, 0x00, 0x00 }}; + v32_uni b4_err0_uni = {{ 0x04, 0x00, 0x00, 0x00 }}; + v32_uni b4_err1_uni = {{ 0x03, 0x30, 0x00, 0x00 }}; + u16 b2_mask = b2_mask_uni.u; + u16 b2_patt = b2_patt_uni.u; + u16 b2_requ = b2_requ_uni.u; + u32 b3_mask = b3_mask_uni.u; + u32 b3_patt = b3_patt_uni.u; + u32 b3_requ = b3_requ_uni.u; + u32 b3_erro = b3_erro_uni.u; + u32 b4_mask = b4_mask_uni.u; + u32 b4_patt = b4_patt_uni.u; + u32 b4_requ = b4_requ_uni.u; + u32 b4_err0 = b4_err0_uni.u; + u32 b4_err1 = b4_err1_uni.u; +#endif + +#define is_valid_seq_2(uni) ( \ + ((uni & b2_mask) == b2_patt) && \ + ((uni & b2_requ)) \ +) + +#define is_valid_seq_3(uni) ( \ + ((uni & b3_mask) == b3_patt) && \ + ((tmp = (uni & b3_requ))) && \ + ((tmp != b3_erro)) \ +) + +#define is_valid_seq_4(uni) ( \ + ((uni & b4_mask) == b4_patt) && \ + ((tmp = (uni & b4_requ))) && \ + ((tmp & b4_err0) == 0 || (tmp & b4_err1) == 0) \ +) + + /* The replacement character U+FFFD, used to indicate invalid character. */ + const v32 rep = { 'F', 'F', 'F', 'D' }; + const v32 pre = { '\\', 'u', '0', '0' }; + + const u8 *src = str; + const u8 *end = str + str_len; + *cur++ = '"'; + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (end > src) ( + if (unlikely(enc_table[*src])) break; + *cur++ = *src++; + ); + */ +#define expr_jump(i) \ + if (unlikely(enc_table[src[i]])) goto stop_char_##i; + +#define expr_stop(i) \ + stop_char_##i: \ + memcpy(cur, src, i); \ + cur += i; src += i; goto copy_utf8; + + while (end - src >= 16) { + repeat16_incr(expr_jump) + byte_copy_16(cur, src); + cur += 16; src += 16; + } + + while (end - src >= 4) { + repeat4_incr(expr_jump) + byte_copy_4(cur, src); + cur += 4; src += 4; + } + + while (end > src) { + expr_jump(0) + *cur++ = *src++; + } + + *cur++ = '"'; + return cur; + + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + +copy_utf8: + if (unlikely(src + 4 > end)) { + if (end == src) goto copy_end; + if (end - src < enc_table[*src] / 2) goto err_one; + } + switch (enc_table[*src]) { + case CHAR_ENC_CPY_1: { + *cur++ = *src++; + goto copy_ascii; + } + case CHAR_ENC_CPY_2: { + u16 v; + v = byte_load_2(src); + if (unlikely(!is_valid_seq_2(v))) goto err_cpy; + + byte_copy_2(cur, src); + cur += 2; + src += 2; + goto copy_utf8; + } + case CHAR_ENC_CPY_3: { + u32 v, tmp; + if (likely(src + 4 <= end)) { + v = byte_load_4(src); + if (unlikely(!is_valid_seq_3(v))) goto err_cpy; + byte_copy_4(cur, src); + } else { + v = byte_load_3(src); + if (unlikely(!is_valid_seq_3(v))) goto err_cpy; + byte_copy_4(cur, &v); + } + cur += 3; + src += 3; + goto copy_utf8; + } + case CHAR_ENC_CPY_4: { + u32 v, tmp; + v = byte_load_4(src); + if (unlikely(!is_valid_seq_4(v))) goto err_cpy; + + byte_copy_4(cur, src); + cur += 4; + src += 4; + goto copy_utf8; + } + case CHAR_ENC_ESC_A: { + byte_move_2(cur, &esc_single_char_table[*src * 2]); + cur += 2; + src += 1; + goto copy_utf8; + } + case CHAR_ENC_ESC_1: { + byte_copy_4(cur + 0, &pre); + byte_copy_2(cur + 4, &esc_hex_char_table[*src * 2]); + cur += 6; + src += 1; + goto copy_utf8; + } + case CHAR_ENC_ESC_2: { + u16 u, v; + v = byte_load_2(src); + if (unlikely(!is_valid_seq_2(v))) goto err_esc; + + u = (u16)(((u16)(src[0] & 0x1F) << 6) | + ((u16)(src[1] & 0x3F) << 0)); + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); + cur += 6; + src += 2; + goto copy_utf8; + } + case CHAR_ENC_ESC_3: { + u16 u; + u32 v, tmp; + v = byte_load_3(src); + if (unlikely(!is_valid_seq_3(v))) goto err_esc; + + u = (u16)(((u16)(src[0] & 0x0F) << 12) | + ((u16)(src[1] & 0x3F) << 6) | + ((u16)(src[2] & 0x3F) << 0)); + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); + cur += 6; + src += 3; + goto copy_utf8; + } + case CHAR_ENC_ESC_4: { + u32 hi, lo, u, v, tmp; + v = byte_load_4(src); + if (unlikely(!is_valid_seq_4(v))) goto err_esc; + + u = ((u32)(src[0] & 0x07) << 18) | + ((u32)(src[1] & 0x3F) << 12) | + ((u32)(src[2] & 0x3F) << 6) | + ((u32)(src[3] & 0x3F) << 0); + u -= 0x10000; + hi = (u >> 10) + 0xD800; + lo = (u & 0x3FF) + 0xDC00; + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(hi >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(hi & 0xFF) * 2]); + byte_copy_2(cur + 6, &pre); + byte_copy_2(cur + 8, &esc_hex_char_table[(lo >> 8) * 2]); + byte_copy_2(cur + 10, &esc_hex_char_table[(lo & 0xFF) * 2]); + cur += 12; + src += 4; + goto copy_utf8; + } + case CHAR_ENC_ERR_1: { + goto err_one; + } + default: break; + } + +copy_end: + *cur++ = '"'; + return cur; + +err_one: + if (esc) goto err_esc; + else goto err_cpy; + +err_cpy: + if (!inv) return NULL; + *cur++ = *src++; + goto copy_utf8; + +err_esc: + if (!inv) return NULL; + byte_copy_2(cur + 0, &pre); + byte_copy_4(cur + 2, &rep); + cur += 6; + src += 1; + goto copy_utf8; + +#undef is_valid_seq_2 +#undef is_valid_seq_3 +#undef is_valid_seq_4 +} + + + +/*============================================================================== + * Writer Utilities + *============================================================================*/ + +/** Write null (requires 8 bytes buffer). */ +static_inline u8 *write_null(u8 *cur) { + v64 v = { 'n', 'u', 'l', 'l', ',', '\n', 0, 0 }; + byte_copy_8(cur, &v); + return cur + 4; +} + +/** Write bool (requires 8 bytes buffer). */ +static_inline u8 *write_bool(u8 *cur, bool val) { + v64 v0 = { 'f', 'a', 'l', 's', 'e', ',', '\n', 0 }; + v64 v1 = { 't', 'r', 'u', 'e', ',', '\n', 0, 0 }; + if (val) { + byte_copy_8(cur, &v1); + } else { + byte_copy_8(cur, &v0); + } + return cur + 5 - val; +} + +/** Write indent (requires level x 4 bytes buffer). + Param spaces should not larger than 4. */ +static_inline u8 *write_indent(u8 *cur, usize level, usize spaces) { + while (level-- > 0) { + byte_copy_4(cur, " "); + cur += spaces; + } + return cur; +} + +/** Write data to file pointer. */ +static bool write_dat_to_fp(FILE *fp, u8 *dat, usize len, + yyjson_write_err *err) { + if (fwrite(dat, len, 1, fp) != 1) { + err->msg = "file writing failed"; + err->code = YYJSON_WRITE_ERROR_FILE_WRITE; + return false; + } + return true; +} + +/** Write data to file. */ +static bool write_dat_to_file(const char *path, u8 *dat, usize len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + err->msg = _msg; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + if (file) fclose(file); \ + return false; \ +} while (false) + + FILE *file = fopen_writeonly(path); + if (file == NULL) { + return_err(FILE_OPEN, "file opening failed"); + } + if (fwrite(dat, len, 1, file) != 1) { + return_err(FILE_WRITE, "file writing failed"); + } + if (fclose(file) != 0) { + file = NULL; + return_err(FILE_WRITE, "file closing failed"); + } + return true; + +#undef return_err +} + + + +/*============================================================================== + * JSON Writer Implementation + *============================================================================*/ + +typedef struct yyjson_write_ctx { + usize tag; +} yyjson_write_ctx; + +static_inline void yyjson_write_ctx_set(yyjson_write_ctx *ctx, + usize size, bool is_obj) { + ctx->tag = (size << 1) | (usize)is_obj; +} + +static_inline void yyjson_write_ctx_get(yyjson_write_ctx *ctx, + usize *size, bool *is_obj) { + usize tag = ctx->tag; + *size = tag >> 1; + *is_obj = (bool)(tag & 1); +} + +/** Write single JSON value. */ +static_inline u8 *yyjson_write_single(yyjson_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + if (hdr) alc.free(alc.ctx, (void *)hdr); \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + hdr = (u8 *)alc.malloc(alc.ctx, _len); \ + if (!hdr) goto fail_alloc; \ + cur = hdr; \ +} while (false) + +#define check_str_len(_len) do { \ + if ((USIZE_MAX < U64_MAX) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + u8 *hdr = NULL, *cur; + usize str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool esc = (flg & YYJSON_WRITE_ESCAPE_UNICODE) != 0; + bool inv = (flg & YYJSON_WRITE_ALLOW_INVALID_UNICODE) != 0; + + switch (unsafe_yyjson_get_type(val)) { + case YYJSON_TYPE_RAW: + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 1); + cur = write_raw(cur, str_ptr, str_len); + break; + + case YYJSON_TYPE_STR: + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 4); + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + break; + + case YYJSON_TYPE_NUM: + incr_len(32); + cur = write_number(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + break; + + case YYJSON_TYPE_BOOL: + incr_len(8); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + break; + + case YYJSON_TYPE_NULL: + incr_len(8); + cur = write_null(cur); + break; + + case YYJSON_TYPE_ARR: + incr_len(4); + byte_copy_2(cur, "[]"); + cur += 2; + break; + + case YYJSON_TYPE_OBJ: + incr_len(4); + byte_copy_2(cur, "{}"); + cur += 2; + break; + + default: + goto fail_type; + } + + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef check_str_len +#undef incr_len +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_write_minify(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ + if (size_add_is_overflow(alc_len, alc_inc)) goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((USIZE_MAX < U64_MAX) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_val *val; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key; + u8 *hdr, *cur, *end, *tmp; + yyjson_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool esc = (flg & YYJSON_WRITE_ESCAPE_UNICODE) != 0; + bool inv = (flg & YYJSON_WRITE_ALLOW_INVALID_UNICODE) != 0; + + alc_len = root->uni.ofs / sizeof(yyjson_val); + alc_len = alc_len * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + val++; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = ((u8)ctn_obj & (u8)~ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16); + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + *cur++ = is_key ? ':' : ','; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + incr_len(32); + cur = write_number(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + incr_len(16); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + goto val_end; + } else { + /* push context, setup new container */ + yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + val++; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + incr_len(16); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + incr_len(16); + cur = write_null(cur); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 2); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + goto val_end; + } + goto fail_type; + +val_end: + val++; + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + goto val_begin; + +ctn_end: + cur--; + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + *cur++ = ','; + if (unlikely((u8 *)ctx >= end)) goto doc_end; + yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); + ctn_len--; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *--cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +/** Write JSON document pretty. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_write_pretty(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ + if (size_add_is_overflow(alc_len, alc_inc)) goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((USIZE_MAX < U64_MAX) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_val *val; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key, no_indent; + u8 *hdr, *cur, *end, *tmp; + yyjson_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool esc = (flg & YYJSON_WRITE_ESCAPE_UNICODE) != 0; + bool inv = (flg & YYJSON_WRITE_ALLOW_INVALID_UNICODE) != 0; + usize spaces = (flg & YYJSON_WRITE_PRETTY_TWO_SPACES) ? 2 : 4; + + alc_len = root->uni.ofs / sizeof(yyjson_val); + alc_len = alc_len * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + val++; + level = 1; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + *cur++ = is_key ? ':' : ','; + *cur++ = is_key ? ' ' : '\n'; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(32 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_number(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } else { + /* push context, setup new container */ + incr_len(32 + (no_indent ? 0 : level * 4)); + yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + cur = write_indent(cur, no_indent ? 0 : level, spaces); + level++; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + val++; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_null(cur); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 3); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + goto fail_type; + +val_end: + val++; + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + goto val_begin; + +ctn_end: + cur -= 2; + *cur++ = '\n'; + incr_len(level * 4); + cur = write_indent(cur, --level, spaces); + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + if (unlikely((u8 *)ctx >= end)) goto doc_end; + yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); + ctn_len--; + *cur++ = ','; + *cur++ = '\n'; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +char *yyjson_val_write_opts(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + usize dummy_dat_len; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_val *root = constcast(yyjson_val *)val; + + err = err ? err : &dummy_err; + dat_len = dat_len ? dat_len : &dummy_dat_len; + +#if YYJSON_DISABLE_NON_STANDARD + flg &= ~YYJSON_WRITE_ALLOW_INF_AND_NAN; + flg &= ~YYJSON_WRITE_ALLOW_INVALID_UNICODE; +#endif + + if (unlikely(!root)) { + *dat_len = 0; + err->msg = "input JSON is NULL"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { + return (char *)yyjson_write_single(root, flg, alc, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)yyjson_write_pretty(root, flg, alc, dat_len, err); + } else { + return (char *)yyjson_write_minify(root, flg, alc, dat_len, err); + } +} + +char *yyjson_write_opts(const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_opts(root, flg, alc_ptr, dat_len, err); +} + +bool yyjson_val_write_file(const char *path, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_val *root = constcast(yyjson_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!path || !*path)) { + err->msg = "input path is invalid"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_file(path, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_val_write_fp(FILE *fp, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_val *root = constcast(yyjson_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!fp)) { + err->msg = "input fp is invalid"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_fp(fp, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_write_file(const char *path, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_file(path, root, flg, alc_ptr, err); +} + +bool yyjson_write_fp(FILE *fp, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_fp(fp, root, flg, alc_ptr, err); +} + + + +/*============================================================================== + * Mutable JSON Writer Implementation + *============================================================================*/ + +typedef struct yyjson_mut_write_ctx { + usize tag; + yyjson_mut_val *ctn; +} yyjson_mut_write_ctx; + +static_inline void yyjson_mut_write_ctx_set(yyjson_mut_write_ctx *ctx, + yyjson_mut_val *ctn, + usize size, bool is_obj) { + ctx->tag = (size << 1) | (usize)is_obj; + ctx->ctn = ctn; +} + +static_inline void yyjson_mut_write_ctx_get(yyjson_mut_write_ctx *ctx, + yyjson_mut_val **ctn, + usize *size, bool *is_obj) { + usize tag = ctx->tag; + *size = tag >> 1; + *is_obj = (bool)(tag & 1); + *ctn = ctx->ctn; +} + +/** Get the estimated number of values for the mutable JSON document. */ +static_inline usize yyjson_mut_doc_estimated_val_num( + const yyjson_mut_doc *doc) { + usize sum = 0; + yyjson_val_chunk *chunk = doc->val_pool.chunks; + while (chunk) { + sum += chunk->chunk_size / sizeof(yyjson_mut_val) - 1; + if (chunk == doc->val_pool.chunks) { + sum -= (usize)(doc->val_pool.end - doc->val_pool.cur); + } + chunk = chunk->next; + } + return sum; +} + +/** Write single JSON value. */ +static_inline u8 *yyjson_mut_write_single(yyjson_mut_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + return yyjson_write_single((yyjson_val *)val, flg, alc, dat_len, err); +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ + if (size_add_is_overflow(alc_len, alc_inc)) goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((USIZE_MAX < U64_MAX) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_mut_val *val, *ctn; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key; + u8 *hdr, *cur, *end, *tmp; + yyjson_mut_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool esc = (flg & YYJSON_WRITE_ESCAPE_UNICODE) != 0; + bool inv = (flg & YYJSON_WRITE_ALLOW_INVALID_UNICODE) != 0; + + alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_mut_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_mut_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + ctn = val; + val = (yyjson_mut_val *)val->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = ((u8)ctn_obj & (u8)~ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16); + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + *cur++ = is_key ? ':' : ','; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + incr_len(32); + cur = write_number(cur, (yyjson_val *)val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + incr_len(16); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + goto val_end; + } else { + /* push context, setup new container */ + yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + ctn = val; + val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + incr_len(16); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + incr_len(16); + cur = write_null(cur); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 2); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + goto val_end; + } + goto fail_type; + +val_end: + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + val = val->next; + goto val_begin; + +ctn_end: + cur--; + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + *cur++ = ','; + if (unlikely((u8 *)ctx >= end)) goto doc_end; + val = ctn->next; + yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); + ctn_len--; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *--cur = '\0'; + *dat_len = (usize)(cur - hdr); + err->code = YYJSON_WRITE_SUCCESS; + err->msg = "success"; + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +/** Write JSON document pretty. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ + if (size_add_is_overflow(alc_len, alc_inc)) goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_len = (usize)(end - (u8 *)ctx); \ + ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ((u8 *)ctx - hdr)), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + (cur - hdr); \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((USIZE_MAX < U64_MAX) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_mut_val *val, *ctn; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key, no_indent; + u8 *hdr, *cur, *end, *tmp; + yyjson_mut_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool esc = (flg & YYJSON_WRITE_ESCAPE_UNICODE) != 0; + bool inv = (flg & YYJSON_WRITE_ALLOW_INVALID_UNICODE) != 0; + usize spaces = (flg & YYJSON_WRITE_PRETTY_TWO_SPACES) ? 2 : 4; + + alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_mut_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_mut_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + ctn = val; + val = (yyjson_mut_val *)val->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + level = 1; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_string(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + *cur++ = is_key ? ':' : ','; + *cur++ = is_key ? ' ' : '\n'; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(32 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_number(cur, (yyjson_val *)val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } else { + /* push context, setup new container */ + incr_len(32 + (no_indent ? 0 : level * 4)); + yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + cur = write_indent(cur, no_indent ? 0 : level, spaces); + level++; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + ctn = val; + val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_null(cur); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 3); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + goto fail_type; + +val_end: + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + val = val->next; + goto val_begin; + +ctn_end: + cur -= 2; + *cur++ = '\n'; + incr_len(level * 4); + cur = write_indent(cur, --level, spaces); + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + if (unlikely((u8 *)ctx >= end)) goto doc_end; + val = ctn->next; + yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); + ctn_len--; + *cur++ = ','; + *cur++ = '\n'; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + err->code = YYJSON_WRITE_SUCCESS; + err->msg = "success"; + return hdr; + +fail_alloc: + return_err(MEMORY_ALLOCATION, "memory allocation failed"); +fail_type: + return_err(INVALID_VALUE_TYPE, "invalid JSON value type"); +fail_num: + return_err(NAN_OR_INF, "nan or inf number is not allowed"); +fail_str: + return_err(INVALID_STRING, "invalid utf-8 encoding in string"); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +static char *yyjson_mut_write_opts_impl(const yyjson_mut_val *val, + usize estimated_val_num, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + usize dummy_dat_len; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + + err = err ? err : &dummy_err; + dat_len = dat_len ? dat_len : &dummy_dat_len; + +#if YYJSON_DISABLE_NON_STANDARD + flg &= ~YYJSON_WRITE_ALLOW_INF_AND_NAN; + flg &= ~YYJSON_WRITE_ALLOW_INVALID_UNICODE; +#endif + + if (unlikely(!root)) { + *dat_len = 0; + err->msg = "input JSON is NULL"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { + return (char *)yyjson_mut_write_single(root, flg, alc, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)yyjson_mut_write_pretty(root, estimated_val_num, + flg, alc, dat_len, err); + } else { + return (char *)yyjson_mut_write_minify(root, estimated_val_num, + flg, alc, dat_len, err); + } +} + +char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + return yyjson_mut_write_opts_impl(val, 0, flg, alc_ptr, dat_len, err); +} + +char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_mut_val *root; + usize estimated_val_num; + if (likely(doc)) { + root = doc->root; + estimated_val_num = yyjson_mut_doc_estimated_val_num(doc); + } else { + root = NULL; + estimated_val_num = 0; + } + return yyjson_mut_write_opts_impl(root, estimated_val_num, + flg, alc_ptr, dat_len, err); +} + +bool yyjson_mut_val_write_file(const char *path, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!path || !*path)) { + err->msg = "input path is invalid"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_mut_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_file(path, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_mut_val_write_fp(FILE *fp, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err dummy_err; + u8 *dat; + usize dat_len = 0; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + bool suc; + + alc_ptr = alc_ptr ? alc_ptr : &YYJSON_DEFAULT_ALC; + err = err ? err : &dummy_err; + if (unlikely(!fp)) { + err->msg = "input fp is invalid"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_mut_val_write_opts(root, flg, alc_ptr, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_fp(fp, dat, dat_len, err); + alc_ptr->free(alc_ptr->ctx, dat); + return suc; +} + +bool yyjson_mut_write_file(const char *path, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_file(path, root, flg, alc_ptr, err); +} + +bool yyjson_mut_write_fp(FILE *fp, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_fp(fp, root, flg, alc_ptr, err); +} + +#endif /* YYJSON_DISABLE_WRITER */ + + + +/*============================================================================== + * Compiler Hint End + *============================================================================*/ + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +# endif +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif /* warning suppress end */ diff --git a/libsvc/Modules/Lib/MJson/yyjson.h b/libsvc/Modules/Lib/MJson/yyjson.h new file mode 100644 index 0000000..1e30b22 --- /dev/null +++ b/libsvc/Modules/Lib/MJson/yyjson.h @@ -0,0 +1,7688 @@ +/*============================================================================== + * Created by Yaoyuan on 2019/3/9. + * Copyright (C) 2019 Yaoyuan . + * + * Released under the MIT License: + * https://github.com/ibireme/yyjson/blob/master/LICENSE + *============================================================================*/ + +/** @file yyjson.h */ + +#ifndef YYJSON_H +#define YYJSON_H + +/*============================================================================== + * Header Files + *============================================================================*/ + +#include +#include +#include +#include +#include +#include + + + +/*============================================================================== + * Compile-time Options + *============================================================================*/ + +/* + Define as 1 to disable JSON reader if JSON parsing is not required. + + This will disable these functions at compile-time: + - yyjson_read_opts() + - yyjson_read_file() + - yyjson_read() + - yyjson_read_number() + - yyjson_mut_read_number() + + This will reduce the binary size by about 60%. + */ +#ifndef YYJSON_DISABLE_READER +#endif + +/* + Define as 1 to disable JSON writer if JSON serialization is not required. + + This will disable these functions at compile-time: + - yyjson_write() + - yyjson_write_file() + - yyjson_write_opts() + - yyjson_val_write() + - yyjson_val_write_file() + - yyjson_val_write_opts() + - yyjson_mut_write() + - yyjson_mut_write_file() + - yyjson_mut_write_opts() + - yyjson_mut_val_write() + - yyjson_mut_val_write_file() + - yyjson_mut_val_write_opts() + + This will reduce the binary size by about 30%. + */ +#ifndef YYJSON_DISABLE_WRITER +#endif + +/* + Define as 1 to disable JSON Pointer, JSON Patch and JSON Merge Patch supports. + + This will disable these functions at compile-time: + - yyjson_ptr_xxx() + - yyjson_mut_ptr_xxx() + - yyjson_doc_ptr_xxx() + - yyjson_mut_doc_ptr_xxx() + - yyjson_patch() + - yyjson_mut_patch() + - yyjson_merge_patch() + - yyjson_mut_merge_patch() + */ +#ifndef YYJSON_DISABLE_UTILS +#endif + +/* + Define as 1 to disable the fast floating-point number conversion in yyjson, + and use libc's `strtod/snprintf` instead. + + This will reduce the binary size by about 30%, but significantly slow down the + floating-point read/write speed. + */ +#ifndef YYJSON_DISABLE_FAST_FP_CONV +#endif + +/* + Define as 1 to disable non-standard JSON support at compile-time: + - Reading and writing inf/nan literal, such as `NaN`, `-Infinity`. + - Single line and multiple line comments. + - Single trailing comma at the end of an object or array. + - Invalid unicode in string value. + + This will also invalidate these run-time options: + - YYJSON_READ_ALLOW_INF_AND_NAN + - YYJSON_READ_ALLOW_COMMENTS + - YYJSON_READ_ALLOW_TRAILING_COMMAS + - YYJSON_READ_ALLOW_INVALID_UNICODE + - YYJSON_WRITE_ALLOW_INF_AND_NAN + - YYJSON_WRITE_ALLOW_INVALID_UNICODE + + This will reduce the binary size by about 10%, and slightly improve the JSON + read/write speed. + */ +#ifndef YYJSON_DISABLE_NON_STANDARD +#endif + +/* + Define as 1 to disable unaligned memory access if target architecture does not + support unaligned memory access (such as some embedded processors). + + If this value is not defined, yyjson will perform some automatic detection. + The wrong definition of this option may cause some performance degradation, + but will not cause any run-time errors. + */ +#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS +#endif + +/* Define as 1 to export symbols when building this library as Windows DLL. */ +#ifndef YYJSON_EXPORTS +#endif + +/* Define as 1 to import symbols when using this library as Windows DLL. */ +#ifndef YYJSON_IMPORTS +#endif + +/* Define as 1 to include for compiler which doesn't support C99. */ +#ifndef YYJSON_HAS_STDINT_H +#endif + +/* Define as 1 to include for compiler which doesn't support C99. */ +#ifndef YYJSON_HAS_STDBOOL_H +#endif + + + +/*============================================================================== + * Compiler Macros + *============================================================================*/ + +/** compiler version (MSVC) */ +#ifdef _MSC_VER +# define YYJSON_MSC_VER _MSC_VER +#else +# define YYJSON_MSC_VER 0 +#endif + +/** compiler version (GCC) */ +#ifdef __GNUC__ +# define YYJSON_GCC_VER __GNUC__ +#else +# define YYJSON_GCC_VER 0 +#endif + +/** C version (STDC) */ +#if defined(__STDC__) && (__STDC__ >= 1) && defined(__STDC_VERSION__) +# define YYJSON_STDC_VER __STDC_VERSION__ +#else +# define YYJSON_STDC_VER 0 +#endif + +/** C++ version */ +#if defined(__cplusplus) +# define YYJSON_CPP_VER __cplusplus +#else +# define YYJSON_CPP_VER 0 +#endif + +/** compiler builtin check (since gcc 10.0, clang 2.6, icc 2021) */ +#ifndef yyjson_has_builtin +# ifdef __has_builtin +# define yyjson_has_builtin(x) __has_builtin(x) +# else +# define yyjson_has_builtin(x) 0 +# endif +#endif + +/** compiler attribute check (since gcc 5.0, clang 2.9, icc 17) */ +#ifndef yyjson_has_attribute +# ifdef __has_attribute +# define yyjson_has_attribute(x) __has_attribute(x) +# else +# define yyjson_has_attribute(x) 0 +# endif +#endif + +/** compiler feature check (since clang 2.6, icc 17) */ +#ifndef yyjson_has_feature +# ifdef __has_feature +# define yyjson_has_feature(x) __has_feature(x) +# else +# define yyjson_has_feature(x) 0 +# endif +#endif + +/** include check (since gcc 5.0, clang 2.7, icc 16, msvc 2017 15.3) */ +#ifndef yyjson_has_include +# ifdef __has_include +# define yyjson_has_include(x) __has_include(x) +# else +# define yyjson_has_include(x) 0 +# endif +#endif + +/** inline for compiler */ +#ifndef yyjson_inline +# if YYJSON_MSC_VER >= 1200 +# define yyjson_inline __forceinline +# elif defined(_MSC_VER) +# define yyjson_inline __inline +# elif yyjson_has_attribute(always_inline) || YYJSON_GCC_VER >= 4 +# define yyjson_inline __inline__ __attribute__((always_inline)) +# elif defined(__clang__) || defined(__GNUC__) +# define yyjson_inline __inline__ +# elif defined(__cplusplus) || YYJSON_STDC_VER >= 199901L +# define yyjson_inline inline +# else +# define yyjson_inline +# endif +#endif + +/** noinline for compiler */ +#ifndef yyjson_noinline +# if YYJSON_MSC_VER >= 1400 +# define yyjson_noinline __declspec(noinline) +# elif yyjson_has_attribute(noinline) || YYJSON_GCC_VER >= 4 +# define yyjson_noinline __attribute__((noinline)) +# else +# define yyjson_noinline +# endif +#endif + +/** align for compiler */ +#ifndef yyjson_align +# if YYJSON_MSC_VER >= 1300 +# define yyjson_align(x) __declspec(align(x)) +# elif yyjson_has_attribute(aligned) || defined(__GNUC__) +# define yyjson_align(x) __attribute__((aligned(x))) +# elif YYJSON_CPP_VER >= 201103L +# define yyjson_align(x) alignas(x) +# else +# define yyjson_align(x) +# endif +#endif + +/** likely for compiler */ +#ifndef yyjson_likely +# if yyjson_has_builtin(__builtin_expect) || \ + (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) +# define yyjson_likely(expr) __builtin_expect(!!(expr), 1) +# else +# define yyjson_likely(expr) (expr) +# endif +#endif + +/** unlikely for compiler */ +#ifndef yyjson_unlikely +# if yyjson_has_builtin(__builtin_expect) || \ + (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) +# define yyjson_unlikely(expr) __builtin_expect(!!(expr), 0) +# else +# define yyjson_unlikely(expr) (expr) +# endif +#endif + +/** deprecate warning */ +#ifndef yyjson_deprecated +# if YYJSON_MSC_VER >= 1400 +# define yyjson_deprecated(msg) __declspec(deprecated(msg)) +# elif yyjson_has_feature(attribute_deprecated_with_message) || \ + (YYJSON_GCC_VER > 4 || (YYJSON_GCC_VER == 4 && __GNUC_MINOR__ >= 5)) +# define yyjson_deprecated(msg) __attribute__((deprecated(msg))) +# elif YYJSON_GCC_VER >= 3 +# define yyjson_deprecated(msg) __attribute__((deprecated)) +# else +# define yyjson_deprecated(msg) +# endif +#endif + +/** function export */ +#ifndef yyjson_api +# if defined(_WIN32) +# if defined(YYJSON_EXPORTS) && YYJSON_EXPORTS +# define yyjson_api __declspec(dllexport) +# elif defined(YYJSON_IMPORTS) && YYJSON_IMPORTS +# define yyjson_api __declspec(dllimport) +# else +# define yyjson_api +# endif +# elif yyjson_has_attribute(visibility) || YYJSON_GCC_VER >= 4 +# define yyjson_api __attribute__((visibility("default"))) +# else +# define yyjson_api +# endif +#endif + +/** inline function export */ +#ifndef yyjson_api_inline +# define yyjson_api_inline static yyjson_inline +#endif + +/** stdint (C89 compatible) */ +#if (defined(YYJSON_HAS_STDINT_H) && YYJSON_HAS_STDINT_H) || \ + YYJSON_MSC_VER >= 1600 || YYJSON_STDC_VER >= 199901L || \ + defined(_STDINT_H) || defined(_STDINT_H_) || \ + defined(__CLANG_STDINT_H) || defined(_STDINT_H_INCLUDED) || \ + yyjson_has_include() +# include +#elif defined(_MSC_VER) +# if _MSC_VER < 1300 + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; +# else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; +# endif +#else +# if UCHAR_MAX == 0xFFU + typedef signed char int8_t; + typedef unsigned char uint8_t; +# else +# error cannot find 8-bit integer type +# endif +# if USHRT_MAX == 0xFFFFU + typedef unsigned short uint16_t; + typedef signed short int16_t; +# elif UINT_MAX == 0xFFFFU + typedef unsigned int uint16_t; + typedef signed int int16_t; +# else +# error cannot find 16-bit integer type +# endif +# if UINT_MAX == 0xFFFFFFFFUL + typedef unsigned int uint32_t; + typedef signed int int32_t; +# elif ULONG_MAX == 0xFFFFFFFFUL + typedef unsigned long uint32_t; + typedef signed long int32_t; +# elif USHRT_MAX == 0xFFFFFFFFUL + typedef unsigned short uint32_t; + typedef signed short int32_t; +# else +# error cannot find 32-bit integer type +# endif +# if defined(__INT64_TYPE__) && defined(__UINT64_TYPE__) + typedef __INT64_TYPE__ int64_t; + typedef __UINT64_TYPE__ uint64_t; +# elif defined(__GNUC__) || defined(__clang__) +# if !defined(_SYS_TYPES_H) && !defined(__int8_t_defined) + __extension__ typedef long long int64_t; +# endif + __extension__ typedef unsigned long long uint64_t; +# elif defined(_LONG_LONG) || defined(__MWERKS__) || defined(_CRAYC) || \ + defined(__SUNPRO_C) || defined(__SUNPRO_CC) + typedef long long int64_t; + typedef unsigned long long uint64_t; +# elif (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || \ + defined(__WATCOM_INT64__) || defined (__alpha) || defined (__DECC) + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; +# else +# error cannot find 64-bit integer type +# endif +#endif + +/** stdbool (C89 compatible) */ +#if (defined(YYJSON_HAS_STDBOOL_H) && YYJSON_HAS_STDBOOL_H) || \ + (yyjson_has_include() && !defined(__STRICT_ANSI__)) || \ + YYJSON_MSC_VER >= 1800 || YYJSON_STDC_VER >= 199901L +# include +#elif !defined(__bool_true_false_are_defined) +# define __bool_true_false_are_defined 1 +# if defined(__cplusplus) +# if defined(__GNUC__) && !defined(__STRICT_ANSI__) +# define _Bool bool +# if __cplusplus < 201103L +# define bool bool +# define false false +# define true true +# endif +# endif +# else +# define bool unsigned char +# define true 1 +# define false 0 +# endif +#endif + +/** char bit check */ +#if defined(CHAR_BIT) +# if CHAR_BIT != 8 +# error non 8-bit char is not supported +# endif +#endif + +/** + Microsoft Visual C++ 6.0 doesn't support converting number from u64 to f64: + error C2520: conversion from unsigned __int64 to double not implemented. + */ +#ifndef YYJSON_U64_TO_F64_NO_IMPL +# if (0 < YYJSON_MSC_VER) && (YYJSON_MSC_VER <= 1200) +# define YYJSON_U64_TO_F64_NO_IMPL 1 +# else +# define YYJSON_U64_TO_F64_NO_IMPL 0 +# endif +#endif + + + +/*============================================================================== + * Compile Hint Begin + *============================================================================*/ + +/* extern "C" begin */ +#ifdef __cplusplus +extern "C" { +#endif + +/* warning suppress begin */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4800) /* 'int': forcing value to 'true' or 'false' */ +#endif + + + +/*============================================================================== + * Version + *============================================================================*/ + +/** The major version of yyjson. */ +#define YYJSON_VERSION_MAJOR 0 + +/** The minor version of yyjson. */ +#define YYJSON_VERSION_MINOR 7 + +/** The patch version of yyjson. */ +#define YYJSON_VERSION_PATCH 0 + +/** The version of yyjson in hex: `(major << 16) | (minor << 8) | (patch)`. */ +#define YYJSON_VERSION_HEX 0x000700 + +/** The version string of yyjson. */ +#define YYJSON_VERSION_STRING "0.7.0" + +/** The version of yyjson in hex, same as `YYJSON_VERSION_HEX`. */ +yyjson_api uint32_t yyjson_version(void); + + + +/*============================================================================== + * JSON Types + *============================================================================*/ + +/** Type of JSON value (3 bit). */ +typedef uint8_t yyjson_type; +#define YYJSON_TYPE_NONE ((uint8_t)0) /* _____000 */ +#define YYJSON_TYPE_RAW ((uint8_t)1) /* _____001 */ +#define YYJSON_TYPE_NULL ((uint8_t)2) /* _____010 */ +#define YYJSON_TYPE_BOOL ((uint8_t)3) /* _____011 */ +#define YYJSON_TYPE_NUM ((uint8_t)4) /* _____100 */ +#define YYJSON_TYPE_STR ((uint8_t)5) /* _____101 */ +#define YYJSON_TYPE_ARR ((uint8_t)6) /* _____110 */ +#define YYJSON_TYPE_OBJ ((uint8_t)7) /* _____111 */ + +/** Subtype of JSON value (2 bit). */ +typedef uint8_t yyjson_subtype; +#define YYJSON_SUBTYPE_NONE ((uint8_t)(0 << 3)) /* ___00___ */ +#define YYJSON_SUBTYPE_FALSE ((uint8_t)(0 << 3)) /* ___00___ */ +#define YYJSON_SUBTYPE_TRUE ((uint8_t)(1 << 3)) /* ___01___ */ +#define YYJSON_SUBTYPE_UINT ((uint8_t)(0 << 3)) /* ___00___ */ +#define YYJSON_SUBTYPE_SINT ((uint8_t)(1 << 3)) /* ___01___ */ +#define YYJSON_SUBTYPE_REAL ((uint8_t)(2 << 3)) /* ___10___ */ + +/** Mask and bits of JSON value tag. */ +#define YYJSON_TYPE_MASK ((uint8_t)0x07) /* _____111 */ +#define YYJSON_TYPE_BIT ((uint8_t)3) +#define YYJSON_SUBTYPE_MASK ((uint8_t)0x18) /* ___11___ */ +#define YYJSON_SUBTYPE_BIT ((uint8_t)2) +#define YYJSON_RESERVED_MASK ((uint8_t)0xE0) /* 111_____ */ +#define YYJSON_RESERVED_BIT ((uint8_t)3) +#define YYJSON_TAG_MASK ((uint8_t)0xFF) /* 11111111 */ +#define YYJSON_TAG_BIT ((uint8_t)8) + +/** Padding size for JSON reader. */ +#define YYJSON_PADDING_SIZE 4 + + + +/*============================================================================== + * Allocator + *============================================================================*/ + +/** + A memory allocator. + + Typically you don't need to use it, unless you want to customize your own + memory allocator. + */ +typedef struct yyjson_alc { + /** Same as libc's malloc(size), should not be NULL. */ + void *(*malloc)(void *ctx, size_t size); + /** Same as libc's realloc(ptr, size), should not be NULL. */ + void *(*realloc)(void *ctx, void *ptr, size_t old_size, size_t size); + /** Same as libc's free(ptr), should not be NULL. */ + void (*free)(void *ctx, void *ptr); + /** A context for malloc/realloc/free, can be NULL. */ + void *ctx; +} yyjson_alc; + +/** + A pool allocator uses fixed length pre-allocated memory. + + This allocator may be used to avoid malloc/realloc calls. The pre-allocated + memory should be held by the caller. The maximum amount of memory required to + read a JSON can be calculated using the `yyjson_read_max_memory_usage()` + function, but the amount of memory required to write a JSON cannot be directly + calculated. + + This is not a general-purpose allocator. If used to read multiple JSON + documents and only some of them are released, it may cause memory + fragmentation, leading to performance degradation and memory waste. Therefore, + it is recommended to use this allocator only for reading or writing a single + JSON document. + + @param alc The allocator to be initialized. + If this parameter is NULL, the function will fail and return false. + If `buf` or `size` is invalid, this will be set to an empty allocator. + @param buf The buffer memory for this allocator. + If this parameter is NULL, the function will fail and return false. + @param size The size of `buf`, in bytes. + If this parameter is less than 8 words (32/64 bytes on 32/64-bit OS), the + function will fail and return false. + @return true if the `alc` has been successfully initialized. + + @par Example + @code + // parse JSON with stack memory + char buf[1024]; + yyjson_alc alc; + yyjson_alc_pool_init(&alc, buf, 1024); + + const char *json = "{\"name\":\"Helvetica\",\"size\":16}" + yyjson_doc *doc = yyjson_read_opts(json, strlen(json), 0, &alc, NULL); + // the memory of `doc` is on the stack + @endcode + */ +yyjson_api bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, size_t size); + + + +/*============================================================================== + * JSON Structure + *============================================================================*/ + +/** + An immutable document for reading JSON. + This document holds memory for all its JSON values and strings. When it is no + longer used, the user should call `yyjson_doc_free()` to free its memory. + */ +typedef struct yyjson_doc yyjson_doc; + +/** + An immutable value for reading JSON. + A JSON Value has the same lifetime as its document. The memory is held by its + document and and cannot be freed alone. + */ +typedef struct yyjson_val yyjson_val; + +/** + A mutable document for building JSON. + This document holds memory for all its JSON values and strings. When it is no + longer used, the user should call `yyjson_mut_doc_free()` to free its memory. + */ +typedef struct yyjson_mut_doc yyjson_mut_doc; + +/** + A mutable value for building JSON. + A JSON Value has the same lifetime as its document. The memory is held by its + document and and cannot be freed alone. + */ +typedef struct yyjson_mut_val yyjson_mut_val; + + + +/*============================================================================== + * JSON Reader API + *============================================================================*/ + +/** Run-time options for JSON reader. */ +typedef uint32_t yyjson_read_flag; + +/** Default option (RFC 8259 compliant): + - Read positive integer as uint64_t. + - Read negative integer as int64_t. + - Read floating-point number as double with round-to-nearest mode. + - Read integer which cannot fit in uint64_t or int64_t as double. + - Report error if double number is infinity. + - Report error if string contains invalid UTF-8 character or BOM. + - Report error on trailing commas, comments, inf and nan literals. */ +static const yyjson_read_flag YYJSON_READ_NOFLAG = 0 << 0; + +/** Read the input data in-situ. + This option allows the reader to modify and use input data to store string + values, which can increase reading speed slightly. + The caller should hold the input data before free the document. + The input data must be padded by at least `YYJSON_PADDING_SIZE` bytes. + For example: `[1,2]` should be `[1,2]\0\0\0\0`, input length should be 5. */ +static const yyjson_read_flag YYJSON_READ_INSITU = 1 << 0; + +/** Stop when done instead of issuing an error if there's additional content + after a JSON document. This option may be used to parse small pieces of JSON + in larger data, such as `NDJSON`. */ +static const yyjson_read_flag YYJSON_READ_STOP_WHEN_DONE = 1 << 1; + +/** Allow single trailing comma at the end of an object or array, + such as `[1,2,3,]`, `{"a":1,"b":2,}` (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_TRAILING_COMMAS = 1 << 2; + +/** Allow C-style single line and multiple line comments (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_COMMENTS = 1 << 3; + +/** Allow inf/nan number and literal, case-insensitive, + such as 1e999, NaN, inf, -Infinity (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_INF_AND_NAN = 1 << 4; + +/** Read all numbers as raw strings (value with `YYJSON_TYPE_RAW` type), + inf/nan literal is also read as raw with `ALLOW_INF_AND_NAN` flag. */ +static const yyjson_read_flag YYJSON_READ_NUMBER_AS_RAW = 1 << 5; + +/** Allow reading invalid unicode when parsing string values (non-standard). + Invalid characters will be allowed to appear in the string values, but + invalid escape sequences will still be reported as errors. + This flag does not affect the performance of correctly encoded strings. + + @warning Strings in JSON values may contain incorrect encoding when this + option is used, you need to handle these strings carefully to avoid security + risks. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_INVALID_UNICODE = 1 << 6; + +/** Read big numbers as raw strings. These big numbers include integers that + cannot be represented by `int64_t` and `uint64_t`, and floating-point + numbers that cannot be represented by finite `double`. + The flag will be overridden by `YYJSON_READ_NUMBER_AS_RAW` flag. */ +static const yyjson_read_flag YYJSON_READ_BIGNUM_AS_RAW = 1 << 7; + + + +/** Result code for JSON reader. */ +typedef uint32_t yyjson_read_code; + +/** Success, no error. */ +static const yyjson_read_code YYJSON_READ_SUCCESS = 0; + +/** Invalid parameter, such as NULL input string or 0 input length. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_read_code YYJSON_READ_ERROR_MEMORY_ALLOCATION = 2; + +/** Input JSON string is empty. */ +static const yyjson_read_code YYJSON_READ_ERROR_EMPTY_CONTENT = 3; + +/** Unexpected content after document, such as `[123]abc`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CONTENT = 4; + +/** Unexpected ending, such as `[123`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_END = 5; + +/** Unexpected character inside the document, such as `[abc]`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CHARACTER = 6; + +/** Invalid JSON structure, such as `[1,]`. */ +static const yyjson_read_code YYJSON_READ_ERROR_JSON_STRUCTURE = 7; + +/** Invalid comment, such as unclosed multi-line comment. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_COMMENT = 8; + +/** Invalid number, such as `123.e12`, `000`. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_NUMBER = 9; + +/** Invalid string, such as invalid escaped character inside a string. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_STRING = 10; + +/** Invalid JSON literal, such as `truu`. */ +static const yyjson_read_code YYJSON_READ_ERROR_LITERAL = 11; + +/** Failed to open a file. */ +static const yyjson_read_code YYJSON_READ_ERROR_FILE_OPEN = 12; + +/** Failed to read a file. */ +static const yyjson_read_code YYJSON_READ_ERROR_FILE_READ = 13; + +/** Error information for JSON reader. */ +typedef struct yyjson_read_err { + /** Error code, see `yyjson_read_code` for all possible values. */ + yyjson_read_code code; + /** Error message, constant, no need to free (NULL if success). */ + const char *msg; + /** Error byte position for input data (0 if success). */ + size_t pos; +} yyjson_read_err; + + + +/** + Read JSON with options. + + This function is thread-safe when: + 1. The `dat` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + The `dat` will not be modified without the flag `YYJSON_READ_INSITU`, so you + can pass a `const char *` string and case it to `char *` if you don't use + the `YYJSON_READ_INSITU` flag. + @param len The length of JSON data in bytes. + If this parameter is 0, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + */ +yyjson_api yyjson_doc *yyjson_read_opts(char *dat, + size_t len, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON file. + + This function is thread-safe when: + 1. The file is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + + @warning On 32-bit operating system, files larger than 2GB may fail to read. + */ +yyjson_api yyjson_doc *yyjson_read_file(const char *path, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read JSON from a file pointer. + + @param fp The file pointer. + The data will be read from the current position of the FILE to the end. + If this fp is NULL or invalid, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + + @warning On 32-bit operating system, files larger than 2GB may fail to read. + */ +yyjson_api yyjson_doc *yyjson_read_fp(FILE *fp, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON string. + + This function is thread-safe. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + @param len The length of JSON data in bytes. + If this parameter is 0, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + */ +yyjson_api_inline yyjson_doc *yyjson_read(const char *dat, + size_t len, + yyjson_read_flag flg) { + flg &= ~YYJSON_READ_INSITU; /* const string cannot be modified */ + return yyjson_read_opts((char *)(void *)(size_t)(const void *)dat, + len, flg, NULL, NULL); +} + +/** + Returns the size of maximum memory usage to read a JSON data. + + You may use this value to avoid malloc() or calloc() call inside the reader + to get better performance, or read multiple JSON with one piece of memory. + + @param len The length of JSON data in bytes. + @param flg The JSON read options. + @return The maximum memory size to read this JSON, or 0 if overflow. + + @par Example + @code + // read multiple JSON with same pre-allocated memory + + char *dat1, *dat2, *dat3; // JSON data + size_t len1, len2, len3; // JSON length + size_t max_len = MAX(len1, MAX(len2, len3)); + yyjson_doc *doc; + + // use one allocator for multiple JSON + size_t size = yyjson_read_max_memory_usage(max_len, 0); + void *buf = malloc(size); + yyjson_alc alc; + yyjson_alc_pool_init(&alc, buf, size); + + // no more alloc() or realloc() call during reading + doc = yyjson_read_opts(dat1, len1, 0, &alc, NULL); + yyjson_doc_free(doc); + doc = yyjson_read_opts(dat2, len2, 0, &alc, NULL); + yyjson_doc_free(doc); + doc = yyjson_read_opts(dat3, len3, 0, &alc, NULL); + yyjson_doc_free(doc); + + free(buf); + @endcode + @see yyjson_alc_pool_init() + */ +yyjson_api_inline size_t yyjson_read_max_memory_usage(size_t len, + yyjson_read_flag flg) { + /* + 1. The max value count is (json_size / 2 + 1), + for example: "[1,2,3,4]" size is 9, value count is 5. + 2. Some broken JSON may cost more memory during reading, but fail at end, + for example: "[[[[[[[[". + 3. yyjson use 16 bytes per value, see struct yyjson_val. + 4. yyjson use dynamic memory with a growth factor of 1.5. + + The max memory size is (json_size / 2 * 16 * 1.5 + padding). + */ + size_t mul = (size_t)12 + !(flg & YYJSON_READ_INSITU); + size_t pad = 256; + size_t max = (size_t)(~(size_t)0); + if (flg & YYJSON_READ_STOP_WHEN_DONE) len = len < 256 ? 256 : len; + if (len >= (max - pad - mul) / mul) return 0; + return len * mul + pad; +} + +/** + Read a JSON number. + + This function is thread-safe when data is not modified by other threads. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is required. + If this parameter is NULL, the function will fail and return NULL. + @param val The output value where result is stored. + If this parameter is NULL, the function will fail and return NULL. + The value will hold either UINT or SINT or REAL number; + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`. + @param alc The memory allocator used for long number. + It is only used when the built-in floating point reader is disabled. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return If successful, a pointer to the character after the last character + used in the conversion, NULL if an error occurs. + */ +yyjson_api const char *yyjson_read_number(const char *dat, + yyjson_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON number. + + This function is thread-safe when data is not modified by other threads. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is required. + If this parameter is NULL, the function will fail and return NULL. + @param val The output value where result is stored. + If this parameter is NULL, the function will fail and return NULL. + The value will hold either UINT or SINT or REAL number; + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`. + @param alc The memory allocator used for long number. + It is only used when the built-in floating point reader is disabled. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return If successful, a pointer to the character after the last character + used in the conversion, NULL if an error occurs. + */ +yyjson_api_inline const char *yyjson_mut_read_number(const char *dat, + yyjson_mut_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err) { + return yyjson_read_number(dat, (yyjson_val *)val, flg, alc, err); +} + + +/*============================================================================== + * JSON Writer API + *============================================================================*/ + +/** Run-time options for JSON writer. */ +typedef uint32_t yyjson_write_flag; + +/** Default option: + - Write JSON minify. + - Report error on inf or nan number. + - Report error on invalid UTF-8 string. + - Do not escape unicode or slash. */ +static const yyjson_write_flag YYJSON_WRITE_NOFLAG = 0 << 0; + +/** Write JSON pretty with 4 space indent. */ +static const yyjson_write_flag YYJSON_WRITE_PRETTY = 1 << 0; + +/** Escape unicode as `uXXXX`, make the output ASCII only. */ +static const yyjson_write_flag YYJSON_WRITE_ESCAPE_UNICODE = 1 << 1; + +/** Escape '/' as '\/'. */ +static const yyjson_write_flag YYJSON_WRITE_ESCAPE_SLASHES = 1 << 2; + +/** Write inf and nan number as 'Infinity' and 'NaN' literal (non-standard). */ +static const yyjson_write_flag YYJSON_WRITE_ALLOW_INF_AND_NAN = 1 << 3; + +/** Write inf and nan number as null literal. + This flag will override `YYJSON_WRITE_ALLOW_INF_AND_NAN` flag. */ +static const yyjson_write_flag YYJSON_WRITE_INF_AND_NAN_AS_NULL = 1 << 4; + +/** Allow invalid unicode when encoding string values (non-standard). + Invalid characters in string value will be copied byte by byte. + If `YYJSON_WRITE_ESCAPE_UNICODE` flag is also set, invalid character will be + escaped as `U+FFFD` (replacement character). + This flag does not affect the performance of correctly encoded strings. */ +static const yyjson_write_flag YYJSON_WRITE_ALLOW_INVALID_UNICODE = 1 << 5; + +/** Write JSON pretty with 2 space indent. + This flag will override `YYJSON_WRITE_PRETTY` flag. */ +static const yyjson_write_flag YYJSON_WRITE_PRETTY_TWO_SPACES = 1 << 6; + + + +/** Result code for JSON writer */ +typedef uint32_t yyjson_write_code; + +/** Success, no error. */ +static const yyjson_write_code YYJSON_WRITE_SUCCESS = 0; + +/** Invalid parameter, such as NULL document. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_MEMORY_ALLOCATION = 2; + +/** Invalid value type in JSON document. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_VALUE_TYPE = 3; + +/** NaN or Infinity number occurs. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_NAN_OR_INF = 4; + +/** Failed to open a file. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_OPEN = 5; + +/** Failed to write a file. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_WRITE = 6; + +/** Invalid unicode in string. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_STRING = 7; + +/** Error information for JSON writer. */ +typedef struct yyjson_write_err { + /** Error code, see `yyjson_write_code` for all possible values. */ + yyjson_write_code code; + /** Error message, constant, no need to free (NULL if success). */ + const char *msg; +} yyjson_write_err; + + + +/*============================================================================== + * JSON Document Writer API + *============================================================================*/ + +/** + Write a document to JSON string with options. + + This function is thread-safe when: + The `alc` is thread-safe or NULL. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_write_opts(const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a document to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_write_file(const char *path, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this fp is NULL or invalid, the function will fail and return false. + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_write_fp(FILE *fp, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to JSON string. + + This function is thread-safe. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_write(const yyjson_doc *doc, + yyjson_write_flag flg, + size_t *len) { + return yyjson_write_opts(doc, flg, NULL, len, NULL); +} + + + +/** + Write a document to JSON string with options. + + This function is thread-safe when: + 1. The `doc` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a document to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `doc` is not modified by other threads. + 3. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_write_file(const char *path, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this fp is NULL or invalid, the function will fail and return false. + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_write_fp(FILE *fp, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to JSON string. + + This function is thread-safe when: + The `doc` is not modified by other threads. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_mut_write(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + size_t *len) { + return yyjson_mut_write_opts(doc, flg, NULL, len, NULL); +} + + + +/*============================================================================== + * JSON Value Writer API + *============================================================================*/ + +/** + Write a value to JSON string with options. + + This function is thread-safe when: + The `alc` is thread-safe or NULL. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_val_write_opts(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_val_write_file(const char *path, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this path is NULL or invalid, the function will fail and return false. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_val_write_fp(FILE *fp, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON string. + + This function is thread-safe. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_val_write(const yyjson_val *val, + yyjson_write_flag flg, + size_t *len) { + return yyjson_val_write_opts(val, flg, NULL, len, NULL); +} + +/** + Write a value to JSON string with options. + + This function is thread-safe when: + 1. The `val` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `val` is not modified by other threads. + 3. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_val_write_file(const char *path, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this path is NULL or invalid, the function will fail and return false. + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_val_write_fp(FILE *fp, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON string. + + This function is thread-safe when: + The `val` is not modified by other threads. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_mut_val_write(const yyjson_mut_val *val, + yyjson_write_flag flg, + size_t *len) { + return yyjson_mut_val_write_opts(val, flg, NULL, len, NULL); +} + + + +/*============================================================================== + * JSON Document API + *============================================================================*/ + +/** Returns the root value of this JSON document. + Returns NULL if `doc` is NULL. */ +yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc); + +/** Returns read size of input JSON data. + Returns 0 if `doc` is NULL. + For example: the read size of `[1,2,3]` is 7 bytes. */ +yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc); + +/** Returns total value count in this JSON document. + Returns 0 if `doc` is NULL. + For example: the value count of `[1,2,3]` is 4. */ +yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc); + +/** Release the JSON document and free the memory. + After calling this function, the `doc` and all values from the `doc` are no + longer available. This function will do nothing if the `doc` is NULL. */ +yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc); + + + +/*============================================================================== + * JSON Value Type API + *============================================================================*/ + +/** Returns whether the JSON value is raw. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_raw(yyjson_val *val); + +/** Returns whether the JSON value is `null`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_null(yyjson_val *val); + +/** Returns whether the JSON value is `true`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_true(yyjson_val *val); + +/** Returns whether the JSON value is `false`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_false(yyjson_val *val); + +/** Returns whether the JSON value is bool (true/false). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_bool(yyjson_val *val); + +/** Returns whether the JSON value is unsigned integer (uint64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_uint(yyjson_val *val); + +/** Returns whether the JSON value is signed integer (int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_sint(yyjson_val *val); + +/** Returns whether the JSON value is integer (uint64_t/int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_int(yyjson_val *val); + +/** Returns whether the JSON value is real number (double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_real(yyjson_val *val); + +/** Returns whether the JSON value is number (uint64_t/int64_t/double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_num(yyjson_val *val); + +/** Returns whether the JSON value is string. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_str(yyjson_val *val); + +/** Returns whether the JSON value is array. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_arr(yyjson_val *val); + +/** Returns whether the JSON value is object. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_obj(yyjson_val *val); + +/** Returns whether the JSON value is container (array/object). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val); + + + +/*============================================================================== + * JSON Value Content API + *============================================================================*/ + +/** Returns the JSON value's type. + Returns YYJSON_TYPE_NONE if `val` is NULL. */ +yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val); + +/** Returns the JSON value's subtype. + Returns YYJSON_SUBTYPE_NONE if `val` is NULL. */ +yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val); + +/** Returns the JSON value's tag. + Returns 0 if `val` is NULL. */ +yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val); + +/** Returns the JSON value's type description. + The return value should be one of these strings: "raw", "null", "string", + "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ +yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val); + +/** Returns the content if the value is raw. + Returns NULL if `val` is NULL or type is not raw. */ +yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val); + +/** Returns the content if the value is bool. + Returns NULL if `val` is NULL or type is not bool. */ +yyjson_api_inline bool yyjson_get_bool(yyjson_val *val); + +/** Returns the content and cast to uint64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val); + +/** Returns the content and cast to int64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val); + +/** Returns the content and cast to int. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int yyjson_get_int(yyjson_val *val); + +/** Returns the content if the value is real number, or 0.0 on error. + Returns 0.0 if `val` is NULL or type is not real(double). */ +yyjson_api_inline double yyjson_get_real(yyjson_val *val); + +/** Returns the content and typecast to `double` if the value is number. + Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ +yyjson_api_inline double yyjson_get_num(yyjson_val *val); + +/** Returns the content if the value is string. + Returns NULL if `val` is NULL or type is not string. */ +yyjson_api_inline const char *yyjson_get_str(yyjson_val *val); + +/** Returns the content length (string length, array size, object size. + Returns 0 if `val` is NULL or type is not string/array/object. */ +yyjson_api_inline size_t yyjson_get_len(yyjson_val *val); + +/** Returns whether the JSON value is equals to a string. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a UTF-8 string, null-terminator is not required. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, + size_t len); + +/** Returns whether two JSON values are equal (deep compare). + Returns false if input is NULL. + @note the result may be inaccurate if object has duplicate keys. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); + +/** Set the value to raw. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, + const char *raw, size_t len); + +/** Set the value to null. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_null(yyjson_val *val); + +/** Set the value to bool. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num); + +/** Set the value to uint. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num); + +/** Set the value to sint. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num); + +/** Set the value to int. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num); + +/** Set the value to real. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num); + +/** Set the value to string (null-terminated). + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str); + +/** Set the value to string (with length). + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, + const char *str, size_t len); + + + +/*============================================================================== + * JSON Array API + *============================================================================*/ + +/** Returns the number of elements in this array. + Returns 0 if `arr` is NULL or type is not array. */ +yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr); + +/** Returns the element at the specified position in this array. + Returns NULL if array is NULL/empty or the index is out of bounds. + @warning This function takes a linear search time if array is not flat. + For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat. */ +yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx); + +/** Returns the first element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr); + +/** Returns the last element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. + @warning This function takes a linear search time if array is not flat. + For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat.*/ +yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr); + + + +/*============================================================================== + * JSON Array Iterator API + *============================================================================*/ + +/** + A JSON array iterator. + + @par Example + @code + yyjson_val *val; + yyjson_arr_iter iter = yyjson_arr_iter_with(arr); + while ((val = yyjson_arr_iter_next(&iter))) { + your_func(val); + } + @endcode + */ +typedef struct yyjson_arr_iter { + size_t idx; /**< next value's index */ + size_t max; /**< maximum index (arr.size) */ + yyjson_val *cur; /**< next value */ +} yyjson_arr_iter; + +/** + Initialize an iterator for this array. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, + yyjson_arr_iter *iter); + +/** + Create an iterator with an array , same as `yyjson_arr_iter_init()`. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, an empty iterator will returned. + @return A new iterator for the array. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter); + +/** + Returns the next element in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter); + +/** + Macro for iterating over an array. + It works like iterator, but with a more intuitive API. + + @par Example + @code + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(arr, idx, max, val) { + your_func(idx, val); + } + @endcode + */ +#define yyjson_arr_foreach(arr, idx, max, val) \ + for ((idx) = 0, \ + (max) = yyjson_arr_size(arr), \ + (val) = yyjson_arr_get_first(arr); \ + (idx) < (max); \ + (idx)++, \ + (val) = unsafe_yyjson_get_next(val)) + + + +/*============================================================================== + * JSON Object API + *============================================================================*/ + +/** Returns the number of key-value pairs in this object. + Returns 0 if `obj` is NULL or type is not object. */ +yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, const char *key); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a UTF-8 string, null-terminator is not required. + The `key_len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, const char *key, + size_t key_len); + + + +/*============================================================================== + * JSON Object Iterator API + *============================================================================*/ + +/** + A JSON object iterator. + + @par Example + @code + yyjson_val *key, *val; + yyjson_obj_iter iter = yyjson_obj_iter_with(obj); + while ((key = yyjson_obj_iter_next(&iter))) { + val = yyjson_obj_iter_get_val(key); + your_func(key, val); + } + @endcode + + If the ordering of the keys is known at compile-time, you can use this method + to speed up value lookups: + @code + // {"k1":1, "k2": 3, "k3": 3} + yyjson_val *key, *val; + yyjson_obj_iter iter = yyjson_obj_iter_with(obj); + yyjson_val *v1 = yyjson_obj_iter_get(&iter, "k1"); + yyjson_val *v3 = yyjson_obj_iter_get(&iter, "k3"); + @endcode + @see yyjson_obj_iter_get() and yyjson_obj_iter_getn() + */ +typedef struct yyjson_obj_iter { + size_t idx; /**< next key's index */ + size_t max; /**< maximum key index (obj.size) */ + yyjson_val *cur; /**< next key */ + yyjson_val *obj; /**< the object being iterated */ +} yyjson_obj_iter; + +/** + Initialize an iterator for this object. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, + yyjson_obj_iter *iter); + +/** + Create an iterator with an object, same as `yyjson_obj_iter_init()`. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, an empty iterator will returned. + @return A new iterator for the object. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter); + +/** + Returns the next key in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter); + +/** + Returns the value for key inside the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_obj_get()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string with null-terminator. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, + const char *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_obj_getn()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The the length of `key`, in bytes. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, + const char *key, + size_t key_len); + +/** + Macro for iterating over an object. + It works like iterator, but with a more intuitive API. + + @par Example + @code + size_t idx, max; + yyjson_val *key, *val; + yyjson_obj_foreach(obj, idx, max, key, val) { + your_func(key, val); + } + @endcode + */ +#define yyjson_obj_foreach(obj, idx, max, key, val) \ + for ((idx) = 0, \ + (max) = yyjson_obj_size(obj), \ + (key) = (obj) ? unsafe_yyjson_get_first(obj) : NULL, \ + (val) = (key) + 1; \ + (idx) < (max); \ + (idx)++, \ + (key) = unsafe_yyjson_get_next(val), \ + (val) = (key) + 1) + + + +/*============================================================================== + * Mutable JSON Document API + *============================================================================*/ + +/** Returns the root value of this JSON document. + Returns NULL if `doc` is NULL. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc); + +/** Sets the root value of this JSON document. + Pass NULL to clear root value of the document. */ +yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, + yyjson_mut_val *root); + +/** + Set the string pool size for a mutable document. + This function does not allocate memory immediately, but uses the size when + the next memory allocation is needed. + + If the caller knows the approximate bytes of strings that the document needs to + store (e.g. copy string with `yyjson_mut_strcpy` function), setting a larger + size can avoid multiple memory allocations and improve performance. + + @param doc The mutable document. + @param len The desired string pool size in bytes (total string length). + @return true if successful, false if size is 0 or overflow. + */ +yyjson_api bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, + size_t len); + +/** + Set the value pool size for a mutable document. + This function does not allocate memory immediately, but uses the size when + the next memory allocation is needed. + + If the caller knows the approximate number of values that the document needs to + store (e.g. create new value with `yyjson_mut_xxx` functions), setting a larger + size can avoid multiple memory allocations and improve performance. + + @param doc The mutable document. + @param count The desired value pool size (number of `yyjson_mut_val`). + @return true if successful, false if size is 0 or overflow. + */ +yyjson_api bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, + size_t count); + +/** Release the JSON document and free the memory. + After calling this function, the `doc` and all values from the `doc` are no + longer available. This function will do nothing if the `doc` is NULL. */ +yyjson_api void yyjson_mut_doc_free(yyjson_mut_doc *doc); + +/** Creates and returns a new mutable JSON document, returns NULL on error. + If allocator is NULL, the default allocator will be used. */ +yyjson_api yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc); + +/** Copies and returns a new mutable document from input, returns NULL on error. + This makes a `deep-copy` on the immutable document. + If allocator is NULL, the default allocator will be used. + @note `imut_doc` -> `mut_doc`. */ +yyjson_api yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new mutable document from input, returns NULL on error. + This makes a `deep-copy` on the mutable document. + If allocator is NULL, the default allocator will be used. + @note `mut_doc` -> `mut_doc`. */ +yyjson_api yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new mutable value from input, returns NULL on error. + This makes a `deep-copy` on the immutable value. + The memory was managed by mutable document. + @note `imut_val` -> `mut_val`. */ +yyjson_api yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *doc, + yyjson_val *val); + +/** Copies and returns a new mutable value from input, returns NULL on error. + This makes a `deep-copy` on the mutable value. + The memory was managed by mutable document. + @note `mut_val` -> `mut_val`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, + yyjson_mut_val *val); + +/** Copies and returns a new immutable document from input, + returns NULL on error. This makes a `deep-copy` on the mutable document. + The returned document should be freed with `yyjson_doc_free()`. + @note `mut_doc` -> `imut_doc`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new immutable document from input, + returns NULL on error. This makes a `deep-copy` on the mutable value. + The returned document should be freed with `yyjson_doc_free()`. + @note `mut_val` -> `imut_doc`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *val, + const yyjson_alc *alc); + + + +/*============================================================================== + * Mutable JSON Value Type API + *============================================================================*/ + +/** Returns whether the JSON value is raw. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val); + +/** Returns whether the JSON value is `null`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val); + +/** Returns whether the JSON value is `true`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val); + +/** Returns whether the JSON value is `false`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val); + +/** Returns whether the JSON value is bool (true/false). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val); + +/** Returns whether the JSON value is unsigned integer (uint64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val); + +/** Returns whether the JSON value is signed integer (int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val); + +/** Returns whether the JSON value is integer (uint64_t/int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val); + +/** Returns whether the JSON value is real number (double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val); + +/** Returns whether the JSON value is number (uint/sint/real). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val); + +/** Returns whether the JSON value is string. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val); + +/** Returns whether the JSON value is array. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val); + +/** Returns whether the JSON value is object. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val); + +/** Returns whether the JSON value is container (array/object). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val); + + + +/*============================================================================== + * Mutable JSON Value Content API + *============================================================================*/ + +/** Returns the JSON value's type. + Returns `YYJSON_TYPE_NONE` if `val` is NULL. */ +yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val); + +/** Returns the JSON value's subtype. + Returns `YYJSON_SUBTYPE_NONE` if `val` is NULL. */ +yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val); + +/** Returns the JSON value's tag. + Returns 0 if `val` is NULL. */ +yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val); + +/** Returns the JSON value's type description. + The return value should be one of these strings: "raw", "null", "string", + "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ +yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val); + +/** Returns the content if the value is raw. + Returns NULL if `val` is NULL or type is not raw. */ +yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val); + +/** Returns the content if the value is bool. + Returns NULL if `val` is NULL or type is not bool. */ +yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val); + +/** Returns the content and cast to uint64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val); + +/** Returns the content and cast to int64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val); + +/** Returns the content and cast to int. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val); + +/** Returns the content if the value is real number. + Returns 0.0 if `val` is NULL or type is not real(double). */ +yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val); + +/** Returns the content and typecast to `double` if the value is number. + Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ +yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val); + +/** Returns the content if the value is string. + Returns NULL if `val` is NULL or type is not string. */ +yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val); + +/** Returns the content length (string length, array size, object size. + Returns 0 if `val` is NULL or type is not string/array/object. */ +yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a null-terminated UTF-8 string. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, + const char *str); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a UTF-8 string, null-terminator is not required. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, + const char *str, size_t len); + +/** Returns whether two JSON values are equal (deep compare). + Returns false if input is NULL. + @note the result may be inaccurate if object has duplicate keys. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs); + +/** Set the value to raw. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, + const char *raw, size_t len); + +/** Set the value to null. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val); + +/** Set the value to bool. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num); + +/** Set the value to uint. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num); + +/** Set the value to sint. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num); + +/** Set the value to int. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num); + +/** Set the value to real. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num); + +/** Set the value to string (null-terminated). + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str); + +/** Set the value to string (with length). + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, + const char *str, size_t len); + +/** Set the value to array. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val); + +/** Set the value to array. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val); + + + +/*============================================================================== + * Mutable JSON Value Creation API + *============================================================================*/ + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a null value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc); + +/** Creates and returns a true value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc); + +/** Creates and returns a false value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc); + +/** Creates and returns a bool value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, + bool val); + +/** Creates and returns an unsigned integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, + uint64_t num); + +/** Creates and returns a signed integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, + int64_t num); + +/** Creates and returns a signed integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, + int64_t num); + +/** Creates and returns an real number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, + double num); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, + size_t len); + + + +/*============================================================================== + * Mutable JSON Array API + *============================================================================*/ + +/** Returns the number of elements in this array. + Returns 0 if `arr` is NULL or type is not array. */ +yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr); + +/** Returns the element at the specified position in this array. + Returns NULL if array is NULL/empty or the index is out of bounds. + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, + size_t idx); + +/** Returns the first element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first(yyjson_mut_val *arr); + +/** Returns the last element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last(yyjson_mut_val *arr); + + + +/*============================================================================== + * Mutable JSON Array Iterator API + *============================================================================*/ + +/** + A mutable JSON array iterator. + + @warning You should not modify the array while iterating over it, but you can + use `yyjson_mut_arr_iter_remove()` to remove current value. + + @par Example + @code + yyjson_mut_val *val; + yyjson_mut_arr_iter iter = yyjson_mut_arr_iter_with(arr); + while ((val = yyjson_mut_arr_iter_next(&iter))) { + your_func(val); + if (your_val_is_unused(val)) { + yyjson_mut_arr_iter_remove(&iter); + } + } + @endcode + */ +typedef struct yyjson_mut_arr_iter { + size_t idx; /**< next value's index */ + size_t max; /**< maximum index (arr.size) */ + yyjson_mut_val *cur; /**< current value */ + yyjson_mut_val *pre; /**< previous value */ + yyjson_mut_val *arr; /**< the array being iterated */ +} yyjson_mut_arr_iter; + +/** + Initialize an iterator for this array. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, + yyjson_mut_arr_iter *iter); + +/** + Create an iterator with an array , same as `yyjson_mut_arr_iter_init()`. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, an empty iterator will returned. + @return A new iterator for the array. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( + yyjson_mut_val *arr); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_mut_arr_iter_has_next( + yyjson_mut_arr_iter *iter); + +/** + Returns the next element in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( + yyjson_mut_arr_iter *iter); + +/** + Removes and returns current element in the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( + yyjson_mut_arr_iter *iter); + +/** + Macro for iterating over an array. + It works like iterator, but with a more intuitive API. + + @warning You should not modify the array while iterating over it. + + @par Example + @code + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(arr, idx, max, val) { + your_func(idx, val); + } + @endcode + */ +#define yyjson_mut_arr_foreach(arr, idx, max, val) \ + for ((idx) = 0, \ + (max) = yyjson_mut_arr_size(arr), \ + (val) = yyjson_mut_arr_get_first(arr); \ + (idx) < (max); \ + (idx)++, \ + (val) = (val)->next) + + + +/*============================================================================== + * Mutable JSON Array Creation API + *============================================================================*/ + +/** + Creates and returns an empty mutable array. + @param doc A mutable document, used for memory allocation only. + @return The new array. NULL if input is NULL or memory allocation failed. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc); + +/** + Creates and returns a new mutable array with the given boolean values. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of boolean values. + @param count The value count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const bool vals[3] = { true, false, true }; + yyjson_mut_val *arr = yyjson_mut_arr_with_bool(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( + yyjson_mut_doc *doc, const bool *vals, size_t count); + +/** + Creates and returns a new mutable array with the given sint numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of sint numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int64_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( + yyjson_mut_doc *doc, const int64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint64_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given real numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of real numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const double vals[3] = { 0.1, 0.2, 0.3 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_real(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( + yyjson_mut_doc *doc, const double *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int8 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int8 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int8_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint8(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( + yyjson_mut_doc *doc, const int8_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int16 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int16 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int16_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint16(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( + yyjson_mut_doc *doc, const int16_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int32 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int32 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int32_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint32(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( + yyjson_mut_doc *doc, const int32_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int64 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int64 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const int64_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( + yyjson_mut_doc *doc, const int64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint8 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint8 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint8_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint8(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( + yyjson_mut_doc *doc, const uint8_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint16 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint16 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint16_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint16(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( + yyjson_mut_doc *doc, const uint16_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint32 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint32 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint32_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint32(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( + yyjson_mut_doc *doc, const uint32_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint64 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint64 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const uint64_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given float numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of float numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const float vals[3] = { -1.0f, 0.0f, 1.0f }; + yyjson_mut_val *arr = yyjson_mut_arr_with_float(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( + yyjson_mut_doc *doc, const float *vals, size_t count); + +/** + Creates and returns a new mutable array with the given double numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of double numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const double vals[3] = { -1.0, 0.0, 1.0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_double(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( + yyjson_mut_doc *doc, const double *vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings, these strings + will not be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 null-terminator strings. + If this array contains NULL, the function will fail and return NULL. + @param count The number of values in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @warning The input strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. If these strings will be + modified, you should use `yyjson_mut_arr_with_strcpy()` instead. + + @par Example + @code + const char *vals[3] = { "a", "b", "c" }; + yyjson_mut_val *arr = yyjson_mut_arr_with_str(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( + yyjson_mut_doc *doc, const char **vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings and string + lengths, these strings will not be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 strings, null-terminator is not required. + If this array contains NULL, the function will fail and return NULL. + @param lens A C array of string lengths, in bytes. + @param count The number of strings in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @warning The input strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. If these strings will be + modified, you should use `yyjson_mut_arr_with_strncpy()` instead. + + @par Example + @code + const char *vals[3] = { "a", "bb", "c" }; + const size_t lens[3] = { 1, 2, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); + +/** + Creates and returns a new mutable array with the given strings, these strings + will be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 null-terminator strings. + If this array contains NULL, the function will fail and return NULL. + @param count The number of values in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const char *vals[3] = { "a", "b", "c" }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strcpy(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( + yyjson_mut_doc *doc, const char **vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings and string + lengths, these strings will be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 strings, null-terminator is not required. + If this array contains NULL, the function will fail and return NULL. + @param lens A C array of string lengths, in bytes. + @param count The number of strings in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @par Example + @code + const char *vals[3] = { "a", "bb", "c" }; + const size_t lens[3] = { 1, 2, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); + + + +/*============================================================================== + * Mutable JSON Array Modification API + *============================================================================*/ + +/** + Inserts a value into an array at a given index. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @param idx The index to which to insert the new value. + Returns false if the index is out of range. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, + yyjson_mut_val *val, size_t idx); + +/** + Inserts a value at the end of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Inserts a value at the head of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Replaces a value at index and returns old value. + @param arr The array to which the value is to be replaced. + Returns false if it is NULL or not an array. + @param idx The index to which to replace the value. + Returns false if the index is out of range. + @param val The new value to replace. Returns false if it is NULL. + @return Old value, or NULL on error. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, + size_t idx, + yyjson_mut_val *val); + +/** + Removes and returns a value at index. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @param idx The index from which to remove the value. + Returns false if the index is out of range. + @return Old value, or NULL on error. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, + size_t idx); + +/** + Removes and returns the first value in this array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @return The first value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( + yyjson_mut_val *arr); + +/** + Removes and returns the last value in this array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @return The last value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( + yyjson_mut_val *arr); + +/** + Removes all values within a specified range in the array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @param idx The start index of the range (0 is the first). + @param len The number of items in the range (can be 0). + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, + size_t idx, size_t len); + +/** + Removes all values in this array. + @param arr The array from which all of the values are to be removed. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr); + +/** + Rotates values in this array for the given number of times. + For example: `[1,2,3,4,5]` rotate 2 is `[3,4,5,1,2]`. + @param arr The array to be rotated. + @param idx Index (or times) to rotate. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, + size_t idx); + + + +/*============================================================================== + * Mutable JSON Array Modification Convenience API + *============================================================================*/ + +/** + Adds a value at the end of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Adds a `null` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a `true` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a `false` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a bool value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The bool value to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + bool val); + +/** + Adds an unsigned integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + uint64_t num); + +/** + Adds a signed integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num); + +/** + Adds a integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num); + +/** + Adds a double value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num); + +/** + Adds a string value at the end of the array (no copy). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A null-terminated UTF-8 string. + @return Whether successful. + @warning The input string is not copied, you should keep this string unmodified + for the lifetime of this JSON document. + */ +yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str); + +/** + Adds a string value at the end of the array (no copy). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A UTF-8 string, null-terminator is not required. + @param len The length of the string, in bytes. + @return Whether successful. + @warning The input string is not copied, you should keep this string unmodified + for the lifetime of this JSON document. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, + size_t len); + +/** + Adds a string value at the end of the array (copied). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A null-terminated UTF-8 string. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str); + +/** + Adds a string value at the end of the array (copied). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A UTF-8 string, null-terminator is not required. + @param len The length of the string, in bytes. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, + size_t len); + +/** + Creates and adds a new array at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return The new array, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Creates and adds a new object at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return The new object, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + + + +/*============================================================================== + * Mutable JSON Object API + *============================================================================*/ + +/** Returns the number of key-value pairs in this object. + Returns 0 if `obj` is NULL or type is not object. */ +yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, + const char *key); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a UTF-8 string, null-terminator is not required. + The `key_len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, + const char *key, + size_t key_len); + + + +/*============================================================================== + * Mutable JSON Object Iterator API + *============================================================================*/ + +/** + A mutable JSON object iterator. + + @warning You should not modify the object while iterating over it, but you can + use `yyjson_mut_obj_iter_remove()` to remove current value. + + @par Example + @code + yyjson_mut_val *key, *val; + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); + while ((key = yyjson_mut_obj_iter_next(&iter))) { + val = yyjson_mut_obj_iter_get_val(key); + your_func(key, val); + if (your_val_is_unused(key, val)) { + yyjson_mut_obj_iter_remove(&iter); + } + } + @endcode + + If the ordering of the keys is known at compile-time, you can use this method + to speed up value lookups: + @code + // {"k1":1, "k2": 3, "k3": 3} + yyjson_mut_val *key, *val; + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); + yyjson_mut_val *v1 = yyjson_mut_obj_iter_get(&iter, "k1"); + yyjson_mut_val *v3 = yyjson_mut_obj_iter_get(&iter, "k3"); + @endcode + @see `yyjson_mut_obj_iter_get()` and `yyjson_mut_obj_iter_getn()` + */ +typedef struct yyjson_mut_obj_iter { + size_t idx; /**< next key's index */ + size_t max; /**< maximum key index (obj.size) */ + yyjson_mut_val *cur; /**< current key */ + yyjson_mut_val *pre; /**< previous key */ + yyjson_mut_val *obj; /**< the object being iterated */ +} yyjson_mut_obj_iter; + +/** + Initialize an iterator for this object. + + @param obj The object to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, + yyjson_mut_obj_iter *iter); + +/** + Create an iterator with an object, same as `yyjson_obj_iter_init()`. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, an empty iterator will returned. + @return A new iterator for the object. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( + yyjson_mut_val *obj); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_mut_obj_iter_has_next( + yyjson_mut_obj_iter *iter); + +/** + Returns the next key in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( + yyjson_mut_obj_iter *iter); + +/** + Returns the value for key inside the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( + yyjson_mut_val *key); + +/** + Removes current key-value pair in the iteration, returns the removed value. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( + yyjson_mut_obj_iter *iter); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_mut_obj_get()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string with null-terminator. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( + yyjson_mut_obj_iter *iter, const char *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_mut_obj_getn()` but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The the length of `key`, in bytes. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( + yyjson_mut_obj_iter *iter, const char *key, size_t key_len); + +/** + Macro for iterating over an object. + It works like iterator, but with a more intuitive API. + + @warning You should not modify the object while iterating over it. + + @par Example + @code + size_t idx, max; + yyjson_val *key, *val; + yyjson_obj_foreach(obj, idx, max, key, val) { + your_func(key, val); + } + @endcode + */ +#define yyjson_mut_obj_foreach(obj, idx, max, key, val) \ + for ((idx) = 0, \ + (max) = yyjson_mut_obj_size(obj), \ + (key) = (max) ? ((yyjson_mut_val *)(obj)->uni.ptr)->next->next : NULL, \ + (val) = (key) ? (key)->next : NULL; \ + (idx) < (max); \ + (idx)++, \ + (key) = (val)->next, \ + (val) = (key)->next) + + + +/*============================================================================== + * Mutable JSON Object Creation API + *============================================================================*/ + +/** Creates and returns a mutable object, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc); + +/** + Creates and returns a mutable object with keys and values, returns NULL on + error. The keys and values are not copied. The strings should be a + null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. + + @par Example + @code + const char *keys[2] = { "id", "name" }; + const char *vals[2] = { "01", "Harry" }; + yyjson_mut_val *obj = yyjson_mut_obj_with_str(doc, keys, vals, 2); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, + const char **keys, + const char **vals, + size_t count); + +/** + Creates and returns a mutable object with key-value pairs and pair count, + returns NULL on error. The keys and values are not copied. The strings should + be a null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. + + @par Example + @code + const char *kv_pairs[4] = { "id", "01", "name", "Harry" }; + yyjson_mut_val *obj = yyjson_mut_obj_with_kv(doc, kv_pairs, 2); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, + const char **kv_pairs, + size_t pair_count); + + + +/*============================================================================== + * Mutable JSON Object Modification API + *============================================================================*/ + +/** + Adds a key-value pair at the end of the object. + This function allows duplicated key in one object. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); +/** + Sets a key-value pair at the end of the object. + This function may remove all key-value pairs for the given key before add. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. If this value is null, the behavior + is same as `yyjson_mut_obj_remove()`. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Inserts a key-value pair to the object at the given position. + This function allows duplicated key in one object. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. + @param idx The index to which to insert the new pair. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t idx); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a string value. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, + yyjson_mut_val *key); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a UTF-8 string with null-terminator. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( + yyjson_mut_val *obj, const char *key); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The length of the key. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( + yyjson_mut_val *obj, const char *key, size_t key_len); + +/** + Removes all key-value pairs in this object. + @param obj The object from which all of the values are to be removed. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj); + +/** + Replaces value from the object with given key. + If the key is not exist, or the value is NULL, it will fail. + @param obj The object to which the value is to be replaced. + @param key The key, should be a string value. + @param val The value to replace into the object. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Rotates key-value pairs in the object for the given number of times. + For example: `{"a":1,"b":2,"c":3,"d":4}` rotate 1 is + `{"b":2,"c":3,"d":4,"a":1}`. + @param obj The object to be rotated. + @param idx Index (or times) to rotate. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx); + + + +/*============================================================================== + * Mutable JSON Object Modification Convenience API + *============================================================================*/ + +/** Adds a `null` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a `true` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a `false` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a bool value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, bool val); + +/** Adds an unsigned integer value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, uint64_t val); + +/** Adds a signed integer value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, int64_t val); + +/** Adds an int value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, int64_t val); + +/** Adds a double value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, double val); + +/** Adds a string value at the end of the object. + The `key` and `val` should be null-terminated UTF-8 strings. + This function allows duplicated key in one object. + + @warning The key/value string are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, const char *val); + +/** Adds a string value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + The `val` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the `val`, in bytes. + This function allows duplicated key in one object. + + @warning The key/value string are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val, size_t len); + +/** Adds a string value at the end of the object. + The `key` and `val` should be null-terminated UTF-8 strings. + The value string is copied. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val); + +/** Adds a string value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + The `val` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the `val`, in bytes. + This function allows duplicated key in one object. + + @warning The key/value string are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val, size_t len); + +/** Adds a JSON value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string are not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + yyjson_mut_val *val); + +/** Removes all key-value pairs for the given key. + Returns the first value to which the specified key is mapped or NULL if this + object contains no mapping for the key. + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str( + yyjson_mut_val *obj, const char *key); + +/** Removes all key-value pairs for the given key. + Returns the first value to which the specified key is mapped or NULL if this + object contains no mapping for the key. + The `key` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( + yyjson_mut_val *obj, const char *key, size_t len); + +/** Replaces all matching keys with the new key. + Returns true if at least one key was renamed. + The `key` and `new_key` should be a null-terminated UTF-8 string. + The `new_key` is copied and held by doc. + + @warning This function takes a linear search time. + If `new_key` already exists, it will cause duplicate keys. + */ +yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *new_key); + +/** Replaces all matching keys with the new key. + Returns true if at least one key was renamed. + The `key` and `new_key` should be a UTF-8 string, + null-terminator is not required. The `new_key` is copied and held by doc. + + @warning This function takes a linear search time. + If `new_key` already exists, it will cause duplicate keys. + */ +yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + size_t len, + const char *new_key, + size_t new_len); + + + +/*============================================================================== + * JSON Pointer API (RFC 6901) + * https://tools.ietf.org/html/rfc6901 + *============================================================================*/ + +/** JSON Pointer error code. */ +typedef uint32_t yyjson_ptr_code; + +/** No JSON pointer error. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_NONE = 0; + +/** Invalid input parameter, such as NULL input. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_PARAMETER = 1; + +/** JSON pointer syntax error, such as invalid escape, token no prefix. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_SYNTAX = 2; + +/** JSON pointer resolve failed, such as index out of range, key not found. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_RESOLVE = 3; + +/** Document's root is NULL, but it is required for the function call. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_NULL_ROOT = 4; + +/** Cannot set root as the target is not a document. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_SET_ROOT = 5; + +/** The memory allocation failed and a new value could not be created. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_MEMORY_ALLOCATION = 6; + +/** Error information for JSON pointer. */ +typedef struct yyjson_ptr_err { + /** Error code, see `yyjson_ptr_code` for all possible values. */ + yyjson_ptr_code code; + /** Error message, constant, no need to free (NULL if no error). */ + const char *msg; + /** Error byte position for input JSON pointer (0 if no error). */ + size_t pos; +} yyjson_ptr_err; + +/** + A context for JSON pointer operation. + + This struct stores the context of JSON Pointer operation result. The struct + can be used with three helper functions: `ctx_append()`, `ctx_replace()`, and + `ctx_remove()`, which perform the corresponding operations on the container + without re-parsing the JSON Pointer. + + For example: + @code + // doc before: {"a":[0,1,null]} + // ptr: "/a/2" + val = yyjson_mut_doc_ptr_getx(doc, ptr, strlen(ptr), &ctx, &err); + if (yyjson_is_null(val)) { + yyjson_ptr_ctx_remove(&ctx); + } + // doc after: {"a":[0,1]} + @endcode + */ +typedef struct yyjson_ptr_ctx { + /** + The container (parent) of the target value. It can be either an array or + an object. If the target location has no value, but all its parent + containers exist, and the target location can be used to insert a new + value, then `ctn` is the parent container of the target location. + Otherwise, `ctn` is NULL. + */ + yyjson_mut_val *ctn; + /** + The previous sibling of the target value. It can be either a value in an + array or a key in an object. As the container is a `circular linked list` + of elements, `pre` is the previous node of the target value. If the + operation is `add` or `set`, then `pre` is the previous node of the new + value, not the original target value. If the target value does not exist, + `pre` is NULL. + */ + yyjson_mut_val *pre; + /** + The removed value if the operation is `set`, `replace` or `remove`. It can + be used to restore the original state of the document if needed. + */ + yyjson_mut_val *old; +} yyjson_ptr_ctx; + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, + const char *ptr, size_t len); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, + const char *ptr, size_t len); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, + const char *ptr, + size_t len); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, + const char *ptr, + size_t len); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be added. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is added, false otherwise. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is added, false otherwise. + */ +yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be set, pass NULL to remove. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( + yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( + yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( + yyjson_mut_doc *doc, const char *ptr); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( + yyjson_mut_doc *doc, const char *ptr, size_t len); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( + yyjson_mut_doc *doc, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, + const char *ptr); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, + const char *ptr, + size_t len); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Append value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @param key New key if `ctx->ctn` is object, or NULL if `ctx->ctn` is array. + @param val New value to be added. + @return true on success or false on fail. + */ +yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Replace value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @param val New value to be replaced. + @return true on success or false on fail. + @note If success, the old value will be returned via `ctx->old`. + */ +yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, + yyjson_mut_val *val); + +/** + Remove value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @return true on success or false on fail. + @note If success, the old value will be returned via `ctx->old`. + */ +yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx); + + + +/*============================================================================== + * JSON Patch API (RFC 6902) + * https://tools.ietf.org/html/rfc6902 + *============================================================================*/ + +/** Result code for JSON patch. */ +typedef uint32_t yyjson_patch_code; + +/** Success, no error. */ +static const yyjson_patch_code YYJSON_PATCH_SUCCESS = 0; + +/** Invalid parameter, such as NULL input or non-array patch. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_MEMORY_ALLOCATION = 2; + +/** JSON patch operation is not object type. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_OPERATION = 3; + +/** JSON patch operation is missing a required key. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_MISSING_KEY = 4; + +/** JSON patch operation member is invalid. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_MEMBER = 5; + +/** JSON patch operation `test` not equal. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_EQUAL = 6; + +/** JSON patch operation failed on JSON pointer. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_POINTER = 7; + +/** Error information for JSON patch. */ +typedef struct yyjson_patch_err { + /** Error code, see `yyjson_patch_code` for all possible values. */ + yyjson_patch_code code; + /** Index of the error operation (0 if no error). */ + size_t idx; + /** Error message, constant, no need to free (NULL if no error). */ + const char *msg; + /** JSON pointer error if `code == YYJSON_PATCH_ERROR_POINTER`. */ + yyjson_ptr_err ptr; +} yyjson_patch_err; + +/** + Creates and returns a patched JSON value (RFC 6902). + The memory of the returned value is allocated by the `doc`. + The `err` is used to receive error information, pass NULL if not needed. + Returns NULL if the patch could not be applied. + */ +yyjson_api yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch, + yyjson_patch_err *err); + +/** + Creates and returns a patched JSON value (RFC 6902). + The memory of the returned value is allocated by the `doc`. + The `err` is used to receive error information, pass NULL if not needed. + Returns NULL if the patch could not be applied. + */ +yyjson_api yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch, + yyjson_patch_err *err); + + + +/*============================================================================== + * JSON Merge-Patch API (RFC 7386) + * https://tools.ietf.org/html/rfc7386 + *============================================================================*/ + +/** + Creates and returns a merge-patched JSON value (RFC 7386). + The memory of the returned value is allocated by the `doc`. + Returns NULL if the patch could not be applied. + + @warning This function is recursive and may cause a stack overflow if the + object level is too deep. + */ +yyjson_api yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch); + +/** + Creates and returns a merge-patched JSON value (RFC 7386). + The memory of the returned value is allocated by the `doc`. + Returns NULL if the patch could not be applied. + + @warning This function is recursive and may cause a stack overflow if the + object level is too deep. + */ +yyjson_api yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch); + + + +/*============================================================================== + * JSON Structure (Implementation) + *============================================================================*/ + +/** Payload of a JSON value (8 bytes). */ +typedef union yyjson_val_uni { + uint64_t u64; + int64_t i64; + double f64; + const char *str; + void *ptr; + size_t ofs; +} yyjson_val_uni; + +/** + Immutable JSON value, 16 bytes. + */ +struct yyjson_val { + uint64_t tag; /**< type, subtype and length */ + yyjson_val_uni uni; /**< payload */ +}; + +struct yyjson_doc { + /** Root value of the document (nonnull). */ + yyjson_val *root; + /** Allocator used by document (nonnull). */ + yyjson_alc alc; + /** The total number of bytes read when parsing JSON (nonzero). */ + size_t dat_read; + /** The total number of value read when parsing JSON (nonzero). */ + size_t val_read; + /** The string pool used by JSON values (nullable). */ + char *str_pool; +}; + + + +/*============================================================================== + * Unsafe JSON Value API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type unsafe_yyjson_get_type(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (yyjson_type)(tag & YYJSON_TYPE_MASK); +} + +yyjson_api_inline yyjson_subtype unsafe_yyjson_get_subtype(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (yyjson_subtype)(tag & YYJSON_SUBTYPE_MASK); +} + +yyjson_api_inline uint8_t unsafe_yyjson_get_tag(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (uint8_t)(tag & YYJSON_TAG_MASK); +} + +yyjson_api_inline bool unsafe_yyjson_is_raw(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_RAW; +} + +yyjson_api_inline bool unsafe_yyjson_is_null(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NULL; +} + +yyjson_api_inline bool unsafe_yyjson_is_bool(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_BOOL; +} + +yyjson_api_inline bool unsafe_yyjson_is_num(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NUM; +} + +yyjson_api_inline bool unsafe_yyjson_is_str(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_STR; +} + +yyjson_api_inline bool unsafe_yyjson_is_arr(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_ARR; +} + +yyjson_api_inline bool unsafe_yyjson_is_obj(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_OBJ; +} + +yyjson_api_inline bool unsafe_yyjson_is_ctn(void *val) { + uint8_t mask = YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ; + return (unsafe_yyjson_get_tag(val) & mask) == mask; +} + +yyjson_api_inline bool unsafe_yyjson_is_uint(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_sint(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_int(void *val) { + const uint8_t mask = YYJSON_TAG_MASK & (~YYJSON_SUBTYPE_SINT); + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + return (unsafe_yyjson_get_tag(val) & mask) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_real(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_true(void *val) { + const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_false(void *val) { + const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_arr_is_flat(yyjson_val *val) { + size_t ofs = val->uni.ofs; + size_t len = (size_t)(val->tag >> YYJSON_TAG_BIT); + return len * sizeof(yyjson_val) + sizeof(yyjson_val) == ofs; +} + +yyjson_api_inline const char *unsafe_yyjson_get_raw(void *val) { + return ((yyjson_val *)val)->uni.str; +} + +yyjson_api_inline bool unsafe_yyjson_get_bool(void *val) { + uint8_t tag = unsafe_yyjson_get_tag(val); + return (bool)((tag & YYJSON_SUBTYPE_MASK) >> YYJSON_TYPE_BIT); +} + +yyjson_api_inline uint64_t unsafe_yyjson_get_uint(void *val) { + return ((yyjson_val *)val)->uni.u64; +} + +yyjson_api_inline int64_t unsafe_yyjson_get_sint(void *val) { + return ((yyjson_val *)val)->uni.i64; +} + +yyjson_api_inline int unsafe_yyjson_get_int(void *val) { + return (int)((yyjson_val *)val)->uni.i64; +} + +yyjson_api_inline double unsafe_yyjson_get_real(void *val) { + return ((yyjson_val *)val)->uni.f64; +} + +yyjson_api_inline double unsafe_yyjson_get_num(void *val) { + uint8_t tag = unsafe_yyjson_get_tag(val); + if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL)) { + return ((yyjson_val *)val)->uni.f64; + } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT)) { + return (double)((yyjson_val *)val)->uni.i64; + } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT)) { +#if YYJSON_U64_TO_F64_NO_IMPL + uint64_t msb = ((uint64_t)1) << 63; + uint64_t num = ((yyjson_val *)val)->uni.u64; + if ((num & msb) == 0) { + return (double)(int64_t)num; + } else { + return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0; + } +#else + return (double)((yyjson_val *)val)->uni.u64; +#endif + } + return 0.0; +} + +yyjson_api_inline const char *unsafe_yyjson_get_str(void *val) { + return ((yyjson_val *)val)->uni.str; +} + +yyjson_api_inline size_t unsafe_yyjson_get_len(void *val) { + return (size_t)(((yyjson_val *)val)->tag >> YYJSON_TAG_BIT); +} + +yyjson_api_inline yyjson_val *unsafe_yyjson_get_first(yyjson_val *ctn) { + return ctn + 1; +} + +yyjson_api_inline yyjson_val *unsafe_yyjson_get_next(yyjson_val *val) { + bool is_ctn = unsafe_yyjson_is_ctn(val); + size_t ctn_ofs = val->uni.ofs; + size_t ofs = (is_ctn ? ctn_ofs : sizeof(yyjson_val)); + return (yyjson_val *)(void *)((uint8_t *)val + ofs); +} + +yyjson_api_inline bool unsafe_yyjson_equals_strn(void *val, const char *str, + size_t len) { + uint64_t tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + return ((yyjson_val *)val)->tag == tag && + memcmp(((yyjson_val *)val)->uni.str, str, len) == 0; +} + +yyjson_api_inline bool unsafe_yyjson_equals_str(void *val, const char *str) { + return unsafe_yyjson_equals_strn(val, str, strlen(str)); +} + +yyjson_api_inline void unsafe_yyjson_set_type(void *val, yyjson_type type, + yyjson_subtype subtype) { + uint8_t tag = (type | subtype); + uint64_t new_tag = ((yyjson_val *)val)->tag; + new_tag = (new_tag & (~(uint64_t)YYJSON_TAG_MASK)) | (uint64_t)tag; + ((yyjson_val *)val)->tag = new_tag; +} + +yyjson_api_inline void unsafe_yyjson_set_len(void *val, size_t len) { + uint64_t tag = ((yyjson_val *)val)->tag & YYJSON_TAG_MASK; + tag |= (uint64_t)len << YYJSON_TAG_BIT; + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_inc_len(void *val) { + uint64_t tag = ((yyjson_val *)val)->tag; + tag += (uint64_t)(1 << YYJSON_TAG_BIT); + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_set_raw(void *val, const char *raw, + size_t len) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, len); + ((yyjson_val *)val)->uni.str = raw; +} + +yyjson_api_inline void unsafe_yyjson_set_null(void *val) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, 0); +} + +yyjson_api_inline void unsafe_yyjson_set_bool(void *val, bool num) { + yyjson_subtype subtype = num ? YYJSON_SUBTYPE_TRUE : YYJSON_SUBTYPE_FALSE; + unsafe_yyjson_set_type(val, YYJSON_TYPE_BOOL, subtype); + unsafe_yyjson_set_len(val, 0); +} + +yyjson_api_inline void unsafe_yyjson_set_uint(void *val, uint64_t num) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT); + unsafe_yyjson_set_len(val, 0); + ((yyjson_val *)val)->uni.u64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_sint(void *val, int64_t num) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT); + unsafe_yyjson_set_len(val, 0); + ((yyjson_val *)val)->uni.i64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_real(void *val, double num) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL); + unsafe_yyjson_set_len(val, 0); + ((yyjson_val *)val)->uni.f64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, strlen(str)); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_strn(void *val, const char *str, + size_t len) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, len); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_arr(void *val, size_t size) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, size); +} + +yyjson_api_inline void unsafe_yyjson_set_obj(void *val, size_t size) { + unsafe_yyjson_set_type(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE); + unsafe_yyjson_set_len(val, size); +} + + + +/*============================================================================== + * JSON Document API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc) { + return doc ? doc->root : NULL; +} + +yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc) { + return doc ? doc->dat_read : 0; +} + +yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc) { + return doc ? doc->val_read : 0; +} + +yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc) { + if (doc) { + yyjson_alc alc = doc->alc; + if (doc->str_pool) alc.free(alc.ctx, doc->str_pool); + alc.free(alc.ctx, doc); + } +} + + + +/*============================================================================== + * JSON Value Type API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_is_raw(yyjson_val *val) { + return val ? unsafe_yyjson_is_raw(val) : false; +} + +yyjson_api_inline bool yyjson_is_null(yyjson_val *val) { + return val ? unsafe_yyjson_is_null(val) : false; +} + +yyjson_api_inline bool yyjson_is_true(yyjson_val *val) { + return val ? unsafe_yyjson_is_true(val) : false; +} + +yyjson_api_inline bool yyjson_is_false(yyjson_val *val) { + return val ? unsafe_yyjson_is_false(val) : false; +} + +yyjson_api_inline bool yyjson_is_bool(yyjson_val *val) { + return val ? unsafe_yyjson_is_bool(val) : false; +} + +yyjson_api_inline bool yyjson_is_uint(yyjson_val *val) { + return val ? unsafe_yyjson_is_uint(val) : false; +} + +yyjson_api_inline bool yyjson_is_sint(yyjson_val *val) { + return val ? unsafe_yyjson_is_sint(val) : false; +} + +yyjson_api_inline bool yyjson_is_int(yyjson_val *val) { + return val ? unsafe_yyjson_is_int(val) : false; +} + +yyjson_api_inline bool yyjson_is_real(yyjson_val *val) { + return val ? unsafe_yyjson_is_real(val) : false; +} + +yyjson_api_inline bool yyjson_is_num(yyjson_val *val) { + return val ? unsafe_yyjson_is_num(val) : false; +} + +yyjson_api_inline bool yyjson_is_str(yyjson_val *val) { + return val ? unsafe_yyjson_is_str(val) : false; +} + +yyjson_api_inline bool yyjson_is_arr(yyjson_val *val) { + return val ? unsafe_yyjson_is_arr(val) : false; +} + +yyjson_api_inline bool yyjson_is_obj(yyjson_val *val) { + return val ? unsafe_yyjson_is_obj(val) : false; +} + +yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val) { + return val ? unsafe_yyjson_is_ctn(val) : false; +} + + + +/*============================================================================== + * JSON Value Content API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val) { + return val ? unsafe_yyjson_get_type(val) : YYJSON_TYPE_NONE; +} + +yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val) { + return val ? unsafe_yyjson_get_subtype(val) : YYJSON_SUBTYPE_NONE; +} + +yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val) { + return val ? unsafe_yyjson_get_tag(val) : 0; +} + +yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val) { + switch (yyjson_get_tag(val)) { + case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: return "raw"; + case YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE: return "null"; + case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NONE: return "string"; + case YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE: return "array"; + case YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE: return "object"; + case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: return "true"; + case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE: return "false"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: return "uint"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: return "sint"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: return "real"; + default: return "unknown"; + } +} + +yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val) { + return yyjson_is_raw(val) ? unsafe_yyjson_get_raw(val) : NULL; +} + +yyjson_api_inline bool yyjson_get_bool(yyjson_val *val) { + return yyjson_is_bool(val) ? unsafe_yyjson_get_bool(val) : false; +} + +yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_uint(val) : 0; +} + +yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_sint(val) : 0; +} + +yyjson_api_inline int yyjson_get_int(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_int(val) : 0; +} + +yyjson_api_inline double yyjson_get_real(yyjson_val *val) { + return yyjson_is_real(val) ? unsafe_yyjson_get_real(val) : 0.0; +} + +yyjson_api_inline double yyjson_get_num(yyjson_val *val) { + return val ? unsafe_yyjson_get_num(val) : 0.0; +} + +yyjson_api_inline const char *yyjson_get_str(yyjson_val *val) { + return yyjson_is_str(val) ? unsafe_yyjson_get_str(val) : NULL; +} + +yyjson_api_inline size_t yyjson_get_len(yyjson_val *val) { + return val ? unsafe_yyjson_get_len(val) : 0; +} + +yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str) { + if (yyjson_likely(val && str)) { + return unsafe_yyjson_equals_str(val, str); + } + return false; +} + +yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, + size_t len) { + if (yyjson_likely(val && str)) { + return unsafe_yyjson_equals_strn(val, str, len); + } + return false; +} + +yyjson_api bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); + +yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { + if (yyjson_unlikely(!lhs || !rhs)) return false; + return unsafe_yyjson_equals(lhs, rhs); +} + +yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, + const char *raw, size_t len) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_raw(val, raw, len); + return true; +} + +yyjson_api_inline bool yyjson_set_null(yyjson_val *val) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_null(val); + return true; +} + +yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_bool(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_uint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_sint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_sint(val, (int64_t)num); + return true; +} + +yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_real(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + if (yyjson_unlikely(!str)) return false; + unsafe_yyjson_set_str(val, str); + return true; +} + +yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, + const char *str, size_t len) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + if (yyjson_unlikely(!str)) return false; + unsafe_yyjson_set_strn(val, str, len); + return true; +} + + + +/*============================================================================== + * JSON Array API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr) { + return yyjson_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx) { + if (yyjson_likely(yyjson_is_arr(arr))) { + if (yyjson_likely(unsafe_yyjson_get_len(arr) > idx)) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + idx; + } else { + while (idx-- > 0) val = unsafe_yyjson_get_next(val); + return val; + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr) { + if (yyjson_likely(yyjson_is_arr(arr))) { + if (yyjson_likely(unsafe_yyjson_get_len(arr) > 0)) { + return unsafe_yyjson_get_first(arr); + } + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr) { + if (yyjson_likely(yyjson_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(len > 0)) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + (len - 1); + } else { + while (len-- > 1) val = unsafe_yyjson_get_next(val); + return val; + } + } + } + return NULL; +} + + + +/*============================================================================== + * JSON Array Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, + yyjson_arr_iter *iter) { + if (yyjson_likely(yyjson_is_arr(arr) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(arr); + iter->cur = unsafe_yyjson_get_first(arr); + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_arr_iter)); + return false; +} + +yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr) { + yyjson_arr_iter iter; + yyjson_arr_iter_init(arr, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter) { + yyjson_val *val; + if (iter && iter->idx < iter->max) { + val = iter->cur; + iter->cur = unsafe_yyjson_get_next(val); + iter->idx++; + return val; + } + return NULL; +} + + + +/*============================================================================== + * JSON Object API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj) { + return yyjson_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; +} + +yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, + const char *key) { + return yyjson_obj_getn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, + const char *_key, + size_t key_len) { + uint64_t tag = (((uint64_t)key_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + if (yyjson_likely(yyjson_is_obj(obj) && _key)) { + size_t len = unsafe_yyjson_get_len(obj); + yyjson_val *key = unsafe_yyjson_get_first(obj); + while (len-- > 0) { + if (key->tag == tag && + memcmp(key->uni.ptr, _key, key_len) == 0) { + return key + 1; + } + key = unsafe_yyjson_get_next(key + 1); + } + } + return NULL; +} + + + +/*============================================================================== + * JSON Object Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, + yyjson_obj_iter *iter) { + if (yyjson_likely(yyjson_is_obj(obj) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(obj); + iter->cur = unsafe_yyjson_get_first(obj); + iter->obj = obj; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_obj_iter)); + return false; +} + +yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj) { + yyjson_obj_iter iter; + yyjson_obj_iter_init(obj, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_val *key = iter->cur; + iter->idx++; + iter->cur = unsafe_yyjson_get_next(key + 1); + return key; + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key) { + return key ? key + 1 : NULL; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, + const char *key) { + return yyjson_obj_iter_getn(iter, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, + const char *key, + size_t key_len) { + if (iter && key) { + size_t idx = iter->idx; + size_t max = iter->max; + yyjson_val *cur = iter->cur; + if (yyjson_unlikely(idx == max)) { + idx = 0; + cur = unsafe_yyjson_get_first(iter->obj); + } + while (idx++ < max) { + yyjson_val *next = unsafe_yyjson_get_next(cur + 1); + if (unsafe_yyjson_get_len(cur) == key_len && + memcmp(cur->uni.str, key, key_len) == 0) { + iter->idx = idx; + iter->cur = next; + return cur + 1; + } + cur = next; + if (idx == iter->max && iter->idx < iter->max) { + idx = 0; + max = iter->idx; + cur = unsafe_yyjson_get_first(iter->obj); + } + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Structure (Implementation) + *============================================================================*/ + +/** + Mutable JSON value, 24 bytes. + The 'tag' and 'uni' field is same as immutable value. + The 'next' field links all elements inside the container to be a cycle. + */ +struct yyjson_mut_val { + uint64_t tag; /**< type, subtype and length */ + yyjson_val_uni uni; /**< payload */ + yyjson_mut_val *next; /**< the next value in circular linked list */ +}; + +/** + A memory chunk in string memory pool. + */ +typedef struct yyjson_str_chunk { + struct yyjson_str_chunk *next; /* next chunk linked list */ + size_t chunk_size; /* chunk size in bytes */ + /* char str[]; flexible array member */ +} yyjson_str_chunk; + +/** + A memory pool to hold all strings in a mutable document. + */ +typedef struct yyjson_str_pool { + char *cur; /* cursor inside current chunk */ + char *end; /* the end of current chunk */ + size_t chunk_size; /* chunk size in bytes while creating new chunk */ + size_t chunk_size_max; /* maximum chunk size in bytes */ + yyjson_str_chunk *chunks; /* a linked list of chunks, nullable */ +} yyjson_str_pool; + +/** + A memory chunk in value memory pool. + `sizeof(yyjson_val_chunk)` should not larger than `sizeof(yyjson_mut_val)`. + */ +typedef struct yyjson_val_chunk { + struct yyjson_val_chunk *next; /* next chunk linked list */ + size_t chunk_size; /* chunk size in bytes */ + /* char pad[sizeof(yyjson_mut_val) - sizeof(yyjson_val_chunk)]; padding */ + /* yyjson_mut_val vals[]; flexible array member */ +} yyjson_val_chunk; + +/** + A memory pool to hold all values in a mutable document. + */ +typedef struct yyjson_val_pool { + yyjson_mut_val *cur; /* cursor inside current chunk */ + yyjson_mut_val *end; /* the end of current chunk */ + size_t chunk_size; /* chunk size in bytes while creating new chunk */ + size_t chunk_size_max; /* maximum chunk size in bytes */ + yyjson_val_chunk *chunks; /* a linked list of chunks, nullable */ +} yyjson_val_pool; + +struct yyjson_mut_doc { + yyjson_mut_val *root; /**< root value of the JSON document, nullable */ + yyjson_alc alc; /**< a valid allocator, nonnull */ + yyjson_str_pool str_pool; /**< string memory pool */ + yyjson_val_pool val_pool; /**< value memory pool */ +}; + +/* Ensures the capacity to at least equal to the specified byte length. */ +yyjson_api bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, + const yyjson_alc *alc, + size_t len); + +/* Ensures the capacity to at least equal to the specified value count. */ +yyjson_api bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, + const yyjson_alc *alc, + size_t count); + +/* Allocate memory for string. */ +yyjson_api_inline char *unsafe_yyjson_mut_str_alc(yyjson_mut_doc *doc, + size_t len) { + char *mem; + const yyjson_alc *alc = &doc->alc; + yyjson_str_pool *pool = &doc->str_pool; + if (yyjson_unlikely((size_t)(pool->end - pool->cur) <= len)) { + if (yyjson_unlikely(!unsafe_yyjson_str_pool_grow(pool, alc, len + 1))) { + return NULL; + } + } + mem = pool->cur; + pool->cur = mem + len + 1; + return mem; +} + +yyjson_api_inline char *unsafe_yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, size_t len) { + char *mem = unsafe_yyjson_mut_str_alc(doc, len); + if (yyjson_unlikely(!mem)) return NULL; + memcpy((void *)mem, (const void *)str, len); + mem[len] = '\0'; + return mem; +} + +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_val(yyjson_mut_doc *doc, + size_t count) { + yyjson_mut_val *val; + yyjson_alc *alc = &doc->alc; + yyjson_val_pool *pool = &doc->val_pool; + if (yyjson_unlikely((size_t)(pool->end - pool->cur) < count)) { + if (yyjson_unlikely(!unsafe_yyjson_val_pool_grow(pool, alc, count))) { + return NULL; + } + } + val = pool->cur; + pool->cur += count; + return val; +} + + + +/*============================================================================== + * Mutable JSON Document API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc) { + return doc ? doc->root : NULL; +} + +yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, + yyjson_mut_val *root) { + if (doc) doc->root = root; +} + + + +/*============================================================================== + * Mutable JSON Value Type API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_raw(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_null(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_true(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_false(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_bool(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_uint(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_sint(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_int(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_real(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_num(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_str(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_arr(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_obj(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_ctn(val) : false; +} + + + +/*============================================================================== + * Mutable JSON Value Content API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val) { + return yyjson_get_type((yyjson_val *)val); +} + +yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val) { + return yyjson_get_subtype((yyjson_val *)val); +} + +yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val) { + return yyjson_get_tag((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val) { + return yyjson_get_type_desc((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val) { + return yyjson_get_raw((yyjson_val *)val); +} + +yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val) { + return yyjson_get_bool((yyjson_val *)val); +} + +yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val) { + return yyjson_get_uint((yyjson_val *)val); +} + +yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val) { + return yyjson_get_sint((yyjson_val *)val); +} + +yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val) { + return yyjson_get_int((yyjson_val *)val); +} + +yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val) { + return yyjson_get_real((yyjson_val *)val); +} + +yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val) { + return yyjson_get_num((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val) { + return yyjson_get_str((yyjson_val *)val); +} + +yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val) { + return yyjson_get_len((yyjson_val *)val); +} + +yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, + const char *str) { + return yyjson_equals_str((yyjson_val *)val, str); +} + +yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, + const char *str, size_t len) { + return yyjson_equals_strn((yyjson_val *)val, str, len); +} + +yyjson_api bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs); + +yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs) { + if (yyjson_unlikely(!lhs || !rhs)) return false; + return unsafe_yyjson_mut_equals(lhs, rhs); +} + +yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, + const char *raw, size_t len) { + if (yyjson_unlikely(!val || !raw)) return false; + unsafe_yyjson_set_raw(val, raw, len); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_null(val); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_bool(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_uint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_sint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_sint(val, (int64_t)num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_real(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, + const char *str) { + if (yyjson_unlikely(!val || !str)) return false; + unsafe_yyjson_set_str(val, str); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, + const char *str, size_t len) { + if (yyjson_unlikely(!val || !str)) return false; + unsafe_yyjson_set_strn(val, str, len); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_arr(val, 0); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_obj(val, 0); + return true; +} + + + +/*============================================================================== + * Mutable JSON Value Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(str)) return yyjson_mut_rawn(doc, str, strlen(str)); + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(str)) return yyjson_mut_rawncpy(doc, str, strlen(str)); + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_likely(val && new_str)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = new_str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, + bool _val) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)_val << 3); + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, + uint64_t num) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = num; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, + int64_t num) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = num; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, + int64_t num) { + return yyjson_mut_sint(doc, num); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, + double num) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = num; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(str)) return yyjson_mut_strn(doc, str, strlen(str)); + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = str; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, + const char *str) { + if (yyjson_likely(str)) return yyjson_mut_strncpy(doc, str, strlen(str)); + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, + size_t len) { + if (yyjson_likely(doc && str)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_likely(val && new_str)) { + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = new_str; + return val; + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Array API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr) { + return yyjson_mut_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(idx < yyjson_mut_arr_size(arr))) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; + while (idx-- > 0) val = val->next; + return val->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { + return ((yyjson_mut_val *)arr->uni.ptr)->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { + return ((yyjson_mut_val *)arr->uni.ptr); + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Array Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, + yyjson_mut_arr_iter *iter) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(arr); + iter->cur = iter->max ? (yyjson_mut_val *)arr->uni.ptr : NULL; + iter->pre = NULL; + iter->arr = arr; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_mut_arr_iter)); + return false; +} + +yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( + yyjson_mut_val *arr) { + yyjson_mut_arr_iter iter; + yyjson_mut_arr_iter_init(arr, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_mut_arr_iter_has_next(yyjson_mut_arr_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( + yyjson_mut_arr_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_mut_val *val = iter->cur; + iter->pre = val; + iter->cur = val->next; + iter->idx++; + return iter->cur; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( + yyjson_mut_arr_iter *iter) { + if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { + yyjson_mut_val *prev = iter->pre; + yyjson_mut_val *cur = iter->cur; + yyjson_mut_val *next = cur->next; + if (yyjson_unlikely(iter->idx == iter->max)) iter->arr->uni.ptr = prev; + iter->idx--; + iter->max--; + unsafe_yyjson_set_len(iter->arr, iter->max); + prev->next = next; + iter->cur = next; + return cur; + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Array Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +#define yyjson_mut_arr_with_func(func) \ + if (yyjson_likely(doc && ((0 < count && count < \ + (~(size_t)0) / sizeof(yyjson_mut_val) && vals) || count == 0))) { \ + yyjson_mut_val *arr = unsafe_yyjson_mut_val(doc, 1 + count); \ + if (yyjson_likely(arr)) { \ + arr->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; \ + if (count > 0) { \ + size_t i; \ + for (i = 0; i < count; i++) { \ + yyjson_mut_val *val = arr + i + 1; \ + func \ + val->next = val + 1; \ + } \ + arr[count].next = arr + 1; \ + arr->uni.ptr = arr + count; \ + } \ + return arr; \ + } \ + } \ + return NULL + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( + yyjson_mut_doc *doc, const bool *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)vals[i] << 3); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( + yyjson_mut_doc *doc, const int64_t *vals, size_t count) { + return yyjson_mut_arr_with_sint64(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { + return yyjson_mut_arr_with_uint64(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( + yyjson_mut_doc *doc, const double *vals, size_t count) { + return yyjson_mut_arr_with_double(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( + yyjson_mut_doc *doc, const int8_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = (int64_t)vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( + yyjson_mut_doc *doc, const int16_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( + yyjson_mut_doc *doc, const int32_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( + yyjson_mut_doc *doc, const int64_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( + yyjson_mut_doc *doc, const uint8_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( + yyjson_mut_doc *doc, const uint16_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( + yyjson_mut_doc *doc, const uint32_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( + yyjson_mut_doc *doc, const float *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = (double)vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( + yyjson_mut_doc *doc, const double *vals, size_t count) { + yyjson_mut_arr_with_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = vals[i]; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( + yyjson_mut_doc *doc, const char **vals, size_t count) { + yyjson_mut_arr_with_func({ + uint64_t len = (uint64_t)strlen(vals[i]); + val->tag = (len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = vals[i]; + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { + if (yyjson_unlikely(count > 0 && !lens)) return NULL; + yyjson_mut_arr_with_func({ + val->tag = ((uint64_t)lens[i] << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = vals[i]; + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( + yyjson_mut_doc *doc, const char **vals, size_t count) { + size_t len; + const char *str; + yyjson_mut_arr_with_func({ + str = vals[i]; + if (!str) return NULL; + len = strlen(str); + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { + size_t len; + const char *str; + if (yyjson_unlikely(count > 0 && !lens)) return NULL; + yyjson_mut_arr_with_func({ + str = vals[i]; + len = lens[i]; + val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!val->uni.str)) return NULL; + }); +} + +#undef yyjson_mut_arr_with_func + + + +/*============================================================================== + * Mutable JSON Array Modification API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, + yyjson_mut_val *val, size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx <= len)) { + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + arr->uni.ptr = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + if (idx == len) { + prev->next = val; + val->next = next; + arr->uni.ptr = val; + } else { + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = val; + val->next = next; + } + } + return true; + } + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = val; + val->next = next; + } + arr->uni.ptr = val; + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + arr->uni.ptr = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = val; + val->next = next; + } + return true; + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, + size_t idx, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx < len)) { + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = val; + val->next = next->next; + if ((void *)next == arr->uni.ptr) arr->uni.ptr = val; + return next; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + val->next = val; + arr->uni.ptr = val; + return prev; + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx < len)) { + unsafe_yyjson_set_len(arr, len - 1); + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = next->next; + if ((void *)next == arr->uni.ptr) arr->uni.ptr = prev; + return next; + } else { + return ((yyjson_mut_val *)arr->uni.ptr); + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (len > 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = next->next; + unsafe_yyjson_set_len(arr, len - 1); + return next; + } else if (len == 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + unsafe_yyjson_set_len(arr, 0); + return prev; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + unsafe_yyjson_set_len(arr, len - 1); + while (--len > 0) prev = prev->next; + prev->next = next; + next = (yyjson_mut_val *)arr->uni.ptr; + arr->uni.ptr = prev; + return next; + } else if (len == 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + unsafe_yyjson_set_len(arr, 0); + return prev; + } + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, + size_t _idx, size_t _len) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + yyjson_mut_val *prev, *next; + bool tail_removed; + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_unlikely(_idx + _len > len)) return false; + if (yyjson_unlikely(_len == 0)) return true; + unsafe_yyjson_set_len(arr, len - _len); + if (yyjson_unlikely(len == _len)) return true; + tail_removed = (_idx + _len == len); + prev = ((yyjson_mut_val *)arr->uni.ptr); + while (_idx-- > 0) prev = prev->next; + next = prev->next; + while (_len-- > 0) next = next->next; + prev->next = next; + if (yyjson_unlikely(tail_removed)) arr->uni.ptr = prev; + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + unsafe_yyjson_set_len(arr, 0); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && + unsafe_yyjson_get_len(arr) > idx)) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; + while (idx-- > 0) val = val->next; + arr->uni.ptr = (void *)val; + return true; + } + return false; +} + + + +/*============================================================================== + * Mutable JSON Array Modification Convenience API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, + yyjson_mut_val *val) { + return yyjson_mut_arr_append(arr, val); +} + +yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_null(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_true(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_false(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + bool _val) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_bool(doc, _val); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + uint64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_uint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_sint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_sint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_real(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_str(doc, str); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, size_t len) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strn(doc, str, len); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strcpy(doc, str); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, size_t len) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strncpy(doc, str, len); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_arr(doc); + return yyjson_mut_arr_append(arr, val) ? val : NULL; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_obj(doc); + return yyjson_mut_arr_append(arr, val) ? val : NULL; + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj) { + return yyjson_mut_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, + const char *key) { + return yyjson_mut_obj_getn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, + const char *_key, + size_t key_len) { + uint64_t tag = (((uint64_t)key_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + size_t len = yyjson_mut_obj_size(obj); + if (yyjson_likely(len && _key)) { + yyjson_mut_val *key = ((yyjson_mut_val *)obj->uni.ptr)->next->next; + while (len-- > 0) { + if (key->tag == tag && + memcmp(key->uni.ptr, _key, key_len) == 0) { + return key->next; + } + key = key->next->next; + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, + yyjson_mut_obj_iter *iter) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(obj); + iter->cur = iter->max ? (yyjson_mut_val *)obj->uni.ptr : NULL; + iter->pre = NULL; + iter->obj = obj; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_mut_obj_iter)); + return false; +} + +yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( + yyjson_mut_val *obj) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(obj, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_mut_obj_iter_has_next(yyjson_mut_obj_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( + yyjson_mut_obj_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_mut_val *key = iter->cur; + iter->pre = key; + iter->cur = key->next->next; + iter->idx++; + return iter->cur; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( + yyjson_mut_val *key) { + return key ? key->next : NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( + yyjson_mut_obj_iter *iter) { + if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { + yyjson_mut_val *prev = iter->pre; + yyjson_mut_val *cur = iter->cur; + yyjson_mut_val *next = cur->next->next; + if (yyjson_unlikely(iter->idx == iter->max)) iter->obj->uni.ptr = prev; + iter->idx--; + iter->max--; + unsafe_yyjson_set_len(iter->obj, iter->max); + prev->next->next = next; + iter->cur = prev; + return cur->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( + yyjson_mut_obj_iter *iter, const char *key) { + return yyjson_mut_obj_iter_getn(iter, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( + yyjson_mut_obj_iter *iter, const char *key, size_t key_len) { + if (iter && key) { + size_t idx = 0; + size_t max = iter->max; + yyjson_mut_val *pre, *cur = iter->cur; + while (idx++ < max) { + pre = cur; + cur = cur->next->next; + if (unsafe_yyjson_get_len(cur) == key_len && + memcmp(cur->uni.str, key, key_len) == 0) { + iter->idx += idx; + if (iter->idx > max) iter->idx -= max + 1; + iter->pre = pre; + iter->cur = cur; + return cur->next; + } + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, + const char **keys, + const char **vals, + size_t count) { + if (yyjson_likely(doc && ((count > 0 && keys && vals) || (count == 0)))) { + yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); + if (yyjson_likely(obj)) { + obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; + if (count > 0) { + size_t i; + for (i = 0; i < count; i++) { + yyjson_mut_val *key = obj + (i * 2 + 1); + yyjson_mut_val *val = obj + (i * 2 + 2); + uint64_t key_len = (uint64_t)strlen(keys[i]); + uint64_t val_len = (uint64_t)strlen(vals[i]); + key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + key->uni.str = keys[i]; + val->uni.str = vals[i]; + key->next = val; + val->next = val + 1; + } + obj[count * 2].next = obj + 1; + obj->uni.ptr = obj + (count * 2 - 1); + } + return obj; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, + const char **pairs, + size_t count) { + if (yyjson_likely(doc && ((count > 0 && pairs) || (count == 0)))) { + yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); + if (yyjson_likely(obj)) { + obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; + if (count > 0) { + size_t i; + for (i = 0; i < count; i++) { + yyjson_mut_val *key = obj + (i * 2 + 1); + yyjson_mut_val *val = obj + (i * 2 + 2); + const char *key_str = pairs[i * 2 + 0]; + const char *val_str = pairs[i * 2 + 1]; + uint64_t key_len = (uint64_t)strlen(key_str); + uint64_t val_len = (uint64_t)strlen(val_str); + key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + key->uni.str = key_str; + val->uni.str = val_str; + key->next = val; + val->next = val + 1; + } + obj[count * 2].next = obj + 1; + obj->uni.ptr = obj + (count * 2 - 1); + } + return obj; + } + } + return NULL; +} + + + +/*============================================================================== + * Mutable JSON Object Modification API (Implementation) + *============================================================================*/ + +yyjson_api_inline void unsafe_yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t len) { + if (yyjson_likely(len)) { + yyjson_mut_val *prev_val = ((yyjson_mut_val *)obj->uni.ptr)->next; + yyjson_mut_val *next_key = prev_val->next; + prev_val->next = key; + val->next = next_key; + } else { + val->next = key; + } + key->next = val; + obj->uni.ptr = (void *)key; + unsafe_yyjson_set_len(obj, len + 1); +} + +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_obj_remove( + yyjson_mut_val *obj, const char *key, size_t key_len, uint64_t key_tag) { + size_t obj_len = unsafe_yyjson_get_len(obj); + if (obj_len) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; + yyjson_mut_val *cur_key = pre_key->next->next; + yyjson_mut_val *removed_item = NULL; + size_t i; + for (i = 0; i < obj_len; i++) { + if (key_tag == cur_key->tag && + memcmp(key, cur_key->uni.ptr, key_len) == 0) { + if (!removed_item) removed_item = cur_key->next; + cur_key = cur_key->next->next; + pre_key->next->next = cur_key; + if (i + 1 == obj_len) obj->uni.ptr = pre_key; + i--; + obj_len--; + } else { + pre_key = cur_key; + cur_key = cur_key->next->next; + } + } + unsafe_yyjson_set_len(obj, obj_len); + return removed_item; + } else { + return NULL; + } +} + +yyjson_api_inline bool unsafe_yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + size_t key_len = unsafe_yyjson_get_len(key); + size_t obj_len = unsafe_yyjson_get_len(obj); + if (obj_len) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; + yyjson_mut_val *cur_key = pre_key->next->next; + size_t i; + for (i = 0; i < obj_len; i++) { + if (key->tag == cur_key->tag && + memcmp(key->uni.str, cur_key->uni.ptr, key_len) == 0) { + cur_key->next->tag = val->tag; + cur_key->next->uni.u64 = val->uni.u64; + return true; + } else { + cur_key = cur_key->next->next; + } + } + } + return false; +} + +yyjson_api_inline void unsafe_yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx) { + yyjson_mut_val *key = (yyjson_mut_val *)obj->uni.ptr; + while (idx-- > 0) key = key->next->next; + obj->uni.ptr = (void *)key; +} + +yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + unsafe_yyjson_mut_obj_add(obj, key, val, unsafe_yyjson_get_len(obj)); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + bool replaced = false; + size_t key_len; + yyjson_mut_obj_iter iter; + yyjson_mut_val *cur_key; + if (yyjson_unlikely(!yyjson_mut_is_obj(obj) || + !yyjson_mut_is_str(key))) return false; + key_len = unsafe_yyjson_get_len(key); + yyjson_mut_obj_iter_init(obj, &iter); + while ((cur_key = yyjson_mut_obj_iter_next(&iter))) { + if (key->tag == cur_key->tag && + memcmp(key->uni.str, cur_key->uni.ptr, key_len) == 0) { + if (!replaced && val) { + replaced = true; + val->next = cur_key->next->next; + cur_key->next = val; + } else { + yyjson_mut_obj_iter_remove(&iter); + } + } + } + if (!replaced && val) unsafe_yyjson_mut_obj_add(obj, key, val, iter.max); + return true; +} + +yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + size_t len = unsafe_yyjson_get_len(obj); + if (yyjson_likely(len >= idx)) { + if (len > idx) { + void *ptr = obj->uni.ptr; + unsafe_yyjson_mut_obj_rotate(obj, idx); + unsafe_yyjson_mut_obj_add(obj, key, val, len); + obj->uni.ptr = ptr; + } else { + unsafe_yyjson_mut_obj_add(obj, key, val, len); + } + return true; + } + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, + yyjson_mut_val *key) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key))) { + return unsafe_yyjson_mut_obj_remove(obj, key->uni.str, + unsafe_yyjson_get_len(key), + key->tag); + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( + yyjson_mut_val *obj, const char *key) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { + size_t key_len = strlen(key); + uint64_t tag = ((uint64_t)key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + return unsafe_yyjson_mut_obj_remove(obj, key, key_len, tag); + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( + yyjson_mut_val *obj, const char *key, size_t key_len) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { + uint64_t tag = ((uint64_t)key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + return unsafe_yyjson_mut_obj_remove(obj, key, key_len, tag); + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj) { + if (yyjson_likely(yyjson_mut_is_obj(obj))) { + unsafe_yyjson_set_len(obj, 0); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + return unsafe_yyjson_mut_obj_replace(obj, key, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + unsafe_yyjson_get_len(obj) > idx)) { + unsafe_yyjson_mut_obj_rotate(obj, idx); + return true; + } + return false; +} + + + +/*============================================================================== + * Mutable JSON Object Modification Convenience API (Implementation) + *============================================================================*/ + +#define yyjson_mut_obj_add_func(func) \ + if (yyjson_likely(doc && yyjson_mut_is_obj(obj) && _key)) { \ + yyjson_mut_val *key = unsafe_yyjson_mut_val(doc, 2); \ + if (yyjson_likely(key)) { \ + size_t len = unsafe_yyjson_get_len(obj); \ + yyjson_mut_val *val = key + 1; \ + key->tag = YYJSON_TYPE_STR | YYJSON_SUBTYPE_NONE; \ + key->tag |= (uint64_t)strlen(_key) << YYJSON_TAG_BIT; \ + key->uni.str = _key; \ + func \ + unsafe_yyjson_mut_obj_add(obj, key, val, len); \ + return true; \ + } \ + } \ + return false + +yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + bool _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)(_val) << 3); + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + uint64_t _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + val->uni.u64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + int64_t _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + int64_t _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + val->uni.i64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + double _val) { + yyjson_mut_obj_add_func({ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.f64 = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->tag = ((uint64_t)strlen(_val) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val, + size_t _len) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + size_t _len = strlen(_val); + val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); + if (yyjson_unlikely(!val->uni.str)) return false; + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val, + size_t _len) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); + if (yyjson_unlikely(!val->uni.str)) return false; + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + yyjson_mut_val *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val = _val; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str(yyjson_mut_val *obj, + const char *key) { + return yyjson_mut_obj_remove_strn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( + yyjson_mut_val *obj, const char *_key, size_t _len) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && _key)) { + yyjson_mut_val *key; + yyjson_mut_obj_iter iter; + yyjson_mut_val *val_removed = NULL; + yyjson_mut_obj_iter_init(obj, &iter); + while ((key = yyjson_mut_obj_iter_next(&iter)) != NULL) { + if (unsafe_yyjson_get_len(key) == _len && + memcmp(key->uni.str, _key, _len) == 0) { + if (!val_removed) val_removed = key->next; + yyjson_mut_obj_iter_remove(&iter); + } + } + return val_removed; + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *new_key) { + if (!key || !new_key) return false; + return yyjson_mut_obj_rename_keyn(doc, obj, key, strlen(key), + new_key, strlen(new_key)); +} + +yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + size_t len, + const char *new_key, + size_t new_len) { + char *cpy_key = NULL; + yyjson_mut_val *old_key; + yyjson_mut_obj_iter iter; + if (!doc || !obj || !key || !new_key) return false; + yyjson_mut_obj_iter_init(obj, &iter); + while ((old_key = yyjson_mut_obj_iter_next(&iter))) { + if (unsafe_yyjson_equals_strn((void *)old_key, key, len)) { + if (!cpy_key) { + cpy_key = unsafe_yyjson_mut_strncpy(doc, new_key, new_len); + if (!cpy_key) return false; + } + yyjson_mut_set_strn(old_key, cpy_key, new_len); + } + } + return cpy_key != NULL; +} + + + +/*============================================================================== + * JSON Pointer API (Implementation) + *============================================================================*/ + +#define yyjson_ptr_set_err(_code, _msg) do { \ + if (err) { \ + err->code = YYJSON_PTR_ERR_##_code; \ + err->msg = _msg; \ + err->pos = 0; \ + } \ +} while(false) + +/* require: val != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/* require: val != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/* require: val/new_val/doc != NULL, *ptr == '/', len > 0 */ +yyjson_api bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, bool insert_new, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/* require: val/err != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/* require: val/err != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, + const char *ptr) { + if (yyjson_unlikely(!ptr)) return NULL; + return yyjson_doc_ptr_getn(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, + const char *ptr, size_t len) { + return yyjson_doc_ptr_getx(doc, ptr, len, NULL); +} + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, + const char *ptr, size_t len, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return doc->root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_ptr_getx(doc->root, ptr, len, err); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, + const char *ptr) { + if (yyjson_unlikely(!ptr)) return NULL; + return yyjson_ptr_getn(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, + const char *ptr, size_t len) { + return yyjson_ptr_getx(val, ptr, len, NULL); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return val; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_ptr_getx(val, ptr, len, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_getn(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, + const char *ptr, + size_t len) { + return yyjson_mut_doc_ptr_getx(doc, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return doc->root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_getx(doc->root, ptr, len, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_ptr_getn(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_getx(val, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return val; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_doc_ptr_addn(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_addx(doc, ptr, len, new_val, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + if (doc->root) { + yyjson_ptr_set_err(SET_ROOT, "cannot set document's root"); + return false; + } else { + doc->root = new_val; + return true; + } + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (yyjson_unlikely(!doc->root && !create_parent)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return false; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_mut_val *root = yyjson_mut_obj(doc); + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); + return false; + } + if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, + create_parent, true, ctx, err)) { + doc->root = root; + return true; + } + return false; + } + return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, + create_parent, true, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_ptr_addn(val, ptr, strlen(ptr), new_val, doc); +} + +yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + return yyjson_mut_ptr_addx(val, ptr, len, new_val, doc, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !new_val || !doc)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return false; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, + doc, create_parent, true, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_doc_ptr_setn(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_setx(doc, ptr, len, new_val, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + if (ctx) ctx->old = doc->root; + doc->root = new_val; + return true; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (!new_val) { + if (!doc->root) { + yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); + return false; + } + return !!unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); + } + if (yyjson_unlikely(!doc->root && !create_parent)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return false; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_mut_val *root = yyjson_mut_obj(doc); + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); + return false; + } + if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, + create_parent, false, ctx, err)) { + doc->root = root; + return true; + } + return false; + } + return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, + create_parent, false, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_ptr_setn(val, ptr, strlen(ptr), new_val, doc); +} + +yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + return yyjson_mut_ptr_setx(val, ptr, len, new_val, doc, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !doc)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return false; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (!new_val) { + return !!unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); + } + return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, doc, + create_parent, false, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( + yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_replacen(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_replacex(doc, ptr, len, new_val, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_mut_val *root = doc->root; + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); + return NULL; + } + if (ctx) ctx->old = root; + doc->root = new_val; + return root; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_replacex(doc->root, ptr, len, new_val, + ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( + yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val) { + if (!ptr) return NULL; + return yyjson_mut_ptr_replacen(val, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val) { + return yyjson_mut_ptr_replacex(val, ptr, len, new_val, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_replacex(val, ptr, len, new_val, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( + yyjson_mut_doc *doc, const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_removen(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( + yyjson_mut_doc *doc, const char *ptr, size_t len) { + return yyjson_mut_doc_ptr_removex(doc, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( + yyjson_mut_doc *doc, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_mut_val *root = doc->root; + if (ctx) ctx->old = root; + doc->root = NULL; + return root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_ptr_removen(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_removex(val, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); +} + +yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, + yyjson_mut_val *key, + yyjson_mut_val *val) { + yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; + if (!ctx || !ctx->ctn || !val) return false; + ctn = ctx->ctn; + + if (yyjson_mut_is_obj(ctn)) { + if (!key) return false; + key->next = val; + pre_key = ctx->pre; + if (unsafe_yyjson_get_len(ctn) == 0) { + val->next = key; + ctn->uni.ptr = key; + ctx->pre = key; + } else if (!pre_key) { + pre_key = (yyjson_mut_val *)ctn->uni.ptr; + pre_val = pre_key->next; + val->next = pre_val->next; + pre_val->next = key; + ctn->uni.ptr = key; + ctx->pre = pre_key; + } else { + cur_key = pre_key->next->next; + cur_val = cur_key->next; + val->next = cur_val->next; + cur_val->next = key; + if (ctn->uni.ptr == cur_key) ctn->uni.ptr = key; + ctx->pre = cur_key; + } + } else { + pre_val = ctx->pre; + if (unsafe_yyjson_get_len(ctn) == 0) { + val->next = val; + ctn->uni.ptr = val; + ctx->pre = val; + } else if (!pre_val) { + pre_val = (yyjson_mut_val *)ctn->uni.ptr; + val->next = pre_val->next; + pre_val->next = val; + ctn->uni.ptr = val; + ctx->pre = pre_val; + } else { + cur_val = pre_val->next; + val->next = cur_val->next; + cur_val->next = val; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; + ctx->pre = cur_val; + } + } + unsafe_yyjson_inc_len(ctn); + return true; +} + +yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, + yyjson_mut_val *val) { + yyjson_mut_val *ctn, *pre_key, *cur_key, *pre_val, *cur_val; + if (!ctx || !ctx->ctn || !ctx->pre || !val) return false; + ctn = ctx->ctn; + if (yyjson_mut_is_obj(ctn)) { + pre_key = ctx->pre; + pre_val = pre_key->next; + cur_key = pre_val->next; + cur_val = cur_key->next; + /* replace current value */ + cur_key->next = val; + val->next = cur_val->next; + ctx->old = cur_val; + } else { + pre_val = ctx->pre; + cur_val = pre_val->next; + /* replace current value */ + if (pre_val != cur_val) { + val->next = cur_val->next; + pre_val->next = val; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; + } else { + val->next = val; + ctn->uni.ptr = val; + ctx->pre = val; + } + ctx->old = cur_val; + } + return true; +} + +yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx) { + yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; + size_t len; + if (!ctx || !ctx->ctn || !ctx->pre) return false; + ctn = ctx->ctn; + if (yyjson_mut_is_obj(ctn)) { + pre_key = ctx->pre; + pre_val = pre_key->next; + cur_key = pre_val->next; + cur_val = cur_key->next; + /* remove current key-value */ + pre_val->next = cur_val->next; + if (ctn->uni.ptr == cur_key) ctn->uni.ptr = pre_key; + ctx->pre = NULL; + ctx->old = cur_val; + } else { + pre_val = ctx->pre; + cur_val = pre_val->next; + /* remove current key-value */ + pre_val->next = cur_val->next; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = pre_val; + ctx->pre = NULL; + ctx->old = cur_val; + } + len = unsafe_yyjson_get_len(ctn) - 1; + if (len == 0) ctn->uni.ptr = NULL; + unsafe_yyjson_set_len(ctn, len); + return true; +} + +#undef yyjson_ptr_set_err + + + +/*============================================================================== + * JSON Value at Pointer API (Implementation) + *============================================================================*/ + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type bool. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_bool( + yyjson_val *root, const char *ptr, bool *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_bool(val)) { + *value = unsafe_yyjson_get_bool(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type uint. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_uint( + yyjson_val *root, const char *ptr, uint64_t *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_uint(val)) { + *value = unsafe_yyjson_get_uint(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_sint( + yyjson_val *root, const char *ptr, int64_t *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_sint(val)) { + *value = unsafe_yyjson_get_sint(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type real. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_real( + yyjson_val *root, const char *ptr, double *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_real(val)) { + *value = unsafe_yyjson_get_real(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint, + uint or real. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_num( + yyjson_val *root, const char *ptr, double *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_num(val)) { + *value = unsafe_yyjson_get_num(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type string. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_str( + yyjson_val *root, const char *ptr, const char **value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_str(val)) { + *value = unsafe_yyjson_get_str(val); + return true; + } else { + return false; + } +} + + + +/*============================================================================== + * Deprecated + *============================================================================*/ + +/** @deprecated renamed to `yyjson_doc_ptr_get` */ +yyjson_deprecated("renamed to yyjson_doc_ptr_get") +yyjson_api_inline yyjson_val *yyjson_doc_get_pointer(yyjson_doc *doc, + const char *ptr) { + return yyjson_doc_ptr_get(doc, ptr); +} + +/** @deprecated renamed to `yyjson_doc_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_doc_ptr_getn") +yyjson_api_inline yyjson_val *yyjson_doc_get_pointern(yyjson_doc *doc, + const char *ptr, + size_t len) { + return yyjson_doc_ptr_getn(doc, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_doc_ptr_get` */ +yyjson_deprecated("renamed to yyjson_mut_doc_ptr_get") +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointer( + yyjson_mut_doc *doc, const char *ptr) { + return yyjson_mut_doc_ptr_get(doc, ptr); +} + +/** @deprecated renamed to `yyjson_mut_doc_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_mut_doc_ptr_getn") +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointern( + yyjson_mut_doc *doc, const char *ptr, size_t len) { + return yyjson_mut_doc_ptr_getn(doc, ptr, len); +} + +/** @deprecated renamed to `yyjson_ptr_get` */ +yyjson_deprecated("renamed to yyjson_ptr_get") +yyjson_api_inline yyjson_val *yyjson_get_pointer(yyjson_val *val, + const char *ptr) { + return yyjson_ptr_get(val, ptr); +} + +/** @deprecated renamed to `yyjson_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_ptr_getn") +yyjson_api_inline yyjson_val *yyjson_get_pointern(yyjson_val *val, + const char *ptr, + size_t len) { + return yyjson_ptr_getn(val, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_ptr_get` */ +yyjson_deprecated("renamed to yyjson_mut_ptr_get") +yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointer(yyjson_mut_val *val, + const char *ptr) { + return yyjson_mut_ptr_get(val, ptr); +} + +/** @deprecated renamed to `yyjson_mut_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_mut_ptr_getn") +yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointern(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_getn(val, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_ptr_getn` */ +yyjson_deprecated("renamed to unsafe_yyjson_ptr_getn") +yyjson_api_inline yyjson_val *unsafe_yyjson_get_pointer(yyjson_val *val, + const char *ptr, + size_t len) { + yyjson_ptr_err err; + return unsafe_yyjson_ptr_getx(val, ptr, len, &err); +} + +/** @deprecated renamed to `unsafe_yyjson_mut_ptr_getx` */ +yyjson_deprecated("renamed to unsafe_yyjson_mut_ptr_getx") +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_get_pointer( + yyjson_mut_val *val, const char *ptr, size_t len) { + yyjson_ptr_err err; + return unsafe_yyjson_mut_ptr_getx(val, ptr, len, NULL, &err); +} + + + +/*============================================================================== + * Compiler Hint End + *============================================================================*/ + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +# endif +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif /* warning suppress end */ + +#ifdef __cplusplus +} +#endif /* extern "C" end */ + +#endif /* YYJSON_H */ diff --git a/libsvc/Modules/Lib/World/LICENSE.txt b/libsvc/Modules/Lib/World/LICENSE.txt new file mode 100644 index 0000000..1c12e76 --- /dev/null +++ b/libsvc/Modules/Lib/World/LICENSE.txt @@ -0,0 +1,39 @@ +/* ----------------------------------------------------------------- */ +/* WORLD: High-quality speech analysis, */ +/* manipulation and synthesis system */ +/* developed by M. Morise */ +/* http://www.kisc.meiji.ac.jp/~mmorise/world/english/ */ +/* ----------------------------------------------------------------- */ +/* */ +/* Copyright (c) 2010 M. Morise */ +/* */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* - Redistributions of source code must retain the above copyright */ +/* notice, this list of conditions and the following disclaimer. */ +/* - Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials provided */ +/* with the distribution. */ +/* - Neither the name of the M. Morise nor the names of its */ +/* contributors may be used to endorse or promote products derived */ +/* from this software without specific prior written permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND */ +/* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS */ +/* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, */ +/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */ +/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, */ +/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON */ +/* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, */ +/* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ +/* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* ----------------------------------------------------------------- */ diff --git a/libsvc/Modules/Lib/World/src/cheaptrick.cpp b/libsvc/Modules/Lib/World/src/cheaptrick.cpp new file mode 100644 index 0000000..a2f9b19 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/cheaptrick.cpp @@ -0,0 +1,239 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Spectral envelope estimation on the basis of the idea of CheapTrick. +//----------------------------------------------------------------------------- +#include "world/cheaptrick.h" + +#include + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/matlabfunctions.h" + +namespace { + +//----------------------------------------------------------------------------- +// SmoothingWithRecovery() carries out the spectral smoothing and spectral +// recovery on the Cepstrum domain. +//----------------------------------------------------------------------------- +static void SmoothingWithRecovery(double f0, int fs, int fft_size, double q1, + const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, double *spectral_envelope) { + double *smoothing_lifter = new double[fft_size]; + double *compensation_lifter = new double[fft_size]; + + smoothing_lifter[0] = 1.0; + compensation_lifter[0] = (1.0 - 2.0 * q1) + 2.0 * q1; + double quefrency; + for (int i = 1; i <= forward_real_fft->fft_size / 2; ++i) { + quefrency = static_cast(i) / fs; + smoothing_lifter[i] = sin(world::kPi * f0 * quefrency) / + (world::kPi * f0 * quefrency); + compensation_lifter[i] = (1.0 - 2.0 * q1) + 2.0 * q1 * + cos(2.0 * world::kPi * quefrency * f0); + } + + for (int i = 0; i <= fft_size / 2; ++i) + forward_real_fft->waveform[i] = log(forward_real_fft->waveform[i]); + for (int i = 1; i < fft_size / 2; ++i) + forward_real_fft->waveform[fft_size - i] = forward_real_fft->waveform[i]; + fft_execute(forward_real_fft->forward_fft); + + for (int i = 0; i <= fft_size / 2; ++i) { + inverse_real_fft->spectrum[i][0] = forward_real_fft->spectrum[i][0] * + smoothing_lifter[i] * compensation_lifter[i] / fft_size; + inverse_real_fft->spectrum[i][1] = 0.0; + } + fft_execute(inverse_real_fft->inverse_fft); + + for (int i = 0; i <= fft_size / 2; ++i) + spectral_envelope[i] = exp(inverse_real_fft->waveform[i]); + + delete[] smoothing_lifter; + delete[] compensation_lifter; +} + +//----------------------------------------------------------------------------- +// GetPowerSpectrum() calculates the power_spectrum with DC correction. +// DC stands for Direct Current. In this case, the component from 0 to F0 Hz +// is corrected. +//----------------------------------------------------------------------------- +static void GetPowerSpectrum(int fs, double f0, int fft_size, + const ForwardRealFFT *forward_real_fft) { + int half_window_length = matlab_round(1.5 * fs / f0); + + // FFT + for (int i = half_window_length * 2 + 1; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); + + // Calculation of the power spectrum. + double *power_spectrum = forward_real_fft->waveform; + for (int i = 0; i <= fft_size / 2; ++i) + power_spectrum[i] = + forward_real_fft->spectrum[i][0] * forward_real_fft->spectrum[i][0] + + forward_real_fft->spectrum[i][1] * forward_real_fft->spectrum[i][1]; + + // DC correction + DCCorrection(power_spectrum, f0, fs, fft_size, power_spectrum); +} + +//----------------------------------------------------------------------------- +// SetParametersForGetWindowedWaveform() +//----------------------------------------------------------------------------- +static void SetParametersForGetWindowedWaveform(int half_window_length, + int x_length, double currnet_position, int fs, double current_f0, + int *base_index, int *safe_index, double *window) { + for (int i = -half_window_length; i <= half_window_length; ++i) + base_index[i + half_window_length] = i; + int origin = matlab_round(currnet_position * fs + 0.001); + for (int i = 0; i <= half_window_length * 2; ++i) + safe_index[i] = + MyMinInt(x_length - 1, MyMaxInt(0, origin + base_index[i])); + + // Designing of the window function + double average = 0.0; + double position; + for (int i = 0; i <= half_window_length * 2; ++i) { + position = base_index[i] / 1.5 / fs; + window[i] = 0.5 * cos(world::kPi * position * current_f0) + 0.5; + average += window[i] * window[i]; + } + average = sqrt(average); + for (int i = 0; i <= half_window_length * 2; ++i) window[i] /= average; +} + +//----------------------------------------------------------------------------- +// GetWindowedWaveform() windows the waveform by F0-adaptive window +//----------------------------------------------------------------------------- +static void GetWindowedWaveform(const double *x, int x_length, int fs, + double current_f0, double currnet_position, + const ForwardRealFFT *forward_real_fft) { + int half_window_length = matlab_round(1.5 * fs / current_f0); + + int *base_index = new int[half_window_length * 2 + 1]; + int *safe_index = new int[half_window_length * 2 + 1]; + double *window = new double[half_window_length * 2 + 1]; + + SetParametersForGetWindowedWaveform(half_window_length, x_length, + currnet_position, fs, current_f0, base_index, safe_index, window); + + // F0-adaptive windowing + double *waveform = forward_real_fft->waveform; + for (int i = 0; i <= half_window_length * 2; ++i) + waveform[i] = x[safe_index[i]] * window[i] + + randn() * world::kMySafeGuardMinimum; + double tmp_weight1 = 0; + double tmp_weight2 = 0; + for (int i = 0; i <= half_window_length * 2; ++i) { + tmp_weight1 += waveform[i]; + tmp_weight2 += window[i]; + } + double weighting_coefficient = tmp_weight1 / tmp_weight2; + for (int i = 0; i <= half_window_length * 2; ++i) + waveform[i] -= window[i] * weighting_coefficient; + + delete[] base_index; + delete[] safe_index; + delete[] window; +} + +//----------------------------------------------------------------------------- +// AddInfinitesimalNoise() +//----------------------------------------------------------------------------- +static void AddInfinitesimalNoise(const double *input_spectrum, int fft_size, + double *output_spectrum) { + for (int i = 0; i <= fft_size / 2; ++i) + output_spectrum[i] = input_spectrum[i] + fabs(randn()) * world::kEps; +} + +//----------------------------------------------------------------------------- +// CheapTrickGeneralBody() calculates a spectral envelope at a temporal +// position. This function is only used in CheapTrick(). +// Caution: +// forward_fft is allocated in advance to speed up the processing. +//----------------------------------------------------------------------------- +static void CheapTrickGeneralBody(const double *x, int x_length, int fs, + double current_f0, int fft_size, double current_position, double q1, + const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, double *spectral_envelope) { + // F0-adaptive windowing + GetWindowedWaveform(x, x_length, fs, current_f0, current_position, + forward_real_fft); + + // Calculate power spectrum with DC correction + // Note: The calculated power spectrum is stored in an array for waveform. + // In this imprementation, power spectrum is transformed by FFT (NOT IFFT). + // However, the same result is obtained. + // This is tricky but important for simple implementation. + GetPowerSpectrum(fs, current_f0, fft_size, forward_real_fft); + + // Smoothing of the power (linear axis) + // forward_real_fft.waveform is the power spectrum. + LinearSmoothing(forward_real_fft->waveform, current_f0 * 2.0 / 3.0, + fs, fft_size, forward_real_fft->waveform); + + // Add infinitesimal noise + // This is a safeguard to avoid including zero in the spectrum. + AddInfinitesimalNoise(forward_real_fft->waveform, fft_size, + forward_real_fft->waveform); + + // Smoothing (log axis) and spectral recovery on the cepstrum domain. + SmoothingWithRecovery(current_f0, fs, fft_size, q1, forward_real_fft, + inverse_real_fft, spectral_envelope); +} + +} // namespace + +int GetFFTSizeForCheapTrick(int fs, const CheapTrickOption *option) { + return static_cast(pow(2.0, 1.0 + + static_cast(log(3.0 * fs / option->f0_floor + 1) / world::kLog2))); +} + +double GetF0FloorForCheapTrick(int fs, int fft_size) { + return 3.0 * fs / (fft_size - 3.0); +} + +void CheapTrick(const double *x, int x_length, int fs, + const double *temporal_positions, const double *f0, int f0_length, + const CheapTrickOption *option, double **spectrogram) { + int fft_size = option->fft_size; + + randn_reseed(); + + double f0_floor = GetF0FloorForCheapTrick(fs, fft_size); + double *spectral_envelope = new double[fft_size]; + + ForwardRealFFT forward_real_fft = {0}; + InitializeForwardRealFFT(fft_size, &forward_real_fft); + InverseRealFFT inverse_real_fft = {0}; + InitializeInverseRealFFT(fft_size, &inverse_real_fft); + + double current_f0; + for (int i = 0; i < f0_length; ++i) { + current_f0 = f0[i] <= f0_floor ? world::kDefaultF0 : f0[i]; + CheapTrickGeneralBody(x, x_length, fs, current_f0, fft_size, + temporal_positions[i], option->q1, &forward_real_fft, + &inverse_real_fft, spectral_envelope); + for (int j = 0; j <= fft_size / 2; ++j) + spectrogram[i][j] = spectral_envelope[j]; + } + + DestroyForwardRealFFT(&forward_real_fft); + DestroyInverseRealFFT(&inverse_real_fft); + delete[] spectral_envelope; +} + +void InitializeCheapTrickOption(int fs, CheapTrickOption *option) { + // q1 is the parameter used for the spectral recovery. + // Since The parameter is optimized, you don't need to change the parameter. + option->q1 = -0.15; + // f0_floor and fs are used to determine fft_size; + // We strongly recommend not to change this value unless you have enough + // knowledge of the signal processing in CheapTrick. + option->f0_floor = world::kFloorF0; + option->fft_size = GetFFTSizeForCheapTrick(fs, option); +} diff --git a/libsvc/Modules/Lib/World/src/codec.cpp b/libsvc/Modules/Lib/World/src/codec.cpp new file mode 100644 index 0000000..7d56f5d --- /dev/null +++ b/libsvc/Modules/Lib/World/src/codec.cpp @@ -0,0 +1,324 @@ +//----------------------------------------------------------------------------- +// Copyright 2017 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Coder/decoder functions for the spectral envelope and aperiodicity. +//----------------------------------------------------------------------------- +#include "world/codec.h" + +#include + +#include "world/constantnumbers.h" +#include "world/fft.h" +#include "world/matlabfunctions.h" + +namespace { +//----------------------------------------------------------------------------- +// Aperiodicity is initialized by the value 1.0 - world::kMySafeGuardMinimum. +// This value means the frame/frequency index is aperiodic. +//----------------------------------------------------------------------------- +static void InitializeAperiodicity(int f0_length, int fft_size, + double **aperiodicity) { + for (int i = 0; i < f0_length; ++i) + for (int j = 0; j < fft_size / 2 + 1; ++j) + aperiodicity[i][j] = 1.0 - world::kMySafeGuardMinimum; +} + +//----------------------------------------------------------------------------- +// This function identifies whether this frame is voiced or unvoiced. +//----------------------------------------------------------------------------- +static int CheckVUV(const double *coarse_aperiodicity, + int number_of_aperiodicities, double *tmp_aperiodicity) { + double tmp = 0.0; + for (int i = 0; i < number_of_aperiodicities; ++i) { + tmp += coarse_aperiodicity[i]; + tmp_aperiodicity[i + 1] = coarse_aperiodicity[i]; + } + tmp /= number_of_aperiodicities; + + return tmp > -0.5 ? 1 : 0; // -0.5 is not optimized, but okay. +} + +//----------------------------------------------------------------------------- +// Aperiodicity is obtained from the coded aperiodicity. +//----------------------------------------------------------------------------- +static void GetAperiodicity(const double *coarse_frequency_axis, + const double *coarse_aperiodicity, int number_of_aperiodicities, + const double *frequency_axis, int fft_size, double *aperiodicity) { + interp1(coarse_frequency_axis, coarse_aperiodicity, + number_of_aperiodicities + 2, frequency_axis, fft_size / 2 + 1, + aperiodicity); + for (int i = 0; i <= fft_size / 2; ++i) + aperiodicity[i] = pow(10.0, aperiodicity[i] / 20.0); +} + +//----------------------------------------------------------------------------- +// Frequency is converted into its mel representation. +//----------------------------------------------------------------------------- +static inline double FrequencyToMel(double frequency) { + return world::kM0 * log(frequency / world::kF0 + 1.0); +} + +//----------------------------------------------------------------------------- +// Mel is converted into frequency. +//----------------------------------------------------------------------------- +static inline double MelToFrequency(double mel) { + return world::kF0 * (exp(mel / world::kM0) - 1.0); +} + +//----------------------------------------------------------------------------- +// DCT for spectral envelope coding +//----------------------------------------------------------------------------- +static void DCTForCodec(const double *mel_spectrum, int max_dimension, + const fft_complex *weight, const ForwardRealFFT *forward_real_fft, + int number_of_dimensions, double *mel_cepstrum) { + int bias = max_dimension / 2; + for (int i = 0; i < max_dimension / 2; ++i) { + forward_real_fft->waveform[i] = mel_spectrum[i * 2]; + forward_real_fft->waveform[i + bias] = + mel_spectrum[max_dimension - (i * 2) - 1]; + } + fft_execute(forward_real_fft->forward_fft); + + double normalization = sqrt(forward_real_fft->fft_size); + for (int i = 0; i < number_of_dimensions; ++i) + mel_cepstrum[i] = (forward_real_fft->spectrum[i][0] * weight[i][0] - + forward_real_fft->spectrum[i][1] * weight[i][1]) / normalization; +} + +//----------------------------------------------------------------------------- +// IDCT for spectral envelope decoding +//----------------------------------------------------------------------------- +static void IDCTForCodec(const double *mel_cepstrum, int max_dimension, + const fft_complex *weight, const InverseComplexFFT *inverse_complex_fft, + int number_of_dimensions, double *mel_spectrum) { + double normalization = sqrt(inverse_complex_fft->fft_size); + for (int i = 0; i < number_of_dimensions; ++i) { + inverse_complex_fft->input[i][0] = + mel_cepstrum[i] * weight[i][0] * normalization; + inverse_complex_fft->input[i][1] = + -mel_cepstrum[i] * weight[i][1] * normalization; + } + for (int i = number_of_dimensions; i < max_dimension; ++i) { + inverse_complex_fft->input[i][0] = 0.0; + inverse_complex_fft->input[i][1] = 0.0; + } + + fft_execute(inverse_complex_fft->inverse_fft); + + for (int i = 0; i < max_dimension / 2; ++i) { + mel_spectrum[i * 2] = inverse_complex_fft->output[i][0]; + mel_spectrum[(i * 2) + 1] = + inverse_complex_fft->output[max_dimension - i - 1][0]; + } +} + +//----------------------------------------------------------------------------- +// Spectral envelope in a frame is coded +//----------------------------------------------------------------------------- +static void CodeOneFrame(const double *log_spectral_envelope, + const double *frequency_axis, int fft_size, const double *mel_axis, + const fft_complex *weight, int max_dimension, int number_of_dimensions, + const ForwardRealFFT *forward_real_fft, double *coded_spectral_envelope) { + double *mel_spectrum = new double[max_dimension]; + interp1(frequency_axis, log_spectral_envelope, fft_size / 2 + 1, + mel_axis, max_dimension, mel_spectrum); + + // DCT + DCTForCodec(mel_spectrum, max_dimension, weight, forward_real_fft, + number_of_dimensions, coded_spectral_envelope); + + delete[] mel_spectrum; +} + +//----------------------------------------------------------------------------- +// Coded spectral envelope in a frame is decoded +//----------------------------------------------------------------------------- +static void DecodeOneFrame(const double *coded_spectral_envelope, + const double *frequency_axis, int fft_size, const double *mel_axis, + const fft_complex *weight, int max_dimension, int number_of_dimensions, + const InverseComplexFFT *inverse_complex_fft, double *spectral_envelope) { + double *mel_spectrum = new double[max_dimension + 2]; + + // IDCT + IDCTForCodec(coded_spectral_envelope, max_dimension, weight, + inverse_complex_fft, number_of_dimensions, &mel_spectrum[1]); + mel_spectrum[0] = mel_spectrum[1]; + mel_spectrum[max_dimension + 1] = mel_spectrum[max_dimension]; + + interp1(mel_axis, mel_spectrum, max_dimension + 2, frequency_axis, + fft_size / 2 + 1, spectral_envelope); + + for (int i = 0; i < fft_size / 2 + 1; ++i) + spectral_envelope[i] = exp(spectral_envelope[i] / max_dimension); + + delete[] mel_spectrum; +} + +//----------------------------------------------------------------------------- +// GetParameters() generates the required parameters. +//----------------------------------------------------------------------------- +static void GetParametersForCoding(double floor_frequency, + double ceil_frequency, int fs, int fft_size, double *mel_axis, + double *frequency_axis, fft_complex *weight) { + int max_dimension = fft_size / 2; + double floor_mel = FrequencyToMel(floor_frequency); + double ceil_mel = FrequencyToMel(ceil_frequency); + + // Generate the mel axis and the weighting vector for DCT. + for (int i = 0; i < max_dimension; ++i) { + mel_axis[i] = (ceil_mel - floor_mel) * i / max_dimension + floor_mel; + weight[i][0] = 2.0 * cos(i * world::kPi / fft_size) / sqrt(fft_size); + weight[i][1] = 2.0 * sin(i * world::kPi / fft_size) / sqrt(fft_size); + } + weight[0][0] /= sqrt(2.0); + + // Generate the frequency axis on mel scale + for (int i = 0; i <= max_dimension; ++i) + frequency_axis[i] = FrequencyToMel(static_cast(i) * fs / fft_size); +} + +//----------------------------------------------------------------------------- +// GetParameters() generates the required parameters. +//----------------------------------------------------------------------------- +static void GetParametersForDecoding(double floor_frequency, + double ceil_frequency, int fs, int fft_size, int number_of_dimensions, + double *mel_axis, double *frequency_axis, fft_complex *weight) { + int max_dimension = fft_size / 2; + double floor_mel = FrequencyToMel(floor_frequency); + double ceil_mel = FrequencyToMel(ceil_frequency); + + // Generate the weighting vector for IDCT. + for (int i = 0; i < number_of_dimensions; ++i) { + weight[i][0] = cos(i * world::kPi / fft_size) * sqrt(fft_size); + weight[i][1] = sin(i * world::kPi / fft_size) * sqrt(fft_size); + } + weight[0][0] /= sqrt(2.0); + // Generate the mel axis for IDCT. + for (int i = 0; i < max_dimension; ++i) + mel_axis[i + 1] = + MelToFrequency((ceil_mel - floor_mel) * i / max_dimension + floor_mel); + mel_axis[0] = 0; + mel_axis[max_dimension + 1] = fs / 2.0; + + // Generate the frequency axis + for (int i = 0; i < fft_size / 2 + 1; ++i) + frequency_axis[i] = static_cast(i) * fs / fft_size; +} + +} // namespace + +int GetNumberOfAperiodicities(int fs) { + return static_cast(MyMinDouble(world::kUpperLimit, fs / 2.0 - + world::kFrequencyInterval) / world::kFrequencyInterval); +} + +void CodeAperiodicity(const double * const *aperiodicity, int f0_length, + int fs, int fft_size, double **coded_aperiodicity) { + int number_of_aperiodicities = GetNumberOfAperiodicities(fs); + double *coarse_frequency_axis = new double[number_of_aperiodicities]; + for (int i = 0; i < number_of_aperiodicities; ++i) + coarse_frequency_axis[i] = world::kFrequencyInterval * (i + 1.0); + + double *log_aperiodicity = new double[fft_size / 2 + 1]; + + for (int i = 0; i < f0_length; ++i) { + for (int j = 0; j < fft_size / 2 + 1; ++j) + log_aperiodicity[j] = 20 * log10(aperiodicity[i][j]); + interp1Q(0, static_cast(fs) / fft_size, log_aperiodicity, + fft_size / 2 + 1, coarse_frequency_axis, number_of_aperiodicities, + coded_aperiodicity[i]); + } + + delete[] coarse_frequency_axis; + delete[] log_aperiodicity; +} + +void DecodeAperiodicity(const double * const *coded_aperiodicity, + int f0_length, int fs, int fft_size, double **aperiodicity) { + InitializeAperiodicity(f0_length, fft_size, aperiodicity); + int number_of_aperiodicities = GetNumberOfAperiodicities(fs); + double *frequency_axis = new double[fft_size / 2 + 1]; + for (int i = 0; i <= fft_size / 2; ++i) + frequency_axis[i] = static_cast(fs) / fft_size * i; + + double *coarse_frequency_axis = new double[number_of_aperiodicities + 2]; + for (int i = 0; i <= number_of_aperiodicities; ++i) + coarse_frequency_axis[i] = i * world::kFrequencyInterval; + coarse_frequency_axis[number_of_aperiodicities + 1] = fs / 2.0; + + double *coarse_aperiodicity = new double[number_of_aperiodicities + 2]; + coarse_aperiodicity[0] = -60.0; + coarse_aperiodicity[number_of_aperiodicities + 1] = + -world::kMySafeGuardMinimum; + + for (int i = 0; i < f0_length; ++i) { + if (CheckVUV(coded_aperiodicity[i], number_of_aperiodicities, + coarse_aperiodicity) == 1) continue; + GetAperiodicity(coarse_frequency_axis, coarse_aperiodicity, + number_of_aperiodicities, frequency_axis, fft_size, aperiodicity[i]); + } + + delete[] coarse_aperiodicity; + delete[] coarse_frequency_axis; + delete[] frequency_axis; +} + +void CodeSpectralEnvelope(const double * const *spectrogram, int f0_length, + int fs, int fft_size, int number_of_dimensions, + double **coded_spectral_envelope) { + double *mel_axis = new double[fft_size / 2]; + double *frequency_axis = new double[fft_size / 2 + 1]; + double *tmp_spectrum = new double[fft_size / 2 + 1]; + fft_complex *weight = new fft_complex[fft_size / 2]; + + // Generation of the required parameters + GetParametersForCoding(world::kFloorFrequency, + MyMinDouble(fs / 2.0, world::kCeilFrequency), fs, fft_size, + mel_axis, frequency_axis, weight); + + ForwardRealFFT forward_real_fft = { 0 }; + InitializeForwardRealFFT(fft_size / 2, &forward_real_fft); + + for (int i = 0; i < f0_length; ++i) { + for (int j = 0; j < fft_size / 2 + 1; ++j) + tmp_spectrum[j] = log(spectrogram[i][j]); + CodeOneFrame(tmp_spectrum, frequency_axis, fft_size, mel_axis, weight, + fft_size / 2, number_of_dimensions, &forward_real_fft, + coded_spectral_envelope[i]); + } + + DestroyForwardRealFFT(&forward_real_fft); + delete[] weight; + delete[] tmp_spectrum; + delete[] frequency_axis; + delete[] mel_axis; +} + +void DecodeSpectralEnvelope(const double * const *coded_spectral_envelope, + int f0_length, int fs, int fft_size, int number_of_dimensions, + double **spectrogram) { + double *mel_axis = new double[fft_size / 2 + 2]; + double *frequency_axis = new double[fft_size / 2 + 1]; + fft_complex *weight = new fft_complex[fft_size / 2]; + + // Generation of the required parameters + GetParametersForDecoding(world::kFloorFrequency, + MyMinDouble(fs / 2.0, world::kCeilFrequency), + fs, fft_size, number_of_dimensions, mel_axis, frequency_axis, weight); + + InverseComplexFFT inverse_complex_fft = { 0 }; + InitializeInverseComplexFFT(fft_size / 2, &inverse_complex_fft); + + for (int i = 0; i < f0_length; ++i) { + DecodeOneFrame(coded_spectral_envelope[i], frequency_axis, fft_size, + mel_axis, weight, fft_size / 2, number_of_dimensions, + &inverse_complex_fft, spectrogram[i]); + } + + DestroyInverseComplexFFT(&inverse_complex_fft); + delete[] weight; + delete[] frequency_axis; + delete[] mel_axis; +} diff --git a/libsvc/Modules/Lib/World/src/common.cpp b/libsvc/Modules/Lib/World/src/common.cpp new file mode 100644 index 0000000..c44ecae --- /dev/null +++ b/libsvc/Modules/Lib/World/src/common.cpp @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// common.cpp includes functions used in at least two files. +// (1) Common functions +// (2) FFT, IFFT and minimum phase analysis. +// +// In FFT analysis and minimum phase analysis, +// Functions "Initialize*()" allocate the mamory. +// Functions "Destroy*()" free the accolated memory. +// FFT size is used for initialization, and structs are used to keep the memory. +// Functions "GetMinimumPhaseSpectrum()" calculate minimum phase spectrum. +// Forward and inverse FFT do not have the function "Get*()", +// because forward FFT and inverse FFT can run in one step. +// +//----------------------------------------------------------------------------- +#include "world/common.h" + +#include + +#include "world/constantnumbers.h" +#include "world/matlabfunctions.h" + +namespace { +static void SetParametersForLinearSmoothing(int boundary, int fft_size, int fs, + double width, const double *power_spectrum, double *mirroring_spectrum, + double *mirroring_segment, double *frequency_axis) { + for (int i = 0; i < boundary; ++i) + mirroring_spectrum[i] = power_spectrum[boundary - i]; + for (int i = boundary; i < fft_size / 2 + boundary; ++i) + mirroring_spectrum[i] = power_spectrum[i - boundary]; + for (int i = fft_size / 2 + boundary; i <= fft_size / 2 + boundary * 2; ++i) + mirroring_spectrum[i] = + power_spectrum[fft_size / 2 - (i - (fft_size / 2 + boundary))]; + + mirroring_segment[0] = mirroring_spectrum[0] * fs / fft_size; + for (int i = 1; i < fft_size / 2 + boundary * 2 + 1; ++i) + mirroring_segment[i] = mirroring_spectrum[i] * fs / fft_size + + mirroring_segment[i - 1]; + + for (int i = 0; i <= fft_size / 2; ++i) + frequency_axis[i] = static_cast(i) / fft_size * + fs - width / 2.0; +} + +} // namespace + +//----------------------------------------------------------------------------- +int GetSuitableFFTSize(int sample) { + return static_cast(pow(2.0, + static_cast(log(static_cast(sample)) / world::kLog2) + 1.0)); +} + +void DCCorrection(const double *input, double f0, int fs, int fft_size, + double *output) { + int upper_limit = 2 + static_cast(f0 * fft_size / fs); + double *low_frequency_replica = new double[upper_limit]; + double *low_frequency_axis = new double[upper_limit]; + + for (int i = 0; i < upper_limit; ++i) + low_frequency_axis[i] = static_cast(i) * fs / fft_size; + + int upper_limit_replica = upper_limit - 1; + interp1Q(f0 - low_frequency_axis[0], + -static_cast(fs) / fft_size, input, upper_limit + 1, + low_frequency_axis, upper_limit_replica, low_frequency_replica); + + for (int i = 0; i < upper_limit_replica; ++i) + output[i] = input[i] + low_frequency_replica[i]; + + delete[] low_frequency_replica; + delete[] low_frequency_axis; +} + +void LinearSmoothing(const double *input, double width, int fs, int fft_size, + double *output) { + int boundary = static_cast(width * fft_size / fs) + 1; + + // These parameters are set by the other function. + double *mirroring_spectrum = new double[fft_size / 2 + boundary * 2 + 1]; + double *mirroring_segment = new double[fft_size / 2 + boundary * 2 + 1]; + double *frequency_axis = new double[fft_size / 2 + 1]; + SetParametersForLinearSmoothing(boundary, fft_size, fs, width, + input, mirroring_spectrum, mirroring_segment, frequency_axis); + + double *low_levels = new double[fft_size / 2 + 1]; + double *high_levels = new double[fft_size / 2 + 1]; + double origin_of_mirroring_axis = -(boundary - 0.5) * fs / fft_size; + double discrete_frequency_interval = static_cast(fs) / fft_size; + + interp1Q(origin_of_mirroring_axis, discrete_frequency_interval, + mirroring_segment, fft_size / 2 + boundary * 2 + 1, frequency_axis, + fft_size / 2 + 1, low_levels); + + for (int i = 0; i <= fft_size / 2; ++i) frequency_axis[i] += width; + + interp1Q(origin_of_mirroring_axis, discrete_frequency_interval, + mirroring_segment, fft_size / 2 + boundary * 2 + 1, frequency_axis, + fft_size / 2 + 1, high_levels); + + for (int i = 0; i <= fft_size / 2; ++i) + output[i] = (high_levels[i] - low_levels[i]) / width; + + delete[] mirroring_spectrum; + delete[] mirroring_segment; + delete[] frequency_axis; + delete[] low_levels; + delete[] high_levels; +} + +void NuttallWindow(int y_length, double *y) { + double tmp; + for (int i = 0; i < y_length; ++i) { + tmp = i / (y_length - 1.0); + y[i] = 0.355768 - 0.487396 * cos(2.0 * world::kPi * tmp) + + 0.144232 * cos(4.0 * world::kPi * tmp) - + 0.012604 * cos(6.0 * world::kPi * tmp); + } +} + +//----------------------------------------------------------------------------- +// FFT, IFFT and minimum phase analysis +void InitializeForwardRealFFT(int fft_size, ForwardRealFFT *forward_real_fft) { + forward_real_fft->fft_size = fft_size; + forward_real_fft->waveform = new double[fft_size]; + forward_real_fft->spectrum = new fft_complex[fft_size]; + forward_real_fft->forward_fft = fft_plan_dft_r2c_1d(fft_size, + forward_real_fft->waveform, forward_real_fft->spectrum, FFT_ESTIMATE); +} + +void DestroyForwardRealFFT(ForwardRealFFT *forward_real_fft) { + fft_destroy_plan(forward_real_fft->forward_fft); + delete[] forward_real_fft->spectrum; + delete[] forward_real_fft->waveform; +} + +void InitializeInverseRealFFT(int fft_size, InverseRealFFT *inverse_real_fft) { + inverse_real_fft->fft_size = fft_size; + inverse_real_fft->waveform = new double[fft_size]; + inverse_real_fft->spectrum = new fft_complex[fft_size]; + inverse_real_fft->inverse_fft = fft_plan_dft_c2r_1d(fft_size, + inverse_real_fft->spectrum, inverse_real_fft->waveform, FFT_ESTIMATE); +} + +void DestroyInverseRealFFT(InverseRealFFT *inverse_real_fft) { + fft_destroy_plan(inverse_real_fft->inverse_fft); + delete[] inverse_real_fft->spectrum; + delete[] inverse_real_fft->waveform; +} + +void InitializeInverseComplexFFT(int fft_size, + InverseComplexFFT *inverse_complex_fft) { + inverse_complex_fft->fft_size = fft_size; + inverse_complex_fft->input = new fft_complex[fft_size]; + inverse_complex_fft->output = new fft_complex[fft_size]; + inverse_complex_fft->inverse_fft = fft_plan_dft_1d(fft_size, + inverse_complex_fft->input, inverse_complex_fft->output, + FFT_BACKWARD, FFT_ESTIMATE); +} + +void DestroyInverseComplexFFT(InverseComplexFFT *inverse_complex_fft) { + fft_destroy_plan(inverse_complex_fft->inverse_fft); + delete[] inverse_complex_fft->input; + delete[] inverse_complex_fft->output; +} + +void InitializeMinimumPhaseAnalysis(int fft_size, + MinimumPhaseAnalysis *minimum_phase) { + minimum_phase->fft_size = fft_size; + minimum_phase->log_spectrum = new double[fft_size]; + minimum_phase->minimum_phase_spectrum = new fft_complex[fft_size]; + minimum_phase->cepstrum = new fft_complex[fft_size]; + minimum_phase->inverse_fft = fft_plan_dft_r2c_1d(fft_size, + minimum_phase->log_spectrum, minimum_phase->cepstrum, FFT_ESTIMATE); + minimum_phase->forward_fft = fft_plan_dft_1d(fft_size, + minimum_phase->cepstrum, minimum_phase->minimum_phase_spectrum, + FFT_FORWARD, FFT_ESTIMATE); +} + +void GetMinimumPhaseSpectrum(const MinimumPhaseAnalysis *minimum_phase) { + // Mirroring + for (int i = minimum_phase->fft_size / 2 + 1; + i < minimum_phase->fft_size; ++i) + minimum_phase->log_spectrum[i] = + minimum_phase->log_spectrum[minimum_phase->fft_size - i]; + + // This fft_plan carries out "forward" FFT. + // To carriy out the Inverse FFT, the sign of imaginary part + // is inverted after FFT. + fft_execute(minimum_phase->inverse_fft); + minimum_phase->cepstrum[0][1] *= -1.0; + for (int i = 1; i < minimum_phase->fft_size / 2; ++i) { + minimum_phase->cepstrum[i][0] *= 2.0; + minimum_phase->cepstrum[i][1] *= -2.0; + } + minimum_phase->cepstrum[minimum_phase->fft_size / 2][1] *= -1.0; + for (int i = minimum_phase->fft_size / 2 + 1; + i < minimum_phase->fft_size; ++i) { + minimum_phase->cepstrum[i][0] = 0.0; + minimum_phase->cepstrum[i][1] = 0.0; + } + + fft_execute(minimum_phase->forward_fft); + + // Since x is complex number, calculation of exp(x) is as following. + // Note: This FFT library does not keep the aliasing. + double tmp; + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) { + tmp = exp(minimum_phase->minimum_phase_spectrum[i][0] / + minimum_phase->fft_size); + minimum_phase->minimum_phase_spectrum[i][0] = tmp * + cos(minimum_phase->minimum_phase_spectrum[i][1] / + minimum_phase->fft_size); + minimum_phase->minimum_phase_spectrum[i][1] = tmp * + sin(minimum_phase->minimum_phase_spectrum[i][1] / + minimum_phase->fft_size); + } +} + +void DestroyMinimumPhaseAnalysis(MinimumPhaseAnalysis *minimum_phase) { + fft_destroy_plan(minimum_phase->forward_fft); + fft_destroy_plan(minimum_phase->inverse_fft); + delete[] minimum_phase->cepstrum; + delete[] minimum_phase->log_spectrum; + delete[] minimum_phase->minimum_phase_spectrum; +} diff --git a/libsvc/Modules/Lib/World/src/d4c.cpp b/libsvc/Modules/Lib/World/src/d4c.cpp new file mode 100644 index 0000000..4e81042 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/d4c.cpp @@ -0,0 +1,401 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Band-aperiodicity estimation on the basis of the idea of D4C. +//----------------------------------------------------------------------------- +#include "world/d4c.h" + +#include +#include // for std::sort() + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/matlabfunctions.h" + +namespace { +//----------------------------------------------------------------------------- +// SetParametersForGetWindowedWaveform() +//----------------------------------------------------------------------------- +static void SetParametersForGetWindowedWaveform(int half_window_length, + int x_length, double current_position, int fs, double current_f0, + int window_type, double window_length_ratio, int *base_index, + int *safe_index, double *window) { + for (int i = -half_window_length; i <= half_window_length; ++i) + base_index[i + half_window_length] = i; + int origin = matlab_round(current_position * fs + 0.001); + for (int i = 0; i <= half_window_length * 2; ++i) + safe_index[i] = + MyMinInt(x_length - 1, MyMaxInt(0, origin + base_index[i])); + + // Designing of the window function + double position; + if (window_type == world::kHanning) { // Hanning window + for (int i = 0; i <= half_window_length * 2; ++i) { + position = (2.0 * base_index[i] / window_length_ratio) / fs; + window[i] = 0.5 * cos(world::kPi * position * current_f0) + 0.5; + } + } else { // Blackman window + for (int i = 0; i <= half_window_length * 2; ++i) { + position = (2.0 * base_index[i] / window_length_ratio) / fs; + window[i] = 0.42 + 0.5 * cos(world::kPi * position * current_f0) + + 0.08 * cos(world::kPi * position * current_f0 * 2); + } + } +} + +//----------------------------------------------------------------------------- +// GetWindowedWaveform() windows the waveform by F0-adaptive window +// In the variable window_type, 1: hanning, 2: blackman +//----------------------------------------------------------------------------- +static void GetWindowedWaveform(const double *x, int x_length, int fs, + double current_f0, double current_position, int window_type, + double window_length_ratio, double *waveform) { + int half_window_length = + matlab_round(window_length_ratio * fs / current_f0 / 2.0); + + int *base_index = new int[half_window_length * 2 + 1]; + int *safe_index = new int[half_window_length * 2 + 1]; + double *window = new double[half_window_length * 2 + 1]; + + SetParametersForGetWindowedWaveform(half_window_length, x_length, + current_position, fs, current_f0, window_type, window_length_ratio, + base_index, safe_index, window); + + // F0-adaptive windowing + for (int i = 0; i <= half_window_length * 2; ++i) + waveform[i] = + x[safe_index[i]] * window[i] + randn() * world::kMySafeGuardMinimum; + + double tmp_weight1 = 0; + double tmp_weight2 = 0; + for (int i = 0; i <= half_window_length * 2; ++i) { + tmp_weight1 += waveform[i]; + tmp_weight2 += window[i]; + } + double weighting_coefficient = tmp_weight1 / tmp_weight2; + for (int i = 0; i <= half_window_length * 2; ++i) + waveform[i] -= window[i] * weighting_coefficient; + + delete[] base_index; + delete[] safe_index; + delete[] window; +} + +//----------------------------------------------------------------------------- +// GetCentroid() calculates the energy centroid (see the book, time-frequency +// analysis written by L. Cohen). +//----------------------------------------------------------------------------- +static void GetCentroid(const double *x, int x_length, int fs, + double current_f0, int fft_size, double current_position, + const ForwardRealFFT *forward_real_fft, double *centroid) { + for (int i = 0; i < fft_size; ++i) forward_real_fft->waveform[i] = 0.0; + GetWindowedWaveform(x, x_length, fs, current_f0, + current_position, world::kBlackman, 4.0, forward_real_fft->waveform); + double power = 0.0; + for (int i = 0; i <= matlab_round(2.0 * fs / current_f0) * 2; ++i) + power += forward_real_fft->waveform[i] * forward_real_fft->waveform[i]; + for (int i = 0; i <= matlab_round(2.0 * fs / current_f0) * 2; ++i) + forward_real_fft->waveform[i] /= sqrt(power); + + fft_execute(forward_real_fft->forward_fft); + double *tmp_real = new double[fft_size / 2 + 1]; + double *tmp_imag = new double[fft_size / 2 + 1]; + for (int i = 0; i <= fft_size / 2; ++i) { + tmp_real[i] = forward_real_fft->spectrum[i][0]; + tmp_imag[i] = forward_real_fft->spectrum[i][1]; + } + + for (int i = 0; i < fft_size; ++i) + forward_real_fft->waveform[i] *= i + 1.0; + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) + centroid[i] = forward_real_fft->spectrum[i][0] * tmp_real[i] + + tmp_imag[i] * forward_real_fft->spectrum[i][1]; + + delete[] tmp_real; + delete[] tmp_imag; +} + +//----------------------------------------------------------------------------- +// GetStaticCentroid() calculates the temporally static energy centroid. +// Basic idea was proposed by H. Kawahara. +//----------------------------------------------------------------------------- +static void GetStaticCentroid(const double *x, int x_length, int fs, + double current_f0, int fft_size, double current_position, + const ForwardRealFFT *forward_real_fft, double *static_centroid) { + double *centroid1 = new double[fft_size / 2 + 1]; + double *centroid2 = new double[fft_size / 2 + 1]; + + GetCentroid(x, x_length, fs, current_f0, fft_size, + current_position - 0.25 / current_f0, forward_real_fft, centroid1); + GetCentroid(x, x_length, fs, current_f0, fft_size, + current_position + 0.25 / current_f0, forward_real_fft, centroid2); + + for (int i = 0; i <= fft_size / 2; ++i) + static_centroid[i] = centroid1[i] + centroid2[i]; + + DCCorrection(static_centroid, current_f0, fs, fft_size, static_centroid); + delete[] centroid1; + delete[] centroid2; +} + +//----------------------------------------------------------------------------- +// GetSmoothedPowerSpectrum() calculates the smoothed power spectrum. +// The parameters used for smoothing are optimized in davance. +//----------------------------------------------------------------------------- +static void GetSmoothedPowerSpectrum(const double *x, int x_length, int fs, + double current_f0, int fft_size, double current_position, + const ForwardRealFFT *forward_real_fft, double *smoothed_power_spectrum) { + for (int i = 0; i < fft_size; ++i) forward_real_fft->waveform[i] = 0.0; + GetWindowedWaveform(x, x_length, fs, current_f0, + current_position, world::kHanning, 4.0, forward_real_fft->waveform); + + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) + smoothed_power_spectrum[i] = + forward_real_fft->spectrum[i][0] * forward_real_fft->spectrum[i][0] + + forward_real_fft->spectrum[i][1] * forward_real_fft->spectrum[i][1]; + DCCorrection(smoothed_power_spectrum, current_f0, fs, fft_size, + smoothed_power_spectrum); + LinearSmoothing(smoothed_power_spectrum, current_f0, fs, fft_size, + smoothed_power_spectrum); +} + +//----------------------------------------------------------------------------- +// GetStaticGroupDelay() calculates the temporally static group delay. +// This is the fundamental parameter in D4C. +//----------------------------------------------------------------------------- +static void GetStaticGroupDelay(const double *static_centroid, + const double *smoothed_power_spectrum, int fs, double f0, + int fft_size, double *static_group_delay) { + for (int i = 0; i <= fft_size / 2; ++i) + static_group_delay[i] = static_centroid[i] / smoothed_power_spectrum[i]; + LinearSmoothing(static_group_delay, f0 / 2.0, fs, fft_size, + static_group_delay); + + double *smoothed_group_delay = new double[fft_size / 2 + 1]; + LinearSmoothing(static_group_delay, f0, fs, fft_size, + smoothed_group_delay); + + for (int i = 0; i <= fft_size / 2; ++i) + static_group_delay[i] -= smoothed_group_delay[i]; + + delete[] smoothed_group_delay; +} + +//----------------------------------------------------------------------------- +// GetCoarseAperiodicity() calculates the aperiodicity in multiples of 3 kHz. +// The upper limit is given based on the sampling frequency. +//----------------------------------------------------------------------------- +static void GetCoarseAperiodicity(const double *static_group_delay, int fs, + int fft_size, int number_of_aperiodicities, const double *window, + int window_length, const ForwardRealFFT *forward_real_fft, + double *coarse_aperiodicity) { + int boundary = + matlab_round(fft_size * 8.0 / window_length); + int half_window_length = window_length / 2; + + for (int i = 0; i < fft_size; ++i) forward_real_fft->waveform[i] = 0.0; + + double *power_spectrum = new double[fft_size / 2 + 1]; + int center; + for (int i = 0; i < number_of_aperiodicities; ++i) { + center = + static_cast(world::kFrequencyInterval * (i + 1) * fft_size / fs); + for (int j = 0; j <= half_window_length * 2; ++j) + forward_real_fft->waveform[j] = + static_group_delay[center - half_window_length + j] * window[j]; + fft_execute(forward_real_fft->forward_fft); + for (int j = 0 ; j <= fft_size / 2; ++j) + power_spectrum[j] = + forward_real_fft->spectrum[j][0] * forward_real_fft->spectrum[j][0] + + forward_real_fft->spectrum[j][1] * forward_real_fft->spectrum[j][1]; + std::sort(power_spectrum, power_spectrum + fft_size / 2 + 1); + for (int j = 1 ; j <= fft_size / 2; ++j) + power_spectrum[j] += power_spectrum[j - 1]; + coarse_aperiodicity[i] = + 10 * log10(power_spectrum[fft_size / 2 - boundary - 1] / + power_spectrum[fft_size / 2]); + } + delete[] power_spectrum; +} + +static double D4CLoveTrainSub(const double *x, int fs, int x_length, + double current_f0, double current_position, int f0_length, int fft_size, + int boundary0, int boundary1, int boundary2, + ForwardRealFFT *forward_real_fft) { + double *power_spectrum = new double[fft_size]; + + int window_length = matlab_round(1.5 * fs / current_f0) * 2 + 1; + GetWindowedWaveform(x, x_length, fs, current_f0, current_position, + world::kBlackman, 3.0, forward_real_fft->waveform); + + for (int i = window_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); + + for (int i = 0; i <= boundary0; ++i) power_spectrum[i] = 0.0; + for (int i = boundary0 + 1; i < fft_size / 2 + 1; ++i) + power_spectrum[i] = + forward_real_fft->spectrum[i][0] * forward_real_fft->spectrum[i][0] + + forward_real_fft->spectrum[i][1] * forward_real_fft->spectrum[i][1]; + for (int i = boundary0; i <= boundary2; ++i) + power_spectrum[i] += +power_spectrum[i - 1]; + + double aperiodicity0 = power_spectrum[boundary1] / power_spectrum[boundary2]; + delete[] power_spectrum; + return aperiodicity0; +} + +//----------------------------------------------------------------------------- +// D4CLoveTrain() determines the aperiodicity with VUV detection. +// If a frame was determined as the unvoiced section, aperiodicity is set to +// very high value as the safeguard. +// If it was voiced section, the aperiodicity of 0 Hz is set to -60 dB. +//----------------------------------------------------------------------------- +static void D4CLoveTrain(const double *x, int fs, int x_length, + const double *f0, int f0_length, const double *temporal_positions, + double *aperiodicity0) { + double lowest_f0 = 40.0; + int fft_size = static_cast(pow(2.0, 1.0 + + static_cast(log(3.0 * fs / lowest_f0 + 1) / world::kLog2))); + ForwardRealFFT forward_real_fft = { 0 }; + InitializeForwardRealFFT(fft_size, &forward_real_fft); + + // Cumulative powers at 100, 4000, 7900 Hz are used for VUV identification. + int boundary0 = static_cast(ceil(100.0 * fft_size / fs)); + int boundary1 = static_cast(ceil(4000.0 * fft_size / fs)); + int boundary2 = static_cast(ceil(7900.0 * fft_size / fs)); + for (int i = 0; i < f0_length; ++i) { + if (f0[i] == 0.0) { + aperiodicity0[i] = 0.0; + continue; + } + aperiodicity0[i] = D4CLoveTrainSub(x, fs, x_length, + MyMaxDouble(f0[i], lowest_f0), temporal_positions[i], f0_length, + fft_size, boundary0, boundary1, boundary2, &forward_real_fft); + } + + DestroyForwardRealFFT(&forward_real_fft); +} + +//----------------------------------------------------------------------------- +// D4CGeneralBody() calculates a spectral envelope at a temporal +// position. This function is only used in D4C(). +// Caution: +// forward_fft is allocated in advance to speed up the processing. +//----------------------------------------------------------------------------- +static void D4CGeneralBody(const double *x, int x_length, int fs, + double current_f0, int fft_size, double current_position, + int number_of_aperiodicities, const double *window, int window_length, + const ForwardRealFFT *forward_real_fft, double *coarse_aperiodicity) { + double *static_centroid = new double[fft_size / 2 + 1]; + double *smoothed_power_spectrum = new double[fft_size / 2 + 1]; + double *static_group_delay = new double[fft_size / 2 + 1]; + GetStaticCentroid(x, x_length, fs, current_f0, fft_size, current_position, + forward_real_fft, static_centroid); + GetSmoothedPowerSpectrum(x, x_length, fs, current_f0, fft_size, + current_position, forward_real_fft, smoothed_power_spectrum); + GetStaticGroupDelay(static_centroid, smoothed_power_spectrum, + fs, current_f0, fft_size, static_group_delay); + + GetCoarseAperiodicity(static_group_delay, fs, fft_size, + number_of_aperiodicities, window, window_length, forward_real_fft, + coarse_aperiodicity); + + // Revision of the result based on the F0 + for (int i = 0; i < number_of_aperiodicities; ++i) + coarse_aperiodicity[i] = MyMinDouble(0.0, + coarse_aperiodicity[i] + (current_f0 - 100) / 50.0); + + delete[] static_centroid; + delete[] smoothed_power_spectrum; + delete[] static_group_delay; +} + +static void InitializeAperiodicity(int f0_length, int fft_size, + double **aperiodicity) { + for (int i = 0; i < f0_length; ++i) + for (int j = 0; j < fft_size / 2 + 1; ++j) + aperiodicity[i][j] = 1.0 - world::kMySafeGuardMinimum; +} + +static void GetAperiodicity(const double *coarse_frequency_axis, + const double *coarse_aperiodicity, int number_of_aperiodicities, + const double *frequency_axis, int fft_size, double *aperiodicity) { + interp1(coarse_frequency_axis, coarse_aperiodicity, + number_of_aperiodicities + 2, frequency_axis, fft_size / 2 + 1, + aperiodicity); + for (int i = 0; i <= fft_size / 2; ++i) + aperiodicity[i] = pow(10.0, aperiodicity[i] / 20.0); +} + +} // namespace + +void D4C(const double *x, int x_length, int fs, + const double *temporal_positions, const double *f0, int f0_length, + int fft_size, const D4COption *option, double **aperiodicity) { + randn_reseed(); + + InitializeAperiodicity(f0_length, fft_size, aperiodicity); + + int fft_size_d4c = static_cast(pow(2.0, 1.0 + + static_cast(log(4.0 * fs / world::kFloorF0D4C + 1) / + world::kLog2))); + + ForwardRealFFT forward_real_fft = {0}; + InitializeForwardRealFFT(fft_size_d4c, &forward_real_fft); + + int number_of_aperiodicities = + static_cast(MyMinDouble(world::kUpperLimit, fs / 2.0 - + world::kFrequencyInterval) / world::kFrequencyInterval); + // Since the window function is common in D4CGeneralBody(), + // it is designed here to speed up. + int window_length = + static_cast(world::kFrequencyInterval * fft_size_d4c / fs) * 2 + 1; + double *window = new double[window_length]; + NuttallWindow(window_length, window); + + // D4C Love Train (Aperiodicity of 0 Hz is given by the different algorithm) + double *aperiodicity0 = new double[f0_length]; + D4CLoveTrain(x, fs, x_length, f0, f0_length, temporal_positions, + aperiodicity0); + + double *coarse_aperiodicity = new double[number_of_aperiodicities + 2]; + coarse_aperiodicity[0] = -60.0; + coarse_aperiodicity[number_of_aperiodicities + 1] = + -world::kMySafeGuardMinimum; + double *coarse_frequency_axis = new double[number_of_aperiodicities + 2]; + for (int i = 0; i <= number_of_aperiodicities; ++i) + coarse_frequency_axis[i] = i * world::kFrequencyInterval; + coarse_frequency_axis[number_of_aperiodicities + 1] = fs / 2.0; + + double *frequency_axis = new double[fft_size / 2 + 1]; + for (int i = 0; i <= fft_size / 2; ++i) + frequency_axis[i] = static_cast(i) * fs / fft_size; + + for (int i = 0; i < f0_length; ++i) { + if (f0[i] == 0 || aperiodicity0[i] <= option->threshold) continue; + D4CGeneralBody(x, x_length, fs, MyMaxDouble(world::kFloorF0D4C, f0[i]), + fft_size_d4c, temporal_positions[i], number_of_aperiodicities, window, + window_length, &forward_real_fft, &coarse_aperiodicity[1]); + + // Linear interpolation to convert the coarse aperiodicity into its + // spectral representation. + GetAperiodicity(coarse_frequency_axis, coarse_aperiodicity, + number_of_aperiodicities, frequency_axis, fft_size, aperiodicity[i]); + } + + DestroyForwardRealFFT(&forward_real_fft); + delete[] aperiodicity0; + delete[] coarse_frequency_axis; + delete[] coarse_aperiodicity; + delete[] window; + delete[] frequency_axis; +} + +void InitializeD4COption(D4COption *option) { + option->threshold = world::kThreshold; +} diff --git a/libsvc/Modules/Lib/World/src/dio.cpp b/libsvc/Modules/Lib/World/src/dio.cpp new file mode 100644 index 0000000..ec623d9 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/dio.cpp @@ -0,0 +1,666 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// F0 estimation based on DIO (Distributed Inline-filter Operation). +//----------------------------------------------------------------------------- +#include "world/dio.h" + +#include + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/matlabfunctions.h" + +//----------------------------------------------------------------------------- +// struct for GetFourZeroCrossingIntervals() +// "negative" means "zero-crossing point going from positive to negative" +// "positive" means "zero-crossing point going from negative to positive" +//----------------------------------------------------------------------------- +typedef struct { + double *negative_interval_locations; + double *negative_intervals; + int number_of_negatives; + double *positive_interval_locations; + double *positive_intervals; + int number_of_positives; + double *peak_interval_locations; + double *peak_intervals; + int number_of_peaks; + double *dip_interval_locations; + double *dip_intervals; + int number_of_dips; +} ZeroCrossings; + +namespace { +//----------------------------------------------------------------------------- +// DesignLowCutFilter() calculates the coefficients the filter. +//----------------------------------------------------------------------------- +static void DesignLowCutFilter(int N, int fft_size, double *low_cut_filter) { + for (int i = 1; i <= N; ++i) + low_cut_filter[i - 1] = 0.5 - 0.5 * cos(i * 2.0 * world::kPi / (N + 1)); + for (int i = N; i < fft_size; ++i) low_cut_filter[i] = 0.0; + double sum_of_amplitude = 0.0; + for (int i = 0; i < N; ++i) sum_of_amplitude += low_cut_filter[i]; + for (int i = 0; i < N; ++i) + low_cut_filter[i] = -low_cut_filter[i] / sum_of_amplitude; + for (int i = 0; i < (N - 1) / 2; ++i) + low_cut_filter[fft_size - (N - 1) / 2 + i] = low_cut_filter[i]; + for (int i = 0; i < N; ++i) + low_cut_filter[i] = low_cut_filter[i + (N - 1) / 2]; + low_cut_filter[0] += 1.0; +} + +//----------------------------------------------------------------------------- +// GetSpectrumForEstimation() calculates the spectrum for estimation. +// This function carries out downsampling to speed up the estimation process +// and calculates the spectrum of the downsampled signal. +//----------------------------------------------------------------------------- +static void GetSpectrumForEstimation(const double *x, int x_length, + int y_length, double actual_fs, int fft_size, int decimation_ratio, + fft_complex *y_spectrum) { + double *y = new double[fft_size]; + + // Initialization + for (int i = 0; i < fft_size; ++i) y[i] = 0.0; + + // Downsampling + if (decimation_ratio != 1) + decimate(x, x_length, decimation_ratio, y); + else + for (int i = 0; i < x_length; ++i) y[i] = x[i]; + + // Removal of the DC component (y = y - mean value of y) + double mean_y = 0.0; + for (int i = 0; i < y_length; ++i) mean_y += y[i]; + mean_y /= y_length; + for (int i = 0; i < y_length; ++i) y[i] -= mean_y; + for (int i = y_length; i < fft_size; ++i) y[i] = 0.0; + + fft_plan forwardFFT = + fft_plan_dft_r2c_1d(fft_size, y, y_spectrum, FFT_ESTIMATE); + fft_execute(forwardFFT); + + // Low cut filtering (from 0.1.4). Cut off frequency is 50.0 Hz. + int cutoff_in_sample = matlab_round(actual_fs / world::kCutOff); + DesignLowCutFilter(cutoff_in_sample * 2 + 1, fft_size, y); + + fft_complex *filter_spectrum = new fft_complex[fft_size]; + forwardFFT.c_out = filter_spectrum; + fft_execute(forwardFFT); + + double tmp = 0; + for (int i = 0; i <= fft_size / 2; ++i) { + // Complex number multiplications. + tmp = y_spectrum[i][0] * filter_spectrum[i][0] - + y_spectrum[i][1] * filter_spectrum[i][1]; + y_spectrum[i][1] = y_spectrum[i][0] * filter_spectrum[i][1] + + y_spectrum[i][1] * filter_spectrum[i][0]; + y_spectrum[i][0] = tmp; + } + + fft_destroy_plan(forwardFFT); + delete[] y; + delete[] filter_spectrum; +} + +//----------------------------------------------------------------------------- +// GetBestF0Contour() calculates the best f0 contour based on scores of +// all candidates. The F0 with highest score is selected. +//----------------------------------------------------------------------------- +static void GetBestF0Contour(int f0_length, + const double * const * f0_candidates, const double * const * f0_scores, + int number_of_bands, double *best_f0_contour) { + double tmp; + for (int i = 0; i < f0_length; ++i) { + tmp = f0_scores[0][i]; + best_f0_contour[i] = f0_candidates[0][i]; + for (int j = 1; j < number_of_bands; ++j) { + if (tmp > f0_scores[j][i]) { + tmp = f0_scores[j][i]; + best_f0_contour[i] = f0_candidates[j][i]; + } + } + } +} + +//----------------------------------------------------------------------------- +// FixStep1() is the 1st step of the postprocessing. +// This function eliminates the unnatural change of f0 based on allowed_range. +//----------------------------------------------------------------------------- +static void FixStep1(const double *best_f0_contour, int f0_length, + int voice_range_minimum, double allowed_range, double *f0_step1) { + double *f0_base = new double[f0_length]; + // Initialization + for (int i = 0; i < voice_range_minimum; ++i) f0_base[i] = 0.0; + for (int i = voice_range_minimum; i < f0_length - voice_range_minimum; ++i) + f0_base[i] = best_f0_contour[i]; + for (int i = f0_length - voice_range_minimum; i < f0_length; ++i) + f0_base[i] = 0.0; + + // Processing to prevent the jumping of f0 + for (int i = 0; i < voice_range_minimum; ++i) f0_step1[i] = 0.0; + for (int i = voice_range_minimum; i < f0_length; ++i) + f0_step1[i] = fabs((f0_base[i] - f0_base[i - 1]) / + (world::kMySafeGuardMinimum + f0_base[i])) < + allowed_range ? f0_base[i] : 0.0; + + delete[] f0_base; +} + +//----------------------------------------------------------------------------- +// FixStep2() is the 2nd step of the postprocessing. +// This function eliminates the suspected f0 in the anlaut and auslaut. +//----------------------------------------------------------------------------- +static void FixStep2(const double *f0_step1, int f0_length, + int voice_range_minimum, double *f0_step2) { + for (int i = 0; i < f0_length; ++i) f0_step2[i] = f0_step1[i]; + + int center = (voice_range_minimum - 1) / 2; + for (int i = center; i < f0_length - center; ++i) { + for (int j = -center; j <= center; ++j) { + if (f0_step1[i + j] == 0) { + f0_step2[i] = 0.0; + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// GetNumberOfVoicedSections() counts the number of voiced sections. +//----------------------------------------------------------------------------- +static void GetNumberOfVoicedSections(const double *f0, int f0_length, + int *positive_index, int *negative_index, int *positive_count, + int *negative_count) { + *positive_count = *negative_count = 0; + for (int i = 1; i < f0_length; ++i) + if (f0[i] == 0 && f0[i - 1] != 0) + negative_index[(*negative_count)++] = i - 1; + else + if (f0[i - 1] == 0 && f0[i] != 0) + positive_index[(*positive_count)++] = i; +} + +//----------------------------------------------------------------------------- +// SelectOneF0() corrects the f0[current_index] based on +// f0[current_index + sign]. +//----------------------------------------------------------------------------- +static double SelectBestF0(double current_f0, double past_f0, + const double * const * f0_candidates, int number_of_candidates, + int target_index, double allowed_range) { + double reference_f0 = (current_f0 * 3.0 - past_f0) / 2.0; + + double minimum_error = fabs(reference_f0 - f0_candidates[0][target_index]); + double best_f0 = f0_candidates[0][target_index]; + + double current_error; + for (int i = 1; i < number_of_candidates; ++i) { + current_error = fabs(reference_f0 - f0_candidates[i][target_index]); + if (current_error < minimum_error) { + minimum_error = current_error; + best_f0 = f0_candidates[i][target_index]; + } + } + if (fabs(1.0 - best_f0 / reference_f0) > allowed_range) + return 0.0; + return best_f0; +} + +//----------------------------------------------------------------------------- +// FixStep3() is the 3rd step of the postprocessing. +// This function corrects the f0 candidates from backward to forward. +//----------------------------------------------------------------------------- +static void FixStep3(const double *f0_step2, int f0_length, + const double * const * f0_candidates, int number_of_candidates, + double allowed_range, const int *negative_index, int negative_count, + double *f0_step3) { + for (int i = 0; i < f0_length; i++) f0_step3[i] = f0_step2[i]; + + int limit; + for (int i = 0; i < negative_count; ++i) { + limit = i == negative_count - 1 ? f0_length - 1 : negative_index[i + 1]; + for (int j = negative_index[i]; j < limit; ++j) { + f0_step3[j + 1] = + SelectBestF0(f0_step3[j], f0_step3[j - 1], f0_candidates, + number_of_candidates, j + 1, allowed_range); + if (f0_step3[j + 1] == 0) break; + } + } +} + +//----------------------------------------------------------------------------- +// FixStep4() is the 4th step of the postprocessing. +// This function corrects the f0 candidates from forward to backward. +//----------------------------------------------------------------------------- +static void FixStep4(const double *f0_step3, int f0_length, + const double * const * f0_candidates, int number_of_candidates, + double allowed_range, const int *positive_index, int positive_count, + double *f0_step4) { + for (int i = 0; i < f0_length; ++i) f0_step4[i] = f0_step3[i]; + + int limit; + for (int i = positive_count - 1; i >= 0; --i) { + limit = i == 0 ? 1 : positive_index[i - 1]; + for (int j = positive_index[i]; j > limit; --j) { + f0_step4[j - 1] = + SelectBestF0(f0_step4[j], f0_step4[j + 1], f0_candidates, + number_of_candidates, j - 1, allowed_range); + if (f0_step4[j - 1] == 0) break; + } + } +} + +//----------------------------------------------------------------------------- +// FixF0Contour() calculates the definitive f0 contour based on all f0 +// candidates. There are four steps. +//----------------------------------------------------------------------------- +static void FixF0Contour(double frame_period, int number_of_candidates, + int fs, const double * const * f0_candidates, + const double *best_f0_contour, int f0_length, double f0_floor, + double allowed_range, double *fixed_f0_contour) { + int voice_range_minimum = + static_cast(0.5 + 1000.0 / frame_period / f0_floor) * 2 + 1; + + if (f0_length <= voice_range_minimum) return; + + double *f0_tmp1 = new double[f0_length]; + double *f0_tmp2 = new double[f0_length]; + + FixStep1(best_f0_contour, f0_length, voice_range_minimum, + allowed_range, f0_tmp1); + FixStep2(f0_tmp1, f0_length, voice_range_minimum, f0_tmp2); + + int positive_count, negative_count; + int *positive_index = new int[f0_length]; + int *negative_index = new int[f0_length]; + GetNumberOfVoicedSections(f0_tmp2, f0_length, positive_index, + negative_index, &positive_count, &negative_count); + FixStep3(f0_tmp2, f0_length, f0_candidates, number_of_candidates, + allowed_range, negative_index, negative_count, f0_tmp1); + FixStep4(f0_tmp1, f0_length, f0_candidates, number_of_candidates, + allowed_range, positive_index, positive_count, fixed_f0_contour); + + delete[] f0_tmp1; + delete[] f0_tmp2; + delete[] positive_index; + delete[] negative_index; +} + +//----------------------------------------------------------------------------- +// GetFilteredSignal() calculates the signal that is the convolution of the +// input signal and low-pass filter. +// This function is only used in RawEventByDio() +//----------------------------------------------------------------------------- +static void GetFilteredSignal(int half_average_length, int fft_size, + const fft_complex *y_spectrum, int y_length, double *filtered_signal) { + double *low_pass_filter = new double[fft_size]; + // Nuttall window is used as a low-pass filter. + // Cutoff frequency depends on the window length. + NuttallWindow(half_average_length * 4, low_pass_filter); + for (int i = half_average_length * 4; i < fft_size; ++i) + low_pass_filter[i] = 0.0; + + fft_complex *low_pass_filter_spectrum = new fft_complex[fft_size]; + fft_plan forwardFFT = fft_plan_dft_r2c_1d(fft_size, low_pass_filter, + low_pass_filter_spectrum, FFT_ESTIMATE); + fft_execute(forwardFFT); + + // Convolution + double tmp = y_spectrum[0][0] * low_pass_filter_spectrum[0][0] - + y_spectrum[0][1] * low_pass_filter_spectrum[0][1]; + low_pass_filter_spectrum[0][1] = + y_spectrum[0][0] * low_pass_filter_spectrum[0][1] + + y_spectrum[0][1] * low_pass_filter_spectrum[0][0]; + low_pass_filter_spectrum[0][0] = tmp; + for (int i = 1; i <= fft_size / 2; ++i) { + tmp = y_spectrum[i][0] * low_pass_filter_spectrum[i][0] - + y_spectrum[i][1] * low_pass_filter_spectrum[i][1]; + low_pass_filter_spectrum[i][1] = + y_spectrum[i][0] * low_pass_filter_spectrum[i][1] + + y_spectrum[i][1] * low_pass_filter_spectrum[i][0]; + low_pass_filter_spectrum[i][0] = tmp; + low_pass_filter_spectrum[fft_size - i - 1][0] = + low_pass_filter_spectrum[i][0]; + low_pass_filter_spectrum[fft_size - i - 1][1] = + low_pass_filter_spectrum[i][1]; + } + + fft_plan inverseFFT = fft_plan_dft_c2r_1d(fft_size, + low_pass_filter_spectrum, filtered_signal, FFT_ESTIMATE); + fft_execute(inverseFFT); + + // Compensation of the delay. + int index_bias = half_average_length * 2; + for (int i = 0; i < y_length; ++i) + filtered_signal[i] = filtered_signal[i + index_bias]; + + fft_destroy_plan(inverseFFT); + fft_destroy_plan(forwardFFT); + delete[] low_pass_filter_spectrum; + delete[] low_pass_filter; +} + +//----------------------------------------------------------------------------- +// CheckEvent() returns 1, provided that the input value is over 1. +// This function is for RawEventByDio(). +//----------------------------------------------------------------------------- +static inline int CheckEvent(int x) { + return x > 0 ? 1 : 0; +} + +//----------------------------------------------------------------------------- +// ZeroCrossingEngine() calculates the zero crossing points from positive to +// negative. Thanks to Custom.Maid http://custom-made.seesaa.net/ (2012/8/19) +//----------------------------------------------------------------------------- +static int ZeroCrossingEngine(const double *filtered_signal, int y_length, + double fs, double *interval_locations, double *intervals) { + int *negative_going_points = new int[y_length]; + + for (int i = 0; i < y_length - 1; ++i) + negative_going_points[i] = + 0.0 < filtered_signal[i] && filtered_signal[i + 1] <= 0.0 ? i + 1 : 0; + negative_going_points[y_length - 1] = 0; + + int *edges = new int[y_length]; + int count = 0; + for (int i = 0; i < y_length; ++i) + if (negative_going_points[i] > 0) + edges[count++] = negative_going_points[i]; + + if (count < 2) { + delete[] edges; + delete[] negative_going_points; + return 0; + } + + double *fine_edges = new double[count]; + for (int i = 0; i < count; ++i) + fine_edges[i] = + edges[i] - filtered_signal[edges[i] - 1] / + (filtered_signal[edges[i]] - filtered_signal[edges[i] - 1]); + + for (int i = 0; i < count - 1; ++i) { + intervals[i] = fs / (fine_edges[i + 1] - fine_edges[i]); + interval_locations[i] = (fine_edges[i] + fine_edges[i + 1]) / 2.0 / fs; + } + + delete[] fine_edges; + delete[] edges; + delete[] negative_going_points; + return count - 1; +} + +//----------------------------------------------------------------------------- +// GetFourZeroCrossingIntervals() calculates four zero-crossing intervals. +// (1) Zero-crossing going from negative to positive. +// (2) Zero-crossing going from positive to negative. +// (3) Peak, and (4) dip. (3) and (4) are calculated from the zero-crossings of +// the differential of waveform. +//----------------------------------------------------------------------------- +static void GetFourZeroCrossingIntervals(double *filtered_signal, int y_length, + double actual_fs, ZeroCrossings *zero_crossings) { + // x_length / 4 (old version) is fixed at 2013/07/14 + const int kMaximumNumber = y_length; + zero_crossings->negative_interval_locations = new double[kMaximumNumber]; + zero_crossings->positive_interval_locations = new double[kMaximumNumber]; + zero_crossings->peak_interval_locations = new double[kMaximumNumber]; + zero_crossings->dip_interval_locations = new double[kMaximumNumber]; + zero_crossings->negative_intervals = new double[kMaximumNumber]; + zero_crossings->positive_intervals = new double[kMaximumNumber]; + zero_crossings->peak_intervals = new double[kMaximumNumber]; + zero_crossings->dip_intervals = new double[kMaximumNumber]; + + zero_crossings->number_of_negatives = ZeroCrossingEngine(filtered_signal, + y_length, actual_fs, zero_crossings->negative_interval_locations, + zero_crossings->negative_intervals); + + for (int i = 0; i < y_length; ++i) filtered_signal[i] = -filtered_signal[i]; + zero_crossings->number_of_positives = ZeroCrossingEngine(filtered_signal, + y_length, actual_fs, zero_crossings->positive_interval_locations, + zero_crossings->positive_intervals); + + for (int i = 0; i < y_length - 1; ++i) filtered_signal[i] = + filtered_signal[i] - filtered_signal[i + 1]; + zero_crossings->number_of_peaks = ZeroCrossingEngine(filtered_signal, + y_length - 1, actual_fs, zero_crossings->peak_interval_locations, + zero_crossings->peak_intervals); + + for (int i = 0; i < y_length - 1; ++i) + filtered_signal[i] = -filtered_signal[i]; + zero_crossings->number_of_dips = ZeroCrossingEngine(filtered_signal, + y_length - 1, actual_fs, zero_crossings->dip_interval_locations, + zero_crossings->dip_intervals); +} + +//----------------------------------------------------------------------------- +// GetF0CandidateContourSub() calculates the f0 candidates and deviations. +// This is the sub-function of GetF0Candidates() and assumes the calculation. +//----------------------------------------------------------------------------- +static void GetF0CandidateContourSub( + const double * const * interpolated_f0_set, int f0_length, double f0_floor, + double f0_ceil, double boundary_f0, double *f0_candidate, + double *f0_score) { + for (int i = 0; i < f0_length; ++i) { + f0_candidate[i] = (interpolated_f0_set[0][i] + + interpolated_f0_set[1][i] + interpolated_f0_set[2][i] + + interpolated_f0_set[3][i]) / 4.0; + + f0_score[i] = sqrt(((interpolated_f0_set[0][i] - f0_candidate[i]) * + (interpolated_f0_set[0][i] - f0_candidate[i]) + + (interpolated_f0_set[1][i] - f0_candidate[i]) * + (interpolated_f0_set[1][i] - f0_candidate[i]) + + (interpolated_f0_set[2][i] - f0_candidate[i]) * + (interpolated_f0_set[2][i] - f0_candidate[i]) + + (interpolated_f0_set[3][i] - f0_candidate[i]) * + (interpolated_f0_set[3][i] - f0_candidate[i])) / 3.0); + + if (f0_candidate[i] > boundary_f0 || f0_candidate[i] < boundary_f0 / 2.0 || + f0_candidate[i] > f0_ceil || f0_candidate[i] < f0_floor) { + f0_candidate[i] = 0.0; + f0_score[i] = world::kMaximumValue; + } + } +} + +//----------------------------------------------------------------------------- +// GetF0CandidateContour() calculates the F0 candidates based on the +// zero-crossings. +//----------------------------------------------------------------------------- +static void GetF0CandidateContour(const ZeroCrossings *zero_crossings, + double boundary_f0, double f0_floor, double f0_ceil, + const double *temporal_positions, int f0_length, + double *f0_candidate, double *f0_score) { + if (0 == CheckEvent(zero_crossings->number_of_negatives - 2) * + CheckEvent(zero_crossings->number_of_positives - 2) * + CheckEvent(zero_crossings->number_of_peaks - 2) * + CheckEvent(zero_crossings->number_of_dips - 2)) { + for (int i = 0; i < f0_length; ++i) { + f0_score[i] = world::kMaximumValue; + f0_candidate[i] = 0.0; + } + return; + } + + double *interpolated_f0_set[4]; + for (int i = 0; i < 4; ++i) + interpolated_f0_set[i] = new double[f0_length]; + + interp1(zero_crossings->negative_interval_locations, + zero_crossings->negative_intervals, + zero_crossings->number_of_negatives, + temporal_positions, f0_length, interpolated_f0_set[0]); + interp1(zero_crossings->positive_interval_locations, + zero_crossings->positive_intervals, + zero_crossings->number_of_positives, + temporal_positions, f0_length, interpolated_f0_set[1]); + interp1(zero_crossings->peak_interval_locations, + zero_crossings->peak_intervals, zero_crossings->number_of_peaks, + temporal_positions, f0_length, interpolated_f0_set[2]); + interp1(zero_crossings->dip_interval_locations, + zero_crossings->dip_intervals, zero_crossings->number_of_dips, + temporal_positions, f0_length, interpolated_f0_set[3]); + + GetF0CandidateContourSub(interpolated_f0_set, f0_length, f0_floor, + f0_ceil, boundary_f0, f0_candidate, f0_score); + for (int i = 0; i < 4; ++i) delete[] interpolated_f0_set[i]; +} + +//----------------------------------------------------------------------------- +// DestroyZeroCrossings() frees the memory of array in the struct +//----------------------------------------------------------------------------- +static void DestroyZeroCrossings(ZeroCrossings *zero_crossings) { + delete[] zero_crossings->negative_interval_locations; + delete[] zero_crossings->positive_interval_locations; + delete[] zero_crossings->peak_interval_locations; + delete[] zero_crossings->dip_interval_locations; + delete[] zero_crossings->negative_intervals; + delete[] zero_crossings->positive_intervals; + delete[] zero_crossings->peak_intervals; + delete[] zero_crossings->dip_intervals; +} + +//----------------------------------------------------------------------------- +// GetF0CandidateFromRawEvent() calculates F0 candidate contour in 1-ch signal +//----------------------------------------------------------------------------- +static void GetF0CandidateFromRawEvent(double boundary_f0, double fs, + const fft_complex *y_spectrum, int y_length, int fft_size, double f0_floor, + double f0_ceil, const double *temporal_positions, int f0_length, + double *f0_score, double *f0_candidate) { + double *filtered_signal = new double[fft_size]; + GetFilteredSignal(matlab_round(fs / boundary_f0 / 2.0), fft_size, y_spectrum, + y_length, filtered_signal); + + ZeroCrossings zero_crossings = {0}; + GetFourZeroCrossingIntervals(filtered_signal, y_length, fs, + &zero_crossings); + + GetF0CandidateContour(&zero_crossings, boundary_f0, f0_floor, f0_ceil, + temporal_positions, f0_length, f0_candidate, f0_score); + + DestroyZeroCrossings(&zero_crossings); + delete[] filtered_signal; +} + +//----------------------------------------------------------------------------- +// GetF0CandidatesAndScores() calculates all f0 candidates and their scores. +//----------------------------------------------------------------------------- +static void GetF0CandidatesAndScores(const double *boundary_f0_list, + int number_of_bands, double actual_fs, int y_length, + const double *temporal_positions, int f0_length, + const fft_complex *y_spectrum, int fft_size, double f0_floor, + double f0_ceil, double **raw_f0_candidates, double **raw_f0_scores) { + double *f0_candidate = new double[f0_length]; + double *f0_score = new double[f0_length]; + + // Calculation of the acoustics events (zero-crossing) + for (int i = 0; i < number_of_bands; ++i) { + GetF0CandidateFromRawEvent(boundary_f0_list[i], actual_fs, y_spectrum, + y_length, fft_size, f0_floor, f0_ceil, temporal_positions, f0_length, + f0_score, f0_candidate); + for (int j = 0; j < f0_length; ++j) { + // A way to avoid zero division + raw_f0_scores[i][j] = f0_score[j] / + (f0_candidate[j] + world::kMySafeGuardMinimum); + raw_f0_candidates[i][j] = f0_candidate[j]; + } + } + + delete[] f0_candidate; + delete[] f0_score; +} + +//----------------------------------------------------------------------------- +// DioGeneralBody() estimates the F0 based on Distributed Inline-filter +// Operation. +//----------------------------------------------------------------------------- +static void DioGeneralBody(const double *x, int x_length, int fs, + double frame_period, double f0_floor, double f0_ceil, + double channels_in_octave, int speed, double allowed_range, + double *temporal_positions, double *f0) { + int number_of_bands = 1 + static_cast(log(f0_ceil / f0_floor) / + world::kLog2 * channels_in_octave); + double *boundary_f0_list = new double[number_of_bands]; + for (int i = 0; i < number_of_bands; ++i) + boundary_f0_list[i] = f0_floor * pow(2.0, (i + 1) / channels_in_octave); + + // normalization + int decimation_ratio = MyMaxInt(MyMinInt(speed, 12), 1); + int y_length = (1 + static_cast(x_length / decimation_ratio)); + double actual_fs = static_cast(fs) / decimation_ratio; + int fft_size = GetSuitableFFTSize(y_length + + matlab_round(actual_fs / world::kCutOff) * 2 + 1 + + (4 * static_cast(1.0 + actual_fs / boundary_f0_list[0] / 2.0))); + + // Calculation of the spectrum used for the f0 estimation + fft_complex *y_spectrum = new fft_complex[fft_size]; + GetSpectrumForEstimation(x, x_length, y_length, actual_fs, fft_size, + decimation_ratio, y_spectrum); + + double **f0_candidates = new double *[number_of_bands]; + double **f0_scores = new double *[number_of_bands]; + int f0_length = GetSamplesForDIO(fs, x_length, frame_period); + for (int i = 0; i < number_of_bands; ++i) { + f0_candidates[i] = new double[f0_length]; + f0_scores[i] = new double[f0_length]; + } + + for (int i = 0; i < f0_length; ++i) + temporal_positions[i] = i * frame_period / 1000.0; + + GetF0CandidatesAndScores(boundary_f0_list, number_of_bands, + actual_fs, y_length, temporal_positions, f0_length, y_spectrum, + fft_size, f0_floor, f0_ceil, f0_candidates, f0_scores); + + // Selection of the best value based on fundamental-ness. + // This function is related with SortCandidates() in MATLAB. + double *best_f0_contour = new double[f0_length]; + GetBestF0Contour(f0_length, f0_candidates, f0_scores, + number_of_bands, best_f0_contour); + + // Postprocessing to find the best f0-contour. + FixF0Contour(frame_period, number_of_bands, fs, f0_candidates, + best_f0_contour, f0_length, f0_floor, allowed_range, f0); + + delete[] best_f0_contour; + delete[] y_spectrum; + for (int i = 0; i < number_of_bands; ++i) { + delete[] f0_scores[i]; + delete[] f0_candidates[i]; + } + delete[] f0_scores; + delete[] f0_candidates; + delete[] boundary_f0_list; +} + +} // namespace + +int GetSamplesForDIO(int fs, int x_length, double frame_period) { + return static_cast(1000.0 * x_length / fs / frame_period) + 1; +} + +void Dio(const double *x, int x_length, int fs, const DioOption *option, + double *temporal_positions, double *f0) { + DioGeneralBody(x, x_length, fs, option->frame_period, option->f0_floor, + option->f0_ceil, option->channels_in_octave, option->speed, + option->allowed_range, temporal_positions, f0); +} + +void InitializeDioOption(DioOption *option) { + // You can change default parameters. + option->channels_in_octave = 2.0; + option->f0_ceil = world::kCeilF0; + option->f0_floor = world::kFloorF0; + option->frame_period = 5; + + // You can use the value from 1 to 12. + // Default value 11 is for the fs of 44.1 kHz. + // The lower value you use, the better performance you can obtain. + option->speed = 1; + + // You can give a positive real number as the threshold. + // The most strict value is 0, and there is no upper limit. + // On the other hand, I think that the value from 0.02 to 0.2 is reasonable. + option->allowed_range = 0.1; +} diff --git a/libsvc/Modules/Lib/World/src/fft.cpp b/libsvc/Modules/Lib/World/src/fft.cpp new file mode 100644 index 0000000..403db5f --- /dev/null +++ b/libsvc/Modules/Lib/World/src/fft.cpp @@ -0,0 +1,2652 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// This file represents the functions about FFT (Fast Fourier Transform) +// implemented by Mr. Ooura, and wrapper functions implemented by M. Morise. +// We can use these wrapper functions as well as the FFTW functions. +// Please see the FFTW web-page to show the usage of the wrapper functions. +// Ooura FFT: +// (Japanese) http://www.kurims.kyoto-u.ac.jp/~ooura/index-j.html +// (English) http://www.kurims.kyoto-u.ac.jp/~ooura/index.html +// FFTW: +// (English) http://www.fftw.org/ +// 2012/08/24 by M. Morise +//----------------------------------------------------------------------------- +#include "world/fft.h" + +#include +#include + +void cdft(int n, int isgn, double *a, int *ip, double *w); +void rdft(int n, int isgn, double *a, int *ip, double *w); + +namespace { +static void BackwardFFT(fft_plan p) { + if (p.c_out == NULL) { // c2r + p.input[0] = p.c_in[0][0]; + p.input[1] = p.c_in[p.n / 2][0]; + for (int i = 1; i < p.n / 2; ++i) { + p.input[i * 2] = p.c_in[i][0]; + p.input[i * 2 + 1] = -p.c_in[i][1]; + } + rdft(p.n, -1, p.input, p.ip, p.w); + for (int i = 0; i < p.n; ++i) p.out[i] = p.input[i] * 2.0; + } else { // c2c + for (int i = 0; i < p.n; ++i) { + p.input[i * 2] = p.c_in[i][0]; + p.input[i * 2 + 1] = p.c_in[i][1]; + } + cdft(p.n * 2, -1, p.input, p.ip, p.w); + for (int i = 0; i < p.n; ++i) { + p.c_out[i][0] = p.input[i * 2]; + p.c_out[i][1] = -p.input[i * 2 + 1]; + } + } +} + +static void ForwardFFT(fft_plan p) { + if (p.c_in == NULL) { // r2c + for (int i = 0; i < p.n; ++i) p.input[i] = p.in[i]; + rdft(p.n, 1, p.input, p.ip, p.w); + p.c_out[0][0] = p.input[0]; + p.c_out[0][1] = 0.0; + for (int i = 1; i < p.n / 2; ++i) { + p.c_out[i][0] = p.input[i * 2]; + p.c_out[i][1] = -p.input[i * 2 + 1]; + } + p.c_out[p.n / 2][0] = p.input[1]; + p.c_out[p.n / 2][1] = 0.0; + } else { // c2c + for (int i = 0; i < p.n; ++i) { + p.input[i * 2] = p.c_in[i][0]; + p.input[i * 2 + 1] = p.c_in[i][1]; + } + cdft(p.n * 2, 1, p.input, p.ip, p.w); + for (int i = 0; i < p.n; ++i) { + p.c_out[i][0] = p.input[i * 2]; + p.c_out[i][1] = -p.input[i * 2 + 1]; + } + } +} + +} // namespace + +fft_plan fft_plan_dft_1d(int n, fft_complex *in, fft_complex *out, int sign, + unsigned int flags) { + void makewt(int nw, int *ip, double *w); + + fft_plan output = {0}; + output.n = n; + output.in = NULL; + output.c_in = in; + output.out = NULL; + output.c_out = out; + output.sign = sign; + output.flags = flags; + output.input = new double[n * 2]; + output.ip = new int[n]; + output.w = new double[n * 5 / 4]; + + output.ip[0] = 0; + makewt(output.n >> 1, output.ip, output.w); + return output; +} + +fft_plan fft_plan_dft_c2r_1d(int n, fft_complex *in, double *out, + unsigned int flags) { + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + + fft_plan output = {0}; + output.n = n; + output.in = NULL; + output.c_in = in; + output.out = out; + output.c_out = NULL; + output.sign = FFT_BACKWARD; + output.flags = flags; + output.input = new double[n]; + output.ip = new int[n]; + output.w = new double[n * 5 / 4]; + + output.ip[0] = 0; + makewt(output.n >> 2, output.ip, output.w); + makect(output.n >> 2, output.ip, output.w + (output.n >> 2)); + return output; +} + +fft_plan fft_plan_dft_r2c_1d(int n, double *in, fft_complex *out, + unsigned int flags) { + void makewt(int nw, int *ip, double *w); + void makect(int nc, int *ip, double *c); + + fft_plan output = {0}; + output.n = n; + output.in = in; + output.c_in = NULL; + output.out = NULL; + output.c_out = out; + output.sign = FFT_FORWARD; + output.flags = flags; + output.input = new double[n]; + output.ip = new int[n]; + output.w = new double[n * 5 / 4]; + + output.ip[0] = 0; + makewt(output.n >> 2, output.ip, output.w); + makect(output.n >> 2, output.ip, output.w + (output.n >> 2)); + return output; +} + +void fft_execute(fft_plan p) { + if (p.sign == FFT_FORWARD) { + ForwardFFT(p); + } else { // ifft + BackwardFFT(p); + } +} + +void fft_destroy_plan(fft_plan p) { + p.n = 0; + p.in = NULL; + p.c_in = NULL; + p.out = NULL; + p.c_out = NULL; + p.sign = 0; + p.flags = 0; + delete[] p.input; + delete[] p.ip; + delete[] p.w; +} + +//----------------------------------------------------------------------- +// The following functions are reffered by +// http://www.kurims.kyoto-u.ac.jp/~ooura/index.html + +void cdft(int n, int isgn, double *a, int *ip, double *w) { + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void cftbsub(int n, double *a, int *ip, int nw, double *w); + int nw; + + nw = ip[0]; + if (isgn >= 0) { + cftfsub(n, a, ip, nw, w); + } else { + cftbsub(n, a, ip, nw, w); + } +} + + +void rdft(int n, int isgn, double *a, int *ip, double *w) { + void cftfsub(int n, double *a, int *ip, int nw, double *w); + void cftbsub(int n, double *a, int *ip, int nw, double *w); + void rftfsub(int n, double *a, int nc, double *c); + void rftbsub(int n, double *a, int nc, double *c); + double xi; + + int nw = ip[0]; + int nc = ip[1]; + + if (isgn >= 0) { + if (n > 4) { + cftfsub(n, a, ip, nw, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, ip, nw, w); + } + xi = a[0] - a[1]; + a[0] += a[1]; + a[1] = xi; + } else { + a[1] = 0.5 * (a[0] - a[1]); + a[0] -= a[1]; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + cftbsub(n, a, ip, nw, w); + } else if (n == 4) { + cftbsub(n, a, ip, nw, w); + } + } +} + +void makewt(int nw, int *ip, double *w) { + void makeipt(int nw, int *ip); + int j, nwh, nw0, nw1; + double delta, wn4r, wk1r, wk1i, wk3r, wk3i; + + ip[0] = nw; + ip[1] = 1; + if (nw > 2) { + nwh = nw >> 1; + delta = atan(1.0) / nwh; + wn4r = cos(delta * nwh); + w[0] = 1; + w[1] = wn4r; + if (nwh == 4) { + w[2] = cos(delta * 2); + w[3] = sin(delta * 2); + } else if (nwh > 4) { + makeipt(nw, ip); + w[2] = 0.5 / cos(delta * 2); + w[3] = 0.5 / cos(delta * 6); + for (j = 4; j < nwh; j += 4) { + w[j] = cos(delta * j); + w[j + 1] = sin(delta * j); + w[j + 2] = cos(3 * delta * j); + w[j + 3] = -sin(3 * delta * j); + } + } + nw0 = 0; + while (nwh > 2) { + nw1 = nw0 + nwh; + nwh >>= 1; + w[nw1] = 1; + w[nw1 + 1] = wn4r; + if (nwh == 4) { + wk1r = w[nw0 + 4]; + wk1i = w[nw0 + 5]; + w[nw1 + 2] = wk1r; + w[nw1 + 3] = wk1i; + } else if (nwh > 4) { + wk1r = w[nw0 + 4]; + wk3r = w[nw0 + 6]; + w[nw1 + 2] = 0.5 / wk1r; + w[nw1 + 3] = 0.5 / wk3r; + for (j = 4; j < nwh; j += 4) { + wk1r = w[nw0 + 2 * j]; + wk1i = w[nw0 + 2 * j + 1]; + wk3r = w[nw0 + 2 * j + 2]; + wk3i = w[nw0 + 2 * j + 3]; + w[nw1 + j] = wk1r; + w[nw1 + j + 1] = wk1i; + w[nw1 + j + 2] = wk3r; + w[nw1 + j + 3] = wk3i; + } + } + nw0 = nw1; + } + } +} + +void makeipt(int nw, int *ip) { + int j, l, m, m2, p, q; + + ip[2] = 0; + ip[3] = 16; + m = 2; + for (l = nw; l > 32; l >>= 2) { + m2 = m << 1; + q = m2 << 3; + for (j = m; j < m2; j++) { + p = ip[j] << 2; + ip[m + j] = p; + ip[m2 + j] = p + q; + } + m = m2; + } +} + +void makect(int nc, int *ip, double *c) { + int j, nch; + double delta; + + ip[1] = nc; + if (nc > 1) { + nch = nc >> 1; + delta = atan(1.0) / nch; + c[0] = cos(delta * nch); + c[nch] = 0.5 * c[0]; + for (j = 1; j < nch; j++) { + c[j] = 0.5 * cos(delta * j); + c[nc - j] = 0.5 * sin(delta * j); + } + } +} + +// -------- child routines -------- + + +void cftfsub(int n, double *a, int *ip, int nw, double *w) { + void bitrv2(int n, int *ip, double *a); + void bitrv216(double *a); + void bitrv208(double *a); + void cftf1st(int n, double *a, double *w); + void cftrec4(int n, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftfx41(int n, double *a, int nw, double *w); + void cftf161(double *a, double *w); + void cftf081(double *a, double *w); + void cftf040(double *a); + void cftx020(double *a); + + if (n > 8) { + if (n > 32) { + cftf1st(n, a, &w[nw - (n >> 2)]); + if (n > 512) { + cftrec4(n, a, nw, w); + } else if (n > 128) { + cftleaf(n, 1, a, nw, w); + } else { + cftfx41(n, a, nw, w); + } + bitrv2(n, ip, a); + } else if (n == 32) { + cftf161(a, &w[nw - 8]); + bitrv216(a); + } else { + cftf081(a, w); + bitrv208(a); + } + } else if (n == 8) { + cftf040(a); + } else if (n == 4) { + cftx020(a); + } +} + +void cftbsub(int n, double *a, int *ip, int nw, double *w) { + void bitrv2conj(int n, int *ip, double *a); + void bitrv216neg(double *a); + void bitrv208neg(double *a); + void cftb1st(int n, double *a, double *w); + void cftrec4(int n, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftfx41(int n, double *a, int nw, double *w); + void cftf161(double *a, double *w); + void cftf081(double *a, double *w); + void cftb040(double *a); + void cftx020(double *a); + + if (n > 8) { + if (n > 32) { + cftb1st(n, a, &w[nw - (n >> 2)]); + if (n > 512) { + cftrec4(n, a, nw, w); + } else if (n > 128) { + cftleaf(n, 1, a, nw, w); + } else { + cftfx41(n, a, nw, w); + } + bitrv2conj(n, ip, a); + } else if (n == 32) { + cftf161(a, &w[nw - 8]); + bitrv216neg(a); + } else { + cftf081(a, w); + bitrv208neg(a); + } + } else if (n == 8) { + cftb040(a); + } else if (n == 4) { + cftx020(a); + } +} + +void bitrv2(int n, int *ip, double *a) { + int j, j1, k, k1, l, m, nh, nm; + double xr, xi, yr, yi; + + m = 1; + for (l = n >> 2; l > 8; l >>= 2) { + m <<= 1; + } + nh = n >> 1; + nm = 4 * m; + if (l == 8) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + 2 * ip[m + k]; + k1 = 4 * k + 2 * ip[m + j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + 2 * ip[m + k]; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - nm; + k1 += 2 * nm - 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } else { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + ip[m + k]; + k1 = 4 * k + ip[m + j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + ip[m + k]; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } +} + +void bitrv2conj(int n, int *ip, double *a) { + int j, j1, k, k1, l, m, nh, nm; + double xr, xi, yr, yi; + + m = 1; + for (l = n >> 2; l > 8; l >>= 2) { + m <<= 1; + } + nh = n >> 1; + nm = 4 * m; + if (l == 8) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + 2 * ip[m + k]; + k1 = 4 * k + 2 * ip[m + j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + 2 * ip[m + k]; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += nm; + k1 += 2 * nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - nm; + k1 += 2 * nm - 2; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + } + } else { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 4 * j + ip[m + k]; + k1 = 4 * k + ip[m + j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nm; + k1 += nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nm; + k1 -= nm; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 4 * k + ip[m + k]; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += nm; + k1 += nm; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + } + } +} + +void bitrv216(double *a) { + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x7r, x7i, x8r, x8i, x10r, x10i, + x11r, x11i, x12r, x12i, x13r, x13i, x14r, x14i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + a[2] = x8r; + a[3] = x8i; + a[4] = x4r; + a[5] = x4i; + a[6] = x12r; + a[7] = x12i; + a[8] = x2r; + a[9] = x2i; + a[10] = x10r; + a[11] = x10i; + a[14] = x14r; + a[15] = x14i; + a[16] = x1r; + a[17] = x1i; + a[20] = x5r; + a[21] = x5i; + a[22] = x13r; + a[23] = x13i; + a[24] = x3r; + a[25] = x3i; + a[26] = x11r; + a[27] = x11i; + a[28] = x7r; + a[29] = x7i; +} + + +void bitrv216neg(double *a) { + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i, x8r, x8i, + x9r, x9i, x10r, x10i, x11r, x11i, x12r, x12i, + x13r, x13i, x14r, x14i, x15r, x15i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x9r = a[18]; + x9i = a[19]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + x15r = a[30]; + x15i = a[31]; + a[2] = x15r; + a[3] = x15i; + a[4] = x7r; + a[5] = x7i; + a[6] = x11r; + a[7] = x11i; + a[8] = x3r; + a[9] = x3i; + a[10] = x13r; + a[11] = x13i; + a[12] = x5r; + a[13] = x5i; + a[14] = x9r; + a[15] = x9i; + a[16] = x1r; + a[17] = x1i; + a[18] = x14r; + a[19] = x14i; + a[20] = x6r; + a[21] = x6i; + a[22] = x10r; + a[23] = x10i; + a[24] = x2r; + a[25] = x2i; + a[26] = x12r; + a[27] = x12i; + a[28] = x4r; + a[29] = x4i; + a[30] = x8r; + a[31] = x8i; +} + +void bitrv208(double *a) { + double x1r, x1i, x3r, x3i, x4r, x4i, x6r, x6i; + + x1r = a[2]; + x1i = a[3]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x6r = a[12]; + x6i = a[13]; + a[2] = x4r; + a[3] = x4i; + a[6] = x6r; + a[7] = x6i; + a[8] = x1r; + a[9] = x1i; + a[12] = x3r; + a[13] = x3i; +} + +void bitrv208neg(double *a) { + double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + a[2] = x7r; + a[3] = x7i; + a[4] = x3r; + a[5] = x3i; + a[6] = x5r; + a[7] = x5i; + a[8] = x1r; + a[9] = x1i; + a[10] = x6r; + a[11] = x6i; + a[12] = x2r; + a[13] = x2i; + a[14] = x4r; + a[15] = x4i; +} + +void cftf1st(int n, double *a, double *w) { + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + csc1 = w[2]; + csc3 = w[3]; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + k = 0; + for (j = 2; j < mh - 2; j += 4) { + k += 4; + wk1r = csc1 * (wd1r + w[k]); + wk1i = csc1 * (wd1i + w[k + 1]); + wk3r = csc3 * (wd3r + w[k + 2]); + wk3i = csc3 * (wd3i + w[k + 3]); + wd1r = w[k]; + wd1i = w[k + 1]; + wd3r = w[k + 2]; + wd3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + y0r = a[j + 2] + a[j2 + 2]; + y0i = a[j + 3] + a[j2 + 3]; + y1r = a[j + 2] - a[j2 + 2]; + y1i = a[j + 3] - a[j2 + 3]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 + 2] + a[j3 + 2]; + y2i = a[j1 + 3] + a[j3 + 3]; + y3r = a[j1 + 2] - a[j3 + 2]; + y3i = a[j1 + 3] - a[j3 + 3]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i + y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i - y2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r - y3i; + x0i = y1i + y3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = y1r + y3i; + x0i = y1i - y3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + y0r = a[j0 - 2] + a[j2 - 2]; + y0i = a[j0 - 1] + a[j2 - 1]; + y1r = a[j0 - 2] - a[j2 - 2]; + y1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 - 2] + a[j3 - 2]; + y2i = a[j1 - 1] + a[j3 - 1]; + y3r = a[j1 - 2] - a[j3 - 2]; + y3i = a[j1 - 1] - a[j3 - 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i + y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i - y2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = y1r - y3i; + x0i = y1i + y3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = y1r + y3i; + x0i = y1i - y3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + } + wk1r = csc1 * (wd1r + wn4r); + wk1i = csc1 * (wd1i + wn4r); + wk3r = csc3 * (wd3r - wn4r); + wk3i = csc3 * (wd3i - wn4r); + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = a[j0 - 1] + a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i + x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = a[j0 + 3] + a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = a[j0 + 3] - a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i + x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; +} + +void cftb1st(int n, double *a, double *w) { + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = -a[1] - a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = -a[1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j2] = x1r + x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r - x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + csc1 = w[2]; + csc3 = w[3]; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + k = 0; + for (j = 2; j < mh - 2; j += 4) { + k += 4; + wk1r = csc1 * (wd1r + w[k]); + wk1i = csc1 * (wd1i + w[k + 1]); + wk3r = csc3 * (wd3r + w[k + 2]); + wk3i = csc3 * (wd3i + w[k + 3]); + wd1r = w[k]; + wd1i = w[k + 1]; + wd3r = w[k + 2]; + wd3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = -a[j + 1] - a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = -a[j + 1] + a[j2 + 1]; + y0r = a[j + 2] + a[j2 + 2]; + y0i = -a[j + 3] - a[j2 + 3]; + y1r = a[j + 2] - a[j2 + 2]; + y1i = -a[j + 3] + a[j2 + 3]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 + 2] + a[j3 + 2]; + y2i = a[j1 + 3] + a[j3 + 3]; + y3r = a[j1 + 2] - a[j3 + 2]; + y3i = a[j1 + 3] - a[j3 + 3]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i - y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i + y2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r + y3i; + x0i = y1i + y3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = y1r - y3i; + x0i = y1i - y3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + y0r = a[j0 - 2] + a[j2 - 2]; + y0i = -a[j0 - 1] - a[j2 - 1]; + y1r = a[j0 - 2] - a[j2 - 2]; + y1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + y2r = a[j1 - 2] + a[j3 - 2]; + y2i = a[j1 - 1] + a[j3 - 1]; + y3r = a[j1 - 2] - a[j3 - 2]; + y3i = a[j1 - 1] - a[j3 - 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i - y2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i + y2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = y1r + y3i; + x0i = y1i + y3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = y1r - y3i; + x0i = y1i - y3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + } + wk1r = csc1 * (wd1r + wn4r); + wk1i = csc1 * (wd1i + wn4r); + wk3r = csc3 * (wd3r - wn4r); + wk3i = csc3 * (wd3i - wn4r); + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = -a[j0 - 1] - a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i - x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = -a[j0 + 3] - a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = -a[j0 + 3] + a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i - x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; +} + +void cftrec4(int n, double *a, int nw, double *w) { + int cfttree(int n, int j, int k, double *a, int nw, double *w); + void cftleaf(int n, int isplt, double *a, int nw, double *w); + void cftmdl1(int n, double *a, double *w); + int isplt, j, k, m; + + m = n; + while (m > 512) { + m >>= 2; + cftmdl1(m, &a[n - m], &w[nw - (m >> 1)]); + } + cftleaf(m, 1, &a[n - m], nw, w); + k = 0; + for (j = n - m; j > 0; j -= m) { + k++; + isplt = cfttree(m, j, k, a, nw, w); + cftleaf(m, isplt, &a[j - m], nw, w); + } +} + +int cfttree(int n, int j, int k, double *a, int nw, double *w) { + void cftmdl1(int n, double *a, double *w); + void cftmdl2(int n, double *a, double *w); + int i, isplt, m; + + if ((k & 3) != 0) { + isplt = k & 1; + if (isplt != 0) { + cftmdl1(n, &a[j - n], &w[nw - (n >> 1)]); + } else { + cftmdl2(n, &a[j - n], &w[nw - n]); + } + } else { + m = n; + for (i = k; (i & 3) == 0; i >>= 2) { + m <<= 2; + } + isplt = i & 1; + if (isplt != 0) { + while (m > 128) { + cftmdl1(m, &a[j - m], &w[nw - (m >> 1)]); + m >>= 2; + } + } else { + while (m > 128) { + cftmdl2(m, &a[j - m], &w[nw - m]); + m >>= 2; + } + } + } + return isplt; +} + +void cftleaf(int n, int isplt, double *a, int nw, double *w) { + void cftmdl1(int n, double *a, double *w); + void cftmdl2(int n, double *a, double *w); + void cftf161(double *a, double *w); + void cftf162(double *a, double *w); + void cftf081(double *a, double *w); + void cftf082(double *a, double *w); + + if (n == 512) { + cftmdl1(128, a, &w[nw - 64]); + cftf161(a, &w[nw - 8]); + cftf162(&a[32], &w[nw - 32]); + cftf161(&a[64], &w[nw - 8]); + cftf161(&a[96], &w[nw - 8]); + cftmdl2(128, &a[128], &w[nw - 128]); + cftf161(&a[128], &w[nw - 8]); + cftf162(&a[160], &w[nw - 32]); + cftf161(&a[192], &w[nw - 8]); + cftf162(&a[224], &w[nw - 32]); + cftmdl1(128, &a[256], &w[nw - 64]); + cftf161(&a[256], &w[nw - 8]); + cftf162(&a[288], &w[nw - 32]); + cftf161(&a[320], &w[nw - 8]); + cftf161(&a[352], &w[nw - 8]); + if (isplt != 0) { + cftmdl1(128, &a[384], &w[nw - 64]); + cftf161(&a[480], &w[nw - 8]); + } else { + cftmdl2(128, &a[384], &w[nw - 128]); + cftf162(&a[480], &w[nw - 32]); + } + cftf161(&a[384], &w[nw - 8]); + cftf162(&a[416], &w[nw - 32]); + cftf161(&a[448], &w[nw - 8]); + } else { + cftmdl1(64, a, &w[nw - 32]); + cftf081(a, &w[nw - 8]); + cftf082(&a[16], &w[nw - 8]); + cftf081(&a[32], &w[nw - 8]); + cftf081(&a[48], &w[nw - 8]); + cftmdl2(64, &a[64], &w[nw - 64]); + cftf081(&a[64], &w[nw - 8]); + cftf082(&a[80], &w[nw - 8]); + cftf081(&a[96], &w[nw - 8]); + cftf082(&a[112], &w[nw - 8]); + cftmdl1(64, &a[128], &w[nw - 32]); + cftf081(&a[128], &w[nw - 8]); + cftf082(&a[144], &w[nw - 8]); + cftf081(&a[160], &w[nw - 8]); + cftf081(&a[176], &w[nw - 8]); + if (isplt != 0) { + cftmdl1(64, &a[192], &w[nw - 32]); + cftf081(&a[240], &w[nw - 8]); + } else { + cftmdl2(64, &a[192], &w[nw - 64]); + cftf082(&a[240], &w[nw - 8]); + } + cftf081(&a[192], &w[nw - 8]); + cftf082(&a[208], &w[nw - 8]); + cftf081(&a[224], &w[nw - 8]); + } +} + +void cftmdl1(int n, double *a, double *w) { + int j, j0, j1, j2, j3, k, m, mh; + double wn4r, wk1r, wk1i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wn4r = w[1]; + k = 0; + for (j = 2; j < mh; j += 2) { + k += 4; + wk1r = w[k]; + wk1i = w[k + 1]; + wk3r = w[k + 2]; + wk3i = w[k + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + } + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wn4r * (x0r + x0i); + a[j3 + 1] = -wn4r * (x0i - x0r); +} + +void cftmdl2(int n, double *a, double *w) { + int j, j0, j1, j2, j3, k, kr, m, mh; + double wn4r, wk1r, wk1i, wk3r, wk3i, wd1r, wd1i, wd3r, wd3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, y0r, y0i, y2r, y2i; + + mh = n >> 3; + m = 2 * mh; + wn4r = w[1]; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] - a[j2 + 1]; + x0i = a[1] + a[j2]; + x1r = a[0] + a[j2 + 1]; + x1i = a[1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wn4r * (x2r - x2i); + y0i = wn4r * (x2i + x2r); + a[0] = x0r + y0r; + a[1] = x0i + y0i; + a[j1] = x0r - y0r; + a[j1 + 1] = x0i - y0i; + y0r = wn4r * (x3r - x3i); + y0i = wn4r * (x3i + x3r); + a[j2] = x1r - y0i; + a[j2 + 1] = x1i + y0r; + a[j3] = x1r + y0i; + a[j3 + 1] = x1i - y0r; + k = 0; + kr = 2 * m; + for (j = 2; j < mh; j += 2) { + k += 4; + wk1r = w[k]; + wk1i = w[k + 1]; + wk3r = w[k + 2]; + wk3i = w[k + 3]; + kr -= 4; + wd1i = w[kr]; + wd1r = w[kr + 1]; + wd3i = w[kr + 2]; + wd3r = w[kr + 3]; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] - a[j2 + 1]; + x0i = a[j + 1] + a[j2]; + x1r = a[j] + a[j2 + 1]; + x1i = a[j + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wd1r * x2r - wd1i * x2i; + y2i = wd1r * x2i + wd1i * x2r; + a[j] = y0r + y2r; + a[j + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk3r * x1r + wk3i * x1i; + y0i = wk3r * x1i - wk3i * x1r; + y2r = wd3r * x3r + wd3i * x3i; + y2i = wd3r * x3i - wd3i * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wd1i * x0r - wd1r * x0i; + y0i = wd1i * x0i + wd1r * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wd3i * x1r + wd3r * x1i; + y0i = wd3i * x1i - wd3r * x1r; + y2r = wk3i * x3r + wk3r * x3i; + y2i = wk3i * x3i - wk3r * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + } + wk1r = w[m]; + wk1i = w[m + 1]; + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk1i * x1r - wk1r * x1i; + y0i = wk1i * x1i + wk1r * x1r; + y2r = wk1r * x3r - wk1i * x3i; + y2i = wk1r * x3i + wk1i * x3r; + a[j2] = y0r - y2r; + a[j2 + 1] = y0i - y2i; + a[j3] = y0r + y2r; + a[j3 + 1] = y0i + y2i; +} + +void cftfx41(int n, double *a, int nw, double *w) { + void cftf161(double *a, double *w); + void cftf162(double *a, double *w); + void cftf081(double *a, double *w); + void cftf082(double *a, double *w); + + if (n == 128) { + cftf161(a, &w[nw - 8]); + cftf162(&a[32], &w[nw - 32]); + cftf161(&a[64], &w[nw - 8]); + cftf161(&a[96], &w[nw - 8]); + } else { + cftf081(a, &w[nw - 8]); + cftf082(&a[16], &w[nw - 8]); + cftf081(&a[32], &w[nw - 8]); + cftf081(&a[48], &w[nw - 8]); + } +} + +void cftf161(double *a, double *w) { + double wn4r, wk1r, wk1i, + x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = w[1]; + wk1r = w[2]; + wk1i = w[3]; + x0r = a[0] + a[16]; + x0i = a[1] + a[17]; + x1r = a[0] - a[16]; + x1i = a[1] - a[17]; + x2r = a[8] + a[24]; + x2i = a[9] + a[25]; + x3r = a[8] - a[24]; + x3i = a[9] - a[25]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y4r = x0r - x2r; + y4i = x0i - x2i; + y8r = x1r - x3i; + y8i = x1i + x3r; + y12r = x1r + x3i; + y12i = x1i - x3r; + x0r = a[2] + a[18]; + x0i = a[3] + a[19]; + x1r = a[2] - a[18]; + x1i = a[3] - a[19]; + x2r = a[10] + a[26]; + x2i = a[11] + a[27]; + x3r = a[10] - a[26]; + x3i = a[11] - a[27]; + y1r = x0r + x2r; + y1i = x0i + x2i; + y5r = x0r - x2r; + y5i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y9r = wk1r * x0r - wk1i * x0i; + y9i = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y13r = wk1i * x0r - wk1r * x0i; + y13i = wk1i * x0i + wk1r * x0r; + x0r = a[4] + a[20]; + x0i = a[5] + a[21]; + x1r = a[4] - a[20]; + x1i = a[5] - a[21]; + x2r = a[12] + a[28]; + x2i = a[13] + a[29]; + x3r = a[12] - a[28]; + x3i = a[13] - a[29]; + y2r = x0r + x2r; + y2i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y10r = wn4r * (x0r - x0i); + y10i = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + y14r = wn4r * (x0r + x0i); + y14i = wn4r * (x0i - x0r); + x0r = a[6] + a[22]; + x0i = a[7] + a[23]; + x1r = a[6] - a[22]; + x1i = a[7] - a[23]; + x2r = a[14] + a[30]; + x2i = a[15] + a[31]; + x3r = a[14] - a[30]; + x3i = a[15] - a[31]; + y3r = x0r + x2r; + y3i = x0i + x2i; + y7r = x0r - x2r; + y7i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y11r = wk1i * x0r - wk1r * x0i; + y11i = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y15r = wk1r * x0r - wk1i * x0i; + y15i = wk1r * x0i + wk1i * x0r; + x0r = y12r - y14r; + x0i = y12i - y14i; + x1r = y12r + y14r; + x1i = y12i + y14i; + x2r = y13r - y15r; + x2i = y13i - y15i; + x3r = y13r + y15r; + x3i = y13i + y15i; + a[24] = x0r + x2r; + a[25] = x0i + x2i; + a[26] = x0r - x2r; + a[27] = x0i - x2i; + a[28] = x1r - x3i; + a[29] = x1i + x3r; + a[30] = x1r + x3i; + a[31] = x1i - x3r; + x0r = y8r + y10r; + x0i = y8i + y10i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + x3r = y9r - y11r; + x3i = y9i - y11i; + a[16] = x0r + x2r; + a[17] = x0i + x2i; + a[18] = x0r - x2r; + a[19] = x0i - x2i; + a[20] = x1r - x3i; + a[21] = x1i + x3r; + a[22] = x1r + x3i; + a[23] = x1i - x3r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + x0r = y5r + y7i; + x0i = y5i - y7r; + x3r = wn4r * (x0r - x0i); + x3i = wn4r * (x0i + x0r); + x0r = y4r - y6i; + x0i = y4i + y6r; + x1r = y4r + y6i; + x1i = y4i - y6r; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[10] = x0r - x2r; + a[11] = x0i - x2i; + a[12] = x1r - x3i; + a[13] = x1i + x3r; + a[14] = x1r + x3i; + a[15] = x1i - x3r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + x3r = y1r - y3r; + x3i = y1i - y3i; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x0r - x2r; + a[3] = x0i - x2i; + a[4] = x1r - x3i; + a[5] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; +} + +void cftf162(double *a, double *w) { + double wn4r, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i, + x0r, x0i, x1r, x1i, x2r, x2i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = w[1]; + wk1r = w[4]; + wk1i = w[5]; + wk3r = w[6]; + wk3i = -w[7]; + wk2r = w[8]; + wk2i = w[9]; + x1r = a[0] - a[17]; + x1i = a[1] + a[16]; + x0r = a[8] - a[25]; + x0i = a[9] + a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y0r = x1r + x2r; + y0i = x1i + x2i; + y4r = x1r - x2r; + y4i = x1i - x2i; + x1r = a[0] + a[17]; + x1i = a[1] - a[16]; + x0r = a[8] + a[25]; + x0i = a[9] - a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y8r = x1r - x2i; + y8i = x1i + x2r; + y12r = x1r + x2i; + y12i = x1i - x2r; + x0r = a[2] - a[19]; + x0i = a[3] + a[18]; + x1r = wk1r * x0r - wk1i * x0i; + x1i = wk1r * x0i + wk1i * x0r; + x0r = a[10] - a[27]; + x0i = a[11] + a[26]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y1r = x1r + x2r; + y1i = x1i + x2i; + y5r = x1r - x2r; + y5i = x1i - x2i; + x0r = a[2] + a[19]; + x0i = a[3] - a[18]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[10] + a[27]; + x0i = a[11] - a[26]; + x2r = wk1r * x0r + wk1i * x0i; + x2i = wk1r * x0i - wk1i * x0r; + y9r = x1r - x2r; + y9i = x1i - x2i; + y13r = x1r + x2r; + y13i = x1i + x2i; + x0r = a[4] - a[21]; + x0i = a[5] + a[20]; + x1r = wk2r * x0r - wk2i * x0i; + x1i = wk2r * x0i + wk2i * x0r; + x0r = a[12] - a[29]; + x0i = a[13] + a[28]; + x2r = wk2i * x0r - wk2r * x0i; + x2i = wk2i * x0i + wk2r * x0r; + y2r = x1r + x2r; + y2i = x1i + x2i; + y6r = x1r - x2r; + y6i = x1i - x2i; + x0r = a[4] + a[21]; + x0i = a[5] - a[20]; + x1r = wk2i * x0r - wk2r * x0i; + x1i = wk2i * x0i + wk2r * x0r; + x0r = a[12] + a[29]; + x0i = a[13] - a[28]; + x2r = wk2r * x0r - wk2i * x0i; + x2i = wk2r * x0i + wk2i * x0r; + y10r = x1r - x2r; + y10i = x1i - x2i; + y14r = x1r + x2r; + y14i = x1i + x2i; + x0r = a[6] - a[23]; + x0i = a[7] + a[22]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[14] - a[31]; + x0i = a[15] + a[30]; + x2r = wk1i * x0r - wk1r * x0i; + x2i = wk1i * x0i + wk1r * x0r; + y3r = x1r + x2r; + y3i = x1i + x2i; + y7r = x1r - x2r; + y7i = x1i - x2i; + x0r = a[6] + a[23]; + x0i = a[7] - a[22]; + x1r = wk1i * x0r + wk1r * x0i; + x1i = wk1i * x0i - wk1r * x0r; + x0r = a[14] + a[31]; + x0i = a[15] - a[30]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y11r = x1r + x2r; + y11i = x1i + x2i; + y15r = x1r - x2r; + y15i = x1i - x2i; + x1r = y0r + y2r; + x1i = y0i + y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + a[0] = x1r + x2r; + a[1] = x1i + x2i; + a[2] = x1r - x2r; + a[3] = x1i - x2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r - y3r; + x2i = y1i - y3i; + a[4] = x1r - x2i; + a[5] = x1i + x2r; + a[6] = x1r + x2i; + a[7] = x1i - x2r; + x1r = y4r - y6i; + x1i = y4i + y6r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[8] = x1r + x2r; + a[9] = x1i + x2i; + a[10] = x1r - x2r; + a[11] = x1i - x2i; + x1r = y4r + y6i; + x1i = y4i - y6r; + x0r = y5r + y7i; + x0i = y5i - y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[12] = x1r - x2i; + a[13] = x1i + x2r; + a[14] = x1r + x2i; + a[15] = x1i - x2r; + x1r = y8r + y10r; + x1i = y8i + y10i; + x2r = y9r - y11r; + x2i = y9i - y11i; + a[16] = x1r + x2r; + a[17] = x1i + x2i; + a[18] = x1r - x2r; + a[19] = x1i - x2i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + a[20] = x1r - x2i; + a[21] = x1i + x2r; + a[22] = x1r + x2i; + a[23] = x1i - x2r; + x1r = y12r - y14i; + x1i = y12i + y14r; + x0r = y13r + y15i; + x0i = y13i - y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[24] = x1r + x2r; + a[25] = x1i + x2i; + a[26] = x1r - x2r; + a[27] = x1i - x2i; + x1r = y12r + y14i; + x1i = y12i - y14r; + x0r = y13r - y15i; + x0i = y13i + y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[28] = x1r - x2i; + a[29] = x1i + x2r; + a[30] = x1r + x2i; + a[31] = x1i - x2r; +} + +void cftf081(double *a, double *w) { + double wn4r, x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[1]; + x0r = a[0] + a[8]; + x0i = a[1] + a[9]; + x1r = a[0] - a[8]; + x1i = a[1] - a[9]; + x2r = a[4] + a[12]; + x2i = a[5] + a[13]; + x3r = a[4] - a[12]; + x3i = a[5] - a[13]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[2] + a[10]; + x0i = a[3] + a[11]; + x1r = a[2] - a[10]; + x1i = a[3] - a[11]; + x2r = a[6] + a[14]; + x2i = a[7] + a[15]; + x3r = a[6] - a[14]; + x3i = a[7] - a[15]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[8] = y1r + y5r; + a[9] = y1i + y5i; + a[10] = y1r - y5r; + a[11] = y1i - y5i; + a[12] = y3r - y7i; + a[13] = y3i + y7r; + a[14] = y3r + y7i; + a[15] = y3i - y7r; + a[0] = y0r + y4r; + a[1] = y0i + y4i; + a[2] = y0r - y4r; + a[3] = y0i - y4i; + a[4] = y2r - y6i; + a[5] = y2i + y6r; + a[6] = y2r + y6i; + a[7] = y2i - y6r; +} + +void cftf082(double *a, double *w) { + double wn4r, wk1r, wk1i, x0r, x0i, x1r, x1i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[1]; + wk1r = w[2]; + wk1i = w[3]; + y0r = a[0] - a[9]; + y0i = a[1] + a[8]; + y1r = a[0] + a[9]; + y1i = a[1] - a[8]; + x0r = a[4] - a[13]; + x0i = a[5] + a[12]; + y2r = wn4r * (x0r - x0i); + y2i = wn4r * (x0i + x0r); + x0r = a[4] + a[13]; + x0i = a[5] - a[12]; + y3r = wn4r * (x0r - x0i); + y3i = wn4r * (x0i + x0r); + x0r = a[2] - a[11]; + x0i = a[3] + a[10]; + y4r = wk1r * x0r - wk1i * x0i; + y4i = wk1r * x0i + wk1i * x0r; + x0r = a[2] + a[11]; + x0i = a[3] - a[10]; + y5r = wk1i * x0r - wk1r * x0i; + y5i = wk1i * x0i + wk1r * x0r; + x0r = a[6] - a[15]; + x0i = a[7] + a[14]; + y6r = wk1i * x0r - wk1r * x0i; + y6i = wk1i * x0i + wk1r * x0r; + x0r = a[6] + a[15]; + x0i = a[7] - a[14]; + y7r = wk1r * x0r - wk1i * x0i; + y7i = wk1r * x0i + wk1i * x0r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y4r + y6r; + x1i = y4i + y6i; + a[0] = x0r + x1r; + a[1] = x0i + x1i; + a[2] = x0r - x1r; + a[3] = x0i - x1i; + x0r = y0r - y2r; + x0i = y0i - y2i; + x1r = y4r - y6r; + x1i = y4i - y6i; + a[4] = x0r - x1i; + a[5] = x0i + x1r; + a[6] = x0r + x1i; + a[7] = x0i - x1r; + x0r = y1r - y3i; + x0i = y1i + y3r; + x1r = y5r - y7r; + x1i = y5i - y7i; + a[8] = x0r + x1r; + a[9] = x0i + x1i; + a[10] = x0r - x1r; + a[11] = x0i - x1i; + x0r = y1r + y3i; + x0i = y1i - y3r; + x1r = y5r + y7r; + x1i = y5i + y7i; + a[12] = x0r - x1i; + a[13] = x0i + x1r; + a[14] = x0r + x1i; + a[15] = x0i - x1r; +} + + +void cftf040(double *a) { + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r + x3i; + a[7] = x1i - x3r; +} + +void cftb040(double *a) { + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r + x3i; + a[3] = x1i - x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r - x3i; + a[7] = x1i + x3r; +} + +void cftx020(double *a) { + double x0r, x0i; + + x0r = a[0] - a[2]; + x0i = a[1] - a[3]; + a[0] += a[2]; + a[1] += a[3]; + a[2] = x0r; + a[3] = x0i; +} + +void rftfsub(int n, double *a, int nc, double *c) { + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + +void rftbsub(int n, double *a, int nc, double *c) { + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr + wki * xi; + yi = wkr * xi - wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + +void dctsub(int n, double *a, int nc, double *c) { + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[j] - wkr * a[k]; + a[j] = wkr * a[j] + wki * a[k]; + a[k] = xr; + } + a[m] *= c[0]; +} + +void dstsub(int n, double *a, int nc, double *c) { + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[k] - wkr * a[j]; + a[k] = wkr * a[k] + wki * a[j]; + a[j] = xr; + } + a[m] *= c[0]; +} + diff --git a/libsvc/Modules/Lib/World/src/harvest.cpp b/libsvc/Modules/Lib/World/src/harvest.cpp new file mode 100644 index 0000000..762c245 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/harvest.cpp @@ -0,0 +1,1262 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// F0 estimation based on Harvest. +//----------------------------------------------------------------------------- +#include "world/harvest.h" + +#include + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/fft.h" +#include "world/matlabfunctions.h" + +//----------------------------------------------------------------------------- +// struct for RawEventByHarvest() +// "negative" means "zero-crossing point going from positive to negative" +// "positive" means "zero-crossing point going from negative to positive" +//----------------------------------------------------------------------------- +typedef struct { + double *negative_interval_locations; + double *negative_intervals; + int number_of_negatives; + double *positive_interval_locations; + double *positive_intervals; + int number_of_positives; + double *peak_interval_locations; + double *peak_intervals; + int number_of_peaks; + double *dip_interval_locations; + double *dip_intervals; + int number_of_dips; +} ZeroCrossings; + +namespace { +//----------------------------------------------------------------------------- +// Since the waveform of beginning and ending after decimate include noise, +// the input waveform is extended. This is the processing for the +// compatibility with MATLAB version. +//----------------------------------------------------------------------------- +static void GetWaveformAndSpectrumSub(const double *x, int x_length, + int y_length, double actual_fs, int decimation_ratio, double *y) { + if (decimation_ratio == 1) { + for (int i = 0; i < x_length; ++i) y[i] = x[i]; + return; + } + + int lag = + static_cast(ceil(140.0 / decimation_ratio) * decimation_ratio); + int new_x_length = x_length + lag * 2; + double *new_y = new double[new_x_length]; + for (int i = 0; i < new_x_length; ++i) new_y[i] = 0.0; + double *new_x = new double[new_x_length]; + for (int i = 0; i < lag; ++i) new_x[i] = x[0]; + for (int i = lag; i < lag + x_length; ++i) new_x[i] = x[i - lag]; + for (int i = lag + x_length; i < new_x_length; ++i) + new_x[i] = x[x_length - 1]; + + decimate(new_x, new_x_length, decimation_ratio, new_y); + for (int i = 0; i < y_length; ++i) y[i] = new_y[lag / decimation_ratio + i]; + + delete[] new_x; + delete[] new_y; +} + +//----------------------------------------------------------------------------- +// GetWaveformAndSpectrum() calculates the downsampled signal and its spectrum +//----------------------------------------------------------------------------- +static void GetWaveformAndSpectrum(const double *x, int x_length, + int y_length, double actual_fs, int fft_size, int decimation_ratio, + double *y, fft_complex *y_spectrum) { + // Initialization + for (int i = 0; i < fft_size; ++i) y[i] = 0.0; + + // Processing for the compatibility with MATLAB version + GetWaveformAndSpectrumSub(x, x_length, y_length, actual_fs, + decimation_ratio, y); + + // Removal of the DC component (y = y - mean value of y) + double mean_y = 0.0; + for (int i = 0; i < y_length; ++i) mean_y += y[i]; + mean_y /= y_length; + for (int i = 0; i < y_length; ++i) y[i] -= mean_y; + for (int i = y_length; i < fft_size; ++i) y[i] = 0.0; + + fft_plan forwardFFT = + fft_plan_dft_r2c_1d(fft_size, y, y_spectrum, FFT_ESTIMATE); + fft_execute(forwardFFT); + + fft_destroy_plan(forwardFFT); +} + +//----------------------------------------------------------------------------- +// GetFilteredSignal() calculates the signal that is the convolution of the +// input signal and band-pass filter. +//----------------------------------------------------------------------------- +static void GetFilteredSignal(double boundary_f0, int fft_size, double fs, + const fft_complex *y_spectrum, int y_length, double *filtered_signal) { + int filter_length_half = matlab_round(fs / boundary_f0 * 2.0); + double *band_pass_filter = new double[fft_size]; + NuttallWindow(filter_length_half * 2 + 1, band_pass_filter); + for (int i = -filter_length_half; i <= filter_length_half; ++i) + band_pass_filter[i + filter_length_half] *= + cos(2 * world::kPi * boundary_f0 * i / fs); + for (int i = filter_length_half * 2 + 1; i < fft_size; ++i) + band_pass_filter[i] = 0.0; + + fft_complex *band_pass_filter_spectrum = new fft_complex[fft_size]; + fft_plan forwardFFT = fft_plan_dft_r2c_1d(fft_size, band_pass_filter, + band_pass_filter_spectrum, FFT_ESTIMATE); + fft_execute(forwardFFT); + + // Convolution + double tmp = y_spectrum[0][0] * band_pass_filter_spectrum[0][0] - + y_spectrum[0][1] * band_pass_filter_spectrum[0][1]; + band_pass_filter_spectrum[0][1] = + y_spectrum[0][0] * band_pass_filter_spectrum[0][1] + + y_spectrum[0][1] * band_pass_filter_spectrum[0][0]; + band_pass_filter_spectrum[0][0] = tmp; + for (int i = 1; i <= fft_size / 2; ++i) { + tmp = y_spectrum[i][0] * band_pass_filter_spectrum[i][0] - + y_spectrum[i][1] * band_pass_filter_spectrum[i][1]; + band_pass_filter_spectrum[i][1] = + y_spectrum[i][0] * band_pass_filter_spectrum[i][1] + + y_spectrum[i][1] * band_pass_filter_spectrum[i][0]; + band_pass_filter_spectrum[i][0] = tmp; + band_pass_filter_spectrum[fft_size - i - 1][0] = + band_pass_filter_spectrum[i][0]; + band_pass_filter_spectrum[fft_size - i - 1][1] = + band_pass_filter_spectrum[i][1]; + } + + fft_plan inverseFFT = fft_plan_dft_c2r_1d(fft_size, + band_pass_filter_spectrum, filtered_signal, FFT_ESTIMATE); + fft_execute(inverseFFT); + + // Compensation of the delay. + int index_bias = filter_length_half + 1; + for (int i = 0; i < y_length; ++i) + filtered_signal[i] = filtered_signal[i + index_bias]; + + fft_destroy_plan(inverseFFT); + fft_destroy_plan(forwardFFT); + delete[] band_pass_filter_spectrum; + delete[] band_pass_filter; +} + +//----------------------------------------------------------------------------- +// CheckEvent() returns 1, provided that the input value is over 1. +// This function is for RawEventByDio(). +//----------------------------------------------------------------------------- +static inline int CheckEvent(int x) { + return x > 0 ? 1 : 0; +} + +//----------------------------------------------------------------------------- +// ZeroCrossingEngine() calculates the zero crossing points from positive to +// negative. +//----------------------------------------------------------------------------- +static int ZeroCrossingEngine(const double *filtered_signal, int y_length, + double fs, double *interval_locations, double *intervals) { + int *negative_going_points = new int[y_length]; + + for (int i = 0; i < y_length - 1; ++i) + negative_going_points[i] = + 0.0 < filtered_signal[i] && filtered_signal[i + 1] <= 0.0 ? i + 1 : 0; + negative_going_points[y_length - 1] = 0; + + int *edges = new int[y_length]; + int count = 0; + for (int i = 0; i < y_length; ++i) + if (negative_going_points[i] > 0) + edges[count++] = negative_going_points[i]; + + if (count < 2) { + delete[] edges; + delete[] negative_going_points; + return 0; + } + + double *fine_edges = new double[count]; + for (int i = 0; i < count; ++i) + fine_edges[i] = edges[i] - filtered_signal[edges[i] - 1] / + (filtered_signal[edges[i]] - filtered_signal[edges[i] - 1]); + + for (int i = 0; i < count - 1; ++i) { + intervals[i] = fs / (fine_edges[i + 1] - fine_edges[i]); + interval_locations[i] = (fine_edges[i] + fine_edges[i + 1]) / 2.0 / fs; + } + + delete[] fine_edges; + delete[] edges; + delete[] negative_going_points; + return count - 1; +} + +//----------------------------------------------------------------------------- +// GetFourZeroCrossingIntervals() calculates four zero-crossing intervals. +// (1) Zero-crossing going from negative to positive. +// (2) Zero-crossing going from positive to negative. +// (3) Peak, and (4) dip. (3) and (4) are calculated from the zero-crossings of +// the differential of waveform. +//----------------------------------------------------------------------------- +static void GetFourZeroCrossingIntervals(double *filtered_signal, int y_length, + double actual_fs, ZeroCrossings *zero_crossings) { + int maximum_number = y_length; + zero_crossings->negative_interval_locations = new double[maximum_number]; + zero_crossings->positive_interval_locations = new double[maximum_number]; + zero_crossings->peak_interval_locations = new double[maximum_number]; + zero_crossings->dip_interval_locations = new double[maximum_number]; + zero_crossings->negative_intervals = new double[maximum_number]; + zero_crossings->positive_intervals = new double[maximum_number]; + zero_crossings->peak_intervals = new double[maximum_number]; + zero_crossings->dip_intervals = new double[maximum_number]; + + zero_crossings->number_of_negatives = ZeroCrossingEngine(filtered_signal, + y_length, actual_fs, zero_crossings->negative_interval_locations, + zero_crossings->negative_intervals); + + for (int i = 0; i < y_length; ++i) filtered_signal[i] = -filtered_signal[i]; + zero_crossings->number_of_positives = ZeroCrossingEngine(filtered_signal, + y_length, actual_fs, zero_crossings->positive_interval_locations, + zero_crossings->positive_intervals); + + for (int i = 0; i < y_length - 1; ++i) filtered_signal[i] = + filtered_signal[i] - filtered_signal[i + 1]; + zero_crossings->number_of_peaks = ZeroCrossingEngine(filtered_signal, + y_length - 1, actual_fs, zero_crossings->peak_interval_locations, + zero_crossings->peak_intervals); + + for (int i = 0; i < y_length - 1; ++i) + filtered_signal[i] = -filtered_signal[i]; + zero_crossings->number_of_dips = ZeroCrossingEngine(filtered_signal, + y_length - 1, actual_fs, zero_crossings->dip_interval_locations, + zero_crossings->dip_intervals); +} + +static void GetF0CandidateContourSub(const double * const *interpolated_f0_set, + int f0_length, double f0_floor, double f0_ceil, double boundary_f0, + double *f0_candidate) { + double upper = boundary_f0 * 1.1; + double lower = boundary_f0 * 0.9; + for (int i = 0; i < f0_length; ++i) { + f0_candidate[i] = (interpolated_f0_set[0][i] + + interpolated_f0_set[1][i] + interpolated_f0_set[2][i] + + interpolated_f0_set[3][i]) / 4.0; + + if (f0_candidate[i] > upper || f0_candidate[i] < lower || + f0_candidate[i] > f0_ceil || f0_candidate[i] < f0_floor) + f0_candidate[i] = 0.0; + } +} + +//----------------------------------------------------------------------------- +// GetF0CandidateContour() calculates the F0 candidate contour in 1-ch signal. +// Calculation of F0 candidates is carried out in GetF0CandidatesSub(). +//----------------------------------------------------------------------------- +static void GetF0CandidateContour(const ZeroCrossings *zero_crossings, + double boundary_f0, double f0_floor, double f0_ceil, + const double *temporal_positions, int f0_length, double *f0_candidate) { + if (0 == CheckEvent(zero_crossings->number_of_negatives - 2) * + CheckEvent(zero_crossings->number_of_positives - 2) * + CheckEvent(zero_crossings->number_of_peaks - 2) * + CheckEvent(zero_crossings->number_of_dips - 2)) { + for (int i = 0; i < f0_length; ++i) f0_candidate[i] = 0.0; + return; + } + + double *interpolated_f0_set[4]; + for (int i = 0; i < 4; ++i) + interpolated_f0_set[i] = new double[f0_length]; + + interp1(zero_crossings->negative_interval_locations, + zero_crossings->negative_intervals, + zero_crossings->number_of_negatives, + temporal_positions, f0_length, interpolated_f0_set[0]); + interp1(zero_crossings->positive_interval_locations, + zero_crossings->positive_intervals, + zero_crossings->number_of_positives, + temporal_positions, f0_length, interpolated_f0_set[1]); + interp1(zero_crossings->peak_interval_locations, + zero_crossings->peak_intervals, zero_crossings->number_of_peaks, + temporal_positions, f0_length, interpolated_f0_set[2]); + interp1(zero_crossings->dip_interval_locations, + zero_crossings->dip_intervals, zero_crossings->number_of_dips, + temporal_positions, f0_length, interpolated_f0_set[3]); + + GetF0CandidateContourSub(interpolated_f0_set, f0_length, f0_floor, + f0_ceil, boundary_f0, f0_candidate); + for (int i = 0; i < 4; ++i) delete[] interpolated_f0_set[i]; +} + +//----------------------------------------------------------------------------- +// DestroyZeroCrossings() frees the memory of array in the struct +//----------------------------------------------------------------------------- +static void DestroyZeroCrossings(ZeroCrossings *zero_crossings) { + delete[] zero_crossings->negative_interval_locations; + delete[] zero_crossings->positive_interval_locations; + delete[] zero_crossings->peak_interval_locations; + delete[] zero_crossings->dip_interval_locations; + delete[] zero_crossings->negative_intervals; + delete[] zero_crossings->positive_intervals; + delete[] zero_crossings->peak_intervals; + delete[] zero_crossings->dip_intervals; +} + +//----------------------------------------------------------------------------- +// GetF0CandidateFromRawEvent() f0 candidate contour in 1-ch signal +//----------------------------------------------------------------------------- +static void GetF0CandidateFromRawEvent(double boundary_f0, double fs, + const fft_complex *y_spectrum, int y_length, int fft_size, double f0_floor, + double f0_ceil, const double *temporal_positions, int f0_length, + double *f0_candidate) { + double *filtered_signal = new double[fft_size]; + GetFilteredSignal(boundary_f0, fft_size, fs, y_spectrum, + y_length, filtered_signal); + + ZeroCrossings zero_crossings = { 0 }; + GetFourZeroCrossingIntervals(filtered_signal, y_length, fs, + &zero_crossings); + + GetF0CandidateContour(&zero_crossings, boundary_f0, f0_floor, f0_ceil, + temporal_positions, f0_length, f0_candidate); + + DestroyZeroCrossings(&zero_crossings); + delete[] filtered_signal; +} + +//----------------------------------------------------------------------------- +// GetRawF0Candidates() calculates f0 candidates in all channels. +//----------------------------------------------------------------------------- +static void GetRawF0Candidates(const double *boundary_f0_list, + int number_of_bands, double actual_fs, int y_length, + const double *temporal_positions, int f0_length, + const fft_complex *y_spectrum, int fft_size, double f0_floor, + double f0_ceil, double **raw_f0_candidates) { + for (int i = 0; i < number_of_bands; ++i) + GetF0CandidateFromRawEvent(boundary_f0_list[i], actual_fs, y_spectrum, + y_length, fft_size, f0_floor, f0_ceil, temporal_positions, f0_length, + raw_f0_candidates[i]); +} + +//----------------------------------------------------------------------------- +// DetectF0CandidatesSub1() calculates VUV areas. +//----------------------------------------------------------------------------- +static int DetectOfficialF0CandidatesSub1(const int *vuv, + int number_of_channels, int *st, int *ed) { + int number_of_voiced_sections = 0; + int tmp; + for (int i = 1; i < number_of_channels; ++i) { + tmp = vuv[i] - vuv[i - 1]; + if (tmp == 1) st[number_of_voiced_sections] = i; + if (tmp == -1) ed[number_of_voiced_sections++] = i; + } + + return number_of_voiced_sections; +} + +//----------------------------------------------------------------------------- +// DetectOfficialF0CandidatesSub2() calculates F0 candidates in a frame +//----------------------------------------------------------------------------- +static int DetectOfficialF0CandidatesSub2(const int *vuv, + const double * const *raw_f0_candidates, int index, + int number_of_voiced_sections, const int *st, const int *ed, + int max_candidates, double *f0_list) { + int number_of_candidates = 0; + double tmp_f0; + for (int i = 0; i < number_of_voiced_sections; ++i) { + if (ed[i] - st[i] < 10) continue; + + tmp_f0 = 0.0; + for (int j = st[i]; j < ed[i]; ++j) + tmp_f0 += raw_f0_candidates[j][index]; + tmp_f0 /= (ed[i] - st[i]); + f0_list[number_of_candidates++] = tmp_f0; + } + + for (int i = number_of_candidates; i < max_candidates; ++i) f0_list[i] = 0.0; + return number_of_candidates; +} + +//----------------------------------------------------------------------------- +// DetectOfficialF0Candidates() detectes F0 candidates from multi-channel +// candidates. +//----------------------------------------------------------------------------- +static int DetectOfficialF0Candidates(const double * const * raw_f0_candidates, + int number_of_channels, int f0_length, int max_candidates, + double **f0_candidates) { + int number_of_candidates = 0; + + int *vuv = new int[number_of_channels]; + int *st = new int[number_of_channels]; + int *ed = new int[number_of_channels]; + int number_of_voiced_sections; + for (int i = 0; i < f0_length; ++i) { + for (int j = 0; j < number_of_channels; ++j) + vuv[j] = raw_f0_candidates[j][i] > 0 ? 1 : 0; + vuv[0] = vuv[number_of_channels - 1] = 0; + number_of_voiced_sections = DetectOfficialF0CandidatesSub1(vuv, + number_of_channels, st, ed); + number_of_candidates = MyMaxInt(number_of_candidates, + DetectOfficialF0CandidatesSub2(vuv, raw_f0_candidates, i, + number_of_voiced_sections, st, ed, max_candidates, f0_candidates[i])); + } + + delete[] vuv; + delete[] st; + delete[] ed; + return number_of_candidates; +} + +//----------------------------------------------------------------------------- +// OverlapF0Candidates() spreads the candidates to anteroposterior frames. +//----------------------------------------------------------------------------- +static void OverlapF0Candidates(int f0_length, int number_of_candidates, + double **f0_candidates) { + int n = 3; + for (int i = 1; i <= n; ++i) + for (int j = 0; j < number_of_candidates; ++j) { + for (int k = i; k < f0_length; ++k) + f0_candidates[k][j + (number_of_candidates * i)] = + f0_candidates[k - i][j]; + for (int k = 0; k < f0_length - i; ++k) + f0_candidates[k][j + (number_of_candidates * (i + n))] = + f0_candidates[k + i][j]; + } +} + +//----------------------------------------------------------------------------- +// GetBaseIndex() calculates the temporal positions for windowing. +//----------------------------------------------------------------------------- +static void GetBaseIndex(double current_position, const double *base_time, + int base_time_length, double fs, int *base_index) { + // First-aid treatment + int basic_index = + matlab_round((current_position + base_time[0]) * fs + 0.001); + + for (int i = 0; i < base_time_length; ++i) base_index[i] = basic_index + i; +} + +//----------------------------------------------------------------------------- +// GetMainWindow() generates the window function. +//----------------------------------------------------------------------------- +static void GetMainWindow(double current_position, const int *base_index, + int base_time_length, double fs, double window_length_in_time, + double *main_window) { + double tmp = 0.0; + for (int i = 0; i < base_time_length; ++i) { + tmp = (base_index[i] - 1.0) / fs - current_position; + main_window[i] = 0.42 + + 0.5 * cos(2.0 * world::kPi * tmp / window_length_in_time) + + 0.08 * cos(4.0 * world::kPi * tmp / window_length_in_time); + } +} + +//----------------------------------------------------------------------------- +// GetDiffWindow() generates the differentiated window. +// Diff means differential. +//----------------------------------------------------------------------------- +static void GetDiffWindow(const double *main_window, int base_time_length, + double *diff_window) { + diff_window[0] = -main_window[1] / 2.0; + for (int i = 1; i < base_time_length - 1; ++i) + diff_window[i] = -(main_window[i + 1] - main_window[i - 1]) / 2.0; + diff_window[base_time_length - 1] = main_window[base_time_length - 2] / 2.0; +} + +//----------------------------------------------------------------------------- +// GetSpectra() calculates two spectra of the waveform windowed by windows +// (main window and diff window). +//----------------------------------------------------------------------------- +static void GetSpectra(const double *x, int x_length, int fft_size, + const int *base_index, const double *main_window, + const double *diff_window, int base_time_length, + const ForwardRealFFT *forward_real_fft, fft_complex *main_spectrum, + fft_complex *diff_spectrum) { + int *safe_index = new int[base_time_length]; + + for (int i = 0; i < base_time_length; ++i) + safe_index[i] = MyMaxInt(0, MyMinInt(x_length - 1, base_index[i] - 1)); + for (int i = 0; i < base_time_length; ++i) + forward_real_fft->waveform[i] = x[safe_index[i]] * main_window[i]; + for (int i = base_time_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) { + main_spectrum[i][0] = forward_real_fft->spectrum[i][0]; + main_spectrum[i][1] = forward_real_fft->spectrum[i][1]; + } + + for (int i = 0; i < base_time_length; ++i) + forward_real_fft->waveform[i] = x[safe_index[i]] * diff_window[i]; + for (int i = base_time_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) { + diff_spectrum[i][0] = forward_real_fft->spectrum[i][0]; + diff_spectrum[i][1] = forward_real_fft->spectrum[i][1]; + } + + delete[] safe_index; +} + +static void FixF0(const double *power_spectrum, const double *numerator_i, + int fft_size, double fs, double current_f0, int number_of_harmonics, + double *refined_f0, double *score) { + double *amplitude_list = new double[number_of_harmonics]; + double *instantaneous_frequency_list = new double[number_of_harmonics]; + + int index; + for (int i = 0; i < number_of_harmonics; ++i) { + index = matlab_round(current_f0 * fft_size / fs * (i + 1)); + instantaneous_frequency_list[i] = power_spectrum[index] == 0.0 ? 0.0 : + static_cast(index) * fs / fft_size + + numerator_i[index] / power_spectrum[index] * fs / 2.0 / world::kPi; + amplitude_list[i] = sqrt(power_spectrum[index]); + } + double denominator = 0.0; + double numerator = 0.0; + *score = 0.0; + for (int i = 0; i < number_of_harmonics; ++i) { + numerator += amplitude_list[i] * instantaneous_frequency_list[i]; + denominator += amplitude_list[i] * (i + 1.0); + *score += fabs((instantaneous_frequency_list[i] / (i + 1.0) - current_f0) / + current_f0); + } + + *refined_f0 = numerator / (denominator + world::kMySafeGuardMinimum); + *score = 1.0 / (*score / number_of_harmonics + world::kMySafeGuardMinimum); + + delete[] amplitude_list; + delete[] instantaneous_frequency_list; +} + +//----------------------------------------------------------------------------- +// GetMeanF0() calculates the instantaneous frequency. +//----------------------------------------------------------------------------- +static void GetMeanF0(const double *x, int x_length, double fs, + double current_position, double current_f0, int fft_size, + double window_length_in_time, const double *base_time, + int base_time_length, double *refined_f0, double *refined_score) { + ForwardRealFFT forward_real_fft = { 0 }; + InitializeForwardRealFFT(fft_size, &forward_real_fft); + fft_complex *main_spectrum = new fft_complex[fft_size]; + fft_complex *diff_spectrum = new fft_complex[fft_size]; + + int *base_index = new int[base_time_length]; + double *main_window = new double[base_time_length]; + double *diff_window = new double[base_time_length]; + + GetBaseIndex(current_position, base_time, base_time_length, fs, base_index); + GetMainWindow(current_position, base_index, base_time_length, fs, + window_length_in_time, main_window); + GetDiffWindow(main_window, base_time_length, diff_window); + + GetSpectra(x, x_length, fft_size, base_index, main_window, diff_window, + base_time_length, &forward_real_fft, main_spectrum, diff_spectrum); + + double *power_spectrum = new double[fft_size / 2 + 1]; + double *numerator_i = new double[fft_size / 2 + 1]; + for (int j = 0; j <= fft_size / 2; ++j) { + numerator_i[j] = main_spectrum[j][0] * diff_spectrum[j][1] - + main_spectrum[j][1] * diff_spectrum[j][0]; + power_spectrum[j] = main_spectrum[j][0] * main_spectrum[j][0] + + main_spectrum[j][1] * main_spectrum[j][1]; + } + + int number_of_harmonics = + MyMinInt(static_cast(fs / 2.0 / current_f0), 6); + FixF0(power_spectrum, numerator_i, fft_size, fs, current_f0, + number_of_harmonics, refined_f0, refined_score); + + delete[] diff_spectrum; + delete[] diff_window; + delete[] main_window; + delete[] base_index; + delete[] numerator_i; + delete[] power_spectrum; + delete[] main_spectrum; + DestroyForwardRealFFT(&forward_real_fft); +} + +//----------------------------------------------------------------------------- +// GetRefinedF0() calculates F0 and its score based on instantaneous frequency. +//----------------------------------------------------------------------------- +static void GetRefinedF0(const double *x, int x_length, double fs, + double current_position, double current_f0, double f0_floor, double f0_ceil, + double *refined_f0, double *refined_score) { + if (current_f0 <= 0.0) { + *refined_f0 = 0.0; + *refined_score = 0.0; + return; + } + + int half_window_length = static_cast(1.5 * fs / current_f0 + 1.0); + double window_length_in_time = (2.0 * half_window_length + 1.0) / fs; + double *base_time = new double[half_window_length * 2 + 1]; + for (int i = 0; i < half_window_length * 2 + 1; i++) + base_time[i] = (-half_window_length + i) / fs; + int fft_size = static_cast(pow(2.0, 2.0 + + static_cast(log(half_window_length * 2.0 + 1.0) / world::kLog2))); + + GetMeanF0(x, x_length, fs, current_position, current_f0, fft_size, + window_length_in_time, base_time, half_window_length * 2 + 1, + refined_f0, refined_score); + + if (*refined_f0 < f0_floor || *refined_f0 > f0_ceil || + *refined_score < 2.5) { + *refined_f0 = 0.0; + *refined_score = 0.0; + } + + delete[] base_time; +} + +//----------------------------------------------------------------------------- +// RefineF0() modifies the F0 by instantaneous frequency. +//----------------------------------------------------------------------------- +static void RefineF0Candidates(const double *x, int x_length, double fs, + const double *temporal_positions, int f0_length, int max_candidates, + double f0_floor, double f0_ceil, + double **refined_f0_candidates, double **f0_scores) { + for (int i = 0; i < f0_length; i++) + for (int j = 0; j < max_candidates; ++j) + GetRefinedF0(x, x_length, fs, temporal_positions[i], + refined_f0_candidates[i][j], f0_floor, f0_ceil, + &refined_f0_candidates[i][j], &f0_scores[i][j]); +} + +//----------------------------------------------------------------------------- +// SelectBestF0() obtains the nearlest F0 in reference_f0. +//----------------------------------------------------------------------------- +static double SelectBestF0(double reference_f0, const double *f0_candidates, + int number_of_candidates, double allowed_range, double *best_error) { + double best_f0 = 0.0; + *best_error = allowed_range; + + double tmp; + for (int i = 0; i < number_of_candidates; ++i) { + tmp = fabs(reference_f0 - f0_candidates[i]) / reference_f0; + if (tmp > *best_error) continue; + best_f0 = f0_candidates[i]; + *best_error = tmp; + } + + return best_f0; +} + +static void RemoveUnreliableCandidatesSub(int i, int j, + const double * const *tmp_f0_candidates, int number_of_candidates, + double **f0_candidates, double **f0_scores) { + double reference_f0 = f0_candidates[i][j]; + double error1, error2, min_error; + double threshold = 0.05; + if (reference_f0 == 0) return; + SelectBestF0(reference_f0, tmp_f0_candidates[i + 1], + number_of_candidates, 1.0, &error1); + SelectBestF0(reference_f0, tmp_f0_candidates[i - 1], + number_of_candidates, 1.0, &error2); + min_error = MyMinDouble(error1, error2); + if (min_error <= threshold) return; + f0_candidates[i][j] = 0; + f0_scores[i][j] = 0; +} + +//----------------------------------------------------------------------------- +// RemoveUnreliableCandidates(). +//----------------------------------------------------------------------------- +static void RemoveUnreliableCandidates(int f0_length, int number_of_candidates, + double **f0_candidates, double **f0_scores) { + double **tmp_f0_candidates = new double *[f0_length]; + for (int i = 0; i < f0_length; ++i) + tmp_f0_candidates[i] = new double[number_of_candidates]; + for (int i = 0; i < f0_length; ++i) + for (int j = 0; j < number_of_candidates; ++j) + tmp_f0_candidates[i][j] = f0_candidates[i][j]; + + for (int i = 1; i < f0_length - 1; ++i) + for (int j = 0; j < number_of_candidates; ++j) + RemoveUnreliableCandidatesSub(i, j, tmp_f0_candidates, + number_of_candidates, f0_candidates, f0_scores); + + for (int i = 0; i < f0_length; ++i) delete[] tmp_f0_candidates[i]; + delete[] tmp_f0_candidates; +} + +//----------------------------------------------------------------------------- +// SearchF0Base() gets the F0 with the highest score. +//----------------------------------------------------------------------------- +static void SearchF0Base(const double * const *f0_candidates, + const double * const *f0_scores, int f0_length, int number_of_candidates, + double *base_f0_contour) { + double tmp_best_score; + for (int i = 0; i < f0_length; ++i) { + base_f0_contour[i] = tmp_best_score = 0.0; + for (int j = 0; j < number_of_candidates; ++j) + if (f0_scores[i][j] > tmp_best_score) { + base_f0_contour[i] = f0_candidates[i][j]; + tmp_best_score = f0_scores[i][j]; + } + } +} + +//----------------------------------------------------------------------------- +// Step 1: Rapid change of F0 contour is replaced by 0. +//----------------------------------------------------------------------------- +static void FixStep1(const double *f0_base, int f0_length, + double allowed_range, double *f0_step1) { + for (int i = 0; i < f0_length; ++i) f0_step1[i] = 0.0; + double reference_f0; + for (int i = 2; i < f0_length; ++i) { + if (f0_base[i] == 0.0) continue; + reference_f0 = f0_base[i - 1] * 2 - f0_base[i - 2]; + f0_step1[i] = + fabs((f0_base[i] - reference_f0) / reference_f0) > allowed_range && + fabs((f0_base[i] - f0_base[i - 1])) / f0_base[i - 1] > allowed_range ? + 0.0 : f0_base[i]; + } +} + +//----------------------------------------------------------------------------- +// GetBoundaryList() detects boundaries between voiced and unvoiced sections. +//----------------------------------------------------------------------------- +static int GetBoundaryList(const double *f0, int f0_length, + int *boundary_list) { + int number_of_boundaries = 0; + int *vuv = new int[f0_length]; + for (int i = 0; i < f0_length; ++i) + vuv[i] = f0[i] > 0 ? 1 : 0; + vuv[0] = vuv[f0_length - 1] = 0; + + for (int i = 1; i < f0_length; ++i) + if (vuv[i] - vuv[i - 1] != 0) { + boundary_list[number_of_boundaries] = i - number_of_boundaries % 2; + number_of_boundaries++; + } + + delete[] vuv; + return number_of_boundaries; +} + +//----------------------------------------------------------------------------- +// Step 2: Voiced sections with a short period are removed. +//----------------------------------------------------------------------------- +static void FixStep2(const double *f0_step1, int f0_length, + int voice_range_minimum, double *f0_step2) { + for (int i = 0; i < f0_length; ++i) f0_step2[i] = f0_step1[i]; + int *boundary_list = new int[f0_length]; + int number_of_boundaries = + GetBoundaryList(f0_step1, f0_length, boundary_list); + + for (int i = 0; i < number_of_boundaries / 2; ++i) { + if (boundary_list[i * 2 + 1] - boundary_list[i * 2] >= voice_range_minimum) + continue; + for (int j = boundary_list[i * 2]; j <= boundary_list[(i * 2) + 1]; ++j) + f0_step2[j] = 0.0; + } + delete[] boundary_list; +} + +//----------------------------------------------------------------------------- +// GetMultiChannelF0() separates each voiced section into independent channel. +//----------------------------------------------------------------------------- +static void GetMultiChannelF0(const double *f0, int f0_length, + const int *boundary_list, int number_of_boundaries, + double **multi_channel_f0) { + for (int i = 0; i < number_of_boundaries / 2; ++i) { + for (int j = 0; j < boundary_list[i * 2]; ++j) + multi_channel_f0[i][j] = 0.0; + for (int j = boundary_list[i * 2]; j <= boundary_list[i * 2 + 1]; ++j) + multi_channel_f0[i][j] = f0[j]; + for (int j = boundary_list[i * 2 + 1] + 1; j < f0_length; ++j) + multi_channel_f0[i][j] = 0.0; + } +} + +//----------------------------------------------------------------------------- +// abs() often causes bugs, an original function is used. +//----------------------------------------------------------------------------- +static inline int MyAbsInt(int x) { + return x > 0 ? x : -x; +} + +//----------------------------------------------------------------------------- +// ExtendF0() : The Hand erasing the Space. +// The subfunction of Extend(). +//----------------------------------------------------------------------------- +static int ExtendF0(const double *f0, int f0_length, int origin, + int last_point, int shift, const double * const *f0_candidates, + int number_of_candidates, double allowed_range, double *extended_f0) { + int threshold = 4; + double tmp_f0 = extended_f0[origin]; + int shifted_origin = origin; + + int distance = MyAbsInt(last_point - origin); + int *index_list = new int[distance + 1]; + for (int i = 0; i <= distance; ++i) index_list[i] = origin + shift * i; + + int count = 0; + double dammy; + for (int i = 0; i <= distance; ++i) { + extended_f0[index_list[i] + shift] = + SelectBestF0(tmp_f0, f0_candidates[index_list[i] + shift], + number_of_candidates, allowed_range, &dammy); + if (extended_f0[index_list[i] + shift] == 0.0) { + count++; + } else { + tmp_f0 = extended_f0[index_list[i] + shift]; + count = 0; + shifted_origin = index_list[i] + shift; + } + if (count == threshold) break; + } + + delete[] index_list; + return shifted_origin; +} + +//----------------------------------------------------------------------------- +// Swap the f0 contour and boundary. +// It is used in ExtendSub() and MergeF0(); +//----------------------------------------------------------------------------- +static void Swap(int index1, int index2, double **f0, int *boundary) { + double *tmp_pointer; + int tmp_index; + tmp_pointer = f0[index1]; + f0[index1] = f0[index2]; + f0[index2] = tmp_pointer; + tmp_index = boundary[index1 * 2]; + boundary[index1 * 2] = boundary[index2 * 2]; + boundary[index2 * 2] = tmp_index; + tmp_index = boundary[index1 * 2 + 1]; + boundary[index1 * 2 + 1] = boundary[index2 * 2 + 1]; + boundary[index2 * 2 + 1] = tmp_index; +} + +static int ExtendSub(const double * const *extended_f0, + const int *boundary_list, int number_of_sections, + double **selected_extended_f0, int *selected_boundary_list) { + double threshold = 2200.0; + int count = 0; + double mean_f0 = 0.0; + int st, ed; + for (int i = 0; i < number_of_sections; ++i) { + st = boundary_list[i * 2]; + ed = boundary_list[i * 2 + 1]; + for (int j = st; j < ed; ++j) mean_f0 += extended_f0[i][j]; + mean_f0 /= ed - st; + if (threshold / mean_f0 < ed - st) + Swap(count++, i, selected_extended_f0, selected_boundary_list); + } + return count; +} + +//----------------------------------------------------------------------------- +// Extend() : The Hand erasing the Space. +//----------------------------------------------------------------------------- +static int Extend(const double * const *multi_channel_f0, + int number_of_sections, int f0_length, const int *boundary_list, + const double * const *f0_candidates, int number_of_candidates, + double allowed_range, double **extended_f0, int *shifted_boundary_list) { + int threshold = 100; + for (int i = 0; i < number_of_sections; ++i) { + shifted_boundary_list[i * 2 + 1] = ExtendF0(multi_channel_f0[i], + f0_length, boundary_list[i * 2 + 1], + MyMinInt(f0_length - 2, boundary_list[i * 2 + 1] + threshold), 1, + f0_candidates, number_of_candidates, allowed_range, extended_f0[i]); + shifted_boundary_list[i * 2] = ExtendF0(multi_channel_f0[i], f0_length, + boundary_list[i * 2], MyMaxInt(1, boundary_list[i * 2] - threshold), -1, + f0_candidates, number_of_candidates, allowed_range, extended_f0[i]); + } + + return ExtendSub(multi_channel_f0, shifted_boundary_list, + number_of_sections, extended_f0, shifted_boundary_list); +} + +//----------------------------------------------------------------------------- +// Indices are sorted. +//----------------------------------------------------------------------------- +static void MakeSortedOrder(const int *boundary_list, int number_of_sections, + int *order) { + for (int i = 0; i < number_of_sections; ++i) order[i] = i; + int tmp; + for (int i = 1; i < number_of_sections; ++i) + for (int j = i - 1; j >= 0; --j) + if (boundary_list[order[j] * 2] > boundary_list[order[i] * 2]) { + tmp = order[i]; + order[i] = order[j]; + order[j] = tmp; + } else { + break; + } +} + +//----------------------------------------------------------------------------- +// Serach the highest score with the candidate F0. +//----------------------------------------------------------------------------- +static double SearchScore(double f0, const double *f0_candidates, + const double *f0_scores, int number_of_candidates) { + double score = 0.0; + for (int i = 0; i < number_of_candidates; ++i) + if (f0 == f0_candidates[i] && score < f0_scores[i]) score = f0_scores[i]; + return score; +} + +//----------------------------------------------------------------------------- +// Subfunction of MergeF0() +//----------------------------------------------------------------------------- +static int MergeF0Sub(const double *f0_1, int f0_length, int st1, int ed1, + const double *f0_2, int st2, int ed2, const double * const *f0_candidates, + const double * const *f0_scores, int number_of_candidates, + double *merged_f0) { + if (st1 <= st2 && ed1 >= ed2) return ed1; + + double score1 = 0.0; + double score2 = 0.0; + for (int i = st2; i <= ed1; ++i) { + score1 += SearchScore(f0_1[i], f0_candidates[i], f0_scores[i], + number_of_candidates); + score2 += SearchScore(f0_2[i], f0_candidates[i], f0_scores[i], + number_of_candidates); + } + if (score1 > score2) + for (int i = ed1; i <= ed2; ++i) merged_f0[i] = f0_2[i]; + else + for (int i = st2; i <= ed2; ++i) merged_f0[i] = f0_2[i]; + + return ed2; +} + +//----------------------------------------------------------------------------- +// Overlapped F0 contours are merged by the likability score. +//----------------------------------------------------------------------------- +static void MergeF0(const double * const *multi_channel_f0, int *boundary_list, + int number_of_channels, int f0_length, const double * const *f0_candidates, + const double * const *f0_scores, int number_of_candidates, + double *merged_f0) { + int *order = new int[number_of_channels]; + MakeSortedOrder(boundary_list, number_of_channels, order); + + for (int i = 0; i < f0_length; ++i) + merged_f0[i] = multi_channel_f0[0][i]; + + for (int i = 1; i < number_of_channels; ++i) + if (boundary_list[order[i] * 2] - boundary_list[1] > 0) { + for (int j = boundary_list[order[i] * 2]; + j <= boundary_list[order[i] * 2 + 1]; ++j) + merged_f0[j] = multi_channel_f0[order[i]][j]; + boundary_list[0] = boundary_list[order[i] * 2]; + boundary_list[1] = boundary_list[order[i] * 2 + 1]; + } else { + boundary_list[1] = + MergeF0Sub(merged_f0, f0_length, boundary_list[0], boundary_list[1], + multi_channel_f0[order[i]], boundary_list[order[i] * 2], + boundary_list[order[i] * 2 + 1], f0_candidates, f0_scores, + number_of_candidates, merged_f0); + } + + delete[] order; +} + +//----------------------------------------------------------------------------- +// Step 3: Voiced sections are extended based on the continuity of F0 contour +//----------------------------------------------------------------------------- +static void FixStep3(const double *f0_step2, int f0_length, + int number_of_candidates, const double * const *f0_candidates, + double allowed_range, const double * const *f0_scores, double *f0_step3) { + for (int i = 0; i < f0_length; ++i) f0_step3[i] = f0_step2[i]; + int *boundary_list = new int[f0_length]; + int number_of_boundaries = + GetBoundaryList(f0_step2, f0_length, boundary_list); + + double **multi_channel_f0 = new double *[number_of_boundaries / 2]; + for (int i = 0; i < number_of_boundaries / 2; ++i) + multi_channel_f0[i] = new double[f0_length]; + GetMultiChannelF0(f0_step2, f0_length, boundary_list, number_of_boundaries, + multi_channel_f0); + + int number_of_channels = + Extend(multi_channel_f0, number_of_boundaries / 2, f0_length, + boundary_list, f0_candidates, number_of_candidates, allowed_range, + multi_channel_f0, boundary_list); + + if (number_of_channels != 0) + MergeF0(multi_channel_f0, boundary_list, number_of_channels, f0_length, + f0_candidates, f0_scores, number_of_candidates, f0_step3); + + for (int i = 0; i < number_of_boundaries / 2; ++i) + delete[] multi_channel_f0[i]; + delete[] multi_channel_f0; + delete[] boundary_list; +} + +//----------------------------------------------------------------------------- +// Step 4: F0s in short unvoiced section are faked +//----------------------------------------------------------------------------- +static void FixStep4(const double *f0_step3, int f0_length, int threshold, + double *f0_step4) { + for (int i = 0; i < f0_length; ++i) f0_step4[i] = f0_step3[i]; + int *boundary_list = new int[f0_length]; + int number_of_boundaries = + GetBoundaryList(f0_step3, f0_length, boundary_list); + + int distance; + double tmp0, tmp1, coefficient; + int count; + for (int i = 0; i < number_of_boundaries / 2 - 1; ++i) { + distance = boundary_list[(i + 1) * 2] - boundary_list[i * 2 + 1] - 1; + if (distance >= threshold) continue; + tmp0 = f0_step3[boundary_list[i * 2 + 1]] + 1; + tmp1 = f0_step3[boundary_list[(i + 1) * 2]] - 1; + coefficient = (tmp1 - tmp0) / (distance + 1.0); + count = 1; + for (int j = boundary_list[i * 2 + 1] + 1; + j <= boundary_list[(i + 1) * 2] - 1; ++j) + f0_step4[j] = tmp0 + coefficient * count++; + } + delete[] boundary_list; +} + +//----------------------------------------------------------------------------- +// FixF0Contour() obtains the likely F0 contour. +//----------------------------------------------------------------------------- +static void FixF0Contour(const double * const *f0_candidates, + const double * const *f0_scores, int f0_length, int number_of_candidates, + double *best_f0_contour) { + double *tmp_f0_contour1 = new double[f0_length]; + double *tmp_f0_contour2 = new double[f0_length]; + + // These parameters are optimized by speech databases. + SearchF0Base(f0_candidates, f0_scores, f0_length, + number_of_candidates, tmp_f0_contour1); + FixStep1(tmp_f0_contour1, f0_length, 0.008, tmp_f0_contour2); + FixStep2(tmp_f0_contour2, f0_length, 6, tmp_f0_contour1); + FixStep3(tmp_f0_contour1, f0_length, number_of_candidates, f0_candidates, + 0.18, f0_scores, tmp_f0_contour2); + FixStep4(tmp_f0_contour2, f0_length, 9, best_f0_contour); + + delete[] tmp_f0_contour1; + delete[] tmp_f0_contour2; +} + +//----------------------------------------------------------------------------- +// This function uses zero-lag Butterworth filter. +//----------------------------------------------------------------------------- +static void FilteringF0(const double *a, const double *b, double *x, + int x_length, int st, int ed, double *y) { + double w[2] = { 0.0, 0.0 }; + double wt; + double *tmp_x = new double[x_length]; + + for (int i = 0; i < st; ++i) x[i] = x[st]; + for (int i = ed + 1; i < x_length; ++i) x[i] = x[ed]; + + for (int i = 0; i < x_length; ++i) { + wt = x[i] + a[0] * w[0] + a[1] * w[1]; + tmp_x[x_length - i - 1] = b[0] * wt + b[1] * w[0] + b[0] * w[1]; + w[1] = w[0]; + w[0] = wt; + } + + w[0] = w[1] = 0.0; + for (int i = 0; i < x_length; ++i) { + wt = tmp_x[i] + a[0] * w[0] + a[1] * w[1]; + y[x_length - i - 1] = b[0] * wt + b[1] * w[0] + b[0] * w[1]; + w[1] = w[0]; + w[0] = wt; + } + + delete[] tmp_x; +} + +//----------------------------------------------------------------------------- +// SmoothF0Contour() uses the zero-lag Butterworth filter for smoothing. +//----------------------------------------------------------------------------- +static void SmoothF0Contour(const double *f0, int f0_length, + double *smoothed_f0) { + const double b[2] = + { 0.0078202080334971724, 0.015640416066994345 }; + const double a[2] = + { 1.7347257688092754, -0.76600660094326412 }; + int lag = 300; + int new_f0_length = f0_length + lag * 2; + double *f0_contour = new double[new_f0_length]; + for (int i = 0; i < lag; ++i) f0_contour[i] = 0.0; + for (int i = lag; i < lag + f0_length; ++i) f0_contour[i] = f0[i - lag]; + for (int i = lag + f0_length; i < new_f0_length; ++i) f0_contour[i] = 0.0; + + int *boundary_list = new int[new_f0_length]; + int number_of_boundaries = + GetBoundaryList(f0_contour, new_f0_length, boundary_list); + double **multi_channel_f0 = new double *[number_of_boundaries / 2]; + for (int i = 0; i < number_of_boundaries / 2; ++i) + multi_channel_f0[i] = new double[new_f0_length]; + GetMultiChannelF0(f0_contour, new_f0_length, boundary_list, + number_of_boundaries, multi_channel_f0); + + for (int i = 0; i < number_of_boundaries / 2; ++i) { + FilteringF0(a, b, multi_channel_f0[i], new_f0_length, + boundary_list[i * 2], boundary_list[i * 2 + 1], f0_contour); + for (int j = boundary_list[i * 2]; j <= boundary_list[i * 2 + 1]; ++j) + smoothed_f0[j - lag] = f0_contour[j]; + } + + for (int i = 0; i < number_of_boundaries / 2; ++i) + delete[] multi_channel_f0[i]; + delete[] multi_channel_f0; + delete[] f0_contour; + delete[] boundary_list; +} + +//----------------------------------------------------------------------------- +// HarvestGeneralBodySub() is the subfunction of HarvestGeneralBody() +//----------------------------------------------------------------------------- +static int HarvestGeneralBodySub(const double *boundary_f0_list, + int number_of_channels, int f0_length, double actual_fs, int y_length, + const double *temporal_positions, const fft_complex *y_spectrum, + int fft_size, double f0_floor, double f0_ceil, int max_candidates, + double **f0_candidates) { + double **raw_f0_candidates = new double *[number_of_channels]; + for (int i = 0; i < number_of_channels; ++i) + raw_f0_candidates[i] = new double[f0_length]; + + GetRawF0Candidates(boundary_f0_list, number_of_channels, + actual_fs, y_length, temporal_positions, f0_length, y_spectrum, + fft_size, f0_floor, f0_ceil, raw_f0_candidates); + + int number_of_candidates = DetectOfficialF0Candidates(raw_f0_candidates, + number_of_channels, f0_length, max_candidates, f0_candidates); + + OverlapF0Candidates(f0_length, number_of_candidates, f0_candidates); + + for (int i = 0; i < number_of_channels; ++i) + delete[] raw_f0_candidates[i]; + delete[] raw_f0_candidates; + return number_of_candidates; +} + +//----------------------------------------------------------------------------- +// HarvestGeneralBody() estimates the F0 contour based on Harvest. +//----------------------------------------------------------------------------- +static void HarvestGeneralBody(const double *x, int x_length, int fs, + int frame_period, double f0_floor, double f0_ceil, + double channels_in_octave, int speed, double *temporal_positions, + double *f0) { + double adjusted_f0_floor = f0_floor * 0.9; + double adjusted_f0_ceil = f0_ceil * 1.1; + int number_of_channels = + 1 + static_cast(log(adjusted_f0_ceil / adjusted_f0_floor) / + world::kLog2 * channels_in_octave); + double *boundary_f0_list = new double[number_of_channels]; + for (int i = 0; i < number_of_channels; ++i) + boundary_f0_list[i] = + adjusted_f0_floor * pow(2.0, (i + 1) / channels_in_octave); + + // normalization + int decimation_ratio = MyMaxInt(MyMinInt(speed, 12), 1); + int y_length = + static_cast(ceil(static_cast(x_length) / decimation_ratio)); + double actual_fs = static_cast(fs) / decimation_ratio; + int fft_size = GetSuitableFFTSize(y_length + 5 + + 2 * static_cast(2.0 * actual_fs / boundary_f0_list[0])); + + // Calculation of the spectrum used for the f0 estimation + double *y = new double[fft_size]; + fft_complex *y_spectrum = new fft_complex[fft_size]; + GetWaveformAndSpectrum(x, x_length, y_length, actual_fs, fft_size, + decimation_ratio, y, y_spectrum); + + int f0_length = GetSamplesForHarvest(fs, x_length, frame_period); + for (int i = 0; i < f0_length; ++i) { + temporal_positions[i] = i * frame_period / 1000.0; + f0[i] = 0.0; + } + + int overlap_parameter = 7; + int max_candidates = + matlab_round(number_of_channels / 10.0) * overlap_parameter; + double **f0_candidates = new double *[f0_length]; + double **f0_candidates_score = new double *[f0_length]; + for (int i = 0; i < f0_length; ++i) { + f0_candidates[i] = new double[max_candidates]; + f0_candidates_score[i] = new double[max_candidates]; + } + + int number_of_candidates = HarvestGeneralBodySub(boundary_f0_list, + number_of_channels, f0_length, actual_fs, y_length, temporal_positions, + y_spectrum, fft_size, f0_floor, f0_ceil, max_candidates, f0_candidates) * + overlap_parameter; + + RefineF0Candidates(y, y_length, actual_fs, temporal_positions, f0_length, + number_of_candidates, f0_floor, f0_ceil, f0_candidates, + f0_candidates_score); + RemoveUnreliableCandidates(f0_length, number_of_candidates, + f0_candidates, f0_candidates_score); + + double *best_f0_contour = new double[f0_length]; + FixF0Contour(f0_candidates, f0_candidates_score, f0_length, + number_of_candidates, best_f0_contour); + SmoothF0Contour(best_f0_contour, f0_length, f0); + + delete[] y; + delete[] best_f0_contour; + delete[] y_spectrum; + for (int i = 0; i < f0_length; ++i) { + delete[] f0_candidates[i]; + delete[] f0_candidates_score[i]; + } + delete[] f0_candidates; + delete[] f0_candidates_score; + delete[] boundary_f0_list; +} + +} // namespace + +int GetSamplesForHarvest(int fs, int x_length, double frame_period) { + return static_cast(1000.0 * x_length / fs / frame_period) + 1; +} + +void Harvest(const double *x, int x_length, int fs, + const HarvestOption *option, double *temporal_positions, double *f0) { + // Several parameters will be controllable for debug. + double target_fs = 8000.0; + int dimension_ratio = matlab_round(fs / target_fs); + double channels_in_octave = 40; + + if (option->frame_period == 1.0) { + HarvestGeneralBody(x, x_length, fs, 1, option->f0_floor, + option->f0_ceil, channels_in_octave, dimension_ratio, + temporal_positions, f0); + return; + } + + int basic_frame_period = 1; + int basic_f0_length = + GetSamplesForHarvest(fs, x_length, basic_frame_period); + double *basic_f0 = new double[basic_f0_length]; + double *basic_temporal_positions = new double[basic_f0_length]; + HarvestGeneralBody(x, x_length, fs, basic_frame_period, option->f0_floor, + option->f0_ceil, channels_in_octave, dimension_ratio, + basic_temporal_positions, basic_f0); + + int f0_length = GetSamplesForHarvest(fs, x_length, option->frame_period); + for (int i = 0; i < f0_length; ++i) { + temporal_positions[i] = i * option->frame_period / 1000.0; + f0[i] = basic_f0[MyMinInt(basic_f0_length - 1, + matlab_round(temporal_positions[i] * 1000.0))]; + } + + delete[] basic_f0; + delete[] basic_temporal_positions; +} + +void InitializeHarvestOption(HarvestOption *option) { + // You can change default parameters. + option->f0_ceil = world::kCeilF0; + option->f0_floor = world::kFloorF0; + option->frame_period = 5; +} diff --git a/libsvc/Modules/Lib/World/src/matlabfunctions.cpp b/libsvc/Modules/Lib/World/src/matlabfunctions.cpp new file mode 100644 index 0000000..cbe93a8 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/matlabfunctions.cpp @@ -0,0 +1,320 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Matlab functions implemented for WORLD +// Since these functions are implemented as the same function of Matlab, +// the source code does not follow the style guide (Names of variables +// and functions). +// Please see the reference of Matlab to show the usage of functions. +// Caution: +// The functions wavread() and wavwrite() were removed to the /src. +// they were moved to the test/audioio.cpp. (2016/01/28) +//----------------------------------------------------------------------------- +#include "world/matlabfunctions.h" + +#include +#include + +#include "world/constantnumbers.h" + +namespace { +//----------------------------------------------------------------------------- +// FilterForDecimate() calculates the coefficients of low-pass filter and +// carries out the filtering. This function is only used for decimate(). +//----------------------------------------------------------------------------- +static void FilterForDecimate(const double *x, int x_length, int r, double *y) { + double a[3], b[2]; // filter Coefficients + switch (r) { + case 11: // fs : 44100 (default) + a[0] = 2.450743295230728; + a[1] = -2.06794904601978; + a[2] = 0.59574774438332101; + b[0] = 0.0026822508007163792; + b[1] = 0.0080467524021491377; + break; + case 12: // fs : 48000 + a[0] = 2.4981398605924205; + a[1] = -2.1368928194784025; + a[2] = 0.62187513816221485; + b[0] = 0.0021097275904709001; + b[1] = 0.0063291827714127002; + break; + case 10: + a[0] = 2.3936475118069387; + a[1] = -1.9873904075111861; + a[2] = 0.5658879979027055; + b[0] = 0.0034818622251927556; + b[1] = 0.010445586675578267; + break; + case 9: + a[0] = 2.3236003491759578; + a[1] = -1.8921545617463598; + a[2] = 0.53148928133729068; + b[0] = 0.0046331164041389372; + b[1] = 0.013899349212416812; + break; + case 8: // fs : 32000 + a[0] = 2.2357462340187593; + a[1] = -1.7780899984041358; + a[2] = 0.49152555365968692; + b[0] = 0.0063522763407111993; + b[1] = 0.019056829022133598; + break; + case 7: + a[0] = 2.1225239019534703; + a[1] = -1.6395144861046302; + a[2] = 0.44469707800587366; + b[0] = 0.0090366882681608418; + b[1] = 0.027110064804482525; + break; + case 6: // fs : 24000 and 22050 + a[0] = 1.9715352749512141; + a[1] = -1.4686795689225347; + a[2] = 0.3893908434965701; + b[0] = 0.013469181309343825; + b[1] = 0.040407543928031475; + break; + case 5: + a[0] = 1.7610939654280557; + a[1] = -1.2554914843859768; + a[2] = 0.3237186507788215; + b[0] = 0.021334858522387423; + b[1] = 0.06400457556716227; + break; + case 4: // fs : 16000 + a[0] = 1.4499664446880227; + a[1] = -0.98943497080950582; + a[2] = 0.24578252340690215; + b[0] = 0.036710750339322612; + b[1] = 0.11013225101796784; + break; + case 3: + a[0] = 0.95039378983237421; + a[1] = -0.67429146741526791; + a[2] = 0.15412211621346475; + b[0] = 0.071221945171178636; + b[1] = 0.21366583551353591; + break; + case 2: // fs : 8000 + a[0] = 0.041156734567757189; + a[1] = -0.42599112459189636; + a[2] = 0.041037215479961225; + b[0] = 0.16797464681802227; + b[1] = 0.50392394045406674; + break; + default: + a[0] = 0.0; + a[1] = 0.0; + a[2] = 0.0; + b[0] = 0.0; + b[1] = 0.0; + } + + // Filtering on time domain. + double w[3] = {0.0, 0.0, 0.0}; + double wt; + for (int i = 0; i < x_length; ++i) { + wt = x[i] + a[0] * w[0] + a[1] * w[1] + a[2] * w[2]; + y[i] = b[0] * wt + b[1] * w[0] + b[1] * w[1] + b[0] * w[2]; + w[2] = w[1]; + w[1] = w[0]; + w[0] = wt; + } +} + +} // namespace + +void fftshift(const double *x, int x_length, double *y) { + for (int i = 0; i < x_length / 2; ++i) { + y[i] = x[i + x_length / 2]; + y[i + x_length / 2] = x[i]; + } +} + +void histc(const double *x, int x_length, const double *edges, + int edges_length, int *index) { + int count = 1; + + int i = 0; + for (; i < edges_length; ++i) { + index[i] = 1; + if (edges[i] >= x[0]) break; + } + for (; i < edges_length; ++i) { + if (edges[i] < x[count]) { + index[i] = count; + } else { + index[i--] = count++; + } + if (count == x_length) break; + } + count--; + for (i++; i < edges_length; ++i) index[i] = count; +} + +void interp1(const double *x, const double *y, int x_length, const double *xi, + int xi_length, double *yi) { + double *h = new double[x_length - 1]; + int *k = new int[xi_length]; + + for (int i = 0; i < x_length - 1; ++i) h[i] = x[i + 1] - x[i]; + for (int i = 0; i < xi_length; ++i) { + k[i] = 0; + } + + histc(x, x_length, xi, xi_length, k); + + for (int i = 0; i < xi_length; ++i) { + double s = (xi[i] - x[k[i] - 1]) / h[k[i] - 1]; + yi[i] = y[k[i] - 1] + s * (y[k[i]] - y[k[i] - 1]); + } + + delete[] k; + delete[] h; +} + +void decimate(const double *x, int x_length, int r, double *y) { + const int kNFact = 9; + double *tmp1 = new double[x_length + kNFact * 2]; + double *tmp2 = new double[x_length + kNFact * 2]; + + for (int i = 0; i < kNFact; ++i) tmp1[i] = 2 * x[0] - x[kNFact - i]; + for (int i = kNFact; i < kNFact + x_length; ++i) tmp1[i] = x[i - kNFact]; + for (int i = kNFact + x_length; i < 2 * kNFact + x_length; ++i) + tmp1[i] = 2 * x[x_length - 1] - x[x_length - 2 - (i - (kNFact + x_length))]; + + FilterForDecimate(tmp1, 2 * kNFact + x_length, r, tmp2); + for (int i = 0; i < 2 * kNFact + x_length; ++i) + tmp1[i] = tmp2[2 * kNFact + x_length - i - 1]; + FilterForDecimate(tmp1, 2 * kNFact + x_length, r, tmp2); + for (int i = 0; i < 2 * kNFact + x_length; ++i) + tmp1[i] = tmp2[2 * kNFact + x_length - i - 1]; + + int nout = (x_length - 1) / r + 1; + int nbeg = r - r * nout + x_length; + + int count = 0; + for (int i = nbeg; i < x_length + kNFact; i += r) + y[count++] = tmp1[i + kNFact - 1]; + + delete[] tmp1; + delete[] tmp2; +} + +int matlab_round(double x) { + return x > 0 ? static_cast(x + 0.5) : static_cast(x - 0.5); +} + +void diff(const double *x, int x_length, double *y) { + for (int i = 0; i < x_length - 1; ++i) y[i] = x[i + 1] - x[i]; +} + +void interp1Q(double x, double shift, const double *y, int x_length, + const double *xi, int xi_length, double *yi) { + double *xi_fraction = new double[xi_length]; + double *delta_y = new double[x_length]; + int *xi_base = new int[xi_length]; + + double delta_x = shift; + for (int i = 0; i < xi_length; ++i) { + xi_base[i] = static_cast((xi[i] - x) / delta_x); + xi_fraction[i] = (xi[i] - x) / delta_x - xi_base[i]; + } + diff(y, x_length, delta_y); + delta_y[x_length - 1] = 0.0; + + for (int i = 0; i < xi_length; ++i) + yi[i] = y[xi_base[i]] + delta_y[xi_base[i]] * xi_fraction[i]; + + // Bug was fixed at 2013/07/14 by M. Morise + delete[] xi_fraction; + delete[] xi_base; + delete[] delta_y; +} + +// You must not use these variables. +// Note: +// I have no idea to implement the randn() and randn_reseed() without the +// global variables. If you have a good idea, please give me the information. +static uint32_t g_randn_x = 123456789; +static uint32_t g_randn_y = 362436069; +static uint32_t g_randn_z = 521288629; +static uint32_t g_randn_w = 88675123; + +void randn_reseed() { + g_randn_x = 123456789; + g_randn_y = 362436069; + g_randn_z = 521288629; + g_randn_w = 88675123; +} + +double randn(void) { + uint32_t t; + t = g_randn_x ^ (g_randn_x << 11); + g_randn_x = g_randn_y; + g_randn_y = g_randn_z; + g_randn_z = g_randn_w; + g_randn_w = (g_randn_w ^ (g_randn_w >> 19)) ^ (t ^ (t >> 8)); + + uint32_t tmp = g_randn_w >> 4; + for (int i = 0; i < 11; ++i) { + t = g_randn_x ^ (g_randn_x << 11); + g_randn_x = g_randn_y; + g_randn_y = g_randn_z; + g_randn_z = g_randn_w; + g_randn_w = (g_randn_w ^ (g_randn_w >> 19)) ^ (t ^ (t >> 8)); + tmp += g_randn_w >> 4; + } + return tmp / 268435456.0 - 6.0; +} + +void fast_fftfilt(const double *x, int x_length, const double *h, int h_length, + int fft_size, const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, double *y) { + fft_complex *x_spectrum = new fft_complex[fft_size]; + + for (int i = 0; i < x_length; ++i) + forward_real_fft->waveform[i] = x[i] / fft_size; + for (int i = x_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) { + x_spectrum[i][0] = forward_real_fft->spectrum[i][0]; + x_spectrum[i][1] = forward_real_fft->spectrum[i][1]; + } + + for (int i = 0; i < h_length; ++i) + forward_real_fft->waveform[i] = h[i] / fft_size; + for (int i = h_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); + + for (int i = 0; i <= fft_size / 2; ++i) { + inverse_real_fft->spectrum[i][0] = + x_spectrum[i][0] * forward_real_fft->spectrum[i][0] - + x_spectrum[i][1] * forward_real_fft->spectrum[i][1]; + inverse_real_fft->spectrum[i][1] = + x_spectrum[i][0] * forward_real_fft->spectrum[i][1] + + x_spectrum[i][1] * forward_real_fft->spectrum[i][0]; + } + fft_execute(inverse_real_fft->inverse_fft); + + for (int i = 0; i < fft_size; ++i) + y[i] = inverse_real_fft->waveform[i]; + + delete[] x_spectrum; +} + +double matlab_std(const double *x, int x_length) { + double average = 0.0; + for (int i = 0; i < x_length; ++i) average += x[i]; + average /= x_length; + + double s = 0.0; + for (int i = 0; i < x_length; ++i) s += pow(x[i] - average, 2.0); + s /= (x_length - 1); + + return sqrt(s); +} diff --git a/libsvc/Modules/Lib/World/src/stonemask.cpp b/libsvc/Modules/Lib/World/src/stonemask.cpp new file mode 100644 index 0000000..e46b9d2 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/stonemask.cpp @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// F0 estimation based on instantaneous frequency. +// This method is carried out by using the output of Dio(). +//----------------------------------------------------------------------------- +#include "world/stonemask.h" + +#include + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/fft.h" +#include "world/matlabfunctions.h" + +namespace { +//----------------------------------------------------------------------------- +// GetBaseIndex() calculates the temporal positions for windowing. +// Since the result includes negative value and the value that exceeds the +// length of the input signal, it must be modified appropriately. +//----------------------------------------------------------------------------- +static void GetBaseIndex(double current_position, const double *base_time, + int base_time_length, int fs, int *index_raw) { + for (int i = 0; i < base_time_length; ++i) + index_raw[i] = matlab_round((current_position + base_time[i]) * fs); +} + +//----------------------------------------------------------------------------- +// GetMainWindow() generates the window function. +//----------------------------------------------------------------------------- +static void GetMainWindow(double current_position, const int *index_raw, + int base_time_length, int fs, double window_length_in_time, + double *main_window) { + double tmp = 0.0; + for (int i = 0; i < base_time_length; ++i) { + tmp = (index_raw[i] - 1.0) / fs - current_position; + main_window[i] = 0.42 + + 0.5 * cos(2.0 * world::kPi * tmp / window_length_in_time) + + 0.08 * cos(4.0 * world::kPi * tmp / window_length_in_time); + } +} + +//----------------------------------------------------------------------------- +// GetDiffWindow() generates the differentiated window. +// Diff means differential. +//----------------------------------------------------------------------------- +static void GetDiffWindow(const double *main_window, int base_time_length, + double *diff_window) { + diff_window[0] = -main_window[1] / 2.0; + for (int i = 1; i < base_time_length - 1; ++i) + diff_window[i] = -(main_window[i + 1] - main_window[i - 1]) / 2.0; + diff_window[base_time_length - 1] = main_window[base_time_length - 2] / 2.0; +} + +//----------------------------------------------------------------------------- +// GetSpectra() calculates two spectra of the waveform windowed by windows +// (main window and diff window). +//----------------------------------------------------------------------------- +static void GetSpectra(const double *x, int x_length, int fft_size, + const int *index_raw, const double *main_window, const double *diff_window, + int base_time_length, const ForwardRealFFT *forward_real_fft, + fft_complex *main_spectrum, fft_complex *diff_spectrum) { + int *index = new int[base_time_length]; + + for (int i = 0; i < base_time_length; ++i) + index[i] = MyMaxInt(0, MyMinInt(x_length - 1, index_raw[i] - 1)); + for (int i = 0; i < base_time_length; ++i) + forward_real_fft->waveform[i] = x[index[i]] * main_window[i]; + for (int i = base_time_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) { + main_spectrum[i][0] = forward_real_fft->spectrum[i][0]; + main_spectrum[i][1] = forward_real_fft->spectrum[i][1]; + } + + for (int i = 0; i < base_time_length; ++i) + forward_real_fft->waveform[i] = x[index[i]] * diff_window[i]; + for (int i = base_time_length; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); + for (int i = 0; i <= fft_size / 2; ++i) { + diff_spectrum[i][0] = forward_real_fft->spectrum[i][0]; + diff_spectrum[i][1] = forward_real_fft->spectrum[i][1]; + } + + delete[] index; +} + +//----------------------------------------------------------------------------- +// FixF0() fixed the F0 by instantaneous frequency. +//----------------------------------------------------------------------------- +static double FixF0(const double *power_spectrum, const double *numerator_i, + int fft_size, int fs, double initial_f0, int number_of_harmonics) { + double *amplitude_list = new double[number_of_harmonics]; + double *instantaneous_frequency_list = new double[number_of_harmonics]; + int index; + for (int i = 0; i < number_of_harmonics; ++i) { + index = MyMinInt(matlab_round(initial_f0 * fft_size / fs * (i + 1)), + fft_size / 2); + instantaneous_frequency_list[i] = power_spectrum[index] == 0.0 ? 0.0 : + static_cast(index) * fs / fft_size + + numerator_i[index] / power_spectrum[index] * fs / 2.0 / world::kPi; + amplitude_list[i] = sqrt(power_spectrum[index]); + } + double denominator = 0.0; + double numerator = 0.0; + for (int i = 0; i < number_of_harmonics; ++i) { + numerator += amplitude_list[i] * instantaneous_frequency_list[i]; + denominator += amplitude_list[i] * (i + 1); + } + delete[] amplitude_list; + delete[] instantaneous_frequency_list; + return numerator / (denominator + world::kMySafeGuardMinimum); +} + +//----------------------------------------------------------------------------- +// GetTentativeF0() calculates the F0 based on the instantaneous frequency. +//----------------------------------------------------------------------------- +static double GetTentativeF0(const double *power_spectrum, + const double *numerator_i, int fft_size, int fs, double initial_f0) { + double tentative_f0 = + FixF0(power_spectrum, numerator_i, fft_size, fs, initial_f0, 2); + + // If the fixed value is too large, the result will be rejected. + if (tentative_f0 <= 0.0 || tentative_f0 > initial_f0 * 2) return 0.0; + + return FixF0(power_spectrum, numerator_i, fft_size, fs, tentative_f0, 6); +} + +//----------------------------------------------------------------------------- +// GetMeanF0() calculates the instantaneous frequency. +//----------------------------------------------------------------------------- +static double GetMeanF0(const double *x, int x_length, int fs, + double current_position, double initial_f0, int fft_size, + double window_length_in_time, const double *base_time, + int base_time_length) { + ForwardRealFFT forward_real_fft = {0}; + InitializeForwardRealFFT(fft_size, &forward_real_fft); + fft_complex *main_spectrum = new fft_complex[fft_size]; + fft_complex *diff_spectrum = new fft_complex[fft_size]; + + int *index_raw = new int[base_time_length]; + double *main_window = new double[base_time_length]; + double *diff_window = new double[base_time_length]; + + GetBaseIndex(current_position, base_time, base_time_length, fs, index_raw); + GetMainWindow(current_position, index_raw, base_time_length, fs, + window_length_in_time, main_window); + GetDiffWindow(main_window, base_time_length, diff_window); + GetSpectra(x, x_length, fft_size, index_raw, main_window, diff_window, + base_time_length, &forward_real_fft, main_spectrum, diff_spectrum); + + double *power_spectrum = new double[fft_size / 2 + 1]; + double *numerator_i = new double[fft_size / 2 + 1]; + for (int j = 0; j <= fft_size / 2; ++j) { + numerator_i[j] = main_spectrum[j][0] * diff_spectrum[j][1] - + main_spectrum[j][1] * diff_spectrum[j][0]; + power_spectrum[j] = main_spectrum[j][0] * main_spectrum[j][0] + + main_spectrum[j][1] * main_spectrum[j][1]; + } + + double tentative_f0 = GetTentativeF0(power_spectrum, numerator_i, + fft_size, fs, initial_f0); + + delete[] diff_spectrum; + delete[] diff_window; + delete[] main_window; + delete[] index_raw; + delete[] numerator_i; + delete[] power_spectrum; + delete[] main_spectrum; + DestroyForwardRealFFT(&forward_real_fft); + + return tentative_f0; +} + +//----------------------------------------------------------------------------- +// GetRefinedF0() fixes the F0 estimated by Dio(). This function uses +// instantaneous frequency. +//----------------------------------------------------------------------------- +static double GetRefinedF0(const double *x, int x_length, int fs, + double current_potision, double initial_f0) { + if (initial_f0 <= world::kFloorF0StoneMask || initial_f0 > fs / 12.0) + return 0.0; + + int half_window_length = static_cast(1.5 * fs / initial_f0 + 1.0); + double window_length_in_time = (2.0 * half_window_length + 1.0) / fs; + double *base_time = new double[half_window_length * 2 + 1]; + for (int i = 0; i < half_window_length * 2 + 1; i++) + base_time[i] = static_cast(-half_window_length + i) / fs; + int fft_size = static_cast(pow(2.0, 2.0 + + static_cast(log(half_window_length * 2.0 + 1.0) / world::kLog2))); + + double mean_f0 = GetMeanF0(x, x_length, fs, current_potision, + initial_f0, fft_size, window_length_in_time, base_time, + half_window_length * 2 + 1); + + // If amount of correction is overlarge (20 %), initial F0 is employed. + if (fabs(mean_f0 - initial_f0) > initial_f0 * 0.2) mean_f0 = initial_f0; + + delete[] base_time; + + return mean_f0; +} + +} // namespace + +void StoneMask(const double *x, int x_length, int fs, + const double *temporal_positions, const double *f0, int f0_length, + double *refined_f0) { + for (int i = 0; i < f0_length; i++) + refined_f0[i] = + GetRefinedF0(x, x_length, fs, temporal_positions[i], f0[i]); +} diff --git a/libsvc/Modules/Lib/World/src/synthesis.cpp b/libsvc/Modules/Lib/World/src/synthesis.cpp new file mode 100644 index 0000000..458d1c5 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/synthesis.cpp @@ -0,0 +1,397 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Voice synthesis based on f0, spectrogram and aperiodicity. +// forward_real_fft, inverse_real_fft and minimum_phase are used to speed up. +//----------------------------------------------------------------------------- +#include "world/synthesis.h" + +#include + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/matlabfunctions.h" + +namespace { + +static void GetNoiseSpectrum(int noise_size, int fft_size, + const ForwardRealFFT *forward_real_fft) { + double average = 0.0; + for (int i = 0; i < noise_size; ++i) { + forward_real_fft->waveform[i] = randn(); + average += forward_real_fft->waveform[i]; + } + + average /= noise_size; + for (int i = 0; i < noise_size; ++i) + forward_real_fft->waveform[i] -= average; + for (int i = noise_size; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); +} + +//----------------------------------------------------------------------------- +// GetAperiodicResponse() calculates an aperiodic response. +//----------------------------------------------------------------------------- +static void GetAperiodicResponse(int noise_size, int fft_size, + const double *spectrum, const double *aperiodic_ratio, double current_vuv, + const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, + const MinimumPhaseAnalysis *minimum_phase, double *aperiodic_response) { + GetNoiseSpectrum(noise_size, fft_size, forward_real_fft); + + if (current_vuv != 0.0) + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) + minimum_phase->log_spectrum[i] = + log(spectrum[i] * aperiodic_ratio[i]) / 2.0; + else + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) + minimum_phase->log_spectrum[i] = log(spectrum[i]) / 2.0; + GetMinimumPhaseSpectrum(minimum_phase); + + for (int i = 0; i <= fft_size / 2; ++i) { + inverse_real_fft->spectrum[i][0] = + minimum_phase->minimum_phase_spectrum[i][0] * + forward_real_fft->spectrum[i][0] - + minimum_phase->minimum_phase_spectrum[i][1] * + forward_real_fft->spectrum[i][1]; + inverse_real_fft->spectrum[i][1] = + minimum_phase->minimum_phase_spectrum[i][0] * + forward_real_fft->spectrum[i][1] + + minimum_phase->minimum_phase_spectrum[i][1] * + forward_real_fft->spectrum[i][0]; + } + fft_execute(inverse_real_fft->inverse_fft); + fftshift(inverse_real_fft->waveform, fft_size, aperiodic_response); +} + +//----------------------------------------------------------------------------- +// RemoveDCComponent() +//----------------------------------------------------------------------------- +static void RemoveDCComponent(const double *periodic_response, int fft_size, + const double *dc_remover, double *new_periodic_response) { + double dc_component = 0.0; + for (int i = fft_size / 2; i < fft_size; ++i) + dc_component += periodic_response[i]; + for (int i = 0; i < fft_size / 2; ++i) + new_periodic_response[i] = -dc_component * dc_remover[i]; + for (int i = fft_size / 2; i < fft_size; ++i) + new_periodic_response[i] -= dc_component * dc_remover[i]; +} + +//----------------------------------------------------------------------------- +// GetSpectrumWithFractionalTimeShift() calculates a periodic spectrum with +// the fractional time shift under 1/fs. +//----------------------------------------------------------------------------- +static void GetSpectrumWithFractionalTimeShift(int fft_size, + double coefficient, const InverseRealFFT *inverse_real_fft) { + double re, im, re2, im2; + for (int i = 0; i <= fft_size / 2; ++i) { + re = inverse_real_fft->spectrum[i][0]; + im = inverse_real_fft->spectrum[i][1]; + re2 = cos(coefficient * i); + im2 = sqrt(1.0 - re2 * re2); // sin(pshift) + + inverse_real_fft->spectrum[i][0] = re * re2 + im * im2; + inverse_real_fft->spectrum[i][1] = im * re2 - re * im2; + } +} + +//----------------------------------------------------------------------------- +// GetPeriodicResponse() calculates a periodic response. +//----------------------------------------------------------------------------- +static void GetPeriodicResponse(int fft_size, const double *spectrum, + const double *aperiodic_ratio, double current_vuv, + const InverseRealFFT *inverse_real_fft, + const MinimumPhaseAnalysis *minimum_phase, const double *dc_remover, + double fractional_time_shift, int fs, double *periodic_response) { + if (current_vuv <= 0.5 || aperiodic_ratio[0] > 0.999) { + for (int i = 0; i < fft_size; ++i) periodic_response[i] = 0.0; + return; + } + + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) + minimum_phase->log_spectrum[i] = + log(spectrum[i] * (1.0 - aperiodic_ratio[i]) + + world::kMySafeGuardMinimum) / 2.0; + GetMinimumPhaseSpectrum(minimum_phase); + + for (int i = 0; i <= fft_size / 2; ++i) { + inverse_real_fft->spectrum[i][0] = + minimum_phase->minimum_phase_spectrum[i][0]; + inverse_real_fft->spectrum[i][1] = + minimum_phase->minimum_phase_spectrum[i][1]; + } + + // apply fractional time delay of fractional_time_shift seconds + // using linear phase shift + double coefficient = + 2.0 * world::kPi * fractional_time_shift * fs / fft_size; + GetSpectrumWithFractionalTimeShift(fft_size, coefficient, inverse_real_fft); + + fft_execute(inverse_real_fft->inverse_fft); + fftshift(inverse_real_fft->waveform, fft_size, periodic_response); + RemoveDCComponent(periodic_response, fft_size, dc_remover, + periodic_response); +} + +static void GetSpectralEnvelope(double current_time, double frame_period, + int f0_length, const double * const *spectrogram, int fft_size, + double *spectral_envelope) { + int current_frame_floor = MyMinInt(f0_length - 1, + static_cast(floor(current_time / frame_period))); + int current_frame_ceil = MyMinInt(f0_length - 1, + static_cast(ceil(current_time / frame_period))); + double interpolation = current_time / frame_period - current_frame_floor; + + if (current_frame_floor == current_frame_ceil) + for (int i = 0; i <= fft_size / 2; ++i) + spectral_envelope[i] = fabs(spectrogram[current_frame_floor][i]); + else + for (int i = 0; i <= fft_size / 2; ++i) + spectral_envelope[i] = + (1.0 - interpolation) * fabs(spectrogram[current_frame_floor][i]) + + interpolation * fabs(spectrogram[current_frame_ceil][i]); +} + +static void GetAperiodicRatio(double current_time, double frame_period, + int f0_length, const double * const *aperiodicity, int fft_size, + double *aperiodic_spectrum) { + int current_frame_floor = MyMinInt(f0_length - 1, + static_cast(floor(current_time / frame_period))); + int current_frame_ceil = MyMinInt(f0_length - 1, + static_cast(ceil(current_time / frame_period))); + double interpolation = current_time / frame_period - current_frame_floor; + + if (current_frame_floor == current_frame_ceil) + for (int i = 0; i <= fft_size / 2; ++i) + aperiodic_spectrum[i] = + pow(GetSafeAperiodicity(aperiodicity[current_frame_floor][i]), 2.0); + else + for (int i = 0; i <= fft_size / 2; ++i) + aperiodic_spectrum[i] = pow((1.0 - interpolation) * + GetSafeAperiodicity(aperiodicity[current_frame_floor][i]) + + interpolation * + GetSafeAperiodicity(aperiodicity[current_frame_ceil][i]), 2.0); +} + +//----------------------------------------------------------------------------- +// GetOneFrameSegment() calculates a periodic and aperiodic response at a time. +//----------------------------------------------------------------------------- +static void GetOneFrameSegment(double current_vuv, int noise_size, + const double * const *spectrogram, int fft_size, + const double * const *aperiodicity, int f0_length, double frame_period, + double current_time, double fractional_time_shift, int fs, + const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, + const MinimumPhaseAnalysis *minimum_phase, const double *dc_remover, + double *response) { + double *aperiodic_response = new double[fft_size]; + double *periodic_response = new double[fft_size]; + + double *spectral_envelope = new double[fft_size]; + double *aperiodic_ratio = new double[fft_size]; + GetSpectralEnvelope(current_time, frame_period, f0_length, spectrogram, + fft_size, spectral_envelope); + GetAperiodicRatio(current_time, frame_period, f0_length, aperiodicity, + fft_size, aperiodic_ratio); + + // Synthesis of the periodic response + GetPeriodicResponse(fft_size, spectral_envelope, aperiodic_ratio, + current_vuv, inverse_real_fft, minimum_phase, dc_remover, + fractional_time_shift, fs, periodic_response); + + // Synthesis of the aperiodic response + GetAperiodicResponse(noise_size, fft_size, spectral_envelope, + aperiodic_ratio, current_vuv, forward_real_fft, + inverse_real_fft, minimum_phase, aperiodic_response); + + double sqrt_noise_size = sqrt(static_cast(noise_size)); + for (int i = 0; i < fft_size; ++i) + response[i] = + (periodic_response[i] * sqrt_noise_size + aperiodic_response[i]) / + fft_size; + + delete[] spectral_envelope; + delete[] aperiodic_ratio; + delete[] periodic_response; + delete[] aperiodic_response; +} + +static void GetTemporalParametersForTimeBase(const double *f0, int f0_length, + int fs, int y_length, double frame_period, double lowest_f0, + double *time_axis, double *coarse_time_axis, double *coarse_f0, + double *coarse_vuv) { + for (int i = 0; i < y_length; ++i) + time_axis[i] = i / static_cast(fs); + // the array 'coarse_time_axis' is supposed to have 'f0_length + 1' positions + for (int i = 0; i < f0_length; ++i) { + coarse_time_axis[i] = i * frame_period; + coarse_f0[i] = f0[i] < lowest_f0 ? 0.0 : f0[i]; + coarse_vuv[i] = coarse_f0[i] == 0.0 ? 0.0 : 1.0; + } + coarse_time_axis[f0_length] = f0_length * frame_period; + coarse_f0[f0_length] = coarse_f0[f0_length - 1] * 2 - + coarse_f0[f0_length - 2]; + coarse_vuv[f0_length] = coarse_vuv[f0_length - 1] * 2 - + coarse_vuv[f0_length - 2]; +} + +static int GetPulseLocationsForTimeBase(const double *interpolated_f0, + const double *time_axis, int y_length, int fs, double *pulse_locations, + int *pulse_locations_index, double *pulse_locations_time_shift) { + double *total_phase = new double[y_length]; + double *wrap_phase = new double[y_length]; + double *wrap_phase_abs = new double[y_length - 1]; + total_phase[0] = 2.0 * world::kPi * interpolated_f0[0] / fs; + wrap_phase[0] = fmod(total_phase[0], 2.0 * world::kPi); + for (int i = 1; i < y_length; ++i) { + total_phase[i] = total_phase[i - 1] + + 2.0 * world::kPi * interpolated_f0[i] / fs; + wrap_phase[i] = fmod(total_phase[i], 2.0 * world::kPi); + wrap_phase_abs[i - 1] = fabs(wrap_phase[i] - wrap_phase[i - 1]); + } + + int number_of_pulses = 0; + for (int i = 0; i < y_length - 1; ++i) { + if (wrap_phase_abs[i] > world::kPi) { + pulse_locations[number_of_pulses] = time_axis[i]; + pulse_locations_index[number_of_pulses] = i; + + // calculate the time shift in seconds between exact fractional pulse + // position and the integer pulse position (sample i) + // as we don't have access to the exact pulse position, we infer it + // from the point between sample i and sample i + 1 where the + // accummulated phase cross a multiple of 2pi + // this point is found by solving y1 + x * (y2 - y1) = 0 for x, where y1 + // and y2 are the phases corresponding to sample i and i + 1, offset so + // they cross zero; x >= 0 + double y1 = wrap_phase[i] - 2.0 * world::kPi; + double y2 = wrap_phase[i + 1]; + double x = -y1 / (y2 - y1); + pulse_locations_time_shift[number_of_pulses] = x / fs; + + ++number_of_pulses; + } + } + + delete[] wrap_phase_abs; + delete[] wrap_phase; + delete[] total_phase; + + return number_of_pulses; +} + +static int GetTimeBase(const double *f0, int f0_length, int fs, + double frame_period, int y_length, double lowest_f0, + double *pulse_locations, int *pulse_locations_index, + double *pulse_locations_time_shift, double *interpolated_vuv) { + double *time_axis = new double[y_length]; + double *coarse_time_axis = new double[f0_length + 1]; + double *coarse_f0 = new double[f0_length + 1]; + double *coarse_vuv = new double[f0_length + 1]; + GetTemporalParametersForTimeBase(f0, f0_length, fs, y_length, frame_period, + lowest_f0, time_axis, coarse_time_axis, coarse_f0, coarse_vuv); + double *interpolated_f0 = new double[y_length]; + interp1(coarse_time_axis, coarse_f0, f0_length + 1, + time_axis, y_length, interpolated_f0); + interp1(coarse_time_axis, coarse_vuv, f0_length + 1, + time_axis, y_length, interpolated_vuv); + + for (int i = 0; i < y_length; ++i) { + interpolated_vuv[i] = interpolated_vuv[i] > 0.5 ? 1.0 : 0.0; + interpolated_f0[i] = + interpolated_vuv[i] == 0.0 ? world::kDefaultF0 : interpolated_f0[i]; + } + + int number_of_pulses = GetPulseLocationsForTimeBase(interpolated_f0, + time_axis, y_length, fs, pulse_locations, pulse_locations_index, + pulse_locations_time_shift); + + delete[] coarse_vuv; + delete[] coarse_f0; + delete[] coarse_time_axis; + delete[] time_axis; + delete[] interpolated_f0; + + return number_of_pulses; +} + +static void GetDCRemover(int fft_size, double *dc_remover) { + double dc_component = 0.0; + for (int i = 0; i < fft_size / 2; ++i) { + dc_remover[i] = 0.5 - + 0.5 * cos(2.0 * world::kPi * (i + 1.0) / (1.0 + fft_size)); + dc_remover[fft_size - i - 1] = dc_remover[i]; + dc_component += dc_remover[i] * 2.0; + } + for (int i = 0; i < fft_size / 2; ++i) { + dc_remover[i] /= dc_component; + dc_remover[fft_size - i - 1] = dc_remover[i]; + } +} + +} // namespace + +void Synthesis(const double *f0, int f0_length, + const double * const *spectrogram, const double * const *aperiodicity, + int fft_size, double frame_period, int fs, int y_length, double *y) { + randn_reseed(); + + double *impulse_response = new double[fft_size]; + + for (int i = 0; i < y_length; ++i) y[i] = 0.0; + + MinimumPhaseAnalysis minimum_phase = {0}; + InitializeMinimumPhaseAnalysis(fft_size, &minimum_phase); + InverseRealFFT inverse_real_fft = {0}; + InitializeInverseRealFFT(fft_size, &inverse_real_fft); + ForwardRealFFT forward_real_fft = {0}; + InitializeForwardRealFFT(fft_size, &forward_real_fft); + + double *pulse_locations = new double[y_length]; + int *pulse_locations_index = new int[y_length]; + double *pulse_locations_time_shift = new double[y_length]; + double *interpolated_vuv = new double[y_length]; + int number_of_pulses = GetTimeBase(f0, f0_length, fs, frame_period / 1000.0, + y_length, fs / fft_size + 1.0, pulse_locations, pulse_locations_index, + pulse_locations_time_shift, interpolated_vuv); + + double *dc_remover = new double[fft_size]; + GetDCRemover(fft_size, dc_remover); + + frame_period /= 1000.0; + int noise_size; + int index, offset, lower_limit, upper_limit; + for (int i = 0; i < number_of_pulses; ++i) { + noise_size = pulse_locations_index[MyMinInt(number_of_pulses - 1, i + 1)] - + pulse_locations_index[i]; + + GetOneFrameSegment(interpolated_vuv[pulse_locations_index[i]], noise_size, + spectrogram, fft_size, aperiodicity, f0_length, frame_period, + pulse_locations[i], pulse_locations_time_shift[i], fs, + &forward_real_fft, &inverse_real_fft, &minimum_phase, dc_remover, + impulse_response); + offset = pulse_locations_index[i] - fft_size / 2 + 1; + lower_limit = MyMaxInt(0, -offset); + upper_limit = MyMinInt(fft_size, y_length - offset); + for (int j = lower_limit; j < upper_limit; ++j) { + index = j + offset; + y[index] += impulse_response[j]; + } + } + + delete[] dc_remover; + delete[] pulse_locations; + delete[] pulse_locations_index; + delete[] pulse_locations_time_shift; + delete[] interpolated_vuv; + + DestroyMinimumPhaseAnalysis(&minimum_phase); + DestroyInverseRealFFT(&inverse_real_fft); + DestroyForwardRealFFT(&forward_real_fft); + + delete[] impulse_response; +} diff --git a/libsvc/Modules/Lib/World/src/synthesisrealtime.cpp b/libsvc/Modules/Lib/World/src/synthesisrealtime.cpp new file mode 100644 index 0000000..e2ff34d --- /dev/null +++ b/libsvc/Modules/Lib/World/src/synthesisrealtime.cpp @@ -0,0 +1,600 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Voice synthesis based on f0, spectrogram and aperiodicity. +// This is an implementation for real-time applications. +// Note: Several functions are same as those of synthesis.cpp. +// +// Caution: This is an implementation as a prototype. +// Specifications may change. There may be a bug. +// +// Caution: DC removal was re-implemented. However, this implementation is +// different from the implementation in synthesis.cpp. The sound +// quality is almost all the same in both implementations. +//----------------------------------------------------------------------------- +#include "world/synthesisrealtime.h" + +#include +#include + +#include "world/common.h" +#include "world/constantnumbers.h" +#include "world/matlabfunctions.h" + +namespace { + +static void GetNoiseSpectrum(int noise_size, int fft_size, + const ForwardRealFFT *forward_real_fft) { + double average = 0.0; + for (int i = 0; i < noise_size; ++i) { + forward_real_fft->waveform[i] = randn(); + average += forward_real_fft->waveform[i]; + } + + average /= noise_size; + for (int i = 0; i < noise_size; ++i) + forward_real_fft->waveform[i] -= average; + for (int i = noise_size; i < fft_size; ++i) + forward_real_fft->waveform[i] = 0.0; + fft_execute(forward_real_fft->forward_fft); +} + +//----------------------------------------------------------------------------- +// GetAperiodicResponse() calculates an aperiodic response. +//----------------------------------------------------------------------------- +static void GetAperiodicResponse(int noise_size, int fft_size, + const double *spectrum, const double *aperiodic_ratio, double current_vuv, + const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, + const MinimumPhaseAnalysis *minimum_phase, double *aperiodic_response) { + GetNoiseSpectrum(noise_size, fft_size, forward_real_fft); + + if (current_vuv != 0.0) + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) + minimum_phase->log_spectrum[i] = + log(spectrum[i] * aperiodic_ratio[i] + + world::kMySafeGuardMinimum) / 2.0; + else + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) + minimum_phase->log_spectrum[i] = log(spectrum[i]) / 2.0; + GetMinimumPhaseSpectrum(minimum_phase); + + for (int i = 0; i <= fft_size / 2; ++i) { + inverse_real_fft->spectrum[i][0] = + minimum_phase->minimum_phase_spectrum[i][0] * + forward_real_fft->spectrum[i][0] - + minimum_phase->minimum_phase_spectrum[i][1] * + forward_real_fft->spectrum[i][1]; + inverse_real_fft->spectrum[i][1] = + minimum_phase->minimum_phase_spectrum[i][0] * + forward_real_fft->spectrum[i][1] + + minimum_phase->minimum_phase_spectrum[i][1] * + forward_real_fft->spectrum[i][0]; + } + fft_execute(inverse_real_fft->inverse_fft); + fftshift(inverse_real_fft->waveform, fft_size, aperiodic_response); +} + +static void ClearRingBuffer(int start, int end, WorldSynthesizer *synth) { + int pointer; + for (int i = start; i < end; ++i) { + pointer = i % synth->number_of_pointers; + synth->number_of_pulses[pointer] = 0; + if (synth->pulse_locations[pointer] != NULL) { + delete[] synth->pulse_locations[pointer]; + synth->pulse_locations[pointer] = NULL; + } + if (synth->interpolated_vuv[pointer] != NULL) { + delete[] synth->interpolated_vuv[pointer]; + synth->interpolated_vuv[pointer] = NULL; + } + if (synth->pulse_locations_index[pointer] != NULL) { + delete[] synth->pulse_locations_index[pointer]; + synth->pulse_locations_index[pointer] = NULL; + } + } +} + +static int SeekSynthesizer(double current_location, WorldSynthesizer *synth) { + int frame_number = static_cast(current_location / synth->frame_period); + + int tmp_pointer = synth->current_pointer2; + int tmp; + for (int i = 0; i < synth->head_pointer - synth->current_pointer2; ++i) { + tmp = (tmp_pointer + i) % synth->number_of_pointers; + if (synth->f0_origin[tmp] <= frame_number && + frame_number < synth->f0_origin[tmp] + synth->f0_length[tmp]) { + tmp_pointer += i; + break; + } + } + ClearRingBuffer(synth->current_pointer2, tmp_pointer, synth); + synth->current_pointer2 = tmp_pointer; + return 1; +} + +static void SearchPointer(int frame, WorldSynthesizer *synth, int flag, + double **front, double **next) { + int pointer = synth->current_pointer2 % synth->number_of_pointers; + int index = -1; + for (int i = 0; i < synth->f0_length[pointer]; ++i) + if (synth->f0_origin[pointer] + i == frame) { + index = i; + break; + } + + double ***tmp_pointer = + flag == 0 ? synth->spectrogram : synth->aperiodicity; + + *front = tmp_pointer[pointer][index]; + *next = index == synth->f0_length[pointer] - 1 ? + tmp_pointer[(synth->current_pointer2 + 1) % + synth->number_of_pointers][0] : tmp_pointer[pointer][index + 1]; +} + +//----------------------------------------------------------------------------- +// RemoveDCComponent() +//----------------------------------------------------------------------------- +static void RemoveDCComponent(const double *periodic_response, int fft_size, + const double *dc_remover, double *new_periodic_response) { + double dc_component = 0.0; + for (int i = fft_size / 2; i < fft_size; ++i) + dc_component += periodic_response[i]; + for (int i = 0; i < fft_size / 2; ++i) + new_periodic_response[i] = 0.0; + for (int i = fft_size / 2; i < fft_size; ++i) + new_periodic_response[i] -= dc_component * dc_remover[i - fft_size / 2]; +} + +//----------------------------------------------------------------------------- +// GetPeriodicResponse() calculates an aperiodic response. +//----------------------------------------------------------------------------- +static void GetPeriodicResponse(int fft_size, const double *spectrum, + const double *aperiodic_ratio, double current_vuv, + const InverseRealFFT *inverse_real_fft, + const MinimumPhaseAnalysis *minimum_phase, + const double *dc_remover, double *periodic_response) { + if (current_vuv <= 0.5 || aperiodic_ratio[0] > 0.999) { + for (int i = 0; i < fft_size; ++i) periodic_response[i] = 0.0; + return; + } + + for (int i = 0; i <= minimum_phase->fft_size / 2; ++i) + minimum_phase->log_spectrum[i] = + log(spectrum[i] * (1.0 - aperiodic_ratio[i]) + + world::kMySafeGuardMinimum) / 2.0; + GetMinimumPhaseSpectrum(minimum_phase); + + for (int i = 0; i <= fft_size / 2; ++i) { + inverse_real_fft->spectrum[i][0] = + minimum_phase->minimum_phase_spectrum[i][0]; + inverse_real_fft->spectrum[i][1] = + minimum_phase->minimum_phase_spectrum[i][1]; + } + + fft_execute(inverse_real_fft->inverse_fft); + fftshift(inverse_real_fft->waveform, fft_size, periodic_response); + RemoveDCComponent(periodic_response, fft_size, dc_remover, + periodic_response); +} + +static void GetSpectralEnvelope(double current_location, + WorldSynthesizer *synth, double *spectral_envelope) { + int current_frame_floor = + static_cast(current_location / synth->frame_period); + + int current_frame_ceil = + static_cast(ceil(current_location / synth->frame_period)); + double interpolation = + current_location / synth->frame_period - current_frame_floor; + double *front = NULL; + double *next = NULL; + SearchPointer(current_frame_floor, synth, 0, &front, &next); + + if (current_frame_floor == current_frame_ceil) + for (int i = 0; i <= synth->fft_size / 2; ++i) + spectral_envelope[i] = fabs(front[i]); + else + for (int i = 0; i <= synth->fft_size / 2; ++i) + spectral_envelope[i] = + (1.0 - interpolation) * fabs(front[i]) + interpolation * fabs(next[i]); +} + +static void GetAperiodicRatio(double current_location, + WorldSynthesizer *synth, double *aperiodic_spectrum) { + int current_frame_floor = + static_cast(current_location / synth->frame_period); + + int current_frame_ceil = + static_cast(ceil(current_location / synth->frame_period)); + double interpolation = + current_location / synth->frame_period - current_frame_floor; + + double *front = NULL; + double *next = NULL; + SearchPointer(current_frame_floor, synth, 1, &front, &next); + + if (current_frame_floor == current_frame_ceil) + for (int i = 0; i <= synth->fft_size / 2; ++i) + aperiodic_spectrum[i] = pow(GetSafeAperiodicity(front[i]), 2.0); + else + for (int i = 0; i <= synth->fft_size / 2; ++i) + aperiodic_spectrum[i] = + pow((1.0 - interpolation) * GetSafeAperiodicity(front[i]) + + interpolation * GetSafeAperiodicity(next[i]), 2.0); +} + +static double GetCurrentVUV(int current_location, WorldSynthesizer *synth) { + double current_vuv = 0.0; + int pointer = synth->current_pointer % synth->number_of_pointers; + + int start_sample = MyMaxInt(0, + static_cast(ceil((synth->f0_origin[pointer] - 1) * + synth->frame_period * synth->fs))); + + current_vuv = + synth->interpolated_vuv[pointer][current_location - start_sample + 1]; + return current_vuv; +} + +//----------------------------------------------------------------------------- +// GetOneFrameSegment() calculates a periodic and aperiodic response at a time. +//----------------------------------------------------------------------------- +static void GetOneFrameSegment(int noise_size, int current_location, + WorldSynthesizer *synth) { + double *aperiodic_response = new double[synth->fft_size]; + double *periodic_response = new double[synth->fft_size]; + double *spectral_envelope = new double[synth->fft_size]; + double *aperiodic_ratio = new double[synth->fft_size]; + + double tmp_location = static_cast(current_location) / synth->fs; + SeekSynthesizer(tmp_location, synth); + GetSpectralEnvelope(tmp_location, synth, spectral_envelope); + GetAperiodicRatio(tmp_location, synth, aperiodic_ratio); + + double current_vuv = GetCurrentVUV(current_location, synth); + + // Synthesis of the periodic response + GetPeriodicResponse(synth->fft_size, spectral_envelope, aperiodic_ratio, + current_vuv, &synth->inverse_real_fft, &synth->minimum_phase, + synth->dc_remover, periodic_response); + + // Synthesis of the aperiodic response + GetAperiodicResponse(noise_size, synth->fft_size, spectral_envelope, + aperiodic_ratio, current_vuv, &synth->forward_real_fft, + &synth->inverse_real_fft, &synth->minimum_phase, aperiodic_response); + + double sqrt_noise_size = sqrt(static_cast(noise_size)); + for (int i = 0; i < synth->fft_size; ++i) + synth->impulse_response[i] = + (periodic_response[i] * sqrt_noise_size + aperiodic_response[i]) / + synth->fft_size; + + delete[] spectral_envelope; + delete[] aperiodic_ratio; + delete[] periodic_response; + delete[] aperiodic_response; +} + +static void GetTemporalParametersForTimeBase(const double *f0, int f0_length, + WorldSynthesizer *synth, double *coarse_time_axis, double *coarse_f0, + double *coarse_vuv) { + int cumulative_frame = MyMaxInt(0, synth->cumulative_frame - f0_length); + coarse_f0[0] = synth->handoff_f0; + coarse_time_axis[0] = cumulative_frame * synth->frame_period; + coarse_vuv[0] = synth->handoff_f0 == 0 ? 0.0 : 1.0; + for (int i = 0; i < f0_length; ++i) { + coarse_time_axis[i + synth->handoff] = + (i + cumulative_frame + synth->handoff) * synth->frame_period; + coarse_f0[i + synth->handoff] = f0[i]; + coarse_vuv[i + synth->handoff] = f0[i] == 0.0 ? 0.0 : 1.0; + } +} + +static void GetPulseLocationsForTimeBase(const double *interpolated_f0, + const double *time_axis, int number_of_samples, double origin, + WorldSynthesizer *synth) { + double *total_phase = new double[number_of_samples + synth->handoff]; + total_phase[0] = synth->handoff == 1 ? synth->handoff_phase : + 2.0 * world::kPi * interpolated_f0[0] / synth->fs; + + total_phase[1] = total_phase[0] + 2.0 * world::kPi * + interpolated_f0[0] / synth->fs; + for (int i = 1 + synth->handoff; i < number_of_samples + synth->handoff; ++i) + total_phase[i] = total_phase[i - 1] + + 2.0 * world::kPi * interpolated_f0[i - synth->handoff] / synth->fs; + synth->handoff_phase = total_phase[number_of_samples - 1 + synth->handoff]; + + double *wrap_phase = new double[number_of_samples + synth->handoff]; + for (int i = 0; i < number_of_samples + synth->handoff; ++i) + wrap_phase[i] = fmod(total_phase[i], 2.0 * world::kPi); + + double *wrap_phase_abs = new double[number_of_samples + synth->handoff]; + for (int i = 0; i < number_of_samples - 1 + synth->handoff; ++i) + wrap_phase_abs[i] = fabs(wrap_phase[i + 1] - wrap_phase[i]); + + int pointer = synth->head_pointer % synth->number_of_pointers; + int number_of_pulses = 0; + for (int i = 0; i < number_of_samples - 1 + synth->handoff; ++i) + if (wrap_phase_abs[i] > world::kPi) { + synth->pulse_locations[pointer][number_of_pulses] = + time_axis[i] - static_cast(synth->handoff) / synth->fs; + synth->pulse_locations_index[pointer][number_of_pulses] = + matlab_round(synth->pulse_locations[pointer][number_of_pulses] * + synth->fs); + ++number_of_pulses; + } + synth->number_of_pulses[pointer] = number_of_pulses; + + if (number_of_pulses != 0) + synth->last_location = + synth->pulse_locations_index[pointer][number_of_pulses - 1]; + delete[] wrap_phase_abs; + delete[] wrap_phase; + delete[] total_phase; +} + +static void GetTimeBase(const double *f0, int f0_length, int start_sample, + int number_of_samples, WorldSynthesizer *synth) { + double *coarse_time_axis = new double[f0_length + synth->handoff]; + double *coarse_f0 = new double[f0_length + synth->handoff]; + double *coarse_vuv = new double[f0_length + synth->handoff]; + + GetTemporalParametersForTimeBase(f0, f0_length, synth, + coarse_time_axis, coarse_f0, coarse_vuv); + + double *interpolated_f0 = new double[number_of_samples]; + double *time_axis = new double[number_of_samples]; + + for (int i = 0; i < number_of_samples; ++i) + time_axis[i] = (i + start_sample) / static_cast(synth->fs); + + int pointer = synth->head_pointer % synth->number_of_pointers; + interp1(coarse_time_axis, coarse_f0, f0_length + synth->handoff, time_axis, + number_of_samples, interpolated_f0); + interp1(coarse_time_axis, coarse_vuv, f0_length + synth->handoff, time_axis, + number_of_samples, synth->interpolated_vuv[pointer]); + for (int i = 0; i < number_of_samples; ++i) { + synth->interpolated_vuv[pointer][i] = + synth->interpolated_vuv[pointer][i] > 0.5 ? 1.0 : 0.0; + interpolated_f0[i] = + synth->interpolated_vuv[pointer][i] == 0.0 ? + world::kDefaultF0 : interpolated_f0[i]; + } + + GetPulseLocationsForTimeBase(interpolated_f0, time_axis, number_of_samples, + coarse_time_axis[0], synth); + + synth->handoff_f0 = interpolated_f0[number_of_samples - 1]; + delete[] time_axis; + delete[] interpolated_f0; + delete[] coarse_time_axis; + delete[] coarse_f0; + delete[] coarse_vuv; +} + +static int GetNextPulseLocationIndex(WorldSynthesizer *synth) { + int pointer = synth->current_pointer % synth->number_of_pointers; + if (synth->i < synth->number_of_pulses[pointer] - 1) + return synth->pulse_locations_index[pointer][synth->i + 1]; + else if (synth->current_pointer == synth->head_pointer - 1) + return 0; + + for (int i = 1; i < synth->number_of_pointers; ++i) { + pointer = (i + synth->current_pointer) % synth->number_of_pointers; + if (synth->number_of_pulses[pointer] != 0) + return synth->pulse_locations_index[pointer][0]; + } + return 0; +} + +static int UpdateSynthesizer(int current_location, WorldSynthesizer *synth) { + int pointer = synth->current_pointer % synth->number_of_pointers; + if (synth->i < synth->number_of_pulses[pointer] - 1) { + synth->i++; + return 1; + } else { + if (synth->current_pointer == synth->head_pointer - 1) return 0; + } + + for (int i = 1; i < synth->number_of_pointers; ++i) { + pointer = (i + synth->current_pointer) % synth->number_of_pointers; + if (synth->number_of_pulses[pointer] != 0) { + synth->i = 0; + synth->current_pointer += i; + return 1; + } + } + return 0; +} + +static int CheckSynthesizer(WorldSynthesizer *synth) { + if (synth->synthesized_sample + synth->buffer_size >= synth->last_location) + return 0; + + int pointer = synth->current_pointer % synth->number_of_pointers; + while (synth->number_of_pulses[pointer] == 0) { + if (synth->current_pointer == synth->head_pointer) break; + synth->current_pointer++; + pointer = synth->current_pointer % synth->number_of_pointers; + } + return 1; +} + +static void GetDCRemover(int fft_size, double *dc_remover) { + double dc_component = 0.0; + for (int i = 0; i < fft_size / 2; ++i) { + dc_remover[i] = 0.5 - + 0.5 * cos(2.0 * world::kPi * (i + 1.0) / (1.0 + fft_size)); + dc_remover[fft_size - i - 1] = dc_remover[i]; + dc_component += dc_remover[i] * 2.0; + } + for (int i = 0; i < fft_size / 2; ++i) { + dc_remover[i] /= dc_component; + dc_remover[fft_size - i - 1] = dc_remover[i]; + } +} + +} // namespace + +void InitializeSynthesizer(int fs, double frame_period, int fft_size, + int buffer_size, int number_of_pointers, WorldSynthesizer *synth) { + // Set basic parameters + synth->fs = fs; + synth->frame_period = frame_period / 1000.0; + synth->buffer_size = buffer_size; + synth->number_of_pointers = number_of_pointers; + synth->fft_size = fft_size; + + // Memory allocations + synth->f0_length = new int[number_of_pointers]; + synth->spectrogram = new double**[number_of_pointers]; + synth->aperiodicity = new double**[number_of_pointers]; + synth->interpolated_vuv = new double*[number_of_pointers]; + synth->pulse_locations = new double*[number_of_pointers]; + synth->pulse_locations_index = new int*[number_of_pointers]; + synth->number_of_pulses = new int[number_of_pointers]; + synth->f0_origin = new int[number_of_pointers]; + for (int i = 0; i < synth->number_of_pointers; ++i) { + synth->interpolated_vuv[i] = NULL; + synth->pulse_locations[i] = NULL; + synth->pulse_locations_index[i] = NULL; + } + + synth->buffer = new double[buffer_size * 2 + fft_size]; + synth->impulse_response = new double[synth->fft_size]; + synth->dc_remover = new double[synth->fft_size / 2]; + + // Initilize internal parameters + RefreshSynthesizer(synth); + + InitializeMinimumPhaseAnalysis(fft_size, &synth->minimum_phase); + InitializeInverseRealFFT(fft_size, &synth->inverse_real_fft); + InitializeForwardRealFFT(fft_size, &synth->forward_real_fft); +} + +int AddParameters(double *f0, int f0_length, double **spectrogram, + double **aperiodicity, WorldSynthesizer *synth) { + if (synth->head_pointer - synth->current_pointer2 == + synth->number_of_pointers) + return 0; // Since the queue is full, we cannot add the parameters. + int pointer = synth->head_pointer % synth->number_of_pointers; + synth->f0_length[pointer] = f0_length; + synth->f0_origin[pointer] = synth->cumulative_frame + 1; + synth->cumulative_frame += f0_length; + + synth->spectrogram[pointer] = spectrogram; + synth->aperiodicity[pointer] = aperiodicity; + if (synth->cumulative_frame < 1) { + synth->handoff_f0 = f0[f0_length - 1]; + synth->number_of_pulses[pointer] = 0; + synth->head_pointer++; + synth->handoff = 1; + return 1; + } + + int start_sample = + MyMaxInt(0, static_cast(ceil((synth->cumulative_frame - f0_length) * + synth->frame_period * synth->fs))); + int end_sample = + static_cast(ceil((synth->cumulative_frame) * + synth->frame_period * synth->fs)); + int number_of_samples = end_sample - start_sample; + + // Memory allocation + synth->interpolated_vuv[pointer] = new double[number_of_samples + 1]; + synth->pulse_locations[pointer] = new double[number_of_samples]; + synth->pulse_locations_index[pointer] = new int[number_of_samples]; + + GetTimeBase(f0, f0_length, start_sample, number_of_samples, synth); + + synth->handoff_f0 = f0[f0_length - 1]; + synth->head_pointer++; + synth->handoff = 1; + return 1; +} + +void RefreshSynthesizer(WorldSynthesizer *synth) { + ClearRingBuffer(0, synth->number_of_pointers, synth); + synth->handoff_phase = 0; + synth->handoff_f0 = 0; + synth->cumulative_frame = -1; + synth->last_location = 0; + + synth->current_pointer = 0; + synth->current_pointer2 = 0; + synth->head_pointer = 0; + synth->handoff = 0; + + synth->i = 0; + synth->current_frame = 0; + + synth->synthesized_sample = 0; + + for (int i = 0; i < synth->buffer_size * 2 + synth->fft_size; ++i) + synth->buffer[i] = 0; + GetDCRemover(synth->fft_size / 2, synth->dc_remover); +} + +void DestroySynthesizer(WorldSynthesizer *synth) { + RefreshSynthesizer(synth); + delete[] synth->f0_length; + + delete[] synth->spectrogram; + delete[] synth->aperiodicity; + + delete[] synth->buffer; + delete[] synth->impulse_response; + delete[] synth->dc_remover; + + delete[] synth->interpolated_vuv; + delete[] synth->pulse_locations; + delete[] synth->pulse_locations_index; + delete[] synth->number_of_pulses; + delete[] synth->f0_origin; + + DestroyMinimumPhaseAnalysis(&synth->minimum_phase); + DestroyInverseRealFFT(&synth->inverse_real_fft); + DestroyForwardRealFFT(&synth->forward_real_fft); +} + +int IsLocked(WorldSynthesizer *synth) { + int judge = 0; + if (synth->head_pointer - synth->current_pointer2 == + synth->number_of_pointers) + judge++; + if (synth->synthesized_sample + synth->buffer_size >= synth->last_location) + judge++; + + return judge == 2 ? 1 : 0; +} + +int Synthesis2(WorldSynthesizer *synth) { + if (CheckSynthesizer(synth) == 0) + return 0; + for (int i = 0; i < synth->buffer_size + synth->fft_size; ++i) + synth->buffer[i] = synth->buffer[i + synth->buffer_size]; + + int pointer = synth->current_pointer % synth->number_of_pointers; + int noise_size, offset, tmp, index; + int current_location = synth->pulse_locations_index[pointer][synth->i]; + while (current_location < synth->synthesized_sample + synth->buffer_size) { + tmp = GetNextPulseLocationIndex(synth); + noise_size = tmp - current_location; + + GetOneFrameSegment(noise_size, current_location, synth); + offset = + current_location - synth->synthesized_sample - synth->fft_size / 2 + 1; + for (int i = MyMaxInt(0, -offset); i < synth->fft_size; ++i) { + index = i + offset; + synth->buffer[index] += synth->impulse_response[i]; + } + current_location = tmp; + UpdateSynthesizer(current_location, synth); + } + synth->synthesized_sample += synth->buffer_size; + SeekSynthesizer(synth->synthesized_sample, synth); + return 1; +} diff --git a/libsvc/Modules/Lib/World/src/world/cheaptrick.h b/libsvc/Modules/Lib/World/src/world/cheaptrick.h new file mode 100644 index 0000000..0d027a4 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/cheaptrick.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_CHEAPTRICK_H_ +#define WORLD_CHEAPTRICK_H_ + +#include "world/macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// Struct for CheapTrick +//----------------------------------------------------------------------------- +typedef struct { + double q1; + double f0_floor; + int fft_size; +} CheapTrickOption; + +//----------------------------------------------------------------------------- +// CheapTrick() calculates the spectrogram that consists of spectral envelopes +// estimated by CheapTrick. +// +// Input: +// x : Input signal +// x_length : Length of x +// fs : Sampling frequency +// temporal_positions : Time axis +// f0 : F0 contour +// f0_length : Length of F0 contour +// option : Struct to order the parameter for CheapTrick +// +// Output: +// spectrogram : Spectrogram estimated by CheapTrick. +//----------------------------------------------------------------------------- +void CheapTrick(const double *x, int x_length, int fs, + const double *temporal_positions, const double *f0, int f0_length, + const CheapTrickOption *option, double **spectrogram); + +//----------------------------------------------------------------------------- +// InitializeCheapTrickOption allocates the memory to the struct and sets the +// default parameters. +// +// Input: +// fs : Sampling frequency +// +// Output: +// option : Struct for the optional parameter +//----------------------------------------------------------------------------- +void InitializeCheapTrickOption(int fs, CheapTrickOption *option); + +//----------------------------------------------------------------------------- +// GetFFTSizeForCheapTrick() calculates the FFT size based on the sampling +// frequency and the lower limit of f0 (kFloorF0 defined in constantnumbers.h). +// +// Input: +// fs : Sampling frequency +// option : Option struct containing the lower f0 limit +// +// Output: +// FFT size +//----------------------------------------------------------------------------- +int GetFFTSizeForCheapTrick(int fs, const CheapTrickOption *option); + +//----------------------------------------------------------------------------- +// GetF0FloorForCheapTrick() calculates actual lower f0 limit for CheapTrick +// based on the sampling frequency and FFT size used. Whenever f0 is below +// this threshold the spectrum will be analyzed as if the frame is unvoiced +// (using kDefaultF0 defined in constantnumbers.h). +// +// Input: +// fs : Sampling frequency +// fft_size : FFT size +// +// Output: +// Lower f0 limit (Hz) +//----------------------------------------------------------------------------- +double GetF0FloorForCheapTrick(int fs, int fft_size); + +WORLD_END_C_DECLS + +#endif // WORLD_CHEAPTRICK_H_ diff --git a/libsvc/Modules/Lib/World/src/world/codec.h b/libsvc/Modules/Lib/World/src/world/codec.h new file mode 100644 index 0000000..7413cfa --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/codec.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Copyright 2017 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_CODEC_H_ +#define WORLD_CODEC_H_ + +#include "world/macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// GetNumberOfAperiodicities provides the number of dimensions for aperiodicity +// coding. It is determined by only fs. +// +// Input: +// fs : Sampling frequency +// +// Output: +// Number of aperiodicities +//----------------------------------------------------------------------------- +int GetNumberOfAperiodicities(int fs); + +//----------------------------------------------------------------------------- +// CodeAperiodicity codes the aperiodicity. The number of dimensions is +// determined by fs. +// +// Input: +// aperiodicity : Aperiodicity before coding +// f0_length : Length of F0 contour +// fs : Sampling frequency +// fft_size : FFT size +// +// Output: +// coded_aperiodicity : Coded aperiodicity +//----------------------------------------------------------------------------- +void CodeAperiodicity(const double * const *aperiodicity, int f0_length, + int fs, int fft_size, double **coded_aperiodicity); + +//----------------------------------------------------------------------------- +// DecodeAperiodicity decodes the coded aperiodicity. +// +// Input: +// coded_aperiodicity : Coded aperiodicity +// f0_length : Length of F0 contour +// fs : Sampling frequency +// fft_size : FFT size +// +// Output: +// aperiodicity : Decoded aperiodicity +//----------------------------------------------------------------------------- +void DecodeAperiodicity(const double * const *coded_aperiodicity, + int f0_length, int fs, int fft_size, double **aperiodicity); + +//----------------------------------------------------------------------------- +// CodeSpectralEnvelope codes the spectral envelope. +// +// Input: +// aperiodicity : Aperiodicity before coding +// f0_length : Length of F0 contour +// fs : Sampling frequency +// fft_size : FFT size +// number_of_dimensions : Parameter for compression +// +// Output: +// coded_spectral_envelope +//----------------------------------------------------------------------------- +void CodeSpectralEnvelope(const double * const *spectrogram, int f0_length, + int fs, int fft_size, int number_of_dimensions, + double **coded_spectral_envelope); + +//----------------------------------------------------------------------------- +// DecodeSpectralEnvelope decodes the coded spectral envelope. +// +// Input: +// coded_aperiodicity : Coded aperiodicity +// f0_length : Length of F0 contour +// fs : Sampling frequency +// fft_size : FFT size +// number_of_dimensions : Parameter for compression +// +// Output: +// spectrogram +//----------------------------------------------------------------------------- +void DecodeSpectralEnvelope(const double * const *coded_spectral_envelope, + int f0_length, int fs, int fft_size, int number_of_dimensions, + double **spectrogram); + +WORLD_END_C_DECLS + +#endif // WORLD_CODEC_H_ diff --git a/libsvc/Modules/Lib/World/src/world/common.h b/libsvc/Modules/Lib/World/src/world/common.h new file mode 100644 index 0000000..bca1923 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/common.h @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_COMMON_H_ +#define WORLD_COMMON_H_ + +#include "fft.h" +#include "macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// Structs on FFT +//----------------------------------------------------------------------------- +// Forward FFT in the real sequence +typedef struct { + int fft_size; + double *waveform; + fft_complex *spectrum; + fft_plan forward_fft; +} ForwardRealFFT; + +// Inverse FFT in the real sequence +typedef struct { + int fft_size; + double *waveform; + fft_complex *spectrum; + fft_plan inverse_fft; +} InverseRealFFT; + +// Inverse FFT in the complex sequence +typedef struct { + int fft_size; + fft_complex *input; + fft_complex *output; + fft_plan inverse_fft; +} InverseComplexFFT; + +// Minimum phase analysis from logarithmic power spectrum +typedef struct { + int fft_size; + double *log_spectrum; + fft_complex *minimum_phase_spectrum; + fft_complex *cepstrum; + fft_plan inverse_fft; + fft_plan forward_fft; +} MinimumPhaseAnalysis; + +//----------------------------------------------------------------------------- +// GetSuitableFFTSize() calculates the suitable FFT size. +// The size is defined as the minimum length whose length is longer than +// the input sample. +// +// Input: +// sample : Length of the input signal +// +// Output: +// Suitable FFT size +//----------------------------------------------------------------------------- +int GetSuitableFFTSize(int sample); + +//----------------------------------------------------------------------------- +// These four functions are simple max() and min() function +// for "int" and "double" type. +//----------------------------------------------------------------------------- +inline int MyMaxInt(int x, int y) { + return x > y ? x : y; +} + +inline double MyMaxDouble(double x, double y) { + return x > y ? x : y; +} + +inline int MyMinInt(int x, int y) { + return x < y ? x : y; +} + +inline double MyMinDouble(double x, double y) { + return x < y ? x : y; +} + +//----------------------------------------------------------------------------- +// These functions are used in at least two different .cpp files + +//----------------------------------------------------------------------------- +// DCCorrection interpolates the power under f0 Hz +// and is used in CheapTrick() and D4C(). +//----------------------------------------------------------------------------- +void DCCorrection(const double *input, double current_f0, int fs, int fft_size, + double *output); + +//----------------------------------------------------------------------------- +// LinearSmoothing() carries out the spectral smoothing by rectangular window +// whose length is width Hz and is used in CheapTrick() and D4C(). +//----------------------------------------------------------------------------- +void LinearSmoothing(const double *input, double width, int fs, int fft_size, + double *output); + +//----------------------------------------------------------------------------- +// NuttallWindow() calculates the coefficients of Nuttall window whose length +// is y_length and is used in Dio(), Harvest() and D4C(). +//----------------------------------------------------------------------------- +void NuttallWindow(int y_length, double *y); + +//----------------------------------------------------------------------------- +// GetSafeAperiodicity() limit the range of aperiodicity from 0.001 to +// 0.999999999999 (1 - world::kMySafeGuardMinimum). +//----------------------------------------------------------------------------- +inline double GetSafeAperiodicity(double x) { + return MyMaxDouble(0.001, MyMinDouble(0.999999999999, x)); +} + +//----------------------------------------------------------------------------- +// These functions are used to speed up the processing. +// Forward FFT +void InitializeForwardRealFFT(int fft_size, ForwardRealFFT *forward_real_fft); +void DestroyForwardRealFFT(ForwardRealFFT *forward_real_fft); + +// Inverse FFT +void InitializeInverseRealFFT(int fft_size, InverseRealFFT *inverse_real_fft); +void DestroyInverseRealFFT(InverseRealFFT *inverse_real_fft); + +// Inverse FFT (Complex) +void InitializeInverseComplexFFT(int fft_size, + InverseComplexFFT *inverse_complex_fft); +void DestroyInverseComplexFFT(InverseComplexFFT *inverse_complex_fft); + +// Minimum phase analysis (This analysis uses FFT) +void InitializeMinimumPhaseAnalysis(int fft_size, + MinimumPhaseAnalysis *minimum_phase); +void GetMinimumPhaseSpectrum(const MinimumPhaseAnalysis *minimum_phase); +void DestroyMinimumPhaseAnalysis(MinimumPhaseAnalysis *minimum_phase); + +WORLD_END_C_DECLS + +#endif // WORLD_COMMON_H_ diff --git a/libsvc/Modules/Lib/World/src/world/constantnumbers.h b/libsvc/Modules/Lib/World/src/world/constantnumbers.h new file mode 100644 index 0000000..bde4be8 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/constantnumbers.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// This header file only defines constant numbers used for several function. +//----------------------------------------------------------------------------- +#ifndef WORLD_CONSTANT_NUMBERS_H_ +#define WORLD_CONSTANT_NUMBERS_H_ + +namespace world { + // for Dio() + const double kCutOff = 50.0; + + // for StoneMask() + const double kFloorF0StoneMask = 40.0; + + const double kPi = 3.1415926535897932384; + const double kMySafeGuardMinimum = 0.000000000001; + const double kEps = 0.00000000000000022204460492503131; + const double kFloorF0 = 71.0; + const double kCeilF0 = 800.0; + const double kDefaultF0 = 500.0; + const double kLog2 = 0.69314718055994529; + // Maximum standard deviation not to be selected as a best f0. + const double kMaximumValue = 100000.0; + + // Note to me (fs: 48000) + // 71 Hz is the limit to maintain the FFT size at 2048. + // If we use 70 Hz as FLOOR_F0, the FFT size of 4096 is required. + + // for D4C() + const int kHanning = 1; + const int kBlackman = 2; + const double kFrequencyInterval = 3000.0; + const double kUpperLimit = 15000.0; + const double kThreshold = 0.85; + const double kFloorF0D4C = 47.0; + + // for Codec (Mel scale) + // S. Stevens & J. Volkmann, + // The Relation of Pitch to Frequency: A Revised Scale, + // American Journal of Psychology, vol. 53, no. 3, pp. 329-353, 1940. + const double kM0 = 1127.01048; + const double kF0 = 700.0; + const double kFloorFrequency = 40.0; + const double kCeilFrequency = 20000.0; + +} // namespace world + +#endif // WORLD_CONSTANT_NUMBERS_H_ diff --git a/libsvc/Modules/Lib/World/src/world/d4c.h b/libsvc/Modules/Lib/World/src/world/d4c.h new file mode 100644 index 0000000..d4878f2 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/d4c.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_D4C_H_ +#define WORLD_D4C_H_ + +#include "world/macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// Struct for D4C +//----------------------------------------------------------------------------- +typedef struct { + double threshold; +} D4COption; + +//----------------------------------------------------------------------------- +// D4C() calculates the aperiodicity estimated by D4C. +// +// Input: +// x : Input signal +// x_length : Length of x +// fs : Sampling frequency +// temporal_positions : Time axis +// f0 : F0 contour +// f0_length : Length of F0 contour +// fft_size : Number of samples of the aperiodicity in one frame. +// : It is given by the equation fft_size / 2 + 1. +// Output: +// aperiodicity : Aperiodicity estimated by D4C. +//----------------------------------------------------------------------------- +void D4C(const double *x, int x_length, int fs, + const double *temporal_positions, const double *f0, int f0_length, + int fft_size, const D4COption *option, double **aperiodicity); + +//----------------------------------------------------------------------------- +// InitializeD4COption allocates the memory to the struct and sets the +// default parameters. +// +// Output: +// option : Struct for the optional parameter. +//----------------------------------------------------------------------------- +void InitializeD4COption(D4COption *option); + +WORLD_END_C_DECLS + +#endif // WORLD_D4C_H_ diff --git a/libsvc/Modules/Lib/World/src/world/dio.h b/libsvc/Modules/Lib/World/src/world/dio.h new file mode 100644 index 0000000..917fff8 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/dio.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_DIO_H_ +#define WORLD_DIO_H_ + +#include "macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// Struct for DIO +//----------------------------------------------------------------------------- +typedef struct { + double f0_floor; + double f0_ceil; + double channels_in_octave; + double frame_period; // msec + int speed; // (1, 2, ..., 12) + double allowed_range; // Threshold used for fixing the F0 contour. +} DioOption; + +//----------------------------------------------------------------------------- +// DIO +// +// Input: +// x : Input signal +// x_length : Length of x +// fs : Sampling frequency +// option : Struct to order the parameter for DIO +// +// Output: +// temporal_positions : Temporal positions. +// f0 : F0 contour. +//----------------------------------------------------------------------------- +void Dio(const double *x, int x_length, int fs, const DioOption *option, + double *temporal_positions, double *f0); + +//----------------------------------------------------------------------------- +// InitializeDioOption allocates the memory to the struct and sets the +// default parameters. +// +// Output: +// option : Struct for the optional parameter. +//----------------------------------------------------------------------------- +void InitializeDioOption(DioOption *option); + +//----------------------------------------------------------------------------- +// GetSamplesForDIO() calculates the number of samples required for Dio(). +// +// Input: +// fs : Sampling frequency [Hz] +// x_length : Length of the input signal [Sample]. +// frame_period : Frame shift [msec] +// +// Output: +// The number of samples required to store the results of Dio() +//----------------------------------------------------------------------------- +int GetSamplesForDIO(int fs, int x_length, double frame_period); + +WORLD_END_C_DECLS + +#endif // WORLD_DIO_H_ diff --git a/libsvc/Modules/Lib/World/src/world/fft.h b/libsvc/Modules/Lib/World/src/world/fft.h new file mode 100644 index 0000000..c48c9b8 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/fft.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// These functions and variables are defined to use FFT as well as FFTW +// Please see fft.cpp to show the detailed information +//----------------------------------------------------------------------------- +#ifndef WORLD_FFT_H_ +#define WORLD_FFT_H_ + +#include "macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +// Commands for FFT (This is the same as FFTW) +#define FFT_FORWARD 1 +#define FFT_BACKWARD 2 +#define FFT_ESTIMATE 3 + +// Complex number for FFT +typedef double fft_complex[2]; +// Struct used for FFT +typedef struct { + int n; + int sign; + unsigned int flags; + fft_complex *c_in; + double *in; + fft_complex *c_out; + double *out; + double *input; + int *ip; + double *w; +} fft_plan; + +fft_plan fft_plan_dft_1d(int n, fft_complex *in, fft_complex *out, int sign, + unsigned int flags); +fft_plan fft_plan_dft_c2r_1d(int n, fft_complex *in, double *out, + unsigned int flags); +fft_plan fft_plan_dft_r2c_1d(int n, double *in, fft_complex *out, + unsigned int flags); +void fft_execute(fft_plan p); +void fft_destroy_plan(fft_plan p); + +WORLD_END_C_DECLS + +#endif // WORLD_FFT_H_ diff --git a/libsvc/Modules/Lib/World/src/world/harvest.h b/libsvc/Modules/Lib/World/src/world/harvest.h new file mode 100644 index 0000000..f330308 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/harvest.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_HARVEST_H_ +#define WORLD_HARVEST_H_ + +#include "macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// Struct for Harvest +//----------------------------------------------------------------------------- +typedef struct { + double f0_floor; + double f0_ceil; + double frame_period; +} HarvestOption; + +//----------------------------------------------------------------------------- +// Harvest +// +// Input: +// x : Input signal +// x_length : Length of x +// fs : Sampling frequency +// option : Struct to order the parameter for Harvest +// +// Output: +// temporal_positions : Temporal positions. +// f0 : F0 contour. +//----------------------------------------------------------------------------- +void Harvest(const double *x, int x_length, int fs, + const HarvestOption *option, double *temporal_positions, double *f0); + +//----------------------------------------------------------------------------- +// InitializeHarvestOption allocates the memory to the struct and sets the +// default parameters. +// +// Output: +// option : Struct for the optional parameter. +//----------------------------------------------------------------------------- +void InitializeHarvestOption(HarvestOption *option); + +//----------------------------------------------------------------------------- +// GetSamplesForHarvest() calculates the number of samples required for +// Harvest(). +// +// Input: +// fs : Sampling frequency [Hz] +// x_length : Length of the input signal [Sample] +// frame_period : Frame shift [msec] +// +// Output: +// The number of samples required to store the results of Harvest(). +//----------------------------------------------------------------------------- +int GetSamplesForHarvest(int fs, int x_length, double frame_period); + +WORLD_END_C_DECLS + +#endif // WORLD_HARVEST_H_ diff --git a/libsvc/Modules/Lib/World/src/world/macrodefinitions.h b/libsvc/Modules/Lib/World/src/world/macrodefinitions.h new file mode 100644 index 0000000..ced5b37 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/macrodefinitions.h @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2008-2011 The Department of Arts and Culture, +// The Government of the Republic of South Africa. +// +// Contributors: Meraka Institute, CSIR, South Africa. +// Giulio Paci. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +// MODIFIED: Giulio Paci +// DATE : 22 January 2016 +// +// Some usefull macros and defines. +//----------------------------------------------------------------------------- + +#ifndef WORLD_MACRODEFINITIONS_H_ +#define WORLD_MACRODEFINITIONS_H_ + +// +// @file macrodefinitions.h +// Definitions of macros that are common to the whole World engine. +// + +// +// @ingroup WorldDefines +// @defgroup WorldMacros System Macros +// Definitions of macros that are common to the whole World engine. +// @{ +// + +// +// Defines +// + +// +// @def WORLD_BEGIN_C_DECLS +// Start block for enclosing C code for inclusion in C++ programs. +// This allows C++ programs to include the C header files of the World +// engine. @code extern "C" { @endcode +// @hideinitializer +// + +// +// @def WORLD_END_C_DECLS +// End block for enclosing C code for inclusion in C++ programs. +// This allows C++ programs to include the C header files of the World +// engine. @code } @endcode +// @hideinitializer +// + +#undef WORLD_BEGIN_C_DECLS +#undef WORLD_END_C_DECLS +#ifdef __cplusplus +# define WORLD_BEGIN_C_DECLS extern "C" { +# define WORLD_END_C_DECLS } +#else // !__cplusplus +# define WORLD_BEGIN_C_DECLS +# define WORLD_END_C_DECLS +#endif // __cplusplus + +// +// @def WORLD_API +// @hideinitializer +// Declares a symbol to be exported for shared library usage. +// + +// +// @def WORLD_LOCAL +// @hideinitializer +// Declares a symbol hidden from other libraries. +// + +// +// @def WORLD_PLUGIN_API +// @hideinitializer +// Declares a symbol to be exported for plug-in usage. +// + +// Generic helper definitions for shared library support +#if defined _WIN32 || defined __CYGWIN__ +# define WORLD_HELPER_DLL_IMPORT __declspec(dllimport) +# define WORLD_HELPER_DLL_EXPORT __declspec(dllexport) +# define WORLD_HELPER_DLL_LOCAL +#else // ! defined _WIN32 || defined __CYGWIN__ || defined WORLD_WIN32 +# if __GNUC__ >= 4 +# define WORLD_HELPER_DLL_IMPORT __attribute__ ((visibility("default"))) +# define WORLD_HELPER_DLL_EXPORT __attribute__ ((visibility("default"))) +# define WORLD_HELPER_DLL_LOCAL __attribute__ ((visibility("hidden"))) +# else // ! __GNUC__ >= 4 +# define WORLD_HELPER_DLL_IMPORT +# define WORLD_HELPER_DLL_EXPORT +# define WORLD_HELPER_DLL_LOCAL +# endif // __GNUC__ >= 4 +#endif // defined _WIN32 || defined __CYGWIN__ || defined WORLD_WIN32 + +// +// WORLD_LIBRARIES_EXPORTS +// ---------------------- +// WORLD_LIBRARIES_EXPORTS should be defined when compiling a +// shared/dynamic library. +// +// Now we use the generic helper definitions above to define +// WORLD_API and WORLD_LOCAL. WORLD_API is used for the public API symbols. +// It's either DLL imports or DLL exports (or does nothing for static build) +// WORLD_LOCAL is used for non-api symbols. +// +// WORLD_SRC +// ------------------ +// WORLD_SRC should be defined when building World (instead of just using it). +// + +#ifdef WORLD_LIBRARIES_EXPORTS +# ifdef WORLD_SRC +# define WORLD_API WORLD_HELPER_DLL_EXPORT +# else // !WORLD_SRC +# define WORLD_API WORLD_HELPER_DLL_IMPORT +# endif // WORLD_SRC +# define WORLD_LOCAL WORLD_HELPER_DLL_LOCAL +#else // !WORLD_LIBRARIES_EXPORTS (static library) +# define WORLD_API +# define WORLD_LOCAL +#endif // WORLD_LIBRARIES_EXPORTS + +// +// @} +// end documentation +// + +#endif // WORLD_MACRODEFINITIONS_H_ diff --git a/libsvc/Modules/Lib/World/src/world/matlabfunctions.h b/libsvc/Modules/Lib/World/src/world/matlabfunctions.h new file mode 100644 index 0000000..098eef1 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/matlabfunctions.h @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_MATLABFUNCTIONS_H_ +#define WORLD_MATLABFUNCTIONS_H_ +#include "../../../../framework.h" +#include "common.h" +#include "macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// fftshift() swaps the left and right halves of input vector. +// http://www.mathworks.com/help/matlab/ref/fftshift.html +// +// Input: +// x : Input vector +// x_length : Length of x +// +// Output: +// y : Swapped vector x +// +// Caution: +// Lengths of index and edges must be the same. +//----------------------------------------------------------------------------- +void fftshift(const double *x, int x_length, double *y); + +//----------------------------------------------------------------------------- +// histc() counts the number of values in vector x that fall between the +// elements in the edges vector (which must contain monotonically +// nondecreasing values). n is a length(edges) vector containing these counts. +// No elements of x can be complex. +// http://www.mathworks.co.jp/help/techdoc/ref/histc.html +// +// Input: +// x : Input vector +// x_length : Length of x +// edges : Input matrix (1-dimension) +// edges_length : Length of edges +// +// Output: +// index : Result counted in vector x +// Caution: +// Lengths of index and edges must be the same. +//----------------------------------------------------------------------------- +void histc(const double *x, int x_length, const double *edges, + int edges_length, int *index); + +//----------------------------------------------------------------------------- +// interp1() interpolates to find yi, the values of the underlying function Y +// at the points in the vector or array xi. x must be a vector. +// http://www.mathworks.co.jp/help/techdoc/ref/interp1.html +// +// Input: +// x : Input vector (Time axis) +// y : Values at x[n] +// x_length : Length of x (Length of y must be the same) +// xi : Required vector +// xi_length : Length of xi (Length of yi must be the same) +// +// Output: +// yi : Interpolated vector +//----------------------------------------------------------------------------- +LibSvcApi void interp1(const double *x, const double *y, int x_length, const double *xi, + int xi_length, double *yi); + +//----------------------------------------------------------------------------- +// decimate() carries out down sampling by both IIR and FIR filters. +// Filter coeffiencts are based on FilterForDecimate(). +// +// Input: +// x : Input signal +// x_length : Length of x +// r : Coefficient used for down sampling +// (fs after down sampling is fs/r) +// Output: +// y : Output signal +//----------------------------------------------------------------------------- +void decimate(const double *x, int x_length, int r, double *y); + +//----------------------------------------------------------------------------- +// matlab_round() calculates rounding. +// +// Input: +// x : Input value +// +// Output: +// y : Rounded value +//----------------------------------------------------------------------------- +int matlab_round(double x); + +//----------------------------------------------------------------------------- +// diff() calculates differences and approximate derivatives +// http://www.mathworks.co.jp/help/techdoc/ref/diff.html +// +// Input: +// x : Input signal +// x_length : Length of x +// +// Output: +// y : Output signal +//----------------------------------------------------------------------------- +void diff(const double *x, int x_length, double *y); + +//----------------------------------------------------------------------------- +// interp1Q() is the special case of interp1(). +// We can use this function, provided that All periods of x-axis is the same. +// +// Input: +// x : Origin of the x-axis +// shift : Period of the x-axis +// y : Values at x[n] +// x_length : Length of x (Length of y must be the same) +// xi : Required vector +// xi_length : Length of xi (Length of yi must be the same) +// +// Output: +// yi : Interpolated vector +// +// Caution: +// Length of xi and yi must be the same. +//----------------------------------------------------------------------------- +void interp1Q(double x, double shift, const double *y, int x_length, + const double *xi, int xi_length, double *yi); + +//----------------------------------------------------------------------------- +// randn() generates pseudorandom numbers based on xorshift method. +// +// Output: +// A generated pseudorandom number +//----------------------------------------------------------------------------- +double randn(void); + +//----------------------------------------------------------------------------- +// randn_reseed() forces to seed the pseudorandom generator using initial +// values. +//----------------------------------------------------------------------------- +void randn_reseed(void); + +//----------------------------------------------------------------------------- +// fast_fftfilt() carries out the convolution on the frequency domain. +// +// Input: +// x : Input signal +// x_length : Length of x +// h : Impulse response +// h_length : Length of h +// fft_size : Length of FFT +// forward_real_fft : Struct to speed up the forward FFT +// inverse_real_fft : Struct to speed up the inverse FFT +// +// Output: +// y : Calculated result. +//----------------------------------------------------------------------------- +void fast_fftfilt(const double *x, int x_length, const double *h, int h_length, + int fft_size, const ForwardRealFFT *forward_real_fft, + const InverseRealFFT *inverse_real_fft, double *y); + +//----------------------------------------------------------------------------- +// matlab_std() calculates the standard deviation of the input vector. +// +// Input: +// x : Input vector +// x_length : Length of x +// +// Output: +// Calculated standard deviation +//----------------------------------------------------------------------------- +double matlab_std(const double *x, int x_length); + +WORLD_END_C_DECLS + +#endif // WORLD_MATLABFUNCTIONS_H_ diff --git a/libsvc/Modules/Lib/World/src/world/stonemask.h b/libsvc/Modules/Lib/World/src/world/stonemask.h new file mode 100644 index 0000000..89e2a03 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/stonemask.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_STONEMASK_H_ +#define WORLD_STONEMASK_H_ + +#include "macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// StoneMask() refines the estimated F0 by Dio() +// +// Input: +// x : Input signal +// x_length : Length of the input signal +// fs : Sampling frequency +// time_axis : Temporal information +// f0 : f0 contour +// f0_length : Length of f0 +// +// Output: +// refined_f0 : Refined F0 +//----------------------------------------------------------------------------- +void StoneMask(const double *x, int x_length, int fs, + const double *temporal_positions, const double *f0, int f0_length, + double *refined_f0); + +WORLD_END_C_DECLS + +#endif // WORLD_STONEMASK_H_ diff --git a/libsvc/Modules/Lib/World/src/world/synthesis.h b/libsvc/Modules/Lib/World/src/world/synthesis.h new file mode 100644 index 0000000..fbc5fd2 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/synthesis.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_SYNTHESIS_H_ +#define WORLD_SYNTHESIS_H_ + +#include "world/macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// Synthesis() synthesize the voice based on f0, spectrogram and +// aperiodicity (not excitation signal). +// +// Input: +// f0 : f0 contour +// f0_length : Length of f0 +// spectrogram : Spectrogram estimated by CheapTrick +// fft_size : FFT size +// aperiodicity : Aperiodicity spectrogram based on D4C +// frame_period : Temporal period used for the analysis +// fs : Sampling frequency +// y_length : Length of the output signal (Memory of y has been +// allocated in advance) +// Output: +// y : Calculated speech +//----------------------------------------------------------------------------- +void Synthesis(const double *f0, int f0_length, + const double * const *spectrogram, const double * const *aperiodicity, + int fft_size, double frame_period, int fs, int y_length, double *y); + +WORLD_END_C_DECLS + +#endif // WORLD_SYNTHESIS_H_ diff --git a/libsvc/Modules/Lib/World/src/world/synthesisrealtime.h b/libsvc/Modules/Lib/World/src/world/synthesisrealtime.h new file mode 100644 index 0000000..a8c5b65 --- /dev/null +++ b/libsvc/Modules/Lib/World/src/world/synthesisrealtime.h @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_SYNTHESISREALTIME_H_ +#define WORLD_SYNTHESISREALTIME_H_ + +#include "world/common.h" +#include "world/macrodefinitions.h" + +WORLD_BEGIN_C_DECLS + +//----------------------------------------------------------------------------- +// A struct for real-time synthesis. +// I use no class for compatibility in C language. +// Please make a class for encapsulating it as needed. +// This synthesizer uses a ring buffer. +//----------------------------------------------------------------------------- +typedef struct { + // Basic parameters + int fs; + double frame_period; + int buffer_size; + int number_of_pointers; + int fft_size; + + // Sound buffer for output. The length is buffer_size [sample]. + double *buffer; + int current_pointer; + int i; + + // For DC removal + double *dc_remover; + + //--------------------------------------------------------------------------- + // Followings are internal parameters. + // You should not modify them if you are not expert. + + // Speech parameters in each pointer. + int *f0_length; + int *f0_origin; + double ***spectrogram; + double ***aperiodicity; + + + // Note: + // This is an extremely rough implementation. + // I should optimize this implementation. + int current_pointer2; + int head_pointer; + int synthesized_sample; + + // Internal parameters. + int handoff; + double handoff_phase; + double handoff_f0; + int last_location; + + int cumulative_frame; + int current_frame; + + double **interpolated_vuv; + double **pulse_locations; + int **pulse_locations_index; + int *number_of_pulses; + + double *impulse_response; + + // FFT + MinimumPhaseAnalysis minimum_phase; + InverseRealFFT inverse_real_fft; + ForwardRealFFT forward_real_fft; +} WorldSynthesizer; + +//----------------------------------------------------------------------------- +// InitializeSynthesizer() initializes the synthesizer based on basic +// parameters. +// +// Input: +// fs : Sampling frequency +// frame_period : Frame period (ms) +// fft_size : FFT size +// buffer_size : Buffer size (sample) +// number_of_pointers : The number of elements in the ring buffer +// +// Output: +// synth : Initialized synthesizer +//----------------------------------------------------------------------------- +void InitializeSynthesizer(int fs, double frame_period, int fft_size, + int buffer_size, int number_of_pointers, WorldSynthesizer *synth); + +//----------------------------------------------------------------------------- +// AddParameters() attempts to add speech parameters. +// You can add several frames at the same time. +// +// Input: +// f0 : F0 contour with length of f0_length +// f0_length : This is associated with the number of frames +// spectrogram : Spectrogram +// aperiodicity : Aperiodicity +// +// Output: +// synth : Synthesizer +// +// Return value: +// 1: True, 0: False. +//----------------------------------------------------------------------------- +int AddParameters(double *f0, int f0_length, double **spectrogram, + double **aperiodicity, WorldSynthesizer *synth); + +//----------------------------------------------------------------------------- +// RefreshSynthesizer() sets the parameters to default. +//----------------------------------------------------------------------------- +void RefreshSynthesizer(WorldSynthesizer *synth); + +//----------------------------------------------------------------------------- +// DestroySynthesizer() release the memory. +//----------------------------------------------------------------------------- +void DestroySynthesizer(WorldSynthesizer *synth); + +//----------------------------------------------------------------------------- +// IsLocked() checks whether the synthesizer is locked or not. +// "Lock" is defined as the situation that the ring buffer cannot add +// parameters and cannot synthesize the waveform. +// It will be caused when the duration calculated by the number of added frames +// is below 1 / F0 + buffer_size / fs. +// If this function returns True, please refresh the synthesizer. +// +// Input: +// Synth : Synthesizer (pointer) +// +// Output: +// 1: True, 0: False. +//----------------------------------------------------------------------------- +int IsLocked(WorldSynthesizer *synth); + +//----------------------------------------------------------------------------- +// Synthesis2() generates speech with length of synth->buffer_size sample. +// The parameters are automatially updated, and memory is also released. +// +// Input: +// Synth : Synthesizer (pointer) +// +// Output: +// 1: True, 0: False. +//----------------------------------------------------------------------------- +int Synthesis2(WorldSynthesizer *synth); + +WORLD_END_C_DECLS + +#endif // WORLD_SYNTHESISREALTIME_H_ diff --git a/libsvc/Modules/Lib/World/tools/audioio.cpp b/libsvc/Modules/Lib/World/tools/audioio.cpp new file mode 100644 index 0000000..adcef2f --- /dev/null +++ b/libsvc/Modules/Lib/World/tools/audioio.cpp @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// .wav input/output functions were modified for compatibility with C language. +// Since these functions (wavread() and wavwrite()) are roughly implemented, +// we recommend more suitable functions provided by other organizations. +// This file is independent of WORLD project and for the test.cpp. +//----------------------------------------------------------------------------- +#include "./audioio.h" + +#include +#include +#include +#include + +#if (defined (__WIN32__) || defined (_WIN32)) && !defined (__MINGW32__) +#pragma warning(disable : 4996) +#endif + +namespace { + +static inline int MyMaxInt(int x, int y) { + return x > y ? x : y; +} + +static inline int MyMinInt(int x, int y) { + return x < y ? x : y; +} + +//----------------------------------------------------------------------------- +// CheckHeader() checks the .wav header. This function can only support the +// monaural wave file. This function is only used in waveread(). +//----------------------------------------------------------------------------- +static int CheckHeader(FILE *fp) { + char data_check[5]; + fread(data_check, 1, 4, fp); // "RIFF" + data_check[4] = '\0'; + if (0 != strcmp(data_check, "RIFF")) { + printf("RIFF error.\n"); + return 0; + } + fseek(fp, 4, SEEK_CUR); + fread(data_check, 1, 4, fp); // "WAVE" + if (0 != strcmp(data_check, "WAVE")) { + printf("WAVE error.\n"); + return 0; + } + fread(data_check, 1, 4, fp); // "fmt " + if (0 != strcmp(data_check, "fmt ")) { + printf("fmt error.\n"); + return 0; + } + fread(data_check, 1, 4, fp); // 1 0 0 0 + if (!(16 == data_check[0] && 0 == data_check[1] && + 0 == data_check[2] && 0 == data_check[3])) { + printf("fmt (2) error.\n"); + return 0; + } + fread(data_check, 1, 2, fp); // 1 0 + if (!(1 == data_check[0] && 0 == data_check[1])) { + printf("Format ID error.\n"); + return 0; + } + fread(data_check, 1, 2, fp); // 1 0 + if (!(1 == data_check[0] && 0 == data_check[1])) { + printf("This function cannot support stereo file\n"); + return 0; + } + return 1; +} + +//----------------------------------------------------------------------------- +// GetParameters() extracts fp, nbit, wav_length from the .wav file +// This function is only used in wavread(). +//----------------------------------------------------------------------------- +static int GetParameters(FILE *fp, int *fs, int *nbit, int *wav_length) { + char data_check[5] = {0}; + data_check[4] = '\0'; + unsigned char for_int_number[4]; + fread(for_int_number, 1, 4, fp); + *fs = 0; + for (int i = 3; i >= 0; --i) *fs = *fs * 256 + for_int_number[i]; + // Quantization + fseek(fp, 6, SEEK_CUR); + fread(for_int_number, 1, 2, fp); + *nbit = for_int_number[0]; + + // Skip until "data" is found. 2011/03/28 + while (0 != fread(data_check, 1, 1, fp)) { + if (data_check[0] == 'd') { + fread(&data_check[1], 1, 3, fp); + if (0 != strcmp(data_check, "data")) + fseek(fp, -3, SEEK_CUR); + else + break; + } + } + if (0 != strcmp(data_check, "data")) { + printf("data error.\n"); + return 0; + } + + fread(for_int_number, 1, 4, fp); // "data" + *wav_length = 0; + for (int i = 3; i >= 0; --i) + *wav_length = *wav_length * 256 + for_int_number[i]; + *wav_length /= (*nbit / 8); + return 1; +} + +} // namespace + +void wavwrite(const double *x, int x_length, int fs, int nbit, + const char *filename) { + FILE *fp = fopen(filename, "wb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return; + } + + char text[4] = {'R', 'I', 'F', 'F'}; + uint32_t long_number = 36 + x_length * 2; + fwrite(text, 1, 4, fp); + fwrite(&long_number, 4, 1, fp); + + text[0] = 'W'; + text[1] = 'A'; + text[2] = 'V'; + text[3] = 'E'; + fwrite(text, 1, 4, fp); + text[0] = 'f'; + text[1] = 'm'; + text[2] = 't'; + text[3] = ' '; + fwrite(text, 1, 4, fp); + + long_number = 16; + fwrite(&long_number, 4, 1, fp); + int16_t short_number = 1; + fwrite(&short_number, 2, 1, fp); + short_number = 1; + fwrite(&short_number, 2, 1, fp); + long_number = fs; + fwrite(&long_number, 4, 1, fp); + long_number = fs * 2; + fwrite(&long_number, 4, 1, fp); + short_number = 2; + fwrite(&short_number, 2, 1, fp); + short_number = 16; + fwrite(&short_number, 2, 1, fp); + + text[0] = 'd'; + text[1] = 'a'; + text[2] = 't'; + text[3] = 'a'; + fwrite(text, 1, 4, fp); + long_number = x_length * 2; + fwrite(&long_number, 4, 1, fp); + + int16_t tmp_signal; + for (int i = 0; i < x_length; ++i) { + tmp_signal = static_cast(MyMaxInt(-32768, + MyMinInt(32767, static_cast(x[i] * 32767)))); + fwrite(&tmp_signal, 2, 1, fp); + } + + fclose(fp); +} + +int GetAudioLength(const char *filename) { + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) { + return 0; + } + + if (0 == CheckHeader(fp)) { + fclose(fp); + return -1; + } + + char data_check[5] = { 0 }; + data_check[4] = '\0'; + unsigned char for_int_number[4]; + + // Quantization + fseek(fp, 10, SEEK_CUR); + fread(for_int_number, 1, 2, fp); + int nbit = for_int_number[0]; + + while (0 != fread(data_check, 1, 1, fp)) { + if ('d' == data_check[0]) { + fread(&data_check[1], 1, 3, fp); + if (0 != strcmp(data_check, "data")) + fseek(fp, -3, SEEK_CUR); + else + break; + } + } + if (0 != strcmp(data_check, "data")) { + fclose(fp); + return -1; + } + + fread(for_int_number, 1, 4, fp); // "data" + fclose(fp); + + int wav_length = 0; + for (int i = 3; i >= 0; --i) + wav_length = wav_length * 256 + for_int_number[i]; + wav_length /= (nbit / 8); + + return wav_length; +} + +void wavread(const char* filename, int *fs, int *nbit, double *x) { + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) { + printf("File not found.\n"); + return; + } + + if (0 == CheckHeader(fp)) { + fclose(fp); + return; + } + + int x_length; + if (0 == GetParameters(fp, fs, nbit, &x_length)) { + fclose(fp); + return; + } + + int quantization_byte = *nbit / 8; + double zero_line = pow(2.0, *nbit - 1); + double tmp, sign_bias; + unsigned char for_int_number[4]; + for (int i = 0; i < x_length; ++i) { + sign_bias = tmp = 0.0; + fread(for_int_number, 1, quantization_byte, fp); // "data" + if (for_int_number[quantization_byte-1] >= 128) { + sign_bias = pow(2.0, *nbit - 1); + for_int_number[quantization_byte - 1] = + for_int_number[quantization_byte - 1] & 0x7F; + } + for (int j = quantization_byte - 1; j >= 0; --j) + tmp = tmp * 256.0 + for_int_number[j]; + x[i] = (tmp - sign_bias) / zero_line; + } + fclose(fp); +} diff --git a/libsvc/Modules/Lib/World/tools/audioio.h b/libsvc/Modules/Lib/World/tools/audioio.h new file mode 100644 index 0000000..4fc5988 --- /dev/null +++ b/libsvc/Modules/Lib/World/tools/audioio.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Copyright 2012 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_AUDIOIO_H_ +#define WORLD_AUDIOIO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//----------------------------------------------------------------------------- +// wavwrite() write a .wav file. +// Input: +// x : Input signal +// x_ength : Signal length of x [sample] +// fs : Sampling frequency [Hz] +// nbit : Quantization bit [bit] +// filename : Name of the output signal. +// Caution: +// The variable nbit is not used in this function. +// This function only supports the 16 bit. +//----------------------------------------------------------------------------- +void wavwrite(const double *x, int x_length, int fs, int nbit, + const char *filename); + +//----------------------------------------------------------------------------- +// GetAudioLength() returns the length of .wav file. +// Input: +// filename : Filename of a .wav file. +// Output: +// The number of samples of the file .wav +//----------------------------------------------------------------------------- +int GetAudioLength(const char *filename); + +//----------------------------------------------------------------------------- +// wavread() read a .wav file. +// The memory of output x must be allocated in advance. +// Input: +// filename : Filename of the input file. +// Output: +// fs : Sampling frequency [Hz] +// nbit : Quantization bit [bit] +// x : The output waveform. +//----------------------------------------------------------------------------- +void wavread(const char* filename, int *fs, int *nbit, double *x); + +#ifdef __cplusplus +} +#endif + +#endif // WORLD_AUDIOIO_H_ diff --git a/libsvc/Modules/Lib/World/tools/parameterio.cpp b/libsvc/Modules/Lib/World/tools/parameterio.cpp new file mode 100644 index 0000000..70288e9 --- /dev/null +++ b/libsvc/Modules/Lib/World/tools/parameterio.cpp @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------------- +// Copyright 2017 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +// +// Save/Load functions for three speech parameters. +//----------------------------------------------------------------------------- +#include "./parameterio.h" + +#include +#include +#include + +namespace { +static void WriteOneParameter(FILE *fp, const char *text, + double parameter, int size) { + fwrite(text, 1, 4, fp); + if (size == 4) { + int parameter_int = static_cast(parameter); + fwrite(¶meter_int, 4, 1, fp); + } else { + fwrite(¶meter, 8, 1, fp); + } +} + +static void LoadParameters(FILE *fp, int *number_of_frames, int *fft_size, + int *number_of_dimensions) { + char data_check[12]; + fread(&data_check, 1, 4, fp); // NOF + fread(number_of_frames, 4, 1, fp); + + fread(&data_check, 1, 12, fp); // FP (skipped) + + fread(&data_check, 1, 4, fp); // FFT + fread(fft_size, 4, 1, fp); + + fread(&data_check, 1, 4, fp); // NOD + fread(number_of_dimensions, 4, 1, fp); + *number_of_dimensions = + *number_of_dimensions == 0 ? *fft_size / 2 + 1 : *number_of_dimensions; + + fread(&data_check, 1, 8, fp); // FS (skipped) +} + +static int CheckHeader(FILE *fp, const char *text) { + char data_check[5]; + fread(data_check, 1, 4, fp); // "F0 " + data_check[4] = '\0'; + if (0 != strcmp(data_check, text)) { + printf("Header error.\n"); + fclose(fp); + return 0; + } + return 1; +} + +} // namespace + +void WriteF0(const char *filename, int f0_length, double frame_period, + const double *temporal_positions, const double *f0, int text_flag) { + if (text_flag == 1) { + FILE *fp = fopen(filename, "w"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return; + } + for (int i = 0; i < f0_length; ++i) + fprintf(fp, "%.5f %.5f\r\n", temporal_positions[i], f0[i]); + fclose(fp); + } else { + FILE *fp = fopen(filename, "wb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return; + } + + // Header + fwrite("F0 ", 1, 4, fp); + + // Parameters + WriteOneParameter(fp, "NOF ", f0_length, 4); + WriteOneParameter(fp, "FP ", frame_period, 8); + + // Data + fwrite(f0, 8, f0_length, fp); + fclose(fp); + } +} + +int ReadF0(const char *filename, double *temporal_positions, double *f0) { + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return 0; + } + + // Header + if (CheckHeader(fp, "F0 ") == 0) return 0; + + // Parameters + char data_check[5]; + fread(data_check, 1, 4, fp); // "NOF " + int number_of_frames; + fread(&number_of_frames, 4, 1, fp); + + fread(data_check, 1, 4, fp); // "FP " + double frame_period; + fread(&frame_period, 8, 1, fp); + + // Data + fread(f0, 8, number_of_frames, fp); + + fclose(fp); + for (int i = 0; i < number_of_frames; ++i) + temporal_positions[i] = i / 1000.0 * frame_period; + return 1; +} + +double GetHeaderInformation(const char *filename, const char *parameter) { + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return 0; + } + + char data_check[5]; + data_check[4] = '\0'; + for (int i = 0; i < 13; ++i) { + fread(data_check, 1, 4, fp); + if (0 != strcmp(data_check, parameter)) continue; + if (0 == strcmp(parameter, "FP ")) { + double answer; + fread(&answer, 8, 1, fp); + fclose(fp); + return answer; + } else { + int answer; + fread(&answer, 4, 1, fp); + fclose(fp); + return static_cast(answer); + } + } + return 0; +} + +void WriteSpectralEnvelope(const char *filename, int fs, int f0_length, + double frame_period, int fft_size, int number_of_dimensions, + const double * const *spectrogram) { + FILE *fp = fopen(filename, "wb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return; + } + + // Header + fwrite("SPEC", 1, 4, fp); + + // Parameters + WriteOneParameter(fp, "NOF ", f0_length, 4); + WriteOneParameter(fp, "FP ", frame_period, 8); + WriteOneParameter(fp, "FFT ", fft_size, 4); + WriteOneParameter(fp, "NOD ", number_of_dimensions, 4); + WriteOneParameter(fp, "FS ", fs, 4); + + number_of_dimensions = + number_of_dimensions == 0 ? fft_size / 2 + 1 : number_of_dimensions; + + // Data + for (int i = 0; i < f0_length; ++i) + fwrite(spectrogram[i], 8, number_of_dimensions, fp); + fclose(fp); +} + +int ReadSpectralEnvelope(const char *filename, double **spectrogram) { + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return 0; + } + + // Header + if (CheckHeader(fp, "SPEC") == 0) return 0; + + // Parameters + int number_of_frames, fft_size, number_of_dimensions; + LoadParameters(fp, &number_of_frames, &fft_size, &number_of_dimensions); + + // Data + for (int i = 0; i < number_of_frames; ++i) + fread(spectrogram[i], 8, number_of_dimensions, fp); + + fclose(fp); + return 1; +} + +void WriteAperiodicity(const char *filename, int fs, int f0_length, + double frame_period, int fft_size, int number_of_dimensions, + const double * const *aperiodicity) { + FILE *fp = fopen(filename, "wb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return; + } + + // Header + fwrite("AP ", 1, 4, fp); + + // Parameters + WriteOneParameter(fp, "NOF ", f0_length, 4); + WriteOneParameter(fp, "FP ", frame_period, 8); + WriteOneParameter(fp, "FFT ", fft_size, 4); + WriteOneParameter(fp, "NOD ", number_of_dimensions, 4); + WriteOneParameter(fp, "FS ", fs, 4); + number_of_dimensions = + number_of_dimensions == 0 ? fft_size / 2 + 1 : number_of_dimensions; + + // Data + for (int i = 0; i < f0_length; ++i) + fwrite(aperiodicity[i], 8, number_of_dimensions, fp); + fclose(fp); +} + +int ReadAperiodicity(const char *filename, double **aperiodicity) { + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) { + printf("File cannot be opened.\n"); + return 0; + } + + // Header + if (CheckHeader(fp, "AP ") == 0) return 0; + + // Parameters + int number_of_frames, fft_size, number_of_dimensions; + LoadParameters(fp, &number_of_frames, &fft_size, &number_of_dimensions); + + // Data + for (int i = 0; i < number_of_frames; ++i) + fread(aperiodicity[i], 8, number_of_dimensions, fp); + + fclose(fp); + return 1; +} diff --git a/libsvc/Modules/Lib/World/tools/parameterio.h b/libsvc/Modules/Lib/World/tools/parameterio.h new file mode 100644 index 0000000..e4c2154 --- /dev/null +++ b/libsvc/Modules/Lib/World/tools/parameterio.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Copyright 2017 Masanori Morise +// Author: mmorise [at] meiji.ac.jp (Masanori Morise) +// Last update: 2021/02/15 +//----------------------------------------------------------------------------- +#ifndef WORLD_PARAMETERIO_H_ +#define WORLD_PARAMETERIO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//----------------------------------------------------------------------------- +// WriteF0() writes the F0 contour. +// +// Input: +// filename : Filename used for file output +// f0_length : Length of F0 contour +// frame_period : Frame shift used for analysis +// temporal_positions : Time axis +// f0 : F0 contour +// text_flag : The file is written as text (NOT binary). +//----------------------------------------------------------------------------- +void WriteF0(const char *filename, int f0_length, double frame_period, + const double *temporal_positions, const double *f0, int text_flag); + +//----------------------------------------------------------------------------- +// ReadF0() reads the F0 contour from a file.. +// +// Input: +// filename : Filename used for file output +// +// Output: +// temporal_positions : Time axis +// f0 : F0 contour +// +// Note: Memory must be allocated before calling this function. +//----------------------------------------------------------------------------- +int ReadF0(const char *filename, double *temporal_positions, double *f0); + +//----------------------------------------------------------------------------- +// GetHeaderInformation() reads a parameter from a file. +// +// Input: +// filename : Filename used for file output +// parameter : Required parameter +// : "NOF ": number of samples (int) +// : "FP ": frame shift (double) +// : "FFT ": FFT size (int) +// : "NOD ": number of dimensions (int) +// : "FS ": sampling frequency +// +// Note: F0 does not include "FFT ", "NOD ", and "FS ". +// These are ignored in cases where they are used for F0 file. +//----------------------------------------------------------------------------- +double GetHeaderInformation(const char *filename, const char *parameter); + +//----------------------------------------------------------------------------- +// WriteSpectralEnvelope() writes the spectral envelope. +// +// Input: +// filename : Filename used for file output +// fs : Sampling frequency +// f0_length : Length of F0 contour +// frame_period : Frame shift used for analysis +// fft_size : FFT size used for analysis +// number_of_dimensions : Number of dimensions per frame +// spectrogram : Spectral envelope estimated by CheapTrick +//----------------------------------------------------------------------------- +void WriteSpectralEnvelope(const char *filename, int fs, int f0_length, + double frame_period, int fft_size, int number_of_dimensions, + const double * const *spectrogram); + +//----------------------------------------------------------------------------- +// ReadSpectralEnvelope() reads the spectral envelope from a file. +// +// Input: +// filename : Filename used for file output +// +// Output: +// spectrogram : Spectral envelope estimated by CheapTrick +// +// Note: Memory must be allocated before calling this function. +//----------------------------------------------------------------------------- +int ReadSpectralEnvelope(const char *filename, double **spectrogram); + +//----------------------------------------------------------------------------- +// WriteAperiodicity() writes the aperiodicity. +// +// Input: +// filename : Filename used for file output +// fs : Sampling frequency +// f0_length : Length of F0 contour +// frame_period : Frame shift used for analysis +// fft_size : FFT size used for analysis +// number_of_dimensions : Number of dimensions per frame +// spectrogram : Spectral envelope estimated by CheapTrick +//----------------------------------------------------------------------------- +void WriteAperiodicity(const char *filename, int fs, int f0_length, + double frame_period, int fft_size, int number_of_dimensions, + const double * const *aperiodicity); + +//----------------------------------------------------------------------------- +// ReadAperiodicity() reads the aperiodicity from a file. +// +// Input: +// filename : Filename used for file output +// +// Output: +// spectrogram : Spectral envelope estimated by CheapTrick +// +// Note: Memory must be allocated before calling this function. +//----------------------------------------------------------------------------- +int ReadAperiodicity(const char *filename, double **aperiodicity); + +#ifdef __cplusplus +} +#endif + +#endif // WORLD_PARAMETERIO_H_ diff --git a/libsvc/Modules/README.md b/libsvc/Modules/README.md new file mode 100644 index 0000000..48be1dd --- /dev/null +++ b/libsvc/Modules/README.md @@ -0,0 +1,43 @@ +# Example +```c++ +#include "Modules/Models/header/Vits.hpp" + +int main(){ + rapidjson::Document Config; + Config.Parse("Your Config"); + + //Progress bar + InferClass::BaseModelType::callback a_callback = [](size_t a, size_t b) {std::cout << std::to_string((float)a * 100.f / (float)b) << "%\n"; }; + + //return params for inference + InferClass::BaseModelType::callback_params b_callback = []() + { + auto cbaaa = InferClass::InferConfigs(); + cbaaa.kmeans_rate = 0.5; + cbaaa.keys = 0; + return cbaaa; + }; + + //modify duration per phoneme + InferClass::TTS::DurationCallback c_callback = [](std::vector&) {}; + + std::vector output; + try + { + std::wstring inp("watashinoonaniomitekudasai"); + auto model = dynamic_cast(new InferClass::VitsSvc(modConfigJson, a_callback, b_callback)); + + output = model->Inference(inp); + + Wav outWav(model->GetSamplingRate(), output.size() * 2, output.data()); + outWav.Writef(L"test.wav"); + + delete model; + } + catch(std::exception& e) + { + std::cout << e.what(); + } +} + +``` diff --git a/libsvc/Modules/framework.h b/libsvc/Modules/framework.h new file mode 100644 index 0000000..d70645e --- /dev/null +++ b/libsvc/Modules/framework.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef LibSvcDll +#define LibSvcApi __declspec(dllexport) +#else +#define LibSvcApi __declspec(dllimport) +#endif \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/AvCodec/AvCodeResample.h b/libsvc/Modules/header/InferTools/AvCodec/AvCodeResample.h new file mode 100644 index 0000000..c401e90 --- /dev/null +++ b/libsvc/Modules/header/InferTools/AvCodec/AvCodeResample.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include "../../StringPreprocess.hpp" +#include "matlabfunctions.h" +#include "../inferTools.hpp" +extern "C" { +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libswscale/swscale.h" +#include "libswresample/swresample.h" +#include "libavutil/samplefmt.h" +} + +class AudioPreprocess +{ +public: + struct WAV_HEADER { + char RIFF[4] = { 'R','I','F','F' }; //RIFF标识 + unsigned long ChunkSize; //文件大小-8 + char WAVE[4] = { 'W','A','V','E' }; //WAVE块 + char fmt[4] = { 'f','m','t',' ' }; //fmt块 + unsigned long Subchunk1Size; //fmt块大小 + unsigned short AudioFormat; //编码格式 + unsigned short NumOfChan; //声道数 + WAV_HEADER(unsigned long cs = 36, unsigned long sc1s = 16, unsigned short af = 1, unsigned short nc = 1) :ChunkSize(cs), Subchunk1Size(sc1s), AudioFormat(af), NumOfChan(nc) {} + }; + LibSvcApi static WAV_HEADER GetHeader(const std::wstring& path); + LibSvcApi static std::vector arange(double start, double end, double step = 1.0, double div = 1.0); + LibSvcApi std::vector codec(const std::wstring& path, int sr); + LibSvcApi void release(); + LibSvcApi void init(); + LibSvcApi AudioPreprocess(); + ~AudioPreprocess() + { + release(); + } +private: + AVFrame* inFrame; + uint8_t* out_buffer; + SwrContext* swrContext; + AVCodecContext* avCodecContext; + AVFormatContext* avFormatContext; + AVPacket* packet; +}; diff --git a/libsvc/Modules/header/InferTools/Cluster/MoeVSBaseCluster.hpp b/libsvc/Modules/header/InferTools/Cluster/MoeVSBaseCluster.hpp new file mode 100644 index 0000000..8a038e4 --- /dev/null +++ b/libsvc/Modules/header/InferTools/Cluster/MoeVSBaseCluster.hpp @@ -0,0 +1,46 @@ +/** + * FileName: MoeVSBaseCluster.hpp + * Note: MoeVoiceStudioCore 聚类基类 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include + +#define MoeVoiceStudioClusterHeader namespace MoeVoiceStudioCluster { +#define MoeVoiceStudioClusterEnd } + +MoeVoiceStudioClusterHeader + +class MoeVoiceStudioBaseCluster +{ +public: + MoeVoiceStudioBaseCluster() = default; + virtual ~MoeVoiceStudioBaseCluster() = default; + + /** + * \brief 查找聚类最邻近点 + * \param point 待查找的点 + * \param sid 角色ID + * \param n_points 点数 + * \return 查找到的最邻近点 + */ + virtual std::vector find(float* point, long sid, int64_t n_points = 1); +}; + +MoeVoiceStudioClusterEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Cluster/MoeVSClusterManager.hpp b/libsvc/Modules/header/InferTools/Cluster/MoeVSClusterManager.hpp new file mode 100644 index 0000000..92592a8 --- /dev/null +++ b/libsvc/Modules/header/InferTools/Cluster/MoeVSClusterManager.hpp @@ -0,0 +1,75 @@ +/** + * FileName: MoeVSClusterManager.hpp + * Note: MoeVoiceStudioCore 聚类管理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "MoeVSBaseCluster.hpp" +#include +#include +#include "../../framework.h" + +MoeVoiceStudioClusterHeader +class MoeVSCluster +{ +public: + MoeVSCluster() = default; + MoeVSCluster(MoeVoiceStudioBaseCluster* _ext) : _cluster_ext(_ext) {} + MoeVSCluster(const MoeVSCluster&) = delete; + MoeVSCluster(MoeVSCluster&& _ext) noexcept + { + delete _cluster_ext; + _cluster_ext = _ext._cluster_ext; + _ext._cluster_ext = nullptr; + } + MoeVSCluster& operator=(const MoeVSCluster&) = delete; + MoeVSCluster& operator=(MoeVSCluster&& _ext) noexcept + { + if (this == &_ext) + return *this; + delete _cluster_ext; + _cluster_ext = _ext._cluster_ext; + _ext._cluster_ext = nullptr; + return *this; + } + ~MoeVSCluster() + { + delete _cluster_ext; + _cluster_ext = nullptr; + } + MoeVoiceStudioBaseCluster* operator->() const { return _cluster_ext; } +private: + MoeVoiceStudioBaseCluster* _cluster_ext = nullptr; +}; + +using GetMoeVSClusterFn = std::function; + +LibSvcApi void RegisterMoeVSCluster(const std::wstring& _name, const GetMoeVSClusterFn& _constructor_fn); + +/** + * \brief 获取聚类 + * \param _name 类名 + * \param _path 聚类数据路径 + * \param hidden_size hubert维数 + * \param KmeansLen 聚类的长度 + * \return 聚类 + */ +LibSvcApi MoeVSCluster GetMoeVSCluster(const std::wstring& _name, const std::wstring& _path, size_t hidden_size, size_t KmeansLen); + +MoeVoiceStudioClusterEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Cluster/MoeVSIndexCluster.hpp b/libsvc/Modules/header/InferTools/Cluster/MoeVSIndexCluster.hpp new file mode 100644 index 0000000..29fa1f5 --- /dev/null +++ b/libsvc/Modules/header/InferTools/Cluster/MoeVSIndexCluster.hpp @@ -0,0 +1,71 @@ +/** + * FileName: MoeVSIndexCluster.hpp + * Note: MoeVoiceStudioCore 官方聚类(Index) + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#ifdef MoeVoiceStudioIndexCluster +#include +#include "MoeVSBaseCluster.hpp" +#include +#include +#ifdef NDEBUG +#pragma comment (lib,"../../../../../Lib/faiss/out/build/x64-Release/faiss/faiss.lib") +#endif +#ifdef _DEBUG +#pragma comment (lib,"../../../../../Lib/faiss/out/build/x64-Debug/faiss/faiss.lib") +#endif +#pragma comment (lib,"../../../../../Lib/faiss/faiss/libblas.lib") +#pragma comment (lib,"../../../../../Lib/faiss/faiss/liblapack.lib") +#pragma comment (lib,"../../../../../Lib/faiss/faiss/liblapacke.lib") + +MoeVoiceStudioClusterHeader +class IndexClusterCore +{ +public: + IndexClusterCore() = delete; + ~IndexClusterCore(); + IndexClusterCore(const char* _path); + IndexClusterCore(const IndexClusterCore&) = delete; + IndexClusterCore(IndexClusterCore&& move) noexcept; + IndexClusterCore& operator=(const IndexClusterCore&) = delete; + IndexClusterCore& operator=(IndexClusterCore&& move) noexcept; + std::vector find(const float* points, faiss::idx_t n_points, faiss::idx_t n_searched_points = 8); + float* GetVec(faiss::idx_t index); +private: + faiss::Index* IndexPtr = nullptr; + faiss::idx_t Dim = 0; + std::vector IndexsVector; +}; + +class IndexCluster : public MoeVoiceStudioBaseCluster +{ +public: + IndexCluster() = delete; + ~IndexCluster() override = default; + IndexCluster(const std::wstring& _path, size_t hidden_size, size_t KmeansLen); + std::vector find(float* point, long sid, int64_t n_points = 1) override; +private: + std::vector Indexs; + size_t n_hidden_size = 256; +}; + +MoeVoiceStudioClusterEnd + +#endif \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Cluster/MoeVSKmeansCluster.hpp b/libsvc/Modules/header/InferTools/Cluster/MoeVSKmeansCluster.hpp new file mode 100644 index 0000000..d60c7d2 --- /dev/null +++ b/libsvc/Modules/header/InferTools/Cluster/MoeVSKmeansCluster.hpp @@ -0,0 +1,41 @@ +/** + * FileName: MoeVSKmeansCluster.hpp + * Note: MoeVoiceStudioCore 官方聚类(Kmeans) + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "MoeVSBaseCluster.hpp" +#include "../DataStruct/KDTree.hpp" +#include + +MoeVoiceStudioClusterHeader + +class KMeansCluster : public MoeVoiceStudioBaseCluster +{ +public: + KMeansCluster() = delete; + ~KMeansCluster() override = default; + KMeansCluster(const std::wstring& _path, size_t hidden_size, size_t KmeansLen); + std::vector find(float* point, long sid, int64_t n_points = 1) override; +private: + std::vector _tree; + size_t dims = 0; +}; + +MoeVoiceStudioClusterEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/DataStruct/KDTree.hpp b/libsvc/Modules/header/InferTools/DataStruct/KDTree.hpp new file mode 100644 index 0000000..f379d44 --- /dev/null +++ b/libsvc/Modules/header/InferTools/DataStruct/KDTree.hpp @@ -0,0 +1,129 @@ +#pragma once + +/* + * file: KDTree.hpp + * author: J. Frederico Carvalho + * + * This is an adaptation of the KD-tree implementation in rosetta code + * https://rosettacode.org/wiki/K-d_tree + * It is a reimplementation of the C code using C++. + * It also includes a few more queries than the original + * + */ + +#include +#include +#include + +using point_t = std::vector; +using indexArr = std::vector< size_t >; +using pointIndex = std::pair< std::vector< float >, size_t >; + +class KDNode { + public: + using KDNodePtr = std::shared_ptr< KDNode >; + size_t index; + point_t x; + KDNodePtr left; + KDNodePtr right; + + // initializer + KDNode(); + KDNode(const point_t &, const size_t &, const KDNodePtr &, + const KDNodePtr &); + KDNode(const pointIndex &, const KDNodePtr &, const KDNodePtr &); + ~KDNode(); + + // getter + float coord(const size_t &); + + // conversions + explicit operator bool(); + explicit operator point_t(); + explicit operator size_t(); + explicit operator pointIndex(); +}; + +using KDNodePtr = std::shared_ptr< KDNode >; + +KDNodePtr NewKDNodePtr(); + +// square euclidean distance +inline float dist2(const point_t &, const point_t &); +inline float dist2(const KDNodePtr &, const KDNodePtr &); + +// euclidean distance +inline float dist(const point_t &, const point_t &); +inline float dist(const KDNodePtr &, const KDNodePtr &); + +// Need for sorting +class comparer { + public: + size_t idx; + explicit comparer(size_t idx_); + inline bool compare_idx( + const std::pair< std::vector< float >, size_t > &, // + const std::pair< std::vector< float >, size_t > & // + ); +}; + +using pointIndexArr = std::vector< pointIndex >; + +inline void sort_on_idx(const pointIndexArr::iterator &, // + const pointIndexArr::iterator &, // + size_t idx); + +using pointVec = std::vector; + +class KDTree { + KDNodePtr root; + KDNodePtr leaf; + + KDNodePtr make_tree(const pointIndexArr::iterator &begin, // + const pointIndexArr::iterator &end, // + const size_t &length, // + const size_t &level // + ); + + public: + KDTree() = default; + explicit KDTree(pointVec point_array); + + private: + KDNodePtr nearest_( // + const KDNodePtr &branch, // + const point_t &pt, // + const size_t &level, // + const KDNodePtr &best, // + const float&best_dist // + ); + + // default caller + KDNodePtr nearest_(const point_t &pt); + + public: + point_t nearest_point(const point_t &pt); + size_t nearest_index(const point_t &pt); + pointIndex nearest_pointIndex(const point_t &pt); + + private: + pointIndexArr neighborhood_( // + const KDNodePtr &branch, // + const point_t &pt, // + const float&rad, // + const size_t &level // + ); + + public: + pointIndexArr neighborhood( // + const point_t &pt, // + const float&rad); + + pointVec neighborhood_points( // + const point_t &pt, // + const float&rad); + + indexArr neighborhood_indices( // + const point_t &pt, // + const float&rad); +}; diff --git a/libsvc/Modules/header/InferTools/DataStruct/README.md b/libsvc/Modules/header/InferTools/DataStruct/README.md new file mode 100644 index 0000000..271d92a --- /dev/null +++ b/libsvc/Modules/header/InferTools/DataStruct/README.md @@ -0,0 +1 @@ +## KdTree From J. Frederico Carvalho diff --git a/libsvc/Modules/header/InferTools/F0Extractor/BaseF0Extractor.hpp b/libsvc/Modules/header/InferTools/F0Extractor/BaseF0Extractor.hpp new file mode 100644 index 0000000..1bb007e --- /dev/null +++ b/libsvc/Modules/header/InferTools/F0Extractor/BaseF0Extractor.hpp @@ -0,0 +1,85 @@ +/** + * FileName: BaseF0Extractor.hpp + * Note: MoeVoiceStudioCore F0提取算法基类 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include +#include +#define MoeVoiceStudioF0ExtractorHeader namespace MoeVSF0Extractor{ +#define MoeVoiceStudioF0ExtractorEnd } + +MoeVoiceStudioF0ExtractorHeader +#define __NAME__MOEVS(x) std::wstring ClassName = (x) +class BaseF0Extractor +{ +public: + __NAME__MOEVS(L"MoeVSF0Extractor"); + + BaseF0Extractor() = delete; + + /** + * \brief 构造F0提取器 + * \param sampling_rate 采样率 + * \param hop_size HopSize + * \param n_f0_bins F0Bins + * \param max_f0 最大F0 + * \param min_f0 最小F0 + */ + BaseF0Extractor(int sampling_rate, int hop_size, int n_f0_bins = 256, double max_f0 = 1100.0, double min_f0 = 50.0); + + virtual ~BaseF0Extractor() = default; + + /** + * \brief 提取F0 + * \param PCMData 音频PCM数据(SignedInt16 单声道) + * \param TargetLength 目标F0长度 + * \return F0 + */ + virtual std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength); + + /** + * \brief 提取F0 + * \param PCMData 音频PCM数据(SignedInt16 单声道) + * \param TargetLength 目标F0长度 + * \return F0 + */ + std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength); + + /** + * \brief 提取F0 + * \param PCMData 音频PCM数据(SignedInt16 单声道) + * \param TargetLength 目标F0长度 + * \return F0 + */ + std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength); + + static std::vector arange(double start, double end, double step = 1.0, double div = 1.0); +protected: + const uint32_t fs; + const uint32_t hop; + const uint32_t f0_bin; + const double f0_max; + const double f0_min; + double f0_mel_min; + double f0_mel_max; +}; +#undef __NAME__MOEVS +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/F0Extractor/DioF0Extractor.hpp b/libsvc/Modules/header/InferTools/F0Extractor/DioF0Extractor.hpp new file mode 100644 index 0000000..860d0a3 --- /dev/null +++ b/libsvc/Modules/header/InferTools/F0Extractor/DioF0Extractor.hpp @@ -0,0 +1,41 @@ +/** + * FileName: DioF0Extractor.hpp + * Note: MoeVoiceStudioCore 官方F0提取算法 Dio + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "BaseF0Extractor.hpp" + +MoeVoiceStudioF0ExtractorHeader +class DioF0Extractor : public BaseF0Extractor +{ +public: + DioF0Extractor(int sampling_rate, int hop_size, int n_f0_bins = 256, double max_f0 = 1100.0, double min_f0 = 50.0); + + ~DioF0Extractor() override = default; + + void compute_f0(const double* PCMData, size_t PCMLen); + + void InterPf0(size_t TargetLength); + + std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength) override; +private: + std::vector refined_f0; +}; +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/F0Extractor/F0ExtractorManager.hpp b/libsvc/Modules/header/InferTools/F0Extractor/F0ExtractorManager.hpp new file mode 100644 index 0000000..a941f81 --- /dev/null +++ b/libsvc/Modules/header/InferTools/F0Extractor/F0ExtractorManager.hpp @@ -0,0 +1,85 @@ +/** + * FileName: F0ExtractorManager.hpp + * Note: MoeVoiceStudioCore F0提取器管理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "BaseF0Extractor.hpp" +#include +#include "../../framework.h" + +MoeVoiceStudioF0ExtractorHeader + +class F0Extractor +{ +public: + F0Extractor() = delete; + F0Extractor(BaseF0Extractor* _ext) : _f0_ext(_ext) {} + F0Extractor(const F0Extractor&) = delete; + F0Extractor(F0Extractor&& _ext) noexcept + { + delete _f0_ext; + _f0_ext = _ext._f0_ext; + _ext._f0_ext = nullptr; + } + F0Extractor& operator=(const F0Extractor&) = delete; + F0Extractor& operator=(F0Extractor&& _ext) noexcept + { + if (this == &_ext) + return *this; + delete _f0_ext; + _f0_ext = _ext._f0_ext; + _ext._f0_ext = nullptr; + return *this; + } + ~F0Extractor() + { + delete _f0_ext; + _f0_ext = nullptr; + } + BaseF0Extractor* operator->() const { return _f0_ext; } + +private: + BaseF0Extractor* _f0_ext = nullptr; +}; + +using GetF0ExtractorFn = std::function; + +LibSvcApi void RegisterF0Extractor(const std::wstring& _name, const GetF0ExtractorFn& _constructor_fn); + +/** + * \brief 获取F0提取器 + * \param _name 类名 + * \param fs 采样率 + * \param hop HopSize + * \param f0_bin F0Bins + * \param f0_max 最大F0 + * \param f0_min 最小F0 + * \return F0提取器 + */ +LibSvcApi F0Extractor GetF0Extractor(const std::wstring& _name, + uint32_t fs = 48000, + uint32_t hop = 512, + uint32_t f0_bin = 256, + double f0_max = 1100.0, + double f0_min = 50.0); + +LibSvcApi std::vector GetF0ExtractorList(); + +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/F0Extractor/HarvestF0Extractor.hpp b/libsvc/Modules/header/InferTools/F0Extractor/HarvestF0Extractor.hpp new file mode 100644 index 0000000..7b84184 --- /dev/null +++ b/libsvc/Modules/header/InferTools/F0Extractor/HarvestF0Extractor.hpp @@ -0,0 +1,42 @@ +/** + * FileName: HarvestF0Extractor.hpp + * Note: MoeVoiceStudioCore 官方F0提取算法 Harvest + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "BaseF0Extractor.hpp" + +MoeVoiceStudioF0ExtractorHeader +class HarvestF0Extractor : public BaseF0Extractor +{ +public: + HarvestF0Extractor(int sampling_rate, int hop_size, int n_f0_bins = 256, double max_f0 = 1100.0, double min_f0 = 50.0); + + ~HarvestF0Extractor() override = default; + + void compute_f0(const double* PCMData, size_t PCMLen); + + void InterPf0(size_t TargetLength); + + std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength) override; + +private: + std::vector refined_f0; +}; +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/F0Extractor/NetF0Predictors.hpp b/libsvc/Modules/header/InferTools/F0Extractor/NetF0Predictors.hpp new file mode 100644 index 0000000..2b63456 --- /dev/null +++ b/libsvc/Modules/header/InferTools/F0Extractor/NetF0Predictors.hpp @@ -0,0 +1,83 @@ +/** + * FileName: NetF0Predictors.hpp + * Note: MoeVoiceStudioCore 官方F0提取算法 Net + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "BaseF0Extractor.hpp" +#include + +MoeVoiceStudioF0ExtractorHeader + +class NetF0Class +{ +public: + NetF0Class(); + ~NetF0Class() + { + Destory(); + } + void Destory(); + void BuildCPUEnv(unsigned ThreadCount); + void BuildCUDAEnv(unsigned Did); + void BuildDMLEnv(unsigned Did); + void LoadModel(const std::wstring& path); + Ort::Env* NetF0Env = nullptr; + Ort::SessionOptions* NetF0Options = nullptr; + Ort::Session* Model = nullptr; + Ort::MemoryInfo* Memory = nullptr; +private: + std::wstring NetF0PathDir; +}; + +class RMVPEF0Extractor : public BaseF0Extractor +{ +public: + RMVPEF0Extractor(int sampling_rate, int hop_size, int n_f0_bins = 256, double max_f0 = 1100.0, double min_f0 = 50.0); + + ~RMVPEF0Extractor() override = default; + + void InterPf0(size_t TargetLength); + + std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength) override; +private: + std::vector InputNames = { "waveform", "threshold" }; + std::vector OutputNames = { "f0", "uv" }; + std::vector refined_f0; +}; + +class MELPEF0Extractor : public BaseF0Extractor +{ +public: + MELPEF0Extractor(int sampling_rate, int hop_size, int n_f0_bins = 256, double max_f0 = 1100.0, double min_f0 = 50.0); + + ~MELPEF0Extractor() override = default; + + void InterPf0(size_t TargetLength); + + std::vector ExtractF0(const std::vector& PCMData, size_t TargetLength) override; +private: + std::vector InputNames = { "waveform"}; + std::vector OutputNames = { "f0" }; + std::vector refined_f0; +}; + +void EmptyCache(); + +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Sampler/MoeVSBaseSampler.hpp b/libsvc/Modules/header/InferTools/Sampler/MoeVSBaseSampler.hpp new file mode 100644 index 0000000..369f6c3 --- /dev/null +++ b/libsvc/Modules/header/InferTools/Sampler/MoeVSBaseSampler.hpp @@ -0,0 +1,67 @@ +/** + * FileName: MoeVSBaseSampler.hpp + * Note: MoeVoiceStudioCore Diffusion 采样器基类 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#define MoeVoiceStudioSamplerHeader namespace MoeVSSampler { +#define MoeVoiceStudioSamplerEnd } +#include +#include +MoeVoiceStudioSamplerHeader + +class MoeVSBaseSampler +{ +public: + using ProgressCallback = std::function; + + /** + * \brief 构造采样器 + * \param alpha Alphas Onnx模型Session + * \param dfn DenoiseFn Onnx模型Session + * \param pred Predictor Onnx模型Session + * \param Mel_Bins MelBins + * \param _ProgressCallback 进度条回调(直接传模型的回调就可以了) + * \param memory 模型的OrtMemoryInfo + */ + MoeVSBaseSampler(Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, const ProgressCallback& _ProgressCallback, Ort::MemoryInfo* memory); + + virtual ~MoeVSBaseSampler() = default; + + /** + * \brief 采样 + * \param Tensors 输入张量(Tensors[0]为Condition,Tensors[1]为初始噪声) + * \param Steps 采样步数 + * \param SpeedUp 加速倍数 + * \param NoiseScale 噪声规模 + * \param Seed 种子 + * \param Process 当前进度 + * \return Mel张量 + */ + virtual std::vector Sample(std::vector& Tensors, int64_t Steps, int64_t SpeedUp, float NoiseScale, int64_t Seed, size_t& Process); +protected: + int64_t MelBins = 128; + Ort::Session* Alpha = nullptr; + Ort::Session* DenoiseFn = nullptr; + Ort::Session* NoisePredictor = nullptr; + ProgressCallback _callback; + Ort::MemoryInfo* Memory = nullptr; +}; + +MoeVoiceStudioSamplerEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Sampler/MoeVSSamplerManager.hpp b/libsvc/Modules/header/InferTools/Sampler/MoeVSSamplerManager.hpp new file mode 100644 index 0000000..bd14956 --- /dev/null +++ b/libsvc/Modules/header/InferTools/Sampler/MoeVSSamplerManager.hpp @@ -0,0 +1,86 @@ +/** + * FileName: MoeVSSamplerManager.hpp + * Note: MoeVoiceStudioCore Diffusion 采样器管理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "MoeVSBaseSampler.hpp" +#include "../../framework.h" + +MoeVoiceStudioSamplerHeader + +class MoeVSSampler +{ +public: + MoeVSSampler() = delete; + MoeVSSampler(MoeVSBaseSampler* _ext) : _f0_ext(_ext) {} + MoeVSSampler(const MoeVSSampler&) = delete; + MoeVSSampler(MoeVSSampler&& _ext) noexcept + { + delete _f0_ext; + _f0_ext = _ext._f0_ext; + _ext._f0_ext = nullptr; + } + MoeVSSampler& operator=(const MoeVSSampler&) = delete; + MoeVSSampler& operator=(MoeVSSampler&& _ext) noexcept + { + if (this == &_ext) + return *this; + delete _f0_ext; + _f0_ext = _ext._f0_ext; + _ext._f0_ext = nullptr; + return *this; + } + ~MoeVSSampler() + { + delete _f0_ext; + _f0_ext = nullptr; + } + MoeVSBaseSampler* operator->() const { return _f0_ext; } + +private: + MoeVSBaseSampler* _f0_ext = nullptr; +}; + +using GetMoeVSSamplerFn = std::function; + +LibSvcApi void RegisterMoeVSSampler(const std::wstring& _name, const GetMoeVSSamplerFn& _constructor_fn); + +/** + * \brief 获取采样器 + * \param _name 类名 + * \param alpha Alphas Onnx模型Session + * \param dfn DenoiseFn Onnx模型Session + * \param pred Predictor Onnx模型Session + * \param Mel_Bins MelBins + * \param _ProgressCallback 进度条回调(直接传模型的回调就可以了) + * \param memory 模型的OrtMemoryInfo + * \return 采样器 + */ +LibSvcApi MoeVSSampler GetMoeVSSampler(const std::wstring& _name, + Ort::Session* alpha, + Ort::Session* dfn, + Ort::Session* pred, + int64_t Mel_Bins, + const MoeVSBaseSampler::ProgressCallback& _ProgressCallback, + Ort::MemoryInfo* memory); + +LibSvcApi std::vector GetMoeVSSamplerList(); + +MoeVoiceStudioSamplerEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Sampler/MoeVSSamplers.hpp b/libsvc/Modules/header/InferTools/Sampler/MoeVSSamplers.hpp new file mode 100644 index 0000000..ad16edd --- /dev/null +++ b/libsvc/Modules/header/InferTools/Sampler/MoeVSSamplers.hpp @@ -0,0 +1,53 @@ +/** + * FileName: MoeVSSamplers.hpp + * Note: MoeVoiceStudioCore Diffusion 官方采样器 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "MoeVSBaseSampler.hpp" + +MoeVoiceStudioSamplerHeader + +class PndmSampler : public MoeVSBaseSampler +{ +public: + PndmSampler(Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, const ProgressCallback& _ProgressCallback, Ort::MemoryInfo* memory); + ~PndmSampler() override = default; + std::vector Sample(std::vector& Tensors, int64_t Steps, int64_t SpeedUp, float NoiseScale, int64_t Seed, size_t& Process) override; +private: + const std::vector denoiseInput = { "noise", "time", "condition" }; + const std::vector predInput = { "noise", "noise_pred", "time", "time_prev" }; + const std::vector denoiseOutput = { "noise_pred" }; + const std::vector predOutput = { "noise_pred_o" }; +}; + +class DDimSampler : public MoeVSBaseSampler +{ +public: + DDimSampler(Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, const ProgressCallback& _ProgressCallback, Ort::MemoryInfo* memory); + ~DDimSampler() override = default; + std::vector Sample(std::vector& Tensors, int64_t Steps, int64_t SpeedUp, float NoiseScale, int64_t Seed, size_t& Process) override; +private: + const std::vector alphain = { "time" }; + const std::vector alphaout = { "alphas_cumprod" }; + const std::vector denoiseInput = { "noise", "time", "condition" }; + const std::vector denoiseOutput = { "noise_pred" }; +}; + +MoeVoiceStudioSamplerEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/Stft/stft.hpp b/libsvc/Modules/header/InferTools/Stft/stft.hpp new file mode 100644 index 0000000..6605cbe --- /dev/null +++ b/libsvc/Modules/header/InferTools/Stft/stft.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include "fftw3.h" + +namespace DlCodecStft +{ + class STFT + { + public: + STFT() = default; + ~STFT(); + STFT(int WindowSize, int HopSize, int FFTSize = 0); + inline static double PI = 3.14159265358979323846; + std::pair, int64_t> operator()(const std::vector& audioData) const; + private: + int WINDOW_SIZE = 2048; + int HOP_SIZE = WINDOW_SIZE / 4; + int FFT_SIZE = WINDOW_SIZE / 2 + 1; + }; + + class Mel + { + public: + Mel() = delete; + ~Mel() = default; + Mel(int WindowSize, int HopSize, int SamplingRate, int MelSize = 0); + std::pair, int64_t> GetMel(const std::vector& audioData) const; + std::pair, int64_t> operator()(const std::vector& audioData) const; + private: + STFT stft; + int MEL_SIZE = 128; + int FFT_SIZE = 0; + int sr = 22050; + std::vector MelBasis; + }; +} diff --git a/libsvc/Modules/header/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.hpp b/libsvc/Modules/header/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.hpp new file mode 100644 index 0000000..6f42bb8 --- /dev/null +++ b/libsvc/Modules/header/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.hpp @@ -0,0 +1,143 @@ +/** + * FileName: MoeVSCoreTensorExtractor.hpp + * Note: MoeVoiceStudioCore 官方张量预处理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "MoeVoiceStudioTensorExtractor.hpp" + +MoeVoiceStudioTensorExtractorHeader + +class SoVits2TensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + SoVits2TensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~SoVits2TensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + + const std::vector InputNames = { "hidden_unit", "lengths", "pitch", "sid" }; +}; + +class SoVits3TensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + SoVits3TensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~SoVits3TensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + + const std::vector InputNames = { "hidden_unit", "lengths", "pitch", "sid" }; +}; + +class SoVits4TensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + SoVits4TensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~SoVits4TensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + + const std::vector InputNames = { "c", "f0", "mel2ph", "uv", "noise", "sid" }; + const std::vector InputNamesVol = { "c", "f0", "mel2ph", "uv", "noise", "sid", "vol" }; +}; + +class SoVits4DDSPTensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + SoVits4DDSPTensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~SoVits4DDSPTensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + + const std::vector InputNames = { "c", "f0", "mel2ph", "t_window", "noise", "sid" }; + const std::vector InputNamesVol = { "c", "f0", "mel2ph", "t_window", "noise", "sid", "vol" }; +}; + +class RVCTensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + RVCTensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~RVCTensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + + const std::vector InputNames = { "phone", "phone_lengths", "pitch", "pitchf", "ds", "rnd" }; + const std::vector InputNamesVol = { "phone", "phone_lengths", "pitch", "pitchf", "ds", "rnd", "vol" }; +}; + +class DiffSvcTensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + DiffSvcTensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~DiffSvcTensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + const std::vector InputNames = { "hubert", "mel2ph", "spk_embed", "f0" }; + const std::vector OutputNames = { "mel_pred", "f0_pred" }; +}; + +class DiffusionSvcTensorExtractor : public MoeVoiceStudioTensorExtractor +{ +public: + DiffusionSvcTensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) : MoeVoiceStudioTensorExtractor(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other) {} + ~DiffusionSvcTensorExtractor() override = default; + Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ) override; + const std::vector InputNamesVol = { "hubert", "mel2ph", "f0", "volume", "spk_mix" }; + const std::vector InputNames = { "hubert", "mel2ph", "f0", "spk_mix" }; + const std::vector OutputNames = { "mel_pred", "f0_pred", "init_noise" }; +}; + +MoeVoiceStudioTensorExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.hpp b/libsvc/Modules/header/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.hpp new file mode 100644 index 0000000..5a935cd --- /dev/null +++ b/libsvc/Modules/header/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.hpp @@ -0,0 +1,192 @@ +/** + * FileName: MoeVoiceStudioTensorExtractor.hpp + * Note: MoeVoiceStudioCore 张量预处理基类 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include +#include "onnxruntime_cxx_api.h" +#define MoeVoiceStudioTensorExtractorHeader namespace MoeVSTensorPreprocess{ +#define MoeVoiceStudioTensorExtractorEnd } +#include "../../framework.h" + +MoeVoiceStudioTensorExtractorHeader + +class MoeVoiceStudioTensorExtractor +{ +public: + + struct Tensors + { + std::vector HiddenUnit; + std::vector F0; + std::vector Volume; + std::vector SpkMap; + std::vector DDSPNoise; + std::vector Noise; + std::vector Alignment; + std::vector UnVoice; + std::vector NSFF0; + int64_t Length[1] = { 0 }; + int64_t Speaker[1] = { 0 }; + + std::vector HiddenUnitShape; + std::vector FrameShape; + std::vector SpkShape; + std::vector DDSPNoiseShape; + std::vector NoiseShape; + int64_t OneShape[1] = { 1 }; + }; + + struct InferParams + { + float NoiseScale = 0.3f; + float DDSPNoiseScale = 1.0f; + int Seed = 520468; + size_t AudioSize = 0; + int64_t Chara = 0; + float upKeys = 0.f; + void* Other = nullptr; + }; + + struct Others + { + int f0_bin = 256; + float f0_max = 1100.0; + float f0_min = 50.0; + OrtMemoryInfo* Memory = nullptr; + void* Other = nullptr; + }; + + using Params = const InferParams&; + + struct Inputs + { + Tensors Data; + std::vector Tensor; + const char* const* InputNames = nullptr; + const char* const* OutputNames = nullptr; + size_t InputCount = 1; + size_t OutputCount = 1; + }; + + /** + * \brief 构造张量预处理器 + * \param _srcsr 原始采样率 + * \param _sr 目标采样率 + * \param _hop HopSize + * \param _smix 是否启用角色混合 + * \param _volume 是否启用音量emb + * \param _hidden_size hubert的维数 + * \param _nspeaker 角色数 + * \param _other 其他参数,其中的memoryInfo必须为你当前模型的memoryInfo + */ + MoeVoiceStudioTensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other); + + virtual ~MoeVoiceStudioTensorExtractor() = default; + + /** + * \brief 预处理张量 + * \param HiddenUnit HiddenUnit + * \param F0 F0 + * \param Volume 音量 + * \param SpkMap 角色混合数据 + * \param params 参数 + * \return 完成预处理的张量(请将张量接管的所有Vector的数据都存储到Tensors Data中,因为ORT创建的张量要求调用方管理内存,如果不存储到这个位置会导致数据提前析构 + */ + virtual Inputs Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params + ); + + void SetSrcSamplingRates(uint64_t sr) { _SrcSamplingRate = sr; } + + //获取换算为0-255的f0 + [[nodiscard]] std::vector GetNSFF0(const std::vector&) const; + + //将F0中0值单独插值 + static std::vector GetInterpedF0(const std::vector&); + + // + static std::vector InterpUVF0(const std::vector&); + + //获取UnVoiceMask + static std::vector GetUV(const std::vector&); + + //获取对齐矩阵 + static std::vector GetAligments(size_t, size_t); + + //线性组合 + template + static void LinearCombination(std::vector>& _data, size_t default_id, T Value = T(1.0)) + { + if (_data.empty()) + return; + if (default_id > _data.size()) + default_id = 0; + + for (size_t i = 0; i < _data[0].size(); ++i) + { + T Sum = T(0.0); + for (size_t j = 0; j < _data.size(); ++j) + Sum += _data[j][i]; + if (Sum < T(0.0001)) + { + for (size_t j = 0; j < _data.size(); ++j) + _data[j][i] = T(0); + _data[default_id][i] = T(1); + continue; + } + Sum *= T(Value); + for (size_t j = 0; j < _data.size(); ++j) + _data[j][i] /= Sum; + } + } + + //将F0中0值单独插值(可设置是否取log) + LibSvcApi [[nodiscard]] static std::vector GetInterpedF0log(const std::vector&, bool); + + //获取正确的角色混合数据 + [[nodiscard]] std::vector GetCurrectSpkMixData(const std::vector>& _input, size_t dst_len, int64_t curspk) const; + + //获取正确的角色混合数据 + LibSvcApi [[nodiscard]] static std::vector GetSpkMixData(const std::vector>& _input, size_t dst_len, size_t spk_count); +protected: + uint64_t _NSpeaker = 1; + uint64_t _SrcSamplingRate = 32000; + uint64_t _SamplingRate = 32000; + uint64_t _HopSize = 512; + bool _SpeakerMix = false; + bool _Volume = false; + uint64_t _HiddenSize = 256; + int f0_bin = 256; + float f0_max = 1100.0; + float f0_min = 50.0; + float f0_mel_min = 1127.f * log(1.f + f0_min / 700.f); + float f0_mel_max = 1127.f * log(1.f + f0_max / 700.f); + OrtMemoryInfo* Memory = nullptr; +private: + std::wstring __NAME__CLASS__ = L"MoeVoiceStudioTensorExtractor"; +}; + +MoeVoiceStudioTensorExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/TensorExtractor/TensorExtractorManager.hpp b/libsvc/Modules/header/InferTools/TensorExtractor/TensorExtractorManager.hpp new file mode 100644 index 0000000..975089f --- /dev/null +++ b/libsvc/Modules/header/InferTools/TensorExtractor/TensorExtractorManager.hpp @@ -0,0 +1,86 @@ +/** + * FileName: TensorExtractorManager.hpp + * Note: MoeVoiceStudioCore 张量预处理类的注册和管理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include "MoeVoiceStudioTensorExtractor.hpp" + +MoeVoiceStudioTensorExtractorHeader + +class TensorExtractor +{ +public: + TensorExtractor() = default; + TensorExtractor(MoeVoiceStudioTensorExtractor* _ext) : _f0_ext(_ext) {} + TensorExtractor(const TensorExtractor&) = delete; + TensorExtractor(TensorExtractor&& _ext) noexcept + { + delete _f0_ext; + _f0_ext = _ext._f0_ext; + _ext._f0_ext = nullptr; + } + TensorExtractor& operator=(const TensorExtractor&) = delete; + TensorExtractor& operator=(TensorExtractor&& _ext) noexcept + { + if (this == &_ext) + return *this; + delete _f0_ext; + _f0_ext = _ext._f0_ext; + _ext._f0_ext = nullptr; + return *this; + } + ~TensorExtractor() + { + delete _f0_ext; + _f0_ext = nullptr; + } + MoeVoiceStudioTensorExtractor* operator->() const { return _f0_ext; } + +private: + MoeVoiceStudioTensorExtractor* _f0_ext = nullptr; +}; + +using GetTensorExtractorFn = std::function; + +LibSvcApi void RegisterTensorExtractor(const std::wstring& _name, const GetTensorExtractorFn& _constructor_fn); + +/** + * \brief 获取张量预处理器 + * \param _name 类名 + * \param _srcsr 原始采样率 + * \param _sr 目标采样率 + * \param _hop HopSize + * \param _smix 是否启用角色混合 + * \param _volume 是否启用音量emb + * \param _hidden_size hubert的维数 + * \param _nspeaker 角色数 + * \param _other 其他参数,其中的memoryInfo必须为你当前模型的memoryInfo + * \return 张量预处理器 + */ +LibSvcApi TensorExtractor GetTensorExtractor( + const std::wstring& _name, + uint64_t _srcsr, uint64_t _sr, uint64_t _hop, + bool _smix, bool _volume, uint64_t _hidden_size, + uint64_t _nspeaker, + const MoeVoiceStudioTensorExtractor::Others& _other +); + +MoeVoiceStudioTensorExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/header/InferTools/inferTools.hpp b/libsvc/Modules/header/InferTools/inferTools.hpp new file mode 100644 index 0000000..403d6bd --- /dev/null +++ b/libsvc/Modules/header/InferTools/inferTools.hpp @@ -0,0 +1,311 @@ +/** + * FileName: InferTools.hpp + * Note: MoeVoiceStudioCore 推理工具的定义 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include +#include +#include +#include +#include +#define MOEVSINFERTOOLSHEADER namespace InferTools { +#define MOEVSINFERTOOLSEND } + +#define LibDLVoiceCodecThrow(message) { \ + const std::string LibDlCodecThrowMessage = message;\ + const std::string LibDlCodecThrowMessagePrefix = std::string("[In \"") + std::filesystem::path(__FILE__).filename().string() + "\" Line " + std::to_string(__LINE__) + "] "; \ + if(LibDlCodecThrowMessage.substr(0, LibDlCodecThrowMessagePrefix.length()) != LibDlCodecThrowMessagePrefix) \ + throw std::exception((LibDlCodecThrowMessagePrefix + LibDlCodecThrowMessage).c_str()); \ + else \ + throw std::exception(LibDlCodecThrowMessage.c_str()); \ +} + +MOEVSINFERTOOLSHEADER +struct SlicerSettings +{ + int32_t SamplingRate = 48000; + double Threshold = 30.; + double MinLength = 3.; + int32_t WindowLength = 2048; + int32_t HopSize = 512; +}; +class Wav { +public: + static constexpr int HEAD_LENGTH = 1024; + struct WAV_HEADER { + char RIFF[4] = { 'R','I','F','F' }; + unsigned long ChunkSize; + char WAVE[4] = { 'W','A','V','E' }; + char fmt[4] = { 'f','m','t',' ' }; + unsigned long Subchunk1Size; + unsigned short AudioFormat; + unsigned short NumOfChan; + unsigned long SamplesPerSec; + unsigned long bytesPerSec; + unsigned short blockAlign; + unsigned short bitsPerSample; + char Subchunk2ID[4] = { 'd','a','t','a' }; + unsigned long Subchunk2Size; + WAV_HEADER(unsigned long cs = 36, unsigned long sc1s = 16, unsigned short af = 1, unsigned short nc = 1, unsigned long sr = 22050, unsigned long bps = 44100, unsigned short ba = 2, unsigned short bips = 16, unsigned long sc2s = 0) :ChunkSize(cs), Subchunk1Size(sc1s), AudioFormat(af), NumOfChan(nc), SamplesPerSec(sr), bytesPerSec(bps), blockAlign(ba), bitsPerSample(bips), Subchunk2Size(sc2s) {} + }; + using iterator = int16_t*; + Wav(unsigned long cs = 36, unsigned long sc1s = 16, unsigned short af = 1, unsigned short nc = 1, unsigned long sr = 22050, unsigned long bps = 44100, unsigned short ba = 2, unsigned short bips = 16, unsigned long sc2s = 0) :header({ + cs, + sc1s, + af, + nc, + sr, + bps, + ba, + bips, + sc2s + }), Data(nullptr), StartPos(44) { + dataSize = 0; + SData = nullptr; + } + Wav(unsigned long sr, unsigned long length, const void* data) :header({ + 36, + 16, + 1, + 1, + sr, + sr * 2, + 2, + 16, + length + }), Data(new char[length + 1]), StartPos(44) + { + header.ChunkSize = 36 + length; + memcpy(Data, data, length); + SData = reinterpret_cast(Data); + dataSize = length / 2; + } + Wav(const wchar_t* Path); + Wav(const Wav& input); + Wav(Wav&& input) noexcept; + Wav& operator=(const Wav& input) = delete; + Wav& operator=(Wav&& input) noexcept; + ~Wav() { destory(); } + Wav& cat(const Wav& input); + [[nodiscard]] bool isEmpty() const { return this->header.Subchunk2Size == 0; } + [[nodiscard]] const char* getData() const { return Data; } + char* getData() { return Data; } + [[nodiscard]] WAV_HEADER getHeader() const { return header; } + WAV_HEADER& Header() { return header; } + void destory() const { delete[] Data; } + void changeData(const void* indata, long length, int sr) + { + delete[] Data; + Data = new char[length]; + memcpy(Data, indata, length); + header.ChunkSize = 36 + length; + header.Subchunk2Size = length; + header.SamplesPerSec = sr; + header.bytesPerSec = 2 * sr; + } + int16_t& operator[](const size_t index) const + { + if (index < dataSize) + return *(SData + index); + return *(SData + dataSize - 1); + } + [[nodiscard]] iterator begin() const + { + return reinterpret_cast(Data); + } + [[nodiscard]] iterator end() const + { + return reinterpret_cast(Data + header.Subchunk2Size); + } + [[nodiscard]] int64_t getDataLen()const + { + return static_cast(dataSize); + } + void Writef(const std::wstring& filepath) const + { + FILE* FOut = nullptr; + _wfopen_s(&FOut, filepath.c_str(), L"wb"); + if (!FOut) + return; + fwrite(&header, 1, sizeof(header), FOut); + fwrite(Data, 1, dataSize * 2, FOut); + fclose(FOut); + FOut = nullptr; + } + + static void WritePCMData(int samplingrate, int channel, const std::vector& PCMDATA, const std::wstring& filepath) + { + const WAV_HEADER TmpHeader(long(36 + 2 * PCMDATA.size()), 16, 1, short(channel), samplingrate, samplingrate * 2 * channel, short(2 * channel), short(16), long(2 * PCMDATA.size())); + FILE* FOut = nullptr; + _wfopen_s(&FOut, filepath.c_str(), L"wb"); + if (!FOut) return; + fwrite(&TmpHeader, 1, sizeof(TmpHeader), FOut); + fwrite(PCMDATA.data(), 1, PCMDATA.size() * 2, FOut); + fclose(FOut); + FOut = nullptr; + } + +private: + WAV_HEADER header; + char* Data; + int16_t* SData; + size_t dataSize; + int StartPos; +}; + +/** + * \brief 切片机切片(获取切片位置) + * \param input PCM数据(SignedInt16) + * \param _slicer 切片机设置 + * \return 切片位置 + */ +LibSvcApi std::vector SliceAudio(const std::vector& input, const SlicerSettings& _slicer); + +/** + * \brief 均值滤波器 + * \param vec 待滤波的数据 + * \param window_size 窗口大小 + * \return 滤波后数据 + */ +LibSvcApi std::vector mean_filter(const std::vector& vec, size_t window_size); + +template +double getAvg(const T* start, const T* end) +{ + const auto size = end - start + 1; + auto avg = (double)(*start); + for (auto i = 1; i < size; i++) + avg = avg + (abs((double)start[i]) - avg) / (double)(i + 1ull); + return avg; +} + +LibSvcApi std::vector arange(double start, double end, double step = 1.0, double div = 1.0); + +/** + * \brief 重采样(插值) + * \tparam TOut 输出类型 + * \tparam TIn 输入类型 + * \param _Data 输入数据 + * \param src 输入采样率 + * \param dst 输出采样率 + * \param n_Div 给输出的数据统一除以这个数 + * \return 输出数据 + */ +template +static std::vector InterpResample(const std::vector& _Data, long src, long dst, TOut n_Div = TOut(1)) +{ + if (src != dst) + { + const double intstep = double(src) / double(dst); + const auto xi = InferTools::arange(0, double(_Data.size()), intstep); + auto x0 = InferTools::arange(0, double(_Data.size())); + while (x0.size() < _Data.size()) + x0.emplace_back(x0[x0.size() - 1] + 1.0); + while (x0.size() > _Data.size()) + x0.pop_back(); + + std::vector y0(_Data.size()); + for (size_t i = 0; i < _Data.size(); ++i) + y0[i] = double(_Data[i]) / double(n_Div); + + std::vector yi(xi.size()); + interp1(x0.data(), y0.data(), long(x0.size()), xi.data(), long(xi.size()), yi.data()); + + std::vector out(xi.size()); + for (size_t i = 0; i < yi.size(); ++i) + out[i] = TOut(yi[i]); + return out; + } + std::vector out(_Data.size()); + for (size_t i = 0; i < _Data.size(); ++i) + out[i] = TOut(_Data[i]) / n_Div; + return out; +} + +/** + * \brief 重采样(插值) + * \tparam T 数据类型 + * \param Data 输入数据 + * \param src 输入采样率 + * \param dst 输出采样率 + * \return 输出数据 + */ +template +static std::vector InterpFunc(const std::vector& Data, long src, long dst) +{ + if (src != dst) + { + const double intstep = double(src) / double(dst); + auto xi = InferTools::arange(0, double(Data.size()), intstep); + while (xi.size() < size_t(dst)) + xi.emplace_back(xi[xi.size() - 1] + 1.0); + while (xi.size() > size_t(dst)) + xi.pop_back(); + auto x0 = InferTools::arange(0, double(Data.size())); + while (x0.size() < Data.size()) + x0.emplace_back(x0[x0.size() - 1] + 1.0); + while (x0.size() > Data.size()) + x0.pop_back(); + std::vector y0(Data.size()); + for (size_t i = 0; i < Data.size(); ++i) + y0[i] = Data[i] <= T(0.0001) ? NAN : double(Data[i]); + std::vector yi(xi.size()); + interp1(x0.data(), y0.data(), long(x0.size()), xi.data(), long(xi.size()), yi.data()); + std::vector out(xi.size()); + for (size_t i = 0; i < yi.size(); ++i) + out[i] = isnan(yi[i]) ? T(0.0) : T(yi[i]); + return out; + } + return Data; +} +#ifdef MoeVoiceStudioAvxAcc +class FloatTensorWrapper +{ +public: + FloatTensorWrapper() = delete; + ~FloatTensorWrapper() { _data_ptr = nullptr; } + FloatTensorWrapper(float* const data_p, size_t _size) : _data_ptr(data_p), _data_size(_size) {} + FloatTensorWrapper(const FloatTensorWrapper& _copy) = delete; + FloatTensorWrapper& operator=(const FloatTensorWrapper&) = delete; + FloatTensorWrapper(FloatTensorWrapper&& _move) noexcept:_data_ptr(_move._data_ptr), _data_size(_move._data_size) {} + FloatTensorWrapper& operator=(FloatTensorWrapper&& _move) noexcept + { + _data_ptr = _move._data_ptr; + _data_size = _move._data_size; + return *this; + } + template + static const T& Min(const T& a, const T& b) { return (a > b) ? b : a; } + float& operator[](size_t index) const { return *(_data_ptr + Min(index, _data_size)); } + FloatTensorWrapper& operator+=(const FloatTensorWrapper& _right); + FloatTensorWrapper& operator-=(const FloatTensorWrapper& _right); + FloatTensorWrapper& operator*=(const FloatTensorWrapper& _right); + FloatTensorWrapper& operator/=(const FloatTensorWrapper& _right); + FloatTensorWrapper& operator+=(float _right); + FloatTensorWrapper& operator-=(float _right); + FloatTensorWrapper& operator*=(float _right); + FloatTensorWrapper& operator/=(float _right); +private: + float* _data_ptr = nullptr; + size_t _data_size = 0; +}; +#endif +MOEVSINFERTOOLSEND \ No newline at end of file diff --git a/libsvc/Modules/header/Logger/MoeSSLogger.hpp b/libsvc/Modules/header/Logger/MoeSSLogger.hpp new file mode 100644 index 0000000..b3a4066 --- /dev/null +++ b/libsvc/Modules/header/Logger/MoeSSLogger.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include "../StringPreprocess.hpp" +#include "../../framework.h" +#include +#include +#define __MOEVS_DEBUG_MESSAGE(msg) __MOEVS_DEBUG_INFO(__FILE__, __LINE__, msg) +#define logger MoeSSLogger::GetLogger() + +namespace MoeSSLogger +{ + class Logger + { + public: + Logger(); + ~Logger(); + LibSvcApi void log(const std::wstring&); + LibSvcApi void log(const char*); + LibSvcApi void error(const std::wstring&); + LibSvcApi void error(const char*); + private: + std::filesystem::path cur_log_dir, logpath, errorpath; + FILE* log_file = nullptr,* error_file = nullptr; + std::mutex mx; + }; + + LibSvcApi Logger& GetLogger(); +} \ No newline at end of file diff --git a/libsvc/Modules/header/Models/DiffSvc.hpp b/libsvc/Modules/header/Models/DiffSvc.hpp new file mode 100644 index 0000000..0f07582 --- /dev/null +++ b/libsvc/Modules/header/Models/DiffSvc.hpp @@ -0,0 +1,135 @@ +/** + * FileName: DiffSvc.hpp + * Note: MoeVoiceStudioCore Onnx Diffusion系Svc 模型定义 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include "SVC.hpp" + +MoeVoiceStudioCoreHeader + +LibSvcApi void LoadVocoderModel(const std::wstring& VocoderPath); + +LibSvcApi void UnLoadVocoderModel(); + +LibSvcApi bool VocoderEnabled(); + +/** + * \brief DiffSvc模型 + */ +class DiffusionSvc : public SingingVoiceConversion +{ +public: + DiffusionSvc(const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_ = ExecutionProviders::CPU, + unsigned DeviceID_ = 0, unsigned ThreadCount_ = 0); + + /** + * \brief 加载DiffSvc模型 + * \param _PathDict 路径,Key分别为["Hubert", "Hifigan", "Encoder", "DenoiseFn", "NoisePredictor", "AfterProcess", "DiffSvc", "Naive", "Alphas"],其中"DiffSvc"、"Naive"、"Alphas"为可选项 + * \param _Config 配置Json + * \param _ProgressCallback 进度条回调函数 + * \param ExecutionProvider_ Provider + * \param DeviceID_ GPU设备ID + * \param ThreadCount_ 线程数 + */ + DiffusionSvc(const std::map& _PathDict, const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_ = ExecutionProviders::CPU, + unsigned DeviceID_ = 0, unsigned ThreadCount_ = 0); + + ~DiffusionSvc() override; + + void load(const std::map& _PathDict, const MJson& _Config, const ProgressCallback& _ProgressCallback); + + void Destory(); + + [[nodiscard]] std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const override; + + [[nodiscard]] std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) const override; + + [[nodiscard]] std::vector Inference(std::wstring& _Paths, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const override; + + [[nodiscard]] std::vector InferPCMData(const std::vector& PCMData, long srcSr, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const override; + + [[nodiscard]] std::vector ShallowDiffusionInference( + std::vector& _16KAudioHubert, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + std::pair, int64_t>& _Mel, + const std::vector& _SrcF0, + const std::vector& _SrcVolume, + const std::vector>& _SrcSpeakerMap, + size_t& Process, + int64_t SrcSize + ) const; + + [[nodiscard]] int64_t GetMaxStep() const + { + return MaxStep; + } + + [[nodiscard]] bool OldVersion() const + { + return diffSvc; + } + + [[nodiscard]] const std::wstring& GetDiffSvcVer() const + { + return DiffSvcVersion; + } + + [[nodiscard]] int64_t GetMelBins() const + { + return melBins; + } + + void NormMel(std::vector& MelSpec) const; + +private: + Ort::Session* encoder = nullptr; + Ort::Session* denoise = nullptr; + Ort::Session* pred = nullptr; + Ort::Session* after = nullptr; + Ort::Session* alpha = nullptr; + Ort::Session* naive = nullptr; + + Ort::Session* diffSvc = nullptr; + + int64_t melBins = 128; + int64_t Pndms = 100; + int64_t MaxStep = 1000; + float SpecMin = -12; + float SpecMax = 2; + + std::wstring DiffSvcVersion = L"DiffSvc"; + + const std::vector nsfInput = { "c", "f0" }; + const std::vector nsfOutput = { "audio" }; + const std::vector DiffInput = { "hubert", "mel2ph", "spk_embed", "f0", "initial_noise", "speedup" }; + const std::vector DiffOutput = { "mel_pred", "f0_pred" }; + const std::vector afterInput = { "x" }; + const std::vector afterOutput = { "mel_out" }; + const std::vector naiveOutput = { "mel" }; +}; + +LibSvcApi std::vector VocoderInfer(std::vector& Mel, std::vector& F0, int64_t MelBins, int64_t MelSize, const Ort::MemoryInfo* Mem); + +MoeVoiceStudioCoreEnd \ No newline at end of file diff --git a/libsvc/Modules/header/Models/EnvManager.hpp b/libsvc/Modules/header/Models/EnvManager.hpp new file mode 100644 index 0000000..23b5fb6 --- /dev/null +++ b/libsvc/Modules/header/Models/EnvManager.hpp @@ -0,0 +1,57 @@ +/** + * FileName: EnvManager.hpp + * Note: MoeVoiceStudioCore 环境管理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include "../../header/Logger/MoeSSLogger.hpp" + +#define MoeVoiceStudioCoreEnvManagerHeader namespace moevsenv{ +#define MoeVoiceStudioCoreEnvManagerEnd } + +MoeVoiceStudioCoreEnvManagerHeader +class MoeVoiceStudioEnv +{ +public: + MoeVoiceStudioEnv() = default; + ~MoeVoiceStudioEnv() { Destory(); } + LibSvcApi void Load(unsigned ThreadCount, unsigned DeviceID, unsigned Provider); + LibSvcApi void Destory(); + LibSvcApi [[nodiscard]] bool IsEnabled() const; + [[nodiscard]] Ort::Env* GetEnv() const { return GlobalOrtEnv; } + [[nodiscard]] Ort::SessionOptions* GetSessionOptions() const { return GlobalOrtSessionOptions; } + [[nodiscard]] Ort::MemoryInfo* GetMemoryInfo() const { return GlobalOrtMemoryInfo; } + [[nodiscard]] int GetCurThreadCount() const { return (int)CurThreadCount; } + [[nodiscard]] int GetCurDeviceID() const { return (int)CurDeviceID; } + [[nodiscard]] int GetCurProvider() const { return (int)CurProvider; } +private: + void Create(unsigned ThreadCount_, unsigned DeviceID_, unsigned ExecutionProvider_); + Ort::Env* GlobalOrtEnv = nullptr; + Ort::SessionOptions* GlobalOrtSessionOptions = nullptr; + Ort::MemoryInfo* GlobalOrtMemoryInfo = nullptr; + unsigned CurThreadCount = unsigned(-1); + unsigned CurDeviceID = unsigned(-1); + unsigned CurProvider = unsigned(-1); + OrtCUDAProviderOptionsV2* cuda_option_v2 = nullptr; +}; + +LibSvcApi MoeVoiceStudioEnv& GetGlobalMoeVSEnv(); + +MoeVoiceStudioCoreEnvManagerEnd \ No newline at end of file diff --git a/libsvc/Modules/header/Models/ModelBase.hpp b/libsvc/Modules/header/Models/ModelBase.hpp new file mode 100644 index 0000000..ea3f580 --- /dev/null +++ b/libsvc/Modules/header/Models/ModelBase.hpp @@ -0,0 +1,131 @@ +/** + * FileName: ModelBase.hpp + * Note: MoeVoiceStudioCore Onnx 模型基类 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include "MoeVSProject.hpp" +#include "../InferTools/inferTools.hpp" + +#define MoeVoiceStudioCoreHeader namespace MoeVoiceStudioCore{ +#define MoeVoiceStudioCoreEnd } +#define MoeVSNotImplementedError LibDLVoiceCodecThrow("NotImplementedError") +#define MoeVSClassName(__Moe__VSClassName) __NAME__CLASS__.emplace_back((__Moe__VSClassName)) +#define MoeVSMaxPath 1024 +static std::wstring GetCurrentFolder(const std::wstring& defualt = L"") +{ + wchar_t path[MoeVSMaxPath]; +#ifdef _WIN32 + GetModuleFileName(nullptr, path, MoeVSMaxPath); + std::wstring _curPath = path; + _curPath = _curPath.substr(0, _curPath.rfind(L'\\')); + return _curPath; +#else + //TODO Other System +#error Other System ToDO +#endif +} + +MoeVoiceStudioCoreHeader + +class MoeVoiceStudioModule +{ +public: + //进度条回调 + using ProgressCallback = std::function; + + //Provicer + enum class ExecutionProviders + { + CPU = 0, + CUDA = 1, +#ifdef MOEVSDMLPROVIDER + DML = 2 +#endif + }; + + static [[nodiscard]] std::vector GetOpenFileNameMoeVS(); + + static std::vector CutLens(const std::wstring& input); + + /** + * \brief 构造Onnx模型基类 + * \param ExecutionProvider_ ExecutionProvider(可以理解为设备) + * \param DeviceID_ 设备ID + * \param ThreadCount_ 线程数 + */ + MoeVoiceStudioModule(const ExecutionProviders& ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_ = 0); + + virtual ~MoeVoiceStudioModule(); + + /** + * \brief 输入路径推理 + * \param _Datas [路径,多个路径使用换行符隔开, 推理文本] + * \param _InferParams 推理参数 + * \param _SlicerSettings 切片机配置 + * \return 输出路径 + */ + [[nodiscard]] virtual std::vector Inference(std::wstring& _Datas, + const MoeVSProjectSpace::MoeVSParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const; + + /** + * \brief 获取采样率 + * \return 采样率 + */ + [[nodiscard]] long GetSamplingRate() const + { + return _samplingRate; + } + + static float Clamp(float in, float min = -1.f, float max = 1.f) + { + if (in > max) + return max; + if (in < min) + return min; + return in; + } +protected: + //采样率 + long _samplingRate = 22050; + + Ort::Env* env = nullptr; + Ort::SessionOptions* session_options = nullptr; + Ort::MemoryInfo* memory_info = nullptr; + ExecutionProviders _cur_execution_provider = ExecutionProviders::CPU; + ProgressCallback _callback; + + std::vector __NAME__CLASS__ = { L"MoeVoiceStudioModule" }; +public: + //*******************删除的函数********************// + MoeVoiceStudioModule& operator=(MoeVoiceStudioModule&&) = delete; + MoeVoiceStudioModule& operator=(const MoeVoiceStudioModule&) = delete; + MoeVoiceStudioModule(const MoeVoiceStudioModule&) = delete; + MoeVoiceStudioModule(MoeVoiceStudioModule&&) = delete; +}; + +MoeVoiceStudioCoreEnd \ No newline at end of file diff --git a/libsvc/Modules/header/Models/MoeVSProject.hpp b/libsvc/Modules/header/Models/MoeVSProject.hpp new file mode 100644 index 0000000..1dcc529 --- /dev/null +++ b/libsvc/Modules/header/Models/MoeVSProject.hpp @@ -0,0 +1,312 @@ +/** + * FileName: MoeVSProject.hpp + * Note: MoeVoiceStudioCore 项目相关的定义 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include +#include +#include "../StringPreprocess.hpp" +#include "MJson.h" +namespace MoeVSProjectSpace +{ + class FileWrapper + { + public: + FileWrapper() = delete; + FileWrapper(const wchar_t* _path, const wchar_t* _mode) + { + _wfopen_s(&file_, _path, _mode); + } + ~FileWrapper() + { + if (file_) + fclose(file_); + file_ = nullptr; + } + operator FILE* () const + { + return file_; + } + [[nodiscard]] bool IsOpen() const + { + return file_; + } + private: + FILE* file_ = nullptr; + }; + + using size_type = size_t; + + template + size_type GetSize(const std::vector& inp_vec) + { + return inp_vec.size() * sizeof(T); + } + + struct MoeVoiceStudioSvcSlice + { + std::vector Audio; + std::vector F0; + std::vector Volume; + std::vector< std::vector> Speaker; + long OrgLen = 0; + bool IsNotMute = false; + MoeVoiceStudioSvcSlice() = default; + }; + + struct MoeVoiceStudioSvcData + { + /*std::vector> Audio; + std::vector> F0; + std::vector> Volume; + std::vector>> Speaker; + std::vector OrgLen; + std::vector IsNotMute;*/ + std::vector Slices; + std::wstring Path; + MoeVoiceStudioSvcData() = default; + [[nodiscard]] size_type Size() const + { + size_type _size = 0; + const std::string bytePath = to_byte_string(Path); + _size += sizeof(long) * Slices.size() + Slices.size() + bytePath.length() + 1; + for (const auto& Slice : Slices) + { + _size += GetSize(Slice.Audio) + GetSize(Slice.F0) + GetSize(Slice.Volume); + for (const auto& it : Slice.Speaker) + _size += GetSize(it); + } + return _size; + } + }; + + struct MoeVSAudioSliceRef + { + const std::vector& Audio; + const std::vector& F0; + const std::vector& Volume; + const std::vector>& Speaker; + bool IsNotMute; + long OrgLen; + const std::wstring& Path; + size_t Slice = 0; + void* Mel = nullptr; + MoeVSAudioSliceRef( + const std::vector& audio, + const std::vector& f0, + const std::vector& volume, + const std::vector>& speaker, + bool isnotmute, + long orglen, + const std::wstring& path, + size_t sli, + void* mel_tensor_ptr = nullptr + ) : + Audio(audio), + F0(f0), + Volume(volume), + Speaker(speaker), + IsNotMute(isnotmute), + OrgLen(orglen), + Path(path), + Slice(sli), + Mel(mel_tensor_ptr) + {} + }; + + struct MoeVSParams + { + //通用 + float NoiseScale = 0.3f; //噪声修正因子 0-10 + int64_t Seed = 52468; //种子 + int64_t SpeakerId = 0; //角色ID + uint64_t SrcSamplingRate = 48000; //源采样率 + int64_t SpkCount = 2; //模型角色数 + + //SVC + float IndexRate = 0.f; //索引比 0-1 + float ClusterRate = 0.f; //聚类比 0-1 + float DDSPNoiseScale = 0.8f; //DDSP噪声修正因子 0-10 + float Keys = 0.f; //升降调 -64-64 + size_t MeanWindowLength = 2; //均值滤波器窗口大小 1-20 + size_t Pndm = 100; //Diffusion加速倍数 2-200 + size_t Step = 1000; //Diffusion总步数 200-1000 + std::wstring Sampler = L"Pndm"; //Diffusion采样器 + std::wstring F0Method = L"Dio"; //F0提取算法 + bool UseShallowDiffusion = false; //使用浅扩散 + + //SVCRTInfer + int64_t RTSampleSize = 44100; + int64_t CrossFadeLength = 320; + + //TTS + std::vector SpeakerMix; //角色混合比例 + float LengthScale = 1.0f; //时长修正因子 + float DurationPredictorNoiseScale = 0.8f; //随机时长预测器噪声修正因子 + float FactorDpSdp = 0.f; //随机时长预测器与时长预测器混合比例 + float GateThreshold = 0.66666f; //Tacotron2解码器EOS阈值 + int64_t MaxDecodeStep = 2000; //Tacotron2最大解码步数 + std::vector EmotionPrompt; //情感标记 + std::wstring PlaceHolderSymbol = L"|"; //音素分隔符 + float RestTime = 0.5f; //停顿时间,为负数则直接断开音频并创建新音频 + std::string LanguageSymbol = "JP"; //语言 + std::wstring AdditionalInfo; //G2P额外信息 + std::wstring SpeakerName = L"0"; //角色名 + }; + + struct MoeVSTTSToken + { + std::wstring Text; //输入文本 + std::vector Phonemes; //音素序列 + std::vector Tones; //音调序列 + std::vector Durations; //时长序列 + std::vector Language; //语言序列 + + MoeVSTTSToken() = default; + MoeVSTTSToken(const MJsonValue& _JsonDocument, const MoeVSParams& _InitParams); + + [[nodiscard]] std::wstring Serialization() const; + }; + + struct MoeVSTTSSeq + { + std::wstring TextSeq; + std::vector SlicedTokens; + std::vector SpeakerMix; //角色混合比例 + std::vector EmotionPrompt; //情感标记 + std::wstring PlaceHolderSymbol = L"|"; //音素分隔符 + float NoiseScale = 0.3f; //噪声修正因子 0-10 + float LengthScale = 1.0f; //时长修正因子 + float DurationPredictorNoiseScale = 0.3f; //随机时长预测器噪声修正因子 + float FactorDpSdp = 0.3f; //随机时长预测器与时长预测器混合比例 + float GateThreshold = 0.66666f; //Tacotron2解码器EOS阈值 + int64_t MaxDecodeStep = 2000; //Tacotron2最大解码步数 + int64_t Seed = 52468; //种子 + float RestTime = 0.5f; //停顿时间,为负数则直接断开音频并创建新音频 + std::string LanguageSymbol = "ZH"; //语言标记 + std::wstring SpeakerName = L"0"; //角色或名称ID + std::wstring AdditionalInfo; //G2P额外信息 + + MoeVSTTSSeq() = default; + MoeVSTTSSeq(const MJsonValue& _JsonDocument, const MoeVSParams& _InitParams); + + [[nodiscard]] std::wstring Serialization() const; + + bool operator==(const MoeVSTTSSeq& right) const; + }; + + using MoeVSSvcParams = MoeVSParams; + using MoeVSTTSParams = MoeVSParams; + + struct ParamsOffset + { + std::vector OrgAudio; + //std::vector Hidden_Unit; + std::vector F0; + std::vector Volume; + std::vector Speaker; + [[nodiscard]] size_type Size() const + { + return size_type(OrgAudio.size() + F0.size() + Volume.size() + Speaker.size()) * sizeof(size_type); + } + ParamsOffset() = default; + }; + + class MoeVSProject + { + public: + struct Header + { + char ChunkSymbol[8] = { 'M','O','E','V','S','P','R','J' }; + size_type DataHeaderAmount = 0; + }; + struct DataHeader + { + char ChunkSymbol[4] = { 'D','A','T','A' }; + + size_type OrgAudioOffsetPosSize = 0; + + size_type F0OffsetPosSize = 0; + + size_type VolumeOffsetPosSize = 0; + + size_type CharacterOffsetPosSize = 0; + + size_type OrgLenSize = 0; + + size_type SymbolSize = 0; + + size_type PathSize = 0; + + size_type HeaderSize = sizeof(DataHeader) - sizeof(size_type); + }; + struct Data + { + DataHeader Header; + ParamsOffset Offset; + MoeVoiceStudioSvcData ParamData; + [[nodiscard]] size_type Size() const + { + size_type size = sizeof(DataHeader); + size += Offset.Size(); + size += ParamData.Size(); + return size; + } + }; + + LibSvcApi MoeVSProject() = delete; + ~MoeVSProject() = default; + + //从文件读取项目 + LibSvcApi MoeVSProject(const std::wstring& _path); + + //从音频数据构造项目 + LibSvcApi MoeVSProject(const std::vector& _params); + + //写入文件 + LibSvcApi void Write(const std::wstring& _path) const; + + //从项目获取音频数据 + [[nodiscard]] std::vector GetParams() const + { + std::vector Params_data; + Params_data.reserve(data_.size()); + for (const auto& i : data_) + Params_data.emplace_back(i.ParamData); + return Params_data; + } + + //从项目获取音频数据(移动构造) + [[nodiscard]] std::vector GetParamsMove() + { + std::vector Params_data; + Params_data.reserve(data_.size()); + for (auto& i : data_) + Params_data.emplace_back(std::move(i.ParamData)); + return Params_data; + } + private: + static constexpr size_t begin_offset = sizeof(Header); + Header moevs_proj_header_; + std::vector data_pos_; + size_type data_chunk_begin_ = 0; + std::vector data_; + }; +} diff --git a/libsvc/Modules/header/Models/SVC.hpp b/libsvc/Modules/header/Models/SVC.hpp new file mode 100644 index 0000000..69951f5 --- /dev/null +++ b/libsvc/Modules/header/Models/SVC.hpp @@ -0,0 +1,215 @@ +/** + * FileName: SVC.hpp + * Note: MoeVoiceStudioCore OnnxSvc 模型基类 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "ModelBase.hpp" +#include "../Logger/MoeSSLogger.hpp" +#include "MJson.h" +#include "../InferTools/inferTools.hpp" +#include "../InferTools/TensorExtractor/TensorExtractorManager.hpp" +#include "../InferTools/Cluster/MoeVSClusterManager.hpp" + +MoeVoiceStudioCoreHeader + +/* +class OnnxModule +{ +public: + enum class Device + { + CPU = 0, + CUDA = 1, +#ifdef MOEVSDMLPROVIDER + DML = 2 +#endif + }; + using callback = std::function; + using int64 = int64_t; + using MTensor = Ort::Value; + + OnnxModule(); + virtual ~OnnxModule(); + void ChangeDevice(Device _dev); + + static std::vector CutLens(const std::wstring& input); + + [[nodiscard]] long GetSamplingRate() const + { + return _samplingRate; + } + + template + static void LinearCombination(std::vector& _data, T Value = T(1.0)) + { + if(_data.empty()) + { + _data = std::vector(1, Value); + return; + } + T Sum = T(0.0); + for(const auto& i : _data) + Sum += i; + if (Sum < T(0.0001)) + { + _data = std::vector(_data.size(), T(0.0)); + _data[0] = Value; + return; + } + Sum *= T(Value); + for (auto& i : _data) + i /= Sum; + } +protected: + Ort::Env* env = nullptr; + Ort::SessionOptions* session_options = nullptr; + Ort::MemoryInfo* memory_info = nullptr; + + modelType _modelType = modelType::SoVits; + Device device_ = Device::CPU; + + long _samplingRate = 22050; + + callback _callback; + + static constexpr long MaxPath = 8000l; + std::wstring _outputPath = GetCurrentFolder() + L"\\outputs"; +}; + */ + +class SingingVoiceConversion : public MoeVoiceStudioModule +{ +public: + SingingVoiceConversion(const ExecutionProviders& ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_ = 0); + + /** + * \brief 输入路径推理 + * \param _Paths 路径,多个路径使用换行符隔开 + * \param _InferParams 推理参数 + * \param _SlicerSettings 切片机配置 + * \return 输出路径 + */ + [[nodiscard]] std::vector Inference(std::wstring& _Paths, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const override; + + /** + * \brief 推理一个音频 + * \param _Slice 音频数据 + * \param _InferParams 推理参数 + * \return 推理结果(PCM signed-int16 单声道) + */ + [[nodiscard]] virtual std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const; + + /** + * \brief 推理一个音频(使用引用) + * \param _Slice 音频数据引用 + * \param _InferParams 推理参数 + * \param _Process 推理进度 + * \return 推理结果(PCM signed-int16 单声道) + */ + [[nodiscard]] virtual std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) const; + + /** + * \brief 推理一个音频(使用PCM) + * \param PCMData 输入的PCM数据(signed-int16 单声道) + * \param srcSr 输入PCM的采样率 + * \param _InferParams 推理参数 + * \return 推理结果(PCM signed-int16 单声道) + */ + [[nodiscard]] virtual std::vector InferPCMData(const std::vector& PCMData, long srcSr, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const; + + //提取音量 + [[nodiscard]] static std::vector ExtractVolume(const std::vector& OrgAudio, int hop_size); + + //提取音量 + [[nodiscard]] std::vector ExtractVolume(const std::vector& OrgAudio) const; + + //获取HopSize + [[nodiscard]] int GetHopSize() const + { + return HopSize; + } + + //获取HiddenUnitKDims + [[nodiscard]] int64_t GetHiddenUnitKDims() const + { + return HiddenUnitKDims; + } + + //获取角色数量 + [[nodiscard]] int64_t GetSpeakerCount() const + { + return SpeakerCount; + } + + //获取角色混合启用状态 + [[nodiscard]] bool CharaMixEnabled() const + { + return EnableCharaMix; + } + + /** + * \brief 切片一个音频 + * \param input 输入的PCM数据(signed-int16 单声道) + * \param _slice_pos 切片位置(单位为采样) + * \param _slicer 切片机设置 + * \return 音频数据 + */ + LibSvcApi [[nodiscard]] static MoeVSProjectSpace::MoeVoiceStudioSvcData GetAudioSlice(const std::vector& input, + const std::vector& _slice_pos, + const InferTools::SlicerSettings& _slicer); + + /** + * \brief 预处理音频数据 + * \param input 完成切片的音频数据 + * \param __SamplingRate 采样率 + * \param __HopSize HopSize + * \param _f0_method F0算法 + */ + LibSvcApi static void PreProcessAudio(MoeVSProjectSpace::MoeVoiceStudioSvcData& input, + int __SamplingRate = 48000, int __HopSize = 512, const std::wstring& _f0_method = L"Dio"); + + ~SingingVoiceConversion() override; + +protected: + MoeVSTensorPreprocess::TensorExtractor _TensorExtractor; + + Ort::Session* hubert = nullptr; + + int HopSize = 320; + int64_t HiddenUnitKDims = 256; + int64_t SpeakerCount = 1; + bool EnableCharaMix = false; + bool EnableVolume = false; + + MoeVoiceStudioCluster::MoeVSCluster Cluster; + + int64_t ClusterCenterSize = 10000; + bool EnableCluster = false; + bool EnableIndex = false; + + const std::vector hubertOutput = { "embed" }; + const std::vector hubertInput = { "source" }; +}; + +MoeVoiceStudioCoreEnd \ No newline at end of file diff --git a/libsvc/Modules/header/Models/VitsSvc.hpp b/libsvc/Modules/header/Models/VitsSvc.hpp new file mode 100644 index 0000000..0751a5b --- /dev/null +++ b/libsvc/Modules/header/Models/VitsSvc.hpp @@ -0,0 +1,103 @@ +/** + * FileName: VitsSvc.hpp + * Note: MoeVoiceStudioCore Onnx Vits系Svc 模型定义 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "SVC.hpp" +#include "DiffSvc.hpp" + +MoeVoiceStudioCoreHeader + +struct ShallowDiffusionData +{ + std::vector _16KAudio, CUDAF0, CUDAVolume; + std::vector> CUDASpeaker; + bool NeedPadding = false; +}; + +LibSvcApi ShallowDiffusionData& GetDataForShallowDiffusion(); + +class VitsSvc : public SingingVoiceConversion +{ +public: + VitsSvc(const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_ = ExecutionProviders::CPU, + unsigned DeviceID_ = 0, unsigned ThreadCount_ = 0); + + VitsSvc(const std::map& _PathDict, const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_ = ExecutionProviders::CPU, + unsigned DeviceID_ = 0, unsigned ThreadCount_ = 0); + + void load( + const std::map& _PathDict, + const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_, + unsigned DeviceID_, unsigned ThreadCount_, + bool MoeVoiceStudioFrontEnd = false + ); + + ~VitsSvc() override; + + void Destory(); + + [[nodiscard]] std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const override; + + [[nodiscard]] std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) const override; + + [[nodiscard]] std::vector Inference(std::wstring& _Paths, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const override; + + //[[nodiscard]] std::vector InferSliceTensor(const MoeVSProjectSpace::MoeVSAudioSlice& _Slice, size_t SliceIdx, + // const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, std::vector& _Tensors, + // std::vector& SoVitsInput) const; + + [[nodiscard]] std::vector InferPCMData(const std::vector& PCMData, long srcSr, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const override; + + [[nodiscard]] std::vector MelExtractor(const float* PCMAudioBegin, const float* PCMAudioEnd) const = delete; + + [[nodiscard]] Ort::MemoryInfo* GetMemoryInfo() const + { + return memory_info; + } + +private: + Ort::Session* VitsSvcModel = nullptr; + std::wstring VitsSvcVersion = L"SoVits4.0"; + + const std::vector soVitsOutput = { "audio" }; + const std::vector soVitsInput = { "hidden_unit", "lengths", "pitch", "sid" }; + const std::vector RVCInput = { "phone", "phone_lengths", "pitch", "pitchf", "ds", "rnd" }; + const std::vector StftOutput = { "mel" }; + const std::vector StftInput = { "waveform", "aligment"}; + +#ifdef WIN32 +#ifdef MoeVSMui + bool RTSTAT = false; + std::deque> inputBuffer, outputBuffer, rawInputBuffer, rawOutputBuffer; + MRecorder* recoder = nullptr; + Mui::Render::MDS_AudioPlayer* audio_player = nullptr; + Mui::Render::MAudioStream* audio_stream = nullptr; + size_t emptyLen = 30000; +#endif +#endif +}; + +MoeVoiceStudioCoreEnd diff --git a/libsvc/Modules/header/Modules.hpp b/libsvc/Modules/header/Modules.hpp new file mode 100644 index 0000000..72c2870 --- /dev/null +++ b/libsvc/Modules/header/Modules.hpp @@ -0,0 +1,127 @@ +/** + * FileName: Modules.hpp + * Note: MoeVoiceStudioCore组件管理 + * + * Copyright (C) 2022-2023 NaruseMioShirakana (shirakanamio@foxmail.com) + * + * This file is part of MoeVoiceStudioCore library. + * MoeVoiceStudioCore library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MoeVoiceStudioCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with Foobar. + * If not, see . + * + * date: 2022-10-17 Create +*/ + +#pragma once +#include "Models/VitsSvc.hpp" +#include "Models/DiffSvc.hpp" +#include "../framework.h" + +namespace MoeVSModuleManager +{ + LibSvcApi int64_t& GetSpeakerCount(); + LibSvcApi int64_t& GetSamplingRate(); + LibSvcApi int32_t& GetVocoderHopSize(); + LibSvcApi int32_t& GetVocoderMelBins(); + /** + * \brief 初始化所有组件 + */ + LibSvcApi void MoeVoiceStudioCoreInitSetup(); + + /** + * \brief 获取当前VitsSvc模型 + * \return 当前模型的指针 + */ + LibSvcApi MoeVoiceStudioCore::VitsSvc* GetVitsSvcModel(); + + /** + * \brief 获取当前DiffusionSvc模型 + * \return 当前模型的指针 + */ + LibSvcApi MoeVoiceStudioCore::DiffusionSvc* GetDiffusionSvcModel(); + + /** + * \brief 卸载模型 + */ + LibSvcApi void UnloadVitsSvcModel(); + + /** + * \brief 卸载模型 + */ + LibSvcApi void UnloadDiffusionSvcModel(); + + /** + * \brief 载入VitsSvc模型 + * \param Config 一个MJson类的实例(配置文件的JSON) + * \param Callback 进度条回调函数 + * \param ProviderID Provider在所有Provider中的ID(遵循Enum Class的定义) + * \param NumThread CPU推理时的线程数(最好设置高一点,GPU不支持的算子可能也会Fallback到CPU) + * \param DeviceID GPU设备ID + */ + LibSvcApi void LoadVitsSvcModel(const MJson& Config, + const MoeVoiceStudioCore::MoeVoiceStudioModule::ProgressCallback& Callback, + int ProviderID, int NumThread, int DeviceID); + + /** + * \brief 载入DiffusionSvc模型 + * \param Config 一个MJson类的实例(配置文件的JSON) + * \param Callback 进度条回调函数 + * \param ProviderID Provider在所有Provider中的ID(遵循Enum Class的定义) + * \param NumThread CPU推理时的线程数(最好设置高一点,GPU不支持的算子可能也会Fallback到CPU) + * \param DeviceID GPU设备ID + */ + LibSvcApi void LoadDiffusionSvcModel(const MJson& Config, + const MoeVoiceStudioCore::MoeVoiceStudioModule::ProgressCallback& Callback, + int ProviderID, int NumThread, int DeviceID); + + /** + * \brief 载入Vocoder模型 + * \param VocoderPath Vocoder路径 + */ + LibSvcApi void LoadVocoderModel(const std::wstring& VocoderPath); + + /** + * \brief 卸载Vocoder模型 + */ + LibSvcApi void UnloadVocoderModel(); + + /** + * \brief 检查Vocoder是否可用 + * \return Vocoder状态 + */ + LibSvcApi bool VocoderEnabled(); + + /** + * \brief 推理多组数据 + * \param _Slice 数据包 + * \param _InferParams 参数 + * \return 音频 + */ + LibSvcApi std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams); + + /** + * \brief 推理切片数据 + * \param _Slice 切片数据 + * \param _InferParams 参数 + * \param _Process 进度条 + * \return 音频 + */ + LibSvcApi std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process); + + LibSvcApi bool ShallowDiffusionEnabled(); +} + +namespace MoeVSRename +{ + using MoeVSVitsBasedSvc = MoeVoiceStudioCore::VitsSvc; + using MoeVSDiffBasedSvc = MoeVoiceStudioCore::DiffusionSvc; +} + diff --git a/libsvc/Modules/header/StringPreprocess.hpp b/libsvc/Modules/header/StringPreprocess.hpp new file mode 100644 index 0000000..64a3b40 --- /dev/null +++ b/libsvc/Modules/header/StringPreprocess.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "../framework.h" + +LibSvcApi std::string to_byte_string(const std::wstring& input); + +LibSvcApi std::string to_ansi_string(const std::wstring& input); + +LibSvcApi std::wstring to_wide_string(const std::string& input); + +LibSvcApi std::wstring string_vector_to_string(const std::vector& vector); + +LibSvcApi std::wstring wstring_vector_to_string(const std::vector& vector); + +template +std::wstring vector_to_string(const std::vector& vector) +{ + std::wstring vecstr = L"["; + for (const auto& it : vector) + { + std::wstring TmpStr = std::to_wstring(it); + if ((std::is_same_v || std::is_same_v) && TmpStr.find(L'.') != std::string::npos) + { + while (TmpStr.back() == L'0') + TmpStr.pop_back(); + if (TmpStr.back() == L'.') + TmpStr += L"0"; + } + vecstr += TmpStr + L", "; + } + if (vecstr.length() > 2) + vecstr = vecstr.substr(0, vecstr.length() - 2); + vecstr += L']'; + return vecstr; +} \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/AvCodec/AvCodeResample.cpp b/libsvc/Modules/src/InferTools/AvCodec/AvCodeResample.cpp new file mode 100644 index 0000000..56ccf8b --- /dev/null +++ b/libsvc/Modules/src/InferTools/AvCodec/AvCodeResample.cpp @@ -0,0 +1,198 @@ +#include "../../header/InferTools/AvCodec/AvCodeResample.h" + + +AudioPreprocess::WAV_HEADER AudioPreprocess::GetHeader(const std::wstring& path) +{ + WAV_HEADER header; + char buf[1024]; + FILE* stream; + _wfreopen_s(&stream, path.c_str(), L"rb", stderr); + if (stream == nullptr) { + throw (std::exception("Wav Load Error")); + } + fread(buf, 1, 1024, stream); + int pos = 0; + while (pos < InferTools::Wav::HEAD_LENGTH) { + if ((buf[pos] == 'R') && (buf[pos + 1] == 'I') && (buf[pos + 2] == 'F') && (buf[pos + 3] == 'F')) { + pos += 4; + break; + } + ++pos; + } + if (pos >= InferTools::Wav::HEAD_LENGTH) + throw (std::exception("Don't order fried rice (annoyed)")); + header.ChunkSize = *(int*)&buf[pos]; + pos += 8; + while (pos < InferTools::Wav::HEAD_LENGTH) { + if ((buf[pos] == 'f') && (buf[pos + 1] == 'm') && (buf[pos + 2] == 't')) { + pos += 4; + break; + } + ++pos; + } + if (pos >= InferTools::Wav::HEAD_LENGTH) + throw (std::exception("Don't order fried rice (annoyed)")); + header.Subchunk1Size = *(int*)&buf[pos]; + pos += 4; + header.AudioFormat = *(short*)&buf[pos]; + pos += 2; + header.NumOfChan = *(short*)&buf[pos]; + if (stream != nullptr) { + fclose(stream); + } + return header; +} + +std::vector AudioPreprocess::arange(double start, double end, double step, double div) +{ + std::vector output; + while (start < end) + { + output.push_back(start / div); + start += step; + } + return output; +} + +std::vector AudioPreprocess::codec(const std::wstring& path, int sr) +{ + //if (path.substr(path.rfind(L'.')) == L".wav") + // return resample(path, sr); + std::vector outData; + int ret = avformat_open_input(&avFormatContext, to_byte_string(path).c_str(), nullptr, nullptr); + if (ret != 0) { + LibDLVoiceCodecThrow((std::string("Can't Open Audio File [ErrCode]") + std::to_string(ret)).c_str()); + } + ret = avformat_find_stream_info(avFormatContext, nullptr); + if (ret < 0) { + LibDLVoiceCodecThrow("Can't Get Audio Info"); + } + int streamIndex = 0; + for (unsigned i = 0; i < avFormatContext->nb_streams; ++i) { + const AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type; + if (avMediaType == AVMEDIA_TYPE_AUDIO) { + streamIndex = static_cast(i); + } + } + const AVCodecParameters* avCodecParameters = avFormatContext->streams[streamIndex]->codecpar; + const AVCodecID avCodecId = avCodecParameters->codec_id; + const AVCodec* avCodec = avcodec_find_decoder(avCodecId); + if (avCodec == nullptr) + LibDLVoiceCodecThrow("Don't order fried rice (annoyed)"); + if (avCodecContext == nullptr) { + LibDLVoiceCodecThrow("Can't Get Decoder Info"); + } + avcodec_parameters_to_context(avCodecContext, avCodecParameters); + ret = avcodec_open2(avCodecContext, avCodec, nullptr); + if (ret < 0) { + LibDLVoiceCodecThrow("Can't Open Decoder"); + } + packet = (AVPacket*)av_malloc(sizeof(AVPacket)); + const AVSampleFormat inFormat = avCodecContext->sample_fmt; + constexpr AVSampleFormat outFormat = AV_SAMPLE_FMT_S16; + const int inSampleRate = avCodecContext->sample_rate; + const int outSampleRate = sr; + + const auto nSample = static_cast(avFormatContext->duration * sr / AV_TIME_BASE); + + uint64_t in_ch_layout = avCodecContext->channel_layout; + if (path.substr(path.rfind(L'.')) == L".wav") + { + const auto head = GetHeader(path); + if (head.NumOfChan == 1) + in_ch_layout = AV_CH_LAYOUT_MONO; + else if (head.NumOfChan == 2) + in_ch_layout = AV_CH_LAYOUT_STEREO; + else + LibDLVoiceCodecThrow("unsupported Channel Num"); + } + constexpr uint64_t out_ch_layout = AV_CH_LAYOUT_MONO; + swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate, + static_cast(in_ch_layout), inFormat, inSampleRate, 0, nullptr + ); + swr_init(swrContext); + const int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout); + int currentIndex = 0; + out_buffer = (uint8_t*)av_malloc(2ull * sr); + while (av_read_frame(avFormatContext, packet) >= 0) { + if (packet->stream_index == streamIndex) { + avcodec_send_packet(avCodecContext, packet); + ret = avcodec_receive_frame(avCodecContext, inFrame); + if (ret == 0) { + swr_convert(swrContext, &out_buffer, 2ull * sr, + (const uint8_t**)inFrame->data, inFrame->nb_samples); + const int out_buffer_size = av_samples_get_buffer_size(nullptr, outChannelCount, (inFrame->nb_samples * sr / inSampleRate) - 1, outFormat, 1); + outData.insert(outData.end(), out_buffer, out_buffer + out_buffer_size); + } + ++currentIndex; + av_packet_unref(packet); + } + } + //Wav outWav(static_cast(sr), static_cast(outData.size()), outData.data()); + auto outWav = reinterpret_cast(outData.data()); + const auto RawWavLen = int64_t(outData.size()) / 2; + if (nSample != static_cast(RawWavLen)) + { + const double interpOff = static_cast(RawWavLen) / static_cast(nSample); + const auto x0 = arange(0.0, static_cast(RawWavLen), 1.0, 1.0); + std::vector y0(RawWavLen); + for (int64_t i = 0; i < RawWavLen; ++i) + y0[i] = outWav[i] ? static_cast(outWav[i]) : NAN; + const auto yi = new double[nSample]; + auto xi = arange(0.0, static_cast(RawWavLen), interpOff, 1.0); + while (xi.size() < nSample) + xi.push_back(*(xi.end() - 1) + interpOff); + while (xi.size() > nSample) + xi.pop_back(); + interp1(x0.data(), y0.data(), static_cast(RawWavLen), xi.data(), static_cast(nSample), yi); + std::vector DataChun(nSample); + for (size_t i = 0; i < nSample; ++i) + DataChun[i] = isnan(yi[i]) ? 0i16 : static_cast(yi[i]); + delete[] yi; + return DataChun; + } + release(); + return { outWav , outWav + RawWavLen }; +} + +void AudioPreprocess::release() +{ + if (packet) + av_packet_free(&packet); + if (inFrame) + av_frame_free(&inFrame); + if (out_buffer) + av_free(out_buffer); + if (swrContext) + swr_free(&swrContext); + if (avCodecContext) + avcodec_close(avCodecContext); + if (avFormatContext) + avformat_close_input(&avFormatContext); + inFrame = nullptr; + out_buffer = nullptr; + swrContext = nullptr; + avCodecContext = nullptr; + avFormatContext = nullptr; + packet = nullptr; +} + +void AudioPreprocess::init() +{ + inFrame = av_frame_alloc(); + out_buffer = nullptr; + swrContext = swr_alloc(); + avCodecContext = avcodec_alloc_context3(nullptr); + avFormatContext = avformat_alloc_context(); + packet = nullptr; +} + +AudioPreprocess::AudioPreprocess() +{ + inFrame = av_frame_alloc(); + out_buffer = nullptr; + swrContext = swr_alloc(); + avCodecContext = avcodec_alloc_context3(nullptr); + avFormatContext = avformat_alloc_context(); + packet = nullptr; +} diff --git a/libsvc/Modules/src/InferTools/Cluster/MoeVSBaseCluster.cpp b/libsvc/Modules/src/InferTools/Cluster/MoeVSBaseCluster.cpp new file mode 100644 index 0000000..fcc59f1 --- /dev/null +++ b/libsvc/Modules/src/InferTools/Cluster/MoeVSBaseCluster.cpp @@ -0,0 +1,7 @@ +#include "../../../header/InferTools/Cluster/MoeVSBaseCluster.hpp" +#include "../../../header/InferTools/inferTools.hpp" + +std::vector MoeVoiceStudioCluster::MoeVoiceStudioBaseCluster::find(float* point, long sid, int64_t n_points) +{ + LibDLVoiceCodecThrow("NotImplementedError"); +} \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Cluster/MoeVSClusterManager.cpp b/libsvc/Modules/src/InferTools/Cluster/MoeVSClusterManager.cpp new file mode 100644 index 0000000..21b0c2f --- /dev/null +++ b/libsvc/Modules/src/InferTools/Cluster/MoeVSClusterManager.cpp @@ -0,0 +1,28 @@ +#include "../../../header/InferTools/Cluster/MoeVSClusterManager.hpp" +#include +#include +#include "../../../header/Logger/MoeSSLogger.hpp" + +MoeVoiceStudioClusterHeader + +std::map RegisteredMoeVSCluster; + +MoeVSCluster GetMoeVSCluster(const std::wstring& _name, const std::wstring& _path, size_t hidden_size, size_t KmeansLen) +{ + const auto f_ClusterFn = RegisteredMoeVSCluster.find(_name); + if (f_ClusterFn != RegisteredMoeVSCluster.end()) + return f_ClusterFn->second(_path, hidden_size, KmeansLen); + throw std::runtime_error("Unable To Find An Available MoeVSCluster"); +} + +void RegisterMoeVSCluster(const std::wstring& _name, const GetMoeVSClusterFn& _constructor_fn) +{ + if (RegisteredMoeVSCluster.find(_name) != RegisteredMoeVSCluster.end()) + { + logger.log(L"[Warn] MoeVSClusterNameConflict"); + return; + } + RegisteredMoeVSCluster[_name] = _constructor_fn; +} + +MoeVoiceStudioClusterEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Cluster/MoeVSIndexCluster.cpp b/libsvc/Modules/src/InferTools/Cluster/MoeVSIndexCluster.cpp new file mode 100644 index 0000000..b6b8231 --- /dev/null +++ b/libsvc/Modules/src/InferTools/Cluster/MoeVSIndexCluster.cpp @@ -0,0 +1,101 @@ +#ifdef MoeVoiceStudioIndexCluster +#include "../../../header/InferTools/Cluster/MoeVSIndexCluster.hpp" +#include +#include "../../../header/InferTools/inferTools.hpp" +MoeVoiceStudioClusterHeader + +IndexClusterCore::~IndexClusterCore() +{ + delete IndexPtr; + IndexPtr = nullptr; +} + +IndexClusterCore::IndexClusterCore(const char* _path) +{ + IndexPtr = faiss::read_index(_path); + IndexsVector = std::vector(IndexPtr->ntotal * IndexPtr->d, 0.f); + IndexPtr->reconstruct_n(0, IndexPtr->ntotal, IndexsVector.data()); + Dim = IndexPtr->d; +} + +IndexClusterCore::IndexClusterCore(IndexClusterCore&& move) noexcept +{ + IndexPtr = move.IndexPtr; + Dim = move.Dim; + IndexsVector = std::move(move.IndexsVector); + move.IndexPtr = nullptr; +} + +IndexClusterCore& IndexClusterCore::operator=(IndexClusterCore&& move) noexcept +{ + if (&move == this) return *this; + delete IndexPtr; + IndexPtr = move.IndexPtr; + Dim = move.Dim; + IndexsVector = std::move(move.IndexsVector); + move.IndexPtr = nullptr; + return *this; +} + +float* IndexClusterCore::GetVec(faiss::idx_t index) +{ + return IndexsVector.data() + index * Dim; +} + +std::vector IndexClusterCore::find(const float* points, faiss::idx_t n_points, faiss::idx_t n_searched_points) +{ + std::vector result(Dim * n_points); + std::vector distances(n_searched_points * n_points); + std::vector labels(n_searched_points * n_points); + IndexPtr->search(n_points, points, n_searched_points, distances.data(), labels.data()); + for (faiss::idx_t pt = 0; pt < n_points; ++pt) + { + std::vector result_pt(Dim, 0.f); + const auto idx_vec = labels.data() + pt * n_searched_points; // SIZE:[n_searched_points] + const auto dis_vec = distances.data() + pt * n_searched_points; // SIZE:[n_searched_points] + float sum = 0.f; // dis_vec[i] / sum = pGetVec(idx_vec[i]) + for (faiss::idx_t spt = 0; spt < n_searched_points; ++spt) // result_pt = GetVec(idx_vec[i]) + { + if(idx_vec[spt] < 0) + continue; + dis_vec[spt] = (1 / dis_vec[spt]) * (1 / dis_vec[spt]); + sum += dis_vec[spt]; + } + if (sum == 0.f) sum = 1.f; + for (faiss::idx_t spt = 0; spt < n_searched_points; ++spt) + { + if (idx_vec[spt] < 0) + continue; + const auto sedpt = GetVec(idx_vec[spt]); + const auto pcnt = (dis_vec[spt] / sum); + for (faiss::idx_t sptp = 0; sptp < Dim; ++sptp) + result_pt[sptp] += pcnt * sedpt[sptp]; + } + memcpy(result.data() + pt * Dim, result_pt.data(), Dim * sizeof(float)); + } + return result; +} + +IndexCluster::IndexCluster(const std::wstring& _path, size_t hidden_size, size_t KmeansLen) +{ + const auto RawPath = _path + L"/Index-"; + size_t idx = 0; + while(true) + { + std::filesystem::path IndexPath = RawPath + std::to_wstring(idx++) + L".index"; + if(!exists(IndexPath)) break; + Indexs.emplace_back(IndexPath.string().c_str()); + } + if (Indexs.empty()) + LibDLVoiceCodecThrow("Index Is Empty"); +} + +std::vector IndexCluster::find(float* point, long sid, int64_t n_points) +{ + if (size_t(sid) < Indexs.size()) + return Indexs[sid].find(point, n_points); + return { point,point + n_hidden_size * n_points }; +} + +MoeVoiceStudioClusterEnd +#endif \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Cluster/MoeVSKmeansCluster.cpp b/libsvc/Modules/src/InferTools/Cluster/MoeVSKmeansCluster.cpp new file mode 100644 index 0000000..94a53aa --- /dev/null +++ b/libsvc/Modules/src/InferTools/Cluster/MoeVSKmeansCluster.cpp @@ -0,0 +1,42 @@ +#include "../../../header/InferTools/Cluster/MoeVSKmeansCluster.hpp" +#include "../../../header/InferTools/inferTools.hpp" + +std::vector MoeVoiceStudioCluster::KMeansCluster::find(float* point, long sid, int64_t n_points) +{ + if (size_t(sid) < _tree.size()) + { + std::vector res; + res.reserve(dims * n_points * 2); + for (int64_t pt = 0; pt < n_points; ++pt) + { + auto tmp = _tree[sid].nearest_point({ point + pt * dims,point + (pt + 1) * dims }); + res.insert(res.end(), tmp.begin(), tmp.end()); + } + return res; + } + return { point, point + dims * n_points }; +} + +MoeVoiceStudioCluster::KMeansCluster::KMeansCluster(const std::wstring& _path, size_t hidden_size, size_t KmeansLen) +{ + dims = hidden_size; + FILE* file = nullptr; + _wfopen_s(&file, (_path + L"/KMeans.npy").c_str(), L"rb"); + if (!file) + LibDLVoiceCodecThrow("KMeansFileNotExist"); + constexpr long idx = 128; + fseek(file, idx, SEEK_SET); + std::vector tmpData(hidden_size); + const size_t ec = size_t(hidden_size) * sizeof(float); + std::vector> _tmp; + _tmp.reserve(KmeansLen); + while (fread(tmpData.data(), 1, ec, file) == ec) + { + _tmp.emplace_back(tmpData); + if (_tmp.size() == KmeansLen) + { + _tree.emplace_back(_tmp); + _tmp.clear(); + } + } +} diff --git a/libsvc/Modules/src/InferTools/DataStruct/KDTree.cpp b/libsvc/Modules/src/InferTools/DataStruct/KDTree.cpp new file mode 100644 index 0000000..143345a --- /dev/null +++ b/libsvc/Modules/src/InferTools/DataStruct/KDTree.cpp @@ -0,0 +1,323 @@ +/* + * file: KDTree.hpp + * author: J. Frederico Carvalho + * + * This is an adaptation of the KD-tree implementation in rosetta code + * https://rosettacode.org/wiki/K-d_tree + * + * It is a reimplementation of the C code using C++. It also includes a few + * more queries than the original, namely finding all points at a distance + * smaller than some given distance to a point. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "../../../header/InferTools/DataStruct/KDTree.hpp" + +KDNode::KDNode() = default; + +KDNode::KDNode(const point_t &pt, const size_t &idx_, const KDNodePtr &left_, + const KDNodePtr &right_) { + x = pt; + index = idx_; + left = left_; + right = right_; +} + +KDNode::KDNode(const pointIndex &pi, const KDNodePtr &left_, + const KDNodePtr &right_) { + x = pi.first; + index = pi.second; + left = left_; + right = right_; +} + +KDNode::~KDNode() = default; + +float KDNode::coord(const size_t &idx) { return x.at(idx); } +KDNode::operator bool() { return (!x.empty()); } +KDNode::operator point_t() { return x; } +KDNode::operator size_t() { return index; } +KDNode::operator pointIndex() { return pointIndex(x, index); } + +KDNodePtr NewKDNodePtr() { + KDNodePtr mynode = std::make_shared< KDNode >(); + return mynode; +} + +inline float dist2(const point_t &a, const point_t &b) { + float distc = 0; + for (size_t i = 0; i < a.size(); i++) { + float di = a.at(i) - b.at(i); + distc += di * di; + } + return distc; +} + +inline float dist2(const KDNodePtr &a, const KDNodePtr &b) { + return dist2(a->x, b->x); +} + +inline float dist(const point_t &a, const point_t &b) { + return std::sqrt(dist2(a, b)); +} + +inline float dist(const KDNodePtr &a, const KDNodePtr &b) { + return std::sqrt(dist2(a, b)); +} + +comparer::comparer(size_t idx_) : idx{idx_} {}; + +inline bool comparer::compare_idx(const pointIndex &a, // + const pointIndex &b // +) { + return (a.first.at(idx) < b.first.at(idx)); // +} + +inline void sort_on_idx(const pointIndexArr::iterator &begin, // + const pointIndexArr::iterator &end, // + size_t idx) { + comparer comp(idx); + comp.idx = idx; + + using std::placeholders::_1; + using std::placeholders::_2; + + std::nth_element(begin, begin + std::distance(begin, end) / 2, + end, std::bind(&comparer::compare_idx, comp, _1, _2)); +} + +using pointVec = std::vector< point_t >; + +KDNodePtr KDTree::make_tree(const pointIndexArr::iterator &begin, // + const pointIndexArr::iterator &end, // + const size_t &length, // + const size_t &level // +) { + if (begin == end) { + return NewKDNodePtr(); // empty tree + } + + size_t dim = begin->first.size(); + + if (length > 1) { + sort_on_idx(begin, end, level); + } + + auto middle = begin + (length / 2); + + auto l_begin = begin; + auto l_end = middle; + auto r_begin = middle + 1; + auto r_end = end; + + size_t l_len = length / 2; + size_t r_len = length - l_len - 1; + + KDNodePtr left; + if (l_len > 0 && dim > 0) { + left = make_tree(l_begin, l_end, l_len, (level + 1) % dim); + } else { + left = leaf; + } + KDNodePtr right; + if (r_len > 0 && dim > 0) { + right = make_tree(r_begin, r_end, r_len, (level + 1) % dim); + } else { + right = leaf; + } + + // KDNode result = KDNode(); + return std::make_shared< KDNode >(*middle, left, right); +} + +KDTree::KDTree(pointVec point_array) { + leaf = std::make_shared< KDNode >(); + // iterators + pointIndexArr arr; + for (size_t i = 0; i < point_array.size(); i++) { + arr.push_back(pointIndex(point_array.at(i), i)); + } + + auto begin = arr.begin(); + auto end = arr.end(); + + size_t length = arr.size(); + size_t level = 0; // starting + + root = KDTree::make_tree(begin, end, length, level); +} + +KDNodePtr KDTree::nearest_( // + const KDNodePtr &branch, // + const point_t &pt, // + const size_t &level, // + const KDNodePtr &best, // + const float&best_dist // +) { + float d, dx, dx2; + + if (!bool(*branch)) { + return NewKDNodePtr(); // basically, null + } + + point_t branch_pt(*branch); + size_t dim = branch_pt.size(); + + d = dist2(branch_pt, pt); + dx = branch_pt.at(level) - pt.at(level); + dx2 = dx * dx; + + KDNodePtr best_l = best; + float best_dist_l = best_dist; + + if (d < best_dist) { + best_dist_l = d; + best_l = branch; + } + + size_t next_lv = (level + 1) % dim; + KDNodePtr section; + KDNodePtr other; + + // select which branch makes sense to check + if (dx > 0) { + section = branch->left; + other = branch->right; + } else { + section = branch->right; + other = branch->left; + } + + // keep nearest neighbor from further down the tree + KDNodePtr further = nearest_(section, pt, next_lv, best_l, best_dist_l); + if (!further->x.empty()) { + float dl = dist2(further->x, pt); + if (dl < best_dist_l) { + best_dist_l = dl; + best_l = further; + } + } + // only check the other branch if it makes sense to do so + if (dx2 < best_dist_l) { + further = nearest_(other, pt, next_lv, best_l, best_dist_l); + if (!further->x.empty()) { + float dl = dist2(further->x, pt); + if (dl < best_dist_l) { + best_dist_l = dl; + best_l = further; + } + } + } + + return best_l; +}; + +// default caller +KDNodePtr KDTree::nearest_(const point_t &pt) { + size_t level = 0; + // KDNodePtr best = branch; + float branch_dist = dist2(point_t(*root), pt); + return nearest_(root, // beginning of tree + pt, // point we are querying + level, // start from level 0 + root, // best is the root + branch_dist); // best_dist = branch_dist +}; + +point_t KDTree::nearest_point(const point_t &pt) { + return point_t(*nearest_(pt)); +}; +size_t KDTree::nearest_index(const point_t &pt) { + return size_t(*nearest_(pt)); +}; + +pointIndex KDTree::nearest_pointIndex(const point_t &pt) { + KDNodePtr Nearest = nearest_(pt); + return pointIndex(point_t(*Nearest), size_t(*Nearest)); +} + +pointIndexArr KDTree::neighborhood_( // + const KDNodePtr &branch, // + const point_t &pt, // + const float&rad, // + const size_t &level // +) { + float d, dx, dx2; + + if (!bool(*branch)) { + // branch has no point, means it is a leaf, + // no points to add + return pointIndexArr(); + } + + size_t dim = pt.size(); + + float r2 = rad * rad; + + d = dist2(point_t(*branch), pt); + dx = point_t(*branch).at(level) - pt.at(level); + dx2 = dx * dx; + + pointIndexArr nbh, nbh_s, nbh_o; + if (d <= r2) { + nbh.push_back(pointIndex(*branch)); + } + + // + KDNodePtr section; + KDNodePtr other; + if (dx > 0) { + section = branch->left; + other = branch->right; + } else { + section = branch->right; + other = branch->left; + } + + nbh_s = neighborhood_(section, pt, rad, (level + 1) % dim); + nbh.insert(nbh.end(), nbh_s.begin(), nbh_s.end()); + if (dx2 < r2) { + nbh_o = neighborhood_(other, pt, rad, (level + 1) % dim); + nbh.insert(nbh.end(), nbh_o.begin(), nbh_o.end()); + } + + return nbh; +}; + +pointIndexArr KDTree::neighborhood( // + const point_t &pt, // + const float&rad) { + size_t level = 0; + return neighborhood_(root, pt, rad, level); +} + +pointVec KDTree::neighborhood_points( // + const point_t &pt, // + const float&rad) { + size_t level = 0; + pointIndexArr nbh = neighborhood_(root, pt, rad, level); + pointVec nbhp; + nbhp.resize(nbh.size()); + std::transform(nbh.begin(), nbh.end(), nbhp.begin(), + [](pointIndex x) { return x.first; }); + return nbhp; +} + +indexArr KDTree::neighborhood_indices( // + const point_t &pt, // + const float&rad) { + size_t level = 0; + pointIndexArr nbh = neighborhood_(root, pt, rad, level); + indexArr nbhi; + nbhi.resize(nbh.size()); + std::transform(nbh.begin(), nbh.end(), nbhi.begin(), + [](pointIndex x) { return x.second; }); + return nbhi; +} diff --git a/libsvc/Modules/src/InferTools/DataStruct/README.md b/libsvc/Modules/src/InferTools/DataStruct/README.md new file mode 100644 index 0000000..271d92a --- /dev/null +++ b/libsvc/Modules/src/InferTools/DataStruct/README.md @@ -0,0 +1 @@ +## KdTree From J. Frederico Carvalho diff --git a/libsvc/Modules/src/InferTools/F0Extractor/BaseF0Extractor.cpp b/libsvc/Modules/src/InferTools/F0Extractor/BaseF0Extractor.cpp new file mode 100644 index 0000000..68f08a7 --- /dev/null +++ b/libsvc/Modules/src/InferTools/F0Extractor/BaseF0Extractor.cpp @@ -0,0 +1,47 @@ +#include "../../../header/InferTools/F0Extractor/BaseF0Extractor.hpp" +#include +#include "../../../header/Logger/MoeSSLogger.hpp" +#include "../../../header/InferTools/inferTools.hpp" + +MoeVSF0Extractor::BaseF0Extractor::BaseF0Extractor(int sampling_rate, int hop_size, int n_f0_bins, double max_f0, double min_f0) : + fs(sampling_rate), + hop(hop_size), + f0_bin(n_f0_bins), + f0_max(max_f0), + f0_min(min_f0) +{ + f0_mel_min = (1127.0 * log(1.0 + f0_min / 700.0)); + f0_mel_max = (1127.0 * log(1.0 + f0_max / 700.0)); +} + +std::vector MoeVSF0Extractor::BaseF0Extractor::arange(double start, double end, double step, double div) +{ + std::vector output; + while (start < end) + { + output.push_back(start / div); + start += step; + } + return output; +} + +std::vector MoeVSF0Extractor::BaseF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + LibDLVoiceCodecThrow("NotImplementedError"); +} + +std::vector MoeVSF0Extractor::BaseF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + std::vector PCMVector(PCMData.size()); + for (size_t i = 0; i < PCMData.size(); ++i) + PCMVector[i] = double(PCMData[i]); + return ExtractF0(PCMVector, TargetLength); +} + +std::vector MoeVSF0Extractor::BaseF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + std::vector PCMVector(PCMData.size()); + for (size_t i = 0; i < PCMData.size(); ++i) + PCMVector[i] = double(PCMData[i]); + return ExtractF0(PCMVector, TargetLength); +} \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/F0Extractor/DioF0Extractor.cpp b/libsvc/Modules/src/InferTools/F0Extractor/DioF0Extractor.cpp new file mode 100644 index 0000000..ff7db22 --- /dev/null +++ b/libsvc/Modules/src/InferTools/F0Extractor/DioF0Extractor.cpp @@ -0,0 +1,61 @@ +#include "../../../header/InferTools/F0Extractor/DioF0Extractor.hpp" +#include "dio.h" +#include "stonemask.h" +#include "matlabfunctions.h" + +MoeVoiceStudioF0ExtractorHeader +DioF0Extractor::DioF0Extractor(int sampling_rate, int hop_size, int n_f0_bins, double max_f0, double min_f0): + BaseF0Extractor(sampling_rate, hop_size, n_f0_bins, max_f0, min_f0) +{ +} + +void DioF0Extractor::InterPf0(size_t TargetLength) +{ + const auto f0Len = refined_f0.size(); + if (abs((int64_t)TargetLength - (int64_t)f0Len) < 3) + { + refined_f0.resize(TargetLength, 0.0); + return; + } + for (size_t i = 0; i < f0Len; ++i) if (refined_f0[i] < 0.001) refined_f0[i] = NAN; + + auto xi = arange(0.0, (double)f0Len * (double)TargetLength, (double)f0Len, (double)TargetLength); + while (xi.size() < TargetLength) xi.emplace_back(*(xi.end() - 1) + ((double)f0Len / (double)TargetLength)); + while (xi.size() > TargetLength) xi.pop_back(); + + auto x0 = arange(0, (double)f0Len); + while (x0.size() < f0Len) x0.emplace_back(*(x0.end() - 1) + 1.); + while (x0.size() > f0Len) x0.pop_back(); + + auto raw_f0 = std::vector(xi.size()); + interp1(x0.data(), refined_f0.data(), static_cast(x0.size()), xi.data(), (int)xi.size(), raw_f0.data()); + + for (size_t i = 0; i < xi.size(); i++) if (isnan(raw_f0[i])) raw_f0[i] = 0.0; + refined_f0 = std::move(raw_f0); +} + +std::vector DioF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + compute_f0(PCMData.data(), PCMData.size()); + InterPf0(TargetLength); + std::vector f0(refined_f0.size()); + for (size_t i = 0; i < refined_f0.size(); ++i) f0[i] = (float)refined_f0[i]; + return f0; +} + +void DioF0Extractor::compute_f0(const double* PCMData, size_t PCMLen) +{ + DioOption Doption; + InitializeDioOption(&Doption); + Doption.f0_ceil = f0_max; + Doption.f0_floor = f0_min; + Doption.frame_period = 1000.0 * hop / fs; + + const size_t f0Length = GetSamplesForDIO(int(fs), (int)PCMLen, Doption.frame_period); + auto temporal_positions = std::vector(f0Length); + auto raw_f0 = std::vector(f0Length); + refined_f0 = std::vector(f0Length); + Dio(PCMData, (int)PCMLen, int(fs), &Doption, temporal_positions.data(), raw_f0.data()); + StoneMask(PCMData, (int)PCMLen, int(fs), temporal_positions.data(), raw_f0.data(), (int)f0Length, refined_f0.data()); +} +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/F0Extractor/F0ExtractorManager.cpp b/libsvc/Modules/src/InferTools/F0Extractor/F0ExtractorManager.cpp new file mode 100644 index 0000000..25f5165 --- /dev/null +++ b/libsvc/Modules/src/InferTools/F0Extractor/F0ExtractorManager.cpp @@ -0,0 +1,41 @@ +#include "../../../header/InferTools/F0Extractor/F0ExtractorManager.hpp" +#include +#include +#include "../../../header/Logger/MoeSSLogger.hpp" + +MoeVoiceStudioF0ExtractorHeader +std::map RegisteredF0Extractors; + +F0Extractor GetF0Extractor(const std::wstring& _name, + const uint32_t fs, + const uint32_t hop, + const uint32_t f0_bin, + const double f0_max, + const double f0_min) +{ + const auto f_F0Extractor = RegisteredF0Extractors.find(_name); + if (f_F0Extractor != RegisteredF0Extractors.end()) + return f_F0Extractor->second(fs, hop, f0_bin, f0_max, f0_min); + throw std::runtime_error("Unable To Find An Available F0Extractor"); +} + +void RegisterF0Extractor(const std::wstring& _name, const GetF0ExtractorFn& _constructor_fn) +{ + if (RegisteredF0Extractors.find(_name) != RegisteredF0Extractors.end()) + { + logger.log(L"[Warn] F0ExtractorNameConflict"); + return; + } + RegisteredF0Extractors[_name] = _constructor_fn; +} + +std::vector GetF0ExtractorList() +{ + std::vector F0ExtractorsVec; + F0ExtractorsVec.reserve(RegisteredF0Extractors.size()); + for (const auto& i : RegisteredF0Extractors) + F0ExtractorsVec.emplace_back(i.first); + return F0ExtractorsVec; +} + +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/F0Extractor/HarvestF0Extractor.cpp b/libsvc/Modules/src/InferTools/F0Extractor/HarvestF0Extractor.cpp new file mode 100644 index 0000000..c732b92 --- /dev/null +++ b/libsvc/Modules/src/InferTools/F0Extractor/HarvestF0Extractor.cpp @@ -0,0 +1,61 @@ +#include "../../../header/InferTools/F0Extractor/HarvestF0Extractor.hpp" +#include "matlabfunctions.h" +#include "harvest.h" +#include "stonemask.h" + +MoeVoiceStudioF0ExtractorHeader + HarvestF0Extractor::HarvestF0Extractor(int sampling_rate, int hop_size, int n_f0_bins, double max_f0, double min_f0): + BaseF0Extractor(sampling_rate, hop_size, n_f0_bins, max_f0, min_f0) +{ +} + +void HarvestF0Extractor::InterPf0(size_t TargetLength) +{ + const auto f0Len = refined_f0.size(); + if (abs((int64_t)TargetLength - (int64_t)f0Len) < 3) + { + refined_f0.resize(TargetLength, 0.0); + return; + } + for (size_t i = 0; i < f0Len; ++i) if (refined_f0[i] < 0.001) refined_f0[i] = NAN; + + auto xi = arange(0.0, (double)f0Len * (double)TargetLength, (double)f0Len, (double)TargetLength); + while (xi.size() < TargetLength) xi.emplace_back(*(xi.end() - 1) + ((double)f0Len / (double)TargetLength)); + while (xi.size() > TargetLength) xi.pop_back(); + + auto x0 = arange(0, (double)f0Len); + while (x0.size() < f0Len) x0.emplace_back(*(x0.end() - 1) + 1.); + while (x0.size() > f0Len) x0.pop_back(); + + auto raw_f0 = std::vector(xi.size()); + interp1(x0.data(), refined_f0.data(), static_cast(x0.size()), xi.data(), (int)xi.size(), raw_f0.data()); + + for (size_t i = 0; i < xi.size(); i++) if (isnan(raw_f0[i])) raw_f0[i] = 0.0; + refined_f0 = std::move(raw_f0); +} + +std::vector HarvestF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + compute_f0(PCMData.data(), PCMData.size()); + InterPf0(TargetLength); + std::vector f0(refined_f0.size()); + for (size_t i = 0; i < refined_f0.size(); ++i) f0[i] = (float)refined_f0[i]; + return f0; +} + +void HarvestF0Extractor::compute_f0(const double* PCMData, size_t PCMLen) +{ + HarvestOption Doption; + InitializeHarvestOption(&Doption); + Doption.f0_ceil = f0_max; + Doption.f0_floor = f0_min; + Doption.frame_period = 1000.0 * hop / fs; + + const size_t f0Length = GetSamplesForHarvest(int(fs), (int)PCMLen, Doption.frame_period); + auto temporal_positions = std::vector(f0Length); + auto raw_f0 = std::vector(f0Length); + refined_f0 = std::vector(f0Length); + Harvest(PCMData, (int)PCMLen, int(fs), &Doption, temporal_positions.data(), raw_f0.data()); + StoneMask(PCMData, (int)PCMLen, int(fs), temporal_positions.data(), raw_f0.data(), (int)f0Length, refined_f0.data()); +} +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/F0Extractor/NetF0Predictors.cpp b/libsvc/Modules/src/InferTools/F0Extractor/NetF0Predictors.cpp new file mode 100644 index 0000000..d38414f --- /dev/null +++ b/libsvc/Modules/src/InferTools/F0Extractor/NetF0Predictors.cpp @@ -0,0 +1,328 @@ +#include "../../../header/InferTools/F0Extractor/NetF0Predictors.hpp" +#include +#include "matlabfunctions.h" +#include "../../../header/InferTools/F0Extractor/DioF0Extractor.hpp" +#include "../../../header/Logger/MoeSSLogger.hpp" +#include "../../../header/Models/EnvManager.hpp" +#include "../../../header/InferTools/inferTools.hpp" +#ifdef _WIN32 +#include +#else +#error +#endif + +MoeVoiceStudioF0ExtractorHeader + +NetF0Class::NetF0Class() +#ifdef INITF0NETPREDICTOR +{ + try + { + wchar_t path[1024]; +#ifdef _WIN32 + GetModuleFileName(nullptr, path, 1024); + std::wstring _curPath = path; + _curPath = _curPath.substr(0, _curPath.rfind(L'\\')); + const auto cpath = _curPath + L"/F0Predictor/config.json"; + NetF0PathDir = _curPath + L"/F0Predictor/"; +#endif + const MJson json(to_byte_string(cpath).c_str()); + int DeviceId = 0, Device = 0, NumThread = (int)std::thread::hardware_concurrency(); + if (json["DeviceId"].IsInt()) + DeviceId = json["DeviceId"].GetInt(); + if (json["Device"].IsString()) + { + const std::string tmp = json["Device"].GetString(); + if (tmp == "CUDA") + Device = 1; + else if (tmp == "DML") + Device = 2; + } + if (json["NumThread"].IsInt()) + NumThread = json["NumThread"].GetInt(); + if (Device == 0) + BuildCPUEnv(NumThread); + else if (Device == 1) + BuildCUDAEnv(DeviceId); + else if (Device == 2) + BuildDMLEnv(DeviceId); + } + catch (std::exception& e) + { + logger.log(to_wide_string(e.what())); + BuildCPUEnv(std::thread::hardware_concurrency()); + } +} +#else +{ + wchar_t path[1024]; +#ifdef _WIN32 + GetModuleFileName(nullptr, path, 1024); + std::wstring _curPath = path; + _curPath = _curPath.substr(0, _curPath.rfind(L'\\')); + NetF0PathDir = _curPath + L"/F0Predictor/"; +#endif +} +#endif +void NetF0Class::Destory() +{ + delete Model; + Model = nullptr; + NetF0Options = nullptr; + NetF0Env = nullptr; + Memory = nullptr; +} + +void NetF0Class::BuildCPUEnv(unsigned ThreadCount) +{ + Destory(); + NetF0Options = new Ort::SessionOptions; + NetF0Env = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, "OnnxModel"); + NetF0Options->SetIntraOpNumThreads(static_cast(ThreadCount)); + NetF0Options->SetGraphOptimizationLevel(ORT_ENABLE_ALL); + Memory = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)); +} + +void NetF0Class::BuildCUDAEnv(unsigned Did) +{ + Destory(); + const auto AvailableProviders = Ort::GetAvailableProviders(); + bool ret = true; + for (const auto& it : AvailableProviders) + if (it.find("CUDA") != std::string::npos) + ret = false; + if (ret) + LibDLVoiceCodecThrow("CUDA Provider Not Found"); + OrtCUDAProviderOptions cuda_option; + cuda_option.device_id = int(Did); + NetF0Options = new Ort::SessionOptions; + NetF0Env = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, "OnnxModel"); + NetF0Options->AppendExecutionProvider_CUDA(cuda_option); + NetF0Options->SetGraphOptimizationLevel(ORT_ENABLE_BASIC); + NetF0Options->SetIntraOpNumThreads(1); + Memory = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)); +} + +void NetF0Class::BuildDMLEnv(unsigned Did) +{ + Destory(); + const auto AvailableProviders = Ort::GetAvailableProviders(); + std::string ret; + for (const auto& it : AvailableProviders) + if (it.find("Dml") != std::string::npos) + ret = it; + if (ret.empty()) + LibDLVoiceCodecThrow("DML Provider Not Found"); + const OrtApi& ortApi = Ort::GetApi(); + const OrtDmlApi* ortDmlApi = nullptr; + ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); + const Ort::ThreadingOptions threadingOptions; + NetF0Env = new Ort::Env(threadingOptions, ORT_LOGGING_LEVEL_VERBOSE, ""); + NetF0Env->DisableTelemetryEvents(); + NetF0Options = new Ort::SessionOptions; + ortDmlApi->SessionOptionsAppendExecutionProvider_DML(*NetF0Options, int(Did)); + NetF0Options->SetGraphOptimizationLevel(ORT_ENABLE_BASIC); + NetF0Options->DisablePerSessionThreads(); + NetF0Options->SetExecutionMode(ORT_SEQUENTIAL); + NetF0Options->DisableMemPattern(); + Memory = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemType::OrtMemTypeCPU)); +} + +void NetF0Class::LoadModel(const std::wstring& path) +{ + Destory(); + if (!moevsenv::GetGlobalMoeVSEnv().IsEnabled()) + return; + NetF0Env = moevsenv::GetGlobalMoeVSEnv().GetEnv(); + Memory = moevsenv::GetGlobalMoeVSEnv().GetMemoryInfo(); + NetF0Options = moevsenv::GetGlobalMoeVSEnv().GetSessionOptions(); + try + { + Model = new Ort::Session(*NetF0Env, (NetF0PathDir + path).c_str(), *NetF0Options); + } + catch (Ort::Exception& e) + { + logger.log(to_wide_string(e.what())); + delete Model; + Model = nullptr; + } +} + +NetF0Class RMVPECORE; +NetF0Class MELPECORE; + +RMVPEF0Extractor::RMVPEF0Extractor(int sampling_rate, int hop_size, int n_f0_bins, double max_f0, double min_f0) : + BaseF0Extractor(sampling_rate, hop_size, n_f0_bins, max_f0, min_f0) +{ + if (MELPECORE.Model) + MELPECORE.Destory(); + if (!RMVPECORE.Model) + RMVPECORE.LoadModel(L"RMVPE.onnx"); +} + +double average(const double* begin, const double* end) +{ + const auto mp = double(end - begin); + double sum = 0.; + while (begin != end) + sum += *(begin++); + return sum / mp; +} + +std::vector RMVPEF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + if (!RMVPECORE.Model) + return DioF0Extractor((int)fs, (int)hop, (int)f0_bin, f0_max, f0_min).ExtractF0(PCMData, TargetLength); + + const double step = double(fs) / 16000.; + const auto window_len = (size_t)round(step); + const auto half_window_len = window_len / 2; + const auto f0_size = size_t((double)PCMData.size() / step); + const auto pcm_idx_size = PCMData.size() - 2; + std::vector pcm(f0_size, 0.f); + auto idx = double(half_window_len + 1); + for (size_t i = 0; i < f0_size; ++i) + { + const auto index = size_t(round(idx)); + if(index + half_window_len > pcm_idx_size) + break; + if (half_window_len == 0) + pcm[i] = (float)PCMData[index]; + else + pcm[i] = (float)average(&PCMData[index - half_window_len], &PCMData[index + half_window_len]); + idx += step; + } + std::vector Tensors; + const int64_t pcm_shape[] = { 1, (int64_t)pcm.size() }; + constexpr int64_t one_shape[] = { 1 }; + float threshold[] = { 0.03f }; + Tensors.emplace_back(Ort::Value::CreateTensor(*RMVPECORE.Memory, pcm.data(), pcm.size(), pcm_shape, 2)); + Tensors.emplace_back(Ort::Value::CreateTensor(*RMVPECORE.Memory, threshold, 1, one_shape, 1)); + + const auto out = RMVPECORE.Model->Run(Ort::RunOptions{ nullptr }, + InputNames.data(), + Tensors.data(), + Tensors.size(), + OutputNames.data(), + OutputNames.size()); + + const auto osize = out[0].GetTensorTypeAndShapeInfo().GetElementCount(); + const auto of0 = out[0].GetTensorData(); + refined_f0 = std::vector(osize); + for (size_t i = 0; i < osize; ++i) refined_f0[i] = ((of0[i] > 0.001f) ? (double)out[0].GetTensorData()[i] : NAN); + InterPf0(TargetLength); + std::vector finaF0(refined_f0.size()); + for (size_t i = 0; i < refined_f0.size(); ++i) finaF0[i] = isnan(refined_f0[i]) ? 0 : (float)refined_f0[i]; + return finaF0; +} + +void RMVPEF0Extractor::InterPf0(size_t TargetLength) +{ + const auto f0Len = refined_f0.size(); + if (abs((int64_t)TargetLength - (int64_t)f0Len) < 3) + { + refined_f0.resize(TargetLength, 0.0); + return; + } + for (size_t i = 0; i < f0Len; ++i) if (refined_f0[i] < 0.001) refined_f0[i] = NAN; + + auto xi = arange(0.0, (double)f0Len * (double)TargetLength, (double)f0Len, (double)TargetLength); + while (xi.size() < TargetLength) xi.emplace_back(*(xi.end() - 1) + ((double)f0Len / (double)TargetLength)); + while (xi.size() > TargetLength) xi.pop_back(); + + auto x0 = arange(0, (double)f0Len); + while (x0.size() < f0Len) x0.emplace_back(*(x0.end() - 1) + 1.); + while (x0.size() > f0Len) x0.pop_back(); + + auto raw_f0 = std::vector(xi.size()); + interp1(x0.data(), refined_f0.data(), static_cast(x0.size()), xi.data(), (int)xi.size(), raw_f0.data()); + + for (size_t i = 0; i < xi.size(); i++) if (isnan(raw_f0[i])) raw_f0[i] = 0.0; + refined_f0 = std::move(raw_f0); +} + +MELPEF0Extractor::MELPEF0Extractor(int sampling_rate, int hop_size, int n_f0_bins, double max_f0, double min_f0) : + BaseF0Extractor(sampling_rate, hop_size, n_f0_bins, max_f0, min_f0) +{ + if (RMVPECORE.Model) + RMVPECORE.Destory(); + if (!MELPECORE.Model) + MELPECORE.LoadModel(L"MELPE.onnx"); +} + +void MELPEF0Extractor::InterPf0(size_t TargetLength) +{ + const auto f0Len = refined_f0.size(); + if (abs((int64_t)TargetLength - (int64_t)f0Len) < 3) + { + refined_f0.resize(TargetLength, 0.0); + return; + } + for (size_t i = 0; i < f0Len; ++i) if (refined_f0[i] < 0.001) refined_f0[i] = NAN; + + auto xi = arange(0.0, (double)f0Len * (double)TargetLength, (double)f0Len, (double)TargetLength); + while (xi.size() < TargetLength) xi.emplace_back(*(xi.end() - 1) + ((double)f0Len / (double)TargetLength)); + while (xi.size() > TargetLength) xi.pop_back(); + + auto x0 = arange(0, (double)f0Len); + while (x0.size() < f0Len) x0.emplace_back(*(x0.end() - 1) + 1.); + while (x0.size() > f0Len) x0.pop_back(); + + auto raw_f0 = std::vector(xi.size()); + interp1(x0.data(), refined_f0.data(), static_cast(x0.size()), xi.data(), (int)xi.size(), raw_f0.data()); + + for (size_t i = 0; i < xi.size(); i++) if (isnan(raw_f0[i])) raw_f0[i] = 0.0; + refined_f0 = std::move(raw_f0); +} + +std::vector MELPEF0Extractor::ExtractF0(const std::vector& PCMData, size_t TargetLength) +{ + if (!MELPECORE.Model) + return DioF0Extractor((int)fs, (int)hop, (int)f0_bin, f0_max, f0_min).ExtractF0(PCMData, TargetLength); + + const double step = double(fs) / 16000.; + const auto window_len = (size_t)round(step); + const auto half_window_len = window_len / 2; + const auto f0_size = size_t((double)PCMData.size() / step); + const auto pcm_idx_size = PCMData.size() - 2; + std::vector pcm(f0_size, 0.f); + auto idx = double(half_window_len + 1); + for (size_t i = 0; i < f0_size; ++i) + { + const auto index = size_t(round(idx)); + if (index + half_window_len > pcm_idx_size) + break; + if (half_window_len == 0) + pcm[i] = (float)PCMData[index]; + else + pcm[i] = (float)average(&PCMData[index - half_window_len], &PCMData[index + half_window_len]); + idx += step; + } + std::vector Tensors; + const int64_t pcm_shape[] = { 1, (int64_t)pcm.size() }; + Tensors.emplace_back(Ort::Value::CreateTensor(*MELPECORE.Memory, pcm.data(), pcm.size(), pcm_shape, 2)); + + const auto out = MELPECORE.Model->Run(Ort::RunOptions{ nullptr }, + InputNames.data(), + Tensors.data(), + Tensors.size(), + OutputNames.data(), + OutputNames.size()); + + const auto osize = out[0].GetTensorTypeAndShapeInfo().GetElementCount(); + const auto of0 = out[0].GetTensorData(); + refined_f0 = std::vector(osize); + for (size_t i = 0; i < osize; ++i) refined_f0[i] = ((of0[i] > 0.001f) ? (double)out[0].GetTensorData()[i] : NAN); + InterPf0(TargetLength); + std::vector finaF0(refined_f0.size()); + for (size_t i = 0; i < refined_f0.size(); ++i) finaF0[i] = isnan(refined_f0[i]) ? 0 : (float)refined_f0[i]; + return finaF0; +} + +void EmptyCache() +{ + RMVPECORE.Destory(); + MELPECORE.Destory(); +} + +MoeVoiceStudioF0ExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Sampler/MoeVSBaseSampler.cpp b/libsvc/Modules/src/InferTools/Sampler/MoeVSBaseSampler.cpp new file mode 100644 index 0000000..7228c8d --- /dev/null +++ b/libsvc/Modules/src/InferTools/Sampler/MoeVSBaseSampler.cpp @@ -0,0 +1,17 @@ +#include "../../../header/InferTools/Sampler/MoeVSBaseSampler.hpp" +#include "../../../header/InferTools/inferTools.hpp" +MoeVoiceStudioSamplerHeader + +MoeVSBaseSampler::MoeVSBaseSampler(Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, const ProgressCallback& _ProgressCallback, Ort::MemoryInfo* memory) : + MelBins(Mel_Bins), Alpha(alpha), DenoiseFn(dfn), NoisePredictor(pred) +{ + _callback = _ProgressCallback; + Memory = memory; +}; + +std::vector MoeVSBaseSampler::Sample(std::vector& Tensors, int64_t Steps, int64_t SpeedUp, float NoiseScale, int64_t Seed, size_t& Process) +{ + LibDLVoiceCodecThrow("NotImplementedError"); +} + +MoeVoiceStudioSamplerEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Sampler/MoeVSSamplerManager.cpp b/libsvc/Modules/src/InferTools/Sampler/MoeVSSamplerManager.cpp new file mode 100644 index 0000000..3026051 --- /dev/null +++ b/libsvc/Modules/src/InferTools/Sampler/MoeVSSamplerManager.cpp @@ -0,0 +1,39 @@ +#include "../../../header/InferTools/Sampler/MoeVSSamplerManager.hpp" +#include +#include "../../../header/Logger/MoeSSLogger.hpp" +MoeVoiceStudioSamplerHeader +std::map RegisteredMoeVSSamplers; + +MoeVSSampler GetMoeVSSampler(const std::wstring& _name, + Ort::Session* alpha, + Ort::Session* dfn, + Ort::Session* pred, + int64_t Mel_Bins, + const MoeVSBaseSampler::ProgressCallback& _ProgressCallback, + Ort::MemoryInfo* memory) +{ + const auto f_Sampler = RegisteredMoeVSSamplers.find(_name); + if (f_Sampler != RegisteredMoeVSSamplers.end()) + return f_Sampler->second(alpha, dfn, pred, Mel_Bins, _ProgressCallback, memory); + throw std::runtime_error("Unable To Find An Available Sampler"); +} + +void RegisterMoeVSSampler(const std::wstring& _name, const GetMoeVSSamplerFn& _constructor_fn) +{ + if (RegisteredMoeVSSamplers.find(_name) != RegisteredMoeVSSamplers.end()) + { + logger.log(L"[Warn] F0ExtractorNameConflict"); + return; + } + RegisteredMoeVSSamplers[_name] = _constructor_fn; +} + +std::vector GetMoeVSSamplerList() +{ + std::vector SamplersVec; + SamplersVec.reserve(RegisteredMoeVSSamplers.size()); + for (const auto& i : RegisteredMoeVSSamplers) + SamplersVec.emplace_back(i.first); + return SamplersVec; +} +MoeVoiceStudioSamplerEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Sampler/MoeVSSamplers.cpp b/libsvc/Modules/src/InferTools/Sampler/MoeVSSamplers.cpp new file mode 100644 index 0000000..3b20b9c --- /dev/null +++ b/libsvc/Modules/src/InferTools/Sampler/MoeVSSamplers.cpp @@ -0,0 +1,236 @@ +#include "../../../header/InferTools/Sampler/MoeVSSamplers.hpp" +#include +#include +#include "../../../header/InferTools/inferTools.hpp" + +MoeVoiceStudioSamplerHeader + PndmSampler::PndmSampler(Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, const ProgressCallback& _ProgressCallback, Ort::MemoryInfo* memory) : + MoeVSBaseSampler(alpha, dfn, pred, Mel_Bins, _ProgressCallback, memory) {} + +DDimSampler::DDimSampler(Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, const ProgressCallback& _ProgressCallback, Ort::MemoryInfo* memory) : + MoeVSBaseSampler(alpha, dfn, pred, Mel_Bins, _ProgressCallback, memory) {} + +std::vector PndmSampler::Sample(std::vector& Tensors, int64_t Steps, int64_t SpeedUp, float NoiseScale, int64_t Seed, size_t& Process) +{ + std::vector diffusionSteps; + for (int64_t itt = Steps - SpeedUp; itt >= 0; itt -= SpeedUp) + diffusionSteps.push_back(itt); + + std::vector DenoiseIn; + std::vector DenoiseOut; + std::vector PredIn; + std::vector PredOut; + std::vector DiffOut; + + std::deque> noiseList; + + constexpr int64_t timeshape[1] = { 1 }; // PLMS SAMPLING + + for (const auto& t : diffusionSteps) + { + int64_t time[1] = { t }; + int64_t time_prev[1] = { t - SpeedUp > 0 ? t - SpeedUp : 0 }; + if (noiseList.empty()) + { + DenoiseIn.emplace_back(std::move(Tensors[1])); // noise DenoiseIn[0] + DenoiseIn.emplace_back(Ort::Value::CreateTensor(*Memory, time, + 1, timeshape, 1)); // time DenoiseIn[1] + DenoiseIn.emplace_back(std::move(Tensors[0])); // condition DenoiseIn[2] + try + { + DenoiseOut = DenoiseFn->Run(Ort::RunOptions{ nullptr }, + denoiseInput.data(), + DenoiseIn.data(), + DenoiseIn.size(), + denoiseOutput.data(), + denoiseOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: denoise\n") + e1.what()).c_str()); + } + + noiseList.emplace_back(std::vector(DenoiseOut[0].GetTensorData(), + DenoiseOut[0].GetTensorData() + + DenoiseOut[0].GetTensorTypeAndShapeInfo().GetElementCount())); // NoiseListExpand + + PredIn.emplace_back(std::move(DenoiseIn[0])); // noise PredIn[0] + PredIn.emplace_back(std::move(DenoiseOut[0])); // noise_pred PredIn[1] + PredIn.emplace_back(std::move(DenoiseIn[1])); // time PredIn[2] + PredIn.emplace_back(Ort::Value::CreateTensor(*Memory, time_prev, + 1, timeshape, 1)); // time_prev PredIn[3] + try + { + PredOut = NoisePredictor->Run(Ort::RunOptions{ nullptr }, + predInput.data(), + PredIn.data(), + PredIn.size(), + predOutput.data(), + predOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: pred\n") + e1.what()).c_str()); + } + DenoiseIn[0] = std::move(PredOut[0]); // x_pred + DenoiseIn[1] = std::move(PredIn[3]); // time_prev + //DenoiseIn[2] = std::move(DenoiseIn[2]); // condition + try + { + DenoiseOut = DenoiseFn->Run(Ort::RunOptions{ nullptr }, + denoiseInput.data(), + DenoiseIn.data(), + DenoiseIn.size(), + denoiseOutput.data(), + denoiseOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: denoise\n") + e1.what()).c_str()); + } + auto noise_pred_prev = DenoiseOut[0].GetTensorMutableData(); + for (const auto it : noiseList[0]) + (*(noise_pred_prev++) += it) /= 2.0f; + //PredIn[0] = std::move(PredIn[0]); // noise + PredIn[1] = std::move(DenoiseOut[0]); // noise_pred_prime + //PredIn[2] = std::move(PredIn[2]); // time + PredIn[3] = std::move(DenoiseIn[1]); // time_prev + } + else + { + DenoiseIn[0] = std::move(PredOut[0]); // x + DenoiseIn[1] = Ort::Value::CreateTensor(*Memory, time, 1, + timeshape, 1); // time + //DenoiseIn[2] = std::move(DenoiseIn[2]); // condition + try + { + DenoiseOut = DenoiseFn->Run(Ort::RunOptions{ nullptr }, + denoiseInput.data(), + DenoiseIn.data(), + DenoiseIn.size(), + denoiseOutput.data(), + denoiseOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: denoise\n") + e1.what()).c_str()); + } + if (noiseList.size() < 4) + noiseList.emplace_back(std::vector(DenoiseOut[0].GetTensorData(), DenoiseOut[0].GetTensorData() + DenoiseOut[0].GetTensorTypeAndShapeInfo().GetElementCount())); + else + { + noiseList.pop_front(); + noiseList.emplace_back(std::vector(DenoiseOut[0].GetTensorData(), DenoiseOut[0].GetTensorData() + DenoiseOut[0].GetTensorTypeAndShapeInfo().GetElementCount())); + } + auto noise_pred_prime = DenoiseOut[0].GetTensorMutableData(); + if (noiseList.size() == 2) + for (size_t it = 0; it < noiseList[0].size(); ++it) + ((*(noise_pred_prime++) *= 3.0f) -= noiseList[0][it]) /= 2.0f; + if (noiseList.size() == 3) + for (size_t it = 0; it < noiseList[0].size(); ++it) + (((*(noise_pred_prime++) *= 23.0f) -= noiseList[1][it] * 16.0f) += noiseList[0][it] * 5.0f) /= 12.0f; + if (noiseList.size() == 4) + for (size_t it = 0; it < noiseList[0].size(); ++it) + ((((*(noise_pred_prime++) *= 55.0f) -= noiseList[2][it] * 59.0f) += noiseList[1][it] * 37.0f) -= noiseList[0][it] * 9.0f) /= 24.0f; + PredIn[0] = std::move(DenoiseIn[0]); // x + PredIn[1] = std::move(DenoiseOut[0]); // noise_pred_prime + PredIn[2] = std::move(DenoiseIn[1]); // time + PredIn[3] = Ort::Value::CreateTensor(*Memory, time_prev, 1, timeshape, 1); + } + try + { + PredOut = NoisePredictor->Run(Ort::RunOptions{ nullptr }, + predInput.data(), + PredIn.data(), + PredIn.size(), + predOutput.data(), + predOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: pred\n") + e1.what()).c_str()); + } + _callback(++Process, 1); + } + return PredOut; +} + +std::vector DDimSampler::Sample(std::vector& Tensors, int64_t Steps, int64_t SpeedUp, float NoiseScale, int64_t Seed, size_t& Process) +{ + std::vector diffusionSteps; + for (int64_t itt = Steps - SpeedUp; itt >= 0; itt -= SpeedUp) + diffusionSteps.push_back(itt); + std::vector DenoiseIn; + std::vector AlphaIn1, AlphaIn2; + + int64_t time[1] = { 0 }; + int64_t time_prev[1] = { 0 }; + + constexpr int64_t timeshape[1] = { 1 }; // DDIM SAMPLING + AlphaIn1.emplace_back(Ort::Value::CreateTensor(*Memory, time, 1, timeshape, 1)); + AlphaIn2.emplace_back(Ort::Value::CreateTensor(*Memory, time_prev, 1, timeshape, 1)); + DenoiseIn.emplace_back(std::move(Tensors[1])); // noise DenoiseIn[0] + DenoiseIn.emplace_back(Ort::Value::CreateTensor(*Memory, time, 1, timeshape, 1)); // time DenoiseIn[1] + DenoiseIn.emplace_back(std::move(Tensors[0])); // condition DenoiseIn[2] + for (const auto& t : diffusionSteps) + { + std::vector DenoiseOut; + float a_t, a_prev; + try + { + time[0] = t; + a_t = Alpha->Run(Ort::RunOptions{ nullptr }, + alphain.data(), + AlphaIn1.data(), + AlphaIn1.size(), + alphaout.data(), + alphaout.size())[0].GetTensorData()[0]; + time_prev[0] = (((t - SpeedUp) > 0) ? t - SpeedUp : 0); + a_prev = Alpha->Run(Ort::RunOptions{ nullptr }, + alphain.data(), + AlphaIn2.data(), + AlphaIn2.size(), + alphaout.data(), + alphaout.size())[0].GetTensorData()[0]; + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: alphas\n") + e1.what()).c_str()); + } + try + { + DenoiseOut = DenoiseFn->Run(Ort::RunOptions{ nullptr }, + denoiseInput.data(), + DenoiseIn.data(), + DenoiseIn.size(), + denoiseOutput.data(), + denoiseOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: denoise\n") + e1.what()).c_str()); + } + const auto x = DenoiseIn[0].GetTensorMutableData(); + const auto noise_pred = DenoiseOut[0].GetTensorMutableData(); + const auto noise_size = DenoiseOut[0].GetTensorTypeAndShapeInfo().GetElementCount(); + const auto sq_aprev = sqrt(a_prev); + const auto sq_at = sqrt(a_t); + const auto np_m_op = (sqrt((1 - a_prev) / a_prev) - sqrt((1 - a_t) / a_t)); +#ifndef MoeVoiceStudioAvxAcc + for (size_t i = 0; i < noise_size; ++i) + noise_pred[i] = (x[i] / sq_at + (sqrt((1 - a_prev) / a_prev) - sqrt((1 - a_t) / a_t)) * noise_pred[i]) * sq_aprev; +#else + InferTools::FloatTensorWrapper XWrapper(x, noise_size); + InferTools::FloatTensorWrapper PredWrapper(noise_pred, noise_size); + XWrapper /= sq_at; + ((PredWrapper *= np_m_op) += XWrapper) *= sq_aprev; +#endif + DenoiseIn[0] = std::move(DenoiseOut[0]); + _callback(++Process, 1); + } + std::vector out; + out.emplace_back(std::move(DenoiseIn[0])); + return out; +} + +MoeVoiceStudioSamplerEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/Stft/stft.cpp b/libsvc/Modules/src/InferTools/Stft/stft.cpp new file mode 100644 index 0000000..e6d1ee5 --- /dev/null +++ b/libsvc/Modules/src/InferTools/Stft/stft.cpp @@ -0,0 +1,184 @@ +#include "../../../header/InferTools/Stft/stft.hpp" +#include "../../../header/InferTools/inferTools.hpp" +#include "../../../header/Logger/MoeSSLogger.hpp" +#include "cblas.h" +#ifdef max +#undef max +#endif +#ifdef min +#undef min +#endif + +namespace DlCodecStft +{ + void HannWindow(double* data, int size) { + for (int i = 0; i < size; i++) { + const double windowValue = 0.5 * (1 - cos(2 * STFT::PI * i / (size - 1))); + data[i] *= windowValue; + } + } + + void ConvertDoubleToFloat(const std::vector& input, float* output) + { + for (size_t i = 0; i < input.size(); i++) { + output[i] = static_cast(input[i]); + } + } + + double CalculatePowerSpectrum(fftw_complex fc) { + return sqrt(fc[0] * fc[0] + fc[1] * fc[1]); + } + + void CalculatePowerSpectrum(double* real, const double* imag, int size) { + for (int i = 0; i < size; i++) { + real[i] = real[i] * real[i] + imag[i] * imag[i]; + } + } + + void ConvertPowerSpectrumToDecibels(double* data, int size) { + for (int i = 0; i < size; i++) { + data[i] = 10 * log10(data[i]); + } + } + + double HZ2Mel(const double frequency) + { + constexpr auto f_min = 0.0; + constexpr auto f_sp = 200.0 / 3; + auto mel = (frequency - f_min) / f_sp; + constexpr auto min_log_hz = 1000.0; + constexpr auto min_log_mel = (min_log_hz - f_min) / f_sp; + const auto logstep = log(6.4) / 27.0; + if (frequency >= min_log_hz) + mel = min_log_mel + log(frequency / min_log_hz) / logstep; + return mel; + } + + double Mel2HZ(const double mel) + { + constexpr auto f_min = 0.0; + constexpr auto f_sp = 200.0 / 3; + auto freqs = f_min + f_sp * mel; + constexpr auto min_log_hz = 1000.0; + constexpr auto min_log_mel = (min_log_hz - f_min) / f_sp; + const auto logstep = log(6.4) / 27.0; + if (mel >= min_log_mel) + freqs = min_log_hz * exp(logstep * (mel - min_log_mel)); + return freqs; + } + + static double mel_min = HZ2Mel(20.); + static double mel_max = HZ2Mel(11025.); + + STFT::STFT(int WindowSize, int HopSize, int FFTSize) + { + WINDOW_SIZE = WindowSize; + HOP_SIZE = HopSize; + if (FFTSize > 0) + FFT_SIZE = FFTSize; + else + FFT_SIZE = WINDOW_SIZE / 2 + 1; + } + + STFT::~STFT() = default; + + std::pair, int64_t> STFT::operator()(const std::vector& audioData) const + { + const int NUM_FRAMES = (int(audioData.size()) - WINDOW_SIZE) / HOP_SIZE + 1; + std::vector hannWindow(WINDOW_SIZE, 0.0); + const auto fftOut = reinterpret_cast(fftw_malloc(sizeof(fftw_complex) * FFT_SIZE)); + const fftw_plan plan = fftw_plan_dft_r2c_1d(WINDOW_SIZE, hannWindow.data(), fftOut, FFTW_ESTIMATE); + std::vector spectrogram(size_t(NUM_FRAMES) * FFT_SIZE, 0.f); + for (int i = 0; i < NUM_FRAMES; i++) { + std::memcpy(hannWindow.data(), &audioData[size_t(i) * HOP_SIZE], size_t(sizeof(double)) * WINDOW_SIZE); + HannWindow(hannWindow.data(), WINDOW_SIZE); + fftw_execute(plan); + const auto BgnPtn = size_t(unsigned(i * FFT_SIZE)); + for (int j = 0; j < FFT_SIZE; j++) + spectrogram[BgnPtn + j] = float(CalculatePowerSpectrum(fftOut[j])); + } + fftw_destroy_plan(plan); + fftw_free(fftOut); + return { std::move(spectrogram), int64_t(NUM_FRAMES) }; + } + + std::pair, int64_t> Mel::GetMel(const std::vector& audioData) const + { + auto BgnTime = clock(); + const auto Spec = stft(audioData); //[frame, nfft] * [nfft, mel_bins] | [mel_bins, nfft] * [nfft, frame] + logger.log(("[Inference] Slice Stft Use Time " + std::to_string(clock() - BgnTime) + "ms").c_str()); + const auto NFrames = Spec.second; + std::vector Mel(MEL_SIZE * NFrames, 0.f); + BgnTime = clock(); + cblas_sgemm( + CblasRowMajor, + CblasNoTrans, + CblasTrans, + MEL_SIZE, + blasint(NFrames), + FFT_SIZE, + 1.f, + MelBasis.data(), + FFT_SIZE, + Spec.first.data(), + blasint(FFT_SIZE), + 0.f, + Mel.data(), + blasint(NFrames) + ); + for (auto& it : Mel) + it = log(std::max(1e-5f, it)); + logger.log(("[Inference] Slice Mel Use Time " + std::to_string(clock() - BgnTime) + "ms").c_str()); + return { std::move(Mel), (int64_t)NFrames }; + } + + std::pair, int64_t> Mel::operator()(const std::vector& audioData) const + { + return GetMel(audioData); + } + + Mel::Mel(int WindowSize, int HopSize, int SamplingRate, int MelSize) : + stft(WindowSize, HopSize, WindowSize / 2 + 1) + { + if (MelSize > 0) + MEL_SIZE = MelSize; + FFT_SIZE = WindowSize / 2 + 1; + sr = SamplingRate; + + const int nfft = (FFT_SIZE - 1) * 2; + const double fftfreqval = 1. / (double(nfft) / double(SamplingRate)); + auto fftfreqs = InferTools::arange(0, FFT_SIZE + 2); + fftfreqs.resize(FFT_SIZE); + for (auto& i : fftfreqs) + i *= fftfreqval; + + auto mel_f = InferTools::arange(mel_min, mel_max + 1., (mel_max - mel_min) / (MEL_SIZE + 1)); + mel_f.resize(MEL_SIZE + 2); //[MEL_SIZE + 2] + + std::vector fdiff; + std::vector> ramps; //[MEL_SIZE + 2, FFTSize] + + ramps.reserve(MEL_SIZE + 2); + for (auto& i : mel_f) + { + i = Mel2HZ(i); + ramps.emplace_back(FFT_SIZE, i); + } + for (size_t i = 0; i < ramps.size(); ++i) + for (int j = 0; j < FFT_SIZE; ++j) + ramps[i][j] -= fftfreqs[j]; + + fdiff.reserve(MEL_SIZE + 2); //[MEL_SIZE + 1] + for (size_t i = 1; i < mel_f.size(); ++i) + fdiff.emplace_back(mel_f[i] - mel_f[i - 1]); + + MelBasis = std::vector(size_t(FFT_SIZE) * MelSize, 0.f); + + for (int i = 0; i < MelSize; ++i) + { + const auto enorm = 2. / (mel_f[i + 2] - mel_f[i]); + for (int j = 0; j < FFT_SIZE; ++j) + MelBasis[i * FFT_SIZE + j] = (float)(std::max(0., std::min(-ramps[i][j] / fdiff[i], ramps[i + 2][j] / fdiff[i + 1])) * enorm); + } + } +} \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.cpp b/libsvc/Modules/src/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.cpp new file mode 100644 index 0000000..8f5f2fb --- /dev/null +++ b/libsvc/Modules/src/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.cpp @@ -0,0 +1,588 @@ +#include "../../../header/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.hpp" +#include +#include "../../../header/Logger/MoeSSLogger.hpp" +#include "../../../header/InferTools/inferTools.hpp" + +MoeVoiceStudioTensorExtractorHeader + +MoeVoiceStudioTensorExtractor::Inputs SoVits2TensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + + const auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, HubertLen }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + // SvcTensors.Data.SpkShape = { SvcTensors.Data.FrameShape[1], int64_t(_NSpeaker) }; + + SvcTensors.Data.HiddenUnit = HiddenUnit; + SvcTensors.Data.Length[0] = HubertLen; + SvcTensors.Data.F0 = InferTools::InterpFunc(F0, (long)F0.size(), (long)HubertLen); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.NSFF0 = GetNSFF0(SvcTensors.Data.F0); + SvcTensors.Data.Speaker[0] = params.Chara; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Length, + 1, + SvcTensors.Data.OneShape, + 1 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.NSFF0.data(), + SvcTensors.Data.NSFF0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + + SvcTensors.InputNames = InputNames.data(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractor::Inputs SoVits3TensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + + auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, int64_t(params.AudioSize * _SamplingRate / _SrcSamplingRate / _HopSize) }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + // SvcTensors.Data.SpkShape = { SvcTensors.Data.FrameShape[1], int64_t(_NSpeaker) }; + const int64_t upSample = int64_t(_SamplingRate) / 16000; + const auto srcHubertSize = SvcTensors.Data.HiddenUnitShape[1]; + SvcTensors.Data.HiddenUnitShape[1] *= upSample; + HubertSize *= upSample; + SvcTensors.Data.FrameShape[1] = SvcTensors.Data.HiddenUnitShape[1]; + SvcTensors.Data.HiddenUnit.reserve(HubertSize * (upSample + 1)); + for (int64_t itS = 0; itS < srcHubertSize; ++itS) + for (int64_t itSS = 0; itSS < upSample; ++itSS) + SvcTensors.Data.HiddenUnit.insert(SvcTensors.Data.HiddenUnit.end(), HiddenUnit.begin() + itS * (int64_t)_HiddenSize, HiddenUnit.begin() + (itS + 1) * (int64_t)_HiddenSize); + SvcTensors.Data.F0 = GetInterpedF0(InferTools::InterpFunc(F0, long(F0.size()), long(SvcTensors.Data.HiddenUnitShape[1]))); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.Speaker[0] = params.Chara; + SvcTensors.Data.Length[0] = SvcTensors.Data.HiddenUnitShape[1]; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Length, + 1, + SvcTensors.Data.OneShape, + 1 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.F0.data(), + SvcTensors.Data.F0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + + SvcTensors.InputNames = InputNames.data(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractor::Inputs SoVits4TensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + std::mt19937 gen(int(params.Seed)); + std::normal_distribution normal(0, 1); + const auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, int64_t(params.AudioSize * _SamplingRate / _SrcSamplingRate / _HopSize) }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + SvcTensors.Data.SpkShape = { SvcTensors.Data.FrameShape[1], int64_t(_NSpeaker) }; + SvcTensors.Data.NoiseShape = { 1, 192, SvcTensors.Data.FrameShape[1] }; + const auto NoiseSize = SvcTensors.Data.NoiseShape[1] * SvcTensors.Data.NoiseShape[2] * SvcTensors.Data.NoiseShape[0]; + + SvcTensors.Data.HiddenUnit = HiddenUnit; + SvcTensors.Data.F0 = GetInterpedF0(InferTools::InterpFunc(F0, long(F0.size()), long(SvcTensors.Data.FrameShape[1]))); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.Alignment = GetAligments(SvcTensors.Data.FrameShape[1], HubertLen); + SvcTensors.Data.UnVoice = GetUV(F0); + SvcTensors.Data.Noise = std::vector(NoiseSize, 0.f); + for (auto& it : SvcTensors.Data.Noise) + it = normal(gen) * params.NoiseScale; + SvcTensors.Data.Speaker[0] = params.Chara; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.F0.data(), + SvcTensors.Data.F0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Alignment.data(), + SvcTensors.Data.Alignment.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.UnVoice.data(), + SvcTensors.Data.UnVoice.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Noise.data(), + SvcTensors.Data.Noise.size(), + SvcTensors.Data.NoiseShape.data(), + 3 + )); + + if (_SpeakerMix) + { + SvcTensors.Data.SpkMap = GetCurrectSpkMixData(SpkMap, SvcTensors.Data.FrameShape[1], params.Chara); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.SpkMap.data(), + SvcTensors.Data.SpkMap.size(), + SvcTensors.Data.SpkShape.data(), + 2 + )); + } + else + { + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + } + + if (_Volume) + { + SvcTensors.InputNames = InputNamesVol.data(); + SvcTensors.Data.Volume = InferTools::InterpFunc(Volume, long(Volume.size()), long(SvcTensors.Data.FrameShape[1])); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Volume.data(), + SvcTensors.Data.FrameShape[1], + SvcTensors.Data.FrameShape.data(), + 2 + )); + } + else + SvcTensors.InputNames = InputNames.data(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractor::Inputs SoVits4DDSPTensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + std::mt19937 gen(int(params.Seed)); + std::normal_distribution normal(0, 1); + const auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, int64_t(params.AudioSize * _SamplingRate / _SrcSamplingRate / _HopSize) }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + SvcTensors.Data.SpkShape = { SvcTensors.Data.FrameShape[1], int64_t(_NSpeaker) }; + SvcTensors.Data.NoiseShape = { 1, 192, SvcTensors.Data.FrameShape[1] }; + const auto NoiseSize = SvcTensors.Data.NoiseShape[1] * SvcTensors.Data.NoiseShape[2] * SvcTensors.Data.NoiseShape[0]; + SvcTensors.Data.DDSPNoiseShape = { 1, 2048, SvcTensors.Data.FrameShape[1] }; + const int64_t IstftCount = SvcTensors.Data.FrameShape[1] * 2048; + + SvcTensors.Data.HiddenUnit = HiddenUnit; + SvcTensors.Data.F0 = GetInterpedF0(InferTools::InterpFunc(F0, long(F0.size()), long(SvcTensors.Data.FrameShape[1]))); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.Alignment = GetAligments(SvcTensors.Data.FrameShape[1], HubertLen); + SvcTensors.Data.DDSPNoise = std::vector(IstftCount, params.DDSPNoiseScale); + SvcTensors.Data.Noise = std::vector(NoiseSize, 0.f); + for (auto& it : SvcTensors.Data.Noise) + it = normal(gen) * params.NoiseScale; + SvcTensors.Data.Speaker[0] = params.Chara; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.F0.data(), + SvcTensors.Data.F0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Alignment.data(), + SvcTensors.Data.Alignment.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.DDSPNoise.data(), + SvcTensors.Data.DDSPNoise.size(), + SvcTensors.Data.DDSPNoiseShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Noise.data(), + SvcTensors.Data.Noise.size(), + SvcTensors.Data.NoiseShape.data(), + 3 + )); + + if (_SpeakerMix) + { + SvcTensors.Data.SpkMap = GetCurrectSpkMixData(SpkMap, SvcTensors.Data.FrameShape[1], params.Chara); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.SpkMap.data(), + SvcTensors.Data.SpkMap.size(), + SvcTensors.Data.SpkShape.data(), + 2 + )); + } + else + { + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + } + + if (_Volume) + { + SvcTensors.InputNames = InputNamesVol.data(); + SvcTensors.Data.Volume = InferTools::InterpFunc(Volume, long(Volume.size()), long(SvcTensors.Data.FrameShape[1])); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Volume.data(), + SvcTensors.Data.FrameShape[1], + SvcTensors.Data.FrameShape.data(), + 2 + )); + } + else + SvcTensors.InputNames = InputNames.data(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractor::Inputs RVCTensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + std::mt19937 gen(int(params.Seed)); + std::normal_distribution normal(0, 1); + auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, int64_t(params.AudioSize * _SamplingRate / _SrcSamplingRate / _HopSize) }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + constexpr int64_t upSample = 2; + const auto srcHubertSize = SvcTensors.Data.HiddenUnitShape[1]; + SvcTensors.Data.HiddenUnitShape[1] *= upSample; + HubertSize *= upSample; + SvcTensors.Data.FrameShape[1] = SvcTensors.Data.HiddenUnitShape[1]; + SvcTensors.Data.SpkShape = { SvcTensors.Data.FrameShape[1], int64_t(_NSpeaker) }; + SvcTensors.Data.NoiseShape = { 1, 192, SvcTensors.Data.FrameShape[1] }; + const auto NoiseSize = SvcTensors.Data.NoiseShape[1] * SvcTensors.Data.NoiseShape[2] * SvcTensors.Data.NoiseShape[0]; + + SvcTensors.Data.HiddenUnit.reserve(HubertSize); + for (int64_t itS = 0; itS < srcHubertSize; ++itS) + for (int64_t itSS = 0; itSS < upSample; ++itSS) + SvcTensors.Data.HiddenUnit.insert(SvcTensors.Data.HiddenUnit.end(), HiddenUnit.begin() + itS * (int64_t)_HiddenSize, HiddenUnit.begin() + (itS + 1) * (int64_t)_HiddenSize); + SvcTensors.Data.Length[0] = SvcTensors.Data.HiddenUnitShape[1]; + SvcTensors.Data.F0 = GetInterpedF0(InferTools::InterpFunc(F0, long(F0.size()), long(SvcTensors.Data.HiddenUnitShape[1]))); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.NSFF0 = GetNSFF0(SvcTensors.Data.F0); + SvcTensors.Data.Alignment = GetAligments(SvcTensors.Data.FrameShape[1], HubertLen); + SvcTensors.Data.Noise = std::vector(NoiseSize, 0.f); + for (auto& it : SvcTensors.Data.Noise) + it = normal(gen) * params.NoiseScale; + SvcTensors.Data.Speaker[0] = params.Chara; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Length, + 1, + SvcTensors.Data.OneShape, + 1 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.NSFF0.data(), + SvcTensors.Data.NSFF0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.F0.data(), + SvcTensors.Data.F0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + if (_SpeakerMix) + { + SvcTensors.Data.SpkMap = GetCurrectSpkMixData(SpkMap, SvcTensors.Data.FrameShape[1], params.Chara); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.SpkMap.data(), + SvcTensors.Data.SpkMap.size(), + SvcTensors.Data.SpkShape.data(), + 2 + )); + } + else + { + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + } + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Noise.data(), + SvcTensors.Data.Noise.size(), + SvcTensors.Data.NoiseShape.data(), + 3 + )); + + if (_Volume) + { + SvcTensors.InputNames = InputNamesVol.data(); + SvcTensors.Data.Volume = InferTools::InterpFunc(Volume, long(Volume.size()), long(SvcTensors.Data.FrameShape[1])); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Volume.data(), + SvcTensors.Data.FrameShape[1], + SvcTensors.Data.FrameShape.data(), + 2 + )); + } + else + SvcTensors.InputNames = InputNames.data(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractor::Inputs DiffSvcTensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + const auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, int64_t(params.AudioSize * _SamplingRate / _SrcSamplingRate / _HopSize) }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + + SvcTensors.Data.HiddenUnit = HiddenUnit; + SvcTensors.Data.F0 = InferTools::InterpFunc(F0, long(F0.size()), long(SvcTensors.Data.FrameShape[1])); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.F0 = GetInterpedF0log(SvcTensors.Data.F0, true); + SvcTensors.Data.Alignment = GetAligments(SvcTensors.Data.FrameShape[1], HubertLen); + SvcTensors.Data.Speaker[0] = params.Chara; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Alignment.data(), + SvcTensors.Data.Alignment.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.F0.data(), + SvcTensors.Data.F0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.InputNames = InputNames.data(); + + SvcTensors.OutputNames = OutputNames.data(); + + SvcTensors.OutputCount = OutputNames.size(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractor::Inputs DiffusionSvcTensorExtractor::Extract(const std::vector& HiddenUnit, const std::vector& F0, const std::vector& Volume, const std::vector>& SpkMap, Params params) +{ + Inputs SvcTensors; + const auto HubertSize = HiddenUnit.size(); + const auto HubertLen = int64_t(HubertSize) / int64_t(_HiddenSize); + SvcTensors.Data.FrameShape = { 1, int64_t(params.AudioSize * _SamplingRate / _SrcSamplingRate / _HopSize) }; + SvcTensors.Data.HiddenUnitShape = { 1, HubertLen, int64_t(_HiddenSize) }; + SvcTensors.Data.SpkShape = { SvcTensors.Data.FrameShape[1], int64_t(_NSpeaker) }; + + SvcTensors.Data.HiddenUnit = HiddenUnit; + SvcTensors.Data.F0 = InterpUVF0(InferTools::InterpFunc(F0, long(F0.size()), long(SvcTensors.Data.FrameShape[1]))); + for (auto& it : SvcTensors.Data.F0) + it *= (float)pow(2.0, static_cast(params.upKeys) / 12.0); + SvcTensors.Data.Alignment = GetAligments(SvcTensors.Data.FrameShape[1], HubertLen); + SvcTensors.Data.Speaker[0] = params.Chara; + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.HiddenUnit.data(), + HubertSize, + SvcTensors.Data.HiddenUnitShape.data(), + 3 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Alignment.data(), + SvcTensors.Data.Alignment.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.F0.data(), + SvcTensors.Data.F0.size(), + SvcTensors.Data.FrameShape.data(), + 2 + )); + + if (_Volume) + { + SvcTensors.InputNames = InputNamesVol.data(); + SvcTensors.Data.Volume = InferTools::InterpFunc(Volume, long(Volume.size()), long(SvcTensors.Data.FrameShape[1])); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Volume.data(), + SvcTensors.Data.FrameShape[1], + SvcTensors.Data.FrameShape.data(), + 2 + )); + } + else + SvcTensors.InputNames = InputNames.data(); + + if (_SpeakerMix) + { + SvcTensors.Data.SpkMap = GetCurrectSpkMixData(SpkMap, SvcTensors.Data.FrameShape[1], params.Chara); + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.SpkMap.data(), + SvcTensors.Data.SpkMap.size(), + SvcTensors.Data.SpkShape.data(), + 2 + )); + } + else + { + SvcTensors.Tensor.emplace_back(Ort::Value::CreateTensor( + Memory, + SvcTensors.Data.Speaker, + 1, + SvcTensors.Data.OneShape, + 1 + )); + } + + SvcTensors.OutputNames = OutputNames.data(); + + SvcTensors.OutputCount = OutputNames.size(); + + return SvcTensors; +} + +MoeVoiceStudioTensorExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.cpp b/libsvc/Modules/src/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.cpp new file mode 100644 index 0000000..6395f77 --- /dev/null +++ b/libsvc/Modules/src/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.cpp @@ -0,0 +1,289 @@ +#include "../../../header/InferTools/TensorExtractor/MoeVoiceStudioTensorExtractor.hpp" +#include "../../../header/InferTools/inferTools.hpp" +MoeVoiceStudioTensorExtractorHeader + +MoeVoiceStudioTensorExtractor::MoeVoiceStudioTensorExtractor(uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const Others& _other) +{ + _SrcSamplingRate = _srcsr; + _SamplingRate = _sr; + _HopSize = _hop; + _SpeakerMix = _smix; + _Volume = _volume; + _HiddenSize = _hidden_size; + _NSpeaker = _nspeaker; + f0_bin = _other.f0_bin; + f0_max = _other.f0_max; + f0_min = _other.f0_min; + f0_mel_min = 1127.f * log(1.f + f0_min / 700.f); + f0_mel_max = 1127.f * log(1.f + f0_max / 700.f); + Memory = _other.Memory; +} + +MoeVoiceStudioTensorExtractor::Inputs MoeVoiceStudioTensorExtractor::Extract( + const std::vector& HiddenUnit, + const std::vector& F0, + const std::vector& Volume, + const std::vector>& SpkMap, + Params params +) +{ + LibDLVoiceCodecThrow("NotImplementedError"); +} + +std::vector MoeVoiceStudioTensorExtractor::GetCurrectSpkMixData(const std::vector>& _input, size_t dst_len, int64_t curspk) const +{ + std::vector mixData; + mixData.reserve(_NSpeaker * dst_len); + if(_input.empty()) + { + std::vector LenData(_NSpeaker, 0.0); + LenData[curspk] = 1.0; + for (size_t i = 0; i < dst_len; ++i) + mixData.insert(mixData.end(), LenData.begin(), LenData.end()); + } + else + { + std::vector> _spkMap; + for (size_t i = 0; i < _input.size() && i < _NSpeaker; ++i) + _spkMap.emplace_back(InferTools::InterpFunc(_input[i], long(_input[i].size()), long(dst_len))); + LinearCombination(_spkMap, curspk); + const auto curnspk = _input.size(); + if (curnspk < _NSpeaker) + { + std::vector LenData(_NSpeaker - curnspk, 0.0); + for (size_t i = 0; i < dst_len; ++i) + { + for (size_t j = 0; j < curnspk; ++j) + mixData.emplace_back(_spkMap[j][i]); + mixData.insert(mixData.end(), LenData.begin(), LenData.end()); + } + } + else + for (size_t i = 0; i < dst_len; ++i) + for (size_t j = 0; j < _NSpeaker; ++j) + mixData.emplace_back(_spkMap[j][i]); + } + return mixData; +} + +std::vector MoeVoiceStudioTensorExtractor::GetSpkMixData(const std::vector>& _input, size_t dst_len, size_t spk_count) +{ + std::vector mixData; + mixData.reserve(spk_count * dst_len); + if (_input.empty()) + { + std::vector LenData(spk_count, 0.0); + LenData[0] = 1.0; + for (size_t i = 0; i < dst_len; ++i) + mixData.insert(mixData.end(), LenData.begin(), LenData.end()); + } + else + { + std::vector> _spkMap; + for (size_t i = 0; i < _input.size() && i < spk_count; ++i) + _spkMap.emplace_back(InferTools::InterpFunc(_input[i], long(_input[i].size()), long(dst_len))); + LinearCombination(_spkMap, 0); + const auto curnspk = _input.size(); + if (curnspk < spk_count) + { + std::vector LenData(spk_count - curnspk, 0.0); + for (size_t i = 0; i < dst_len; ++i) + { + for (size_t j = 0; j < curnspk; ++j) + mixData.emplace_back(_spkMap[j][i]); + mixData.insert(mixData.end(), LenData.begin(), LenData.end()); + } + } + else + for (size_t i = 0; i < dst_len; ++i) + for (size_t j = 0; j < spk_count; ++j) + mixData.emplace_back(_spkMap[j][i]); + } + return mixData; +} + +std::vector MoeVoiceStudioTensorExtractor::GetNSFF0(const std::vector& F0) const +{ + const auto f0Len = F0.size(); + std::vector NSFF0(f0Len); + for (size_t i = 0; i < f0Len; ++i) + { + float f0_mel = 1127.f * log(1.f + F0[i] / 700.f); + if (f0_mel > 0.f) + f0_mel = (f0_mel - f0_mel_min) * (float(f0_bin) - 2.f) / (f0_mel_max - f0_mel_min) + 1.f; + if (f0_mel < 1.f) + f0_mel = 1.f; + if (f0_mel > float(f0_bin) - 1.f) + f0_mel = float(f0_bin) - 1.f; + NSFF0[i] = (int64_t)round(f0_mel); + } + return NSFF0; +} + +std::vector MoeVoiceStudioTensorExtractor::GetInterpedF0(const std::vector& F0) +{ + const auto specLen = F0.size(); + std::vector Of0(specLen, 0.0); + + float last_value = 0.0; + for (size_t i = 0; i < specLen; ++i) + { + if (F0[i] <= 0.f) + { + size_t j = i + 1; + for (; j < specLen; ++j) + { + if (F0[j] > 0.f) + break; + } + if (j < specLen - 1) + { + if (last_value > 0.f) + { + const auto step = (F0[j] - F0[i - 1]) / float(j - i); + for (size_t k = i; k < j; ++k) + Of0[k] = float(F0[i - 1] + step * float(k - i + 1)); + } + else + for (size_t k = i; k < j; ++k) + Of0[k] = float(F0[j]); + i = j; + } + else + { + for (size_t k = i; k < specLen; ++k) + Of0[k] = float(last_value); + i = specLen; + } + } + else + { + if (i == 0) + { + Of0[i] = float(F0[i]); + continue; + } + Of0[i] = float(F0[i - 1]); + last_value = F0[i]; + } + } + return Of0; +} + +std::vector MoeVoiceStudioTensorExtractor::InterpUVF0(const std::vector& F0) +{ + std::vector NUVF0; + std::vector UVF0Indices, NUVF0Indices; + UVF0Indices.reserve(F0.size()); + NUVF0.reserve(F0.size()); + NUVF0Indices.reserve(F0.size()); + if(F0[0] < 0.0001f) + { + NUVF0.emplace_back(0); + NUVF0Indices.emplace_back(0); + } + for (size_t i = 1; i < F0.size(); ++i) + { + if (F0[i] < 0.0001f) + UVF0Indices.emplace_back((double)i); + else + { + NUVF0.emplace_back((double)F0[i]); + NUVF0Indices.emplace_back((double)i); + } + } + if (UVF0Indices.empty() || NUVF0Indices.empty()) + return F0; + std::vector UVF0(F0.size()); + std::vector Of0 = F0; + interp1(NUVF0Indices.data(), NUVF0.data(), (int)NUVF0.size(), + UVF0Indices.data(), (int)UVF0Indices.size(), UVF0.data()); + for (size_t i = 0; i < UVF0Indices.size(); ++i) + Of0[size_t(UVF0Indices[i])] = (float)UVF0[i]; + return Of0; +} + +std::vector MoeVoiceStudioTensorExtractor::GetUV(const std::vector& F0) +{ + const auto specLen = F0.size(); + std::vector ruv(specLen, 1.0); + for (size_t i = 0; i < specLen; ++i) + { + if (F0[i] < 0.001f) + ruv[i] = 0.f; + } + return ruv; +} + +std::vector MoeVoiceStudioTensorExtractor::GetAligments(size_t specLen, size_t hubertLen) +{ + std::vector mel2ph(specLen + 1, 0); + + size_t startFrame = 0; + const double ph_durs = static_cast(specLen) / static_cast(hubertLen); + for (size_t iph = 0; iph < hubertLen; ++iph) + { + const auto endFrame = static_cast(round(static_cast(iph) * ph_durs + ph_durs)); + for (auto j = startFrame; j < endFrame + 1; ++j) + mel2ph[j] = static_cast(iph) + 1; + startFrame = endFrame + 1; + } + return mel2ph; +} + +std::vector MoeVoiceStudioTensorExtractor::GetInterpedF0log(const std::vector& rF0, bool enable_log) +{ + const auto specLen = rF0.size(); + std::vector F0(specLen); + std::vector Of0(specLen, 0.0); + for (size_t i = 0; i < specLen; ++i) + { + if (enable_log) + F0[i] = log2(rF0[i]); + else + F0[i] = rF0[i]; + if (isnan(F0[i]) || isinf(F0[i])) + F0[i] = 0.f; + } + + float last_value = 0.0; + for (size_t i = 0; i < specLen; ++i) + { + if (F0[i] <= 0.f) + { + size_t j = i + 1; + for (; j < specLen; ++j) + { + if (F0[j] > 0.f) + break; + } + if (j < specLen - 1) + { + if (last_value > 0.f) + { + const auto step = (F0[j] - F0[i - 1]) / float(j - i); + for (size_t k = i; k < j; ++k) + Of0[k] = float(F0[i - 1] + step * float(k - i + 1)); + } + else + for (size_t k = i; k < j; ++k) + Of0[k] = float(F0[j]); + i = j; + } + else + { + for (size_t k = i; k < specLen; ++k) + Of0[k] = float(last_value); + i = specLen; + } + } + else + { + Of0[i] = float(F0[i - 1]); + last_value = F0[i]; + } + } + return Of0; +} + +MoeVoiceStudioTensorExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/TensorExtractor/TensorExtractorManager.cpp b/libsvc/Modules/src/InferTools/TensorExtractor/TensorExtractorManager.cpp new file mode 100644 index 0000000..69888b8 --- /dev/null +++ b/libsvc/Modules/src/InferTools/TensorExtractor/TensorExtractorManager.cpp @@ -0,0 +1,26 @@ +#include "../../../header/InferTools/TensorExtractor/TensorExtractorManager.hpp" +#include +#include "../../../header/Logger/MoeSSLogger.hpp" + +MoeVoiceStudioTensorExtractorHeader + inline std::map RegisteredTensorExtractors; + +void RegisterTensorExtractor(const std::wstring& _name, const GetTensorExtractorFn& _constructor_fn) +{ + if (RegisteredTensorExtractors.find(_name) != RegisteredTensorExtractors.end()) + { + logger.log(L"[Warn] TensorExtractorNameConflict"); + return; + } + RegisteredTensorExtractors[_name] = _constructor_fn; +} + +TensorExtractor GetTensorExtractor(const std::wstring& _name, uint64_t _srcsr, uint64_t _sr, uint64_t _hop, bool _smix, bool _volume, uint64_t _hidden_size, uint64_t _nspeaker, const MoeVoiceStudioTensorExtractor::Others& _other) +{ + const auto f_TensorExtractor = RegisteredTensorExtractors.find(_name); + if (f_TensorExtractor != RegisteredTensorExtractors.end()) + return f_TensorExtractor->second(_srcsr, _sr, _hop, _smix, _volume, _hidden_size, _nspeaker, _other); + throw std::runtime_error("Unable To Find An Available TensorExtractor"); +} + +MoeVoiceStudioTensorExtractorEnd \ No newline at end of file diff --git a/libsvc/Modules/src/InferTools/inferTools.cpp b/libsvc/Modules/src/InferTools/inferTools.cpp new file mode 100644 index 0000000..c033133 --- /dev/null +++ b/libsvc/Modules/src/InferTools/inferTools.cpp @@ -0,0 +1,356 @@ +#include "../../header/InferTools/inferTools.hpp" +#include "string" +#ifdef MoeVoiceStudioAvxAcc +#include +#endif +InferTools::Wav::Wav(const wchar_t* Path) :header(WAV_HEADER()) { + char buf[1024]; + FILE* stream; + _wfreopen_s(&stream, Path, L"rb", stderr); + if (stream == nullptr) { + throw (std::exception("File not exists")); + } + fread(buf, 1, HEAD_LENGTH, stream); + int pos = 0; + while (pos < HEAD_LENGTH) { + if ((buf[pos] == 'R') && (buf[pos + 1] == 'I') && (buf[pos + 2] == 'F') && (buf[pos + 3] == 'F')) { + pos += 4; + break; + } + ++pos; + } + if (pos >= HEAD_LENGTH) + throw (std::exception("Don't order fried rice (annoyed)")); + header.ChunkSize = *(int*)&buf[pos]; + pos += 8; + while (pos < HEAD_LENGTH) { + if ((buf[pos] == 'f') && (buf[pos + 1] == 'm') && (buf[pos + 2] == 't')) { + pos += 4; + break; + } + ++pos; + } + if (pos >= HEAD_LENGTH) + throw (std::exception("Don't order fried rice (annoyed)")); + header.Subchunk1Size = *(int*)&buf[pos]; + pos += 4; + header.AudioFormat = *(short*)&buf[pos]; + pos += 2; + header.NumOfChan = *(short*)&buf[pos]; + pos += 2; + header.SamplesPerSec = *(int*)&buf[pos]; + pos += 4; + header.bytesPerSec = *(int*)&buf[pos]; + pos += 4; + header.blockAlign = *(short*)&buf[pos]; + pos += 2; + header.bitsPerSample = *(short*)&buf[pos]; + pos += 2; + while (pos < HEAD_LENGTH) { + if ((buf[pos] == 'd') && (buf[pos + 1] == 'a') && (buf[pos + 2] == 't') && (buf[pos + 3] == 'a')) { + pos += 4; + break; + } + ++pos; + } + if (pos >= HEAD_LENGTH) + throw (std::exception("Don't order fried rice (annoyed)")); + header.Subchunk2Size = *(int*)&buf[pos]; + pos += 4; + StartPos = pos; + Data = new char[header.Subchunk2Size + 1]; + fseek(stream, StartPos, SEEK_SET); + fread(Data, 1, header.Subchunk2Size, stream); + if (stream != nullptr) { + fclose(stream); + } + SData = reinterpret_cast(Data); + dataSize = header.Subchunk2Size / 2; +} + +InferTools::Wav::Wav(const Wav& input) :header(WAV_HEADER()) { + Data = new char[(input.header.Subchunk2Size + 1)]; + if (Data == nullptr) { LibDLVoiceCodecThrow("OOM") } + memcpy(header.RIFF, input.header.RIFF, 4); + memcpy(header.fmt, input.header.fmt, 4); + memcpy(header.WAVE, input.header.WAVE, 4); + memcpy(header.Subchunk2ID, input.header.Subchunk2ID, 4); + header.ChunkSize = input.header.ChunkSize; + header.Subchunk1Size = input.header.Subchunk1Size; + header.AudioFormat = input.header.AudioFormat; + header.NumOfChan = input.header.NumOfChan; + header.SamplesPerSec = input.header.SamplesPerSec; + header.bytesPerSec = input.header.bytesPerSec; + header.blockAlign = input.header.blockAlign; + header.bitsPerSample = input.header.bitsPerSample; + header.Subchunk2Size = input.header.Subchunk2Size; + StartPos = input.StartPos; + memcpy(Data, input.Data, input.header.Subchunk2Size); + SData = reinterpret_cast(Data); + dataSize = header.Subchunk2Size / 2; +} + +InferTools::Wav::Wav(Wav&& input) noexcept +{ + Data = input.Data; + input.Data = nullptr; + memcpy(header.RIFF, input.header.RIFF, 4); + memcpy(header.fmt, input.header.fmt, 4); + memcpy(header.WAVE, input.header.WAVE, 4); + memcpy(header.Subchunk2ID, input.header.Subchunk2ID, 4); + header.ChunkSize = input.header.ChunkSize; + header.Subchunk1Size = input.header.Subchunk1Size; + header.AudioFormat = input.header.AudioFormat; + header.NumOfChan = input.header.NumOfChan; + header.SamplesPerSec = input.header.SamplesPerSec; + header.bytesPerSec = input.header.bytesPerSec; + header.blockAlign = input.header.blockAlign; + header.bitsPerSample = input.header.bitsPerSample; + header.Subchunk2Size = input.header.Subchunk2Size; + StartPos = input.StartPos; + SData = reinterpret_cast(Data); + dataSize = header.Subchunk2Size / 2; +} + +InferTools::Wav& InferTools::Wav::operator=(Wav&& input) noexcept +{ + destory(); + Data = input.Data; + input.Data = nullptr; + memcpy(header.RIFF, input.header.RIFF, 4); + memcpy(header.fmt, input.header.fmt, 4); + memcpy(header.WAVE, input.header.WAVE, 4); + memcpy(header.Subchunk2ID, input.header.Subchunk2ID, 4); + header.ChunkSize = input.header.ChunkSize; + header.Subchunk1Size = input.header.Subchunk1Size; + header.AudioFormat = input.header.AudioFormat; + header.NumOfChan = input.header.NumOfChan; + header.SamplesPerSec = input.header.SamplesPerSec; + header.bytesPerSec = input.header.bytesPerSec; + header.blockAlign = input.header.blockAlign; + header.bitsPerSample = input.header.bitsPerSample; + header.Subchunk2Size = input.header.Subchunk2Size; + StartPos = input.StartPos; + SData = reinterpret_cast(Data); + dataSize = header.Subchunk2Size / 2; + return *this; +} + +InferTools::Wav& InferTools::Wav::cat(const Wav& input) +{ + if (header.AudioFormat != 1) return *this; + if (header.SamplesPerSec != input.header.bitsPerSample || header.NumOfChan != input.header.NumOfChan) return *this; + char* buffer = new char[(int64_t)header.Subchunk2Size + (int64_t)input.header.Subchunk2Size + 1]; + if (buffer == nullptr)return *this; + memcpy(buffer, Data, header.Subchunk2Size); + memcpy(buffer + header.Subchunk2Size, input.Data, input.header.Subchunk2Size); + header.ChunkSize += input.header.Subchunk2Size; + header.Subchunk2Size += input.header.Subchunk2Size; + delete[] Data; + Data = buffer; + SData = reinterpret_cast(Data); + dataSize = header.Subchunk2Size / 2; + return *this; +} + +std::vector InferTools::SliceAudio(const std::vector& input, const SlicerSettings& _slicer) +{ + if (input.size() < size_t(_slicer.MinLength) * _slicer.SamplingRate) + return {0, input.size()}; + + std::vector slice_point; + bool slice_tag = true; + slice_point.emplace_back(0); + + unsigned long CurLength = 0; + for (size_t i = 0; i + _slicer.WindowLength < input.size(); i += _slicer.HopSize) + { + + if (slice_tag) + { + const auto vol = abs(getAvg(input.data() + i, input.data() + i + _slicer.WindowLength)); + if (vol < _slicer.Threshold) + { + slice_tag = false; + if (CurLength > _slicer.MinLength * _slicer.SamplingRate) + { + CurLength = 0; + slice_point.emplace_back(i + (_slicer.WindowLength / 2)); + } + } + else + slice_tag = true; + } + else + { + const auto vol = abs(getAvg(input.data() + i, input.data() + i + _slicer.WindowLength)); + if (vol < _slicer.Threshold) + slice_tag = false; + else + { + slice_tag = true; + if (CurLength > _slicer.MinLength * _slicer.SamplingRate) + { + CurLength = 0; + slice_point.emplace_back(i + (_slicer.WindowLength / 2)); + } + } + } + CurLength += _slicer.HopSize; + } + slice_point.push_back(input.size()); + return slice_point; +} + +std::vector InferTools::arange(double start, double end, double step, double div) +{ + std::vector output; + while (start < end) + { + output.push_back(start / div); + start += step; + } + return output; +} + +std::vector InferTools::mean_filter(const std::vector& vec, size_t window_size) +{ + std::vector result; + + if (window_size > vec.size() || window_size < 2) + return vec; + + const size_t half = window_size / 2; // 窗口半径,向下取整 + + for (size_t i = half; i < vec.size() - half; i++) { + float sum = 0.0f; + for (size_t j = i - half; j <= i + half; j++) { + sum += vec[j]; + } + result.push_back(sum / (float)(window_size % 2 ? window_size : window_size + 1)); + } + + return result; +} + +#ifdef MoeVoiceStudioAvxAcc +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator+=(const FloatTensorWrapper& _right) +{ + if (_data_size != _right._data_size) + LibDLVoiceCodecThrow("Vector Size MisMatch"); + const size_t num_avx2_elements = _data_size / 8; + for (size_t i = 0; i < num_avx2_elements; i++) { + const __m256 a_avx2 = _mm256_load_ps(&_data_ptr[i * 8]); + const __m256 b_avx2 = _mm256_load_ps(&_right[i * 8]); + const __m256 result_avx2 = _mm256_add_ps(a_avx2, b_avx2); + _mm256_store_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] += _right[i]; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator-=(const FloatTensorWrapper& _right) +{ + if (_data_size != _right._data_size) + LibDLVoiceCodecThrow("Vector Size MisMatch"); + const size_t num_avx2_elements = _data_size / 8; + for (size_t i = 0; i < num_avx2_elements; i++) { + const __m256 a_avx2 = _mm256_load_ps(&_data_ptr[i * 8]); + const __m256 b_avx2 = _mm256_load_ps(&_right[i * 8]); + const __m256 result_avx2 = _mm256_sub_ps(a_avx2, b_avx2); + _mm256_store_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] -= _right[i]; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator*=(const FloatTensorWrapper& _right) +{ + if (_data_size != _right._data_size) + LibDLVoiceCodecThrow("Vector Size MisMatch"); + const size_t num_avx2_elements = _data_size / 8; + for (size_t i = 0; i < num_avx2_elements; i++) { + const __m256 a_avx2 = _mm256_load_ps(&_data_ptr[i * 8]); + const __m256 b_avx2 = _mm256_load_ps(&_right[i * 8]); + const __m256 result_avx2 = _mm256_mul_ps(a_avx2, b_avx2); + _mm256_store_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] *= _right[i]; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator/=(const FloatTensorWrapper& _right) +{ + if (_data_size != _right._data_size) + LibDLVoiceCodecThrow("Vector Size MisMatch"); + const size_t num_avx2_elements = _data_size / 8; + for (size_t i = 0; i < num_avx2_elements; i++) { + const __m256 a_avx2 = _mm256_load_ps(&_data_ptr[i * 8]); + const __m256 b_avx2 = _mm256_load_ps(&_right[i * 8]); + const __m256 result_avx2 = _mm256_div_ps(a_avx2, b_avx2); + _mm256_store_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] /= _right[i]; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator+=(float _right) +{ + const size_t num_avx2_elements = _data_size / 8; + const __m256 value_avx2 = _mm256_set1_ps(_right); + for (size_t i = 0; i < num_avx2_elements; ++i) { + const __m256 vec_avx2 = _mm256_loadu_ps(&_data_ptr[i * 8]); + const __m256 result_avx2 = _mm256_add_ps(vec_avx2, value_avx2); + _mm256_storeu_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] += _right; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator-=(float _right) +{ + const size_t num_avx2_elements = _data_size / 8; + const __m256 value_avx2 = _mm256_set1_ps(_right); + for (size_t i = 0; i < num_avx2_elements; ++i) { + const __m256 vec_avx2 = _mm256_loadu_ps(&_data_ptr[i * 8]); + const __m256 result_avx2 = _mm256_sub_ps(vec_avx2, value_avx2); + _mm256_storeu_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] -= _right; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator*=(float _right) +{ + const size_t num_avx2_elements = _data_size / 8; + const __m256 value_avx2 = _mm256_set1_ps(_right); + for (size_t i = 0; i < num_avx2_elements; ++i) { + const __m256 vec_avx2 = _mm256_loadu_ps(&_data_ptr[i * 8]); + const __m256 result_avx2 = _mm256_mul_ps(vec_avx2, value_avx2); + _mm256_storeu_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] *= _right; + return *this; +} + +InferTools::FloatTensorWrapper& InferTools::FloatTensorWrapper::operator/=(float _right) +{ + const size_t num_avx2_elements = _data_size / 8; + const __m256 value_avx2 = _mm256_set1_ps(_right); + for (size_t i = 0; i < num_avx2_elements; ++i) { + const __m256 vec_avx2 = _mm256_loadu_ps(&_data_ptr[i * 8]); + const __m256 result_avx2 = _mm256_div_ps(vec_avx2, value_avx2); + _mm256_storeu_ps(&_data_ptr[i * 8], result_avx2); + } + for (size_t i = num_avx2_elements * 8; i < _data_size; ++i) + _data_ptr[i] /= _right; + return *this; +} +#endif \ No newline at end of file diff --git a/libsvc/Modules/src/Logger/MoeSSLogger.cpp b/libsvc/Modules/src/Logger/MoeSSLogger.cpp new file mode 100644 index 0000000..5606a05 --- /dev/null +++ b/libsvc/Modules/src/Logger/MoeSSLogger.cpp @@ -0,0 +1,177 @@ +#include "../../header/Logger/MoeSSLogger.hpp" +#include +#include "../../header/StringPreprocess.hpp" +#include + +namespace MoeSSLogger +{ + static Logger MoeVsLogger; + + static std::wstring GetCurrentFolder(const std::wstring& defualt = L"") + { + wchar_t path[1024]; +#ifdef _WIN32 + GetModuleFileName(nullptr, path, 1024); + std::wstring _curPath = path; + _curPath = _curPath.substr(0, _curPath.rfind(L'\\')); + return _curPath; +#else + //TODO Other System +#error Other System ToDO +#endif + } + + void RemoveDir(const std::filesystem::directory_entry& dir) + { + if (!dir.exists()) + return; + if (dir.is_directory() && !is_empty(dir)) + { + const std::filesystem::directory_iterator dirs(dir); + for (const auto& i : dirs) + RemoveDir(i); + } + remove(dir); + } + + Logger::~Logger() + { +#ifndef MoeVoiceStudioCommandLineProg + if (log_file) + fclose(log_file); + if (error_file) + fclose(error_file); + log_file = nullptr; + error_file = nullptr; +#endif + } + + Logger::Logger() + { +#ifndef MoeVoiceStudioCommandLineProg + const std::wstring LogPath = GetCurrentFolder() + L"/log"; + const std::filesystem::path logger_path(LogPath); + if (!exists(logger_path)) + create_directory(logger_path); + const std::filesystem::directory_entry logger_path_entry(LogPath); + if (logger_path_entry.is_directory()) + { + time_t curtime; + time(&curtime); + tm nowtime{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + localtime_s(&nowtime, &curtime); + const std::wstring dir_name = L"MoeVoiceStudioLog " + + std::to_wstring(nowtime.tm_year + 1900) + L'-' + + std::to_wstring(nowtime.tm_mon) + L'-' + + std::to_wstring(nowtime.tm_mday) + L'-' + + std::to_wstring(nowtime.tm_hour) + L'-' + + std::to_wstring(nowtime.tm_min) + L'-' + + std::to_wstring(nowtime.tm_sec); + cur_log_dir = LogPath + L'/' + dir_name; + const std::filesystem::directory_iterator logger_path_list(LogPath); + std::filesystem::directory_entry remove_dir; + size_t log_count = 0; + for (const auto& i : logger_path_list) + if (i.is_directory() && i.path().filename().wstring().substr(0, 17) == L"MoeVoiceStudioLog") + { + if (!log_count) + remove_dir = i; + else if (i.last_write_time() < remove_dir.last_write_time()) + remove_dir = i; + ++log_count; + } + if (log_count > 10) + RemoveDir(remove_dir); + if (!exists(cur_log_dir)) + create_directory(cur_log_dir); + logpath = cur_log_dir; + logpath.append("log.txt"); + errorpath = cur_log_dir; + errorpath.append("error.txt"); + } +#endif + } + + void Logger::log(const std::wstring& format) + { + std::lock_guard mtx(mx); +#ifndef MoeVoiceStudioCommandLineProg + _wfopen_s(&log_file, logpath.c_str(), L"a+"); + if (log_file) + { + fprintf_s(log_file, "%s\n", to_byte_string(format).c_str()); + fclose(log_file); + log_file = nullptr; + } +#else + fprintf_s(stdout, "%s\n", to_byte_string(format).c_str()); +#endif + } + + void Logger::log(const char* format) + { + std::lock_guard mtx(mx); +#ifndef MoeVoiceStudioCommandLineProg + _wfopen_s(&log_file, logpath.c_str(), L"a+"); + if (log_file) + { + fprintf_s(log_file, "%s\n", format); + fclose(log_file); + log_file = nullptr; + } +#else + fprintf_s(stdout, "%s\n", format); +#endif + } + + void Logger::error(const std::wstring& format) + { + std::lock_guard mtx(mx); +#ifndef MoeVoiceStudioCommandLineProg + _wfopen_s(&log_file, logpath.c_str(), L"a+"); + _wfopen_s(&error_file, errorpath.c_str(), L"a+"); + if (log_file) + { + fprintf(log_file, "[ERROR]%s\n", to_byte_string(format).c_str()); + fclose(log_file); + log_file = nullptr; + } + if (error_file) + { + fprintf(error_file, "[ERROR]%s\n", to_byte_string(format).c_str()); + fclose(error_file); + error_file = nullptr; + } +#else + fprintf(stdout, "[ERROR]%s\n", to_byte_string(format).c_str()); +#endif + } + + void Logger::error(const char* format) + { + std::lock_guard mtx(mx); +#ifndef MoeVoiceStudioCommandLineProg + _wfopen_s(&log_file, logpath.c_str(), L"a+"); + _wfopen_s(&error_file, errorpath.c_str(), L"a+"); + if (log_file) + { + fprintf(log_file, "[ERROR]%s\n", format); + fclose(log_file); + log_file = nullptr; + } + if (error_file) + { + fprintf(error_file, "[ERROR]%s\n", format); + fclose(error_file); + error_file = nullptr; + } +#else + fprintf(stdout, "[ERROR]%s\n", format); +#endif + } + + Logger& GetLogger() + { + return MoeVsLogger; + } +} diff --git a/libsvc/Modules/src/Models/DiffSvc.cpp b/libsvc/Modules/src/Models/DiffSvc.cpp new file mode 100644 index 0000000..a57ac02 --- /dev/null +++ b/libsvc/Modules/src/Models/DiffSvc.cpp @@ -0,0 +1,1050 @@ +#include "../../header/Models/DiffSvc.hpp" +#include +#include "../../header/InferTools/AvCodec/AvCodeResample.h" +#include +#include "../../header/InferTools/Sampler/MoeVSSamplerManager.hpp" +#include "../../header/InferTools/F0Extractor/F0ExtractorManager.hpp" +#include "../../header/Models/EnvManager.hpp" + +MoeVoiceStudioCoreHeader + +Ort::Session* Vocoder = nullptr; + +void LoadVocoderModel(const std::wstring& VocoderPath) +{ + Vocoder = new Ort::Session(*moevsenv::GetGlobalMoeVSEnv().GetEnv(), VocoderPath.c_str(), *moevsenv::GetGlobalMoeVSEnv().GetSessionOptions()); +} + +void UnLoadVocoderModel() +{ + delete Vocoder; + Vocoder = nullptr; +} + +bool VocoderEnabled() +{ + return Vocoder; +} + +void DiffusionSvc::Destory() +{ + //AudioEncoder + delete hubert; + hubert = nullptr; + + //DiffusionModel + delete encoder; //Encoder + encoder = nullptr; + delete denoise; //WaveNet + denoise = nullptr; + delete pred; //PndmNoisePredictor + pred = nullptr; + delete after; //AfterProcess + after = nullptr; + delete alpha; //AlphasCumpord + alpha = nullptr; + delete naive; //NaiveShallowDiffusion + naive = nullptr; + + //SingleDiffusionModel + delete diffSvc; + diffSvc = nullptr; +} + +DiffusionSvc::~DiffusionSvc() +{ + logger.log(L"[Info] unloading DiffSvc Models"); + Destory(); + logger.log(L"[Info] DiffSvc Models unloaded"); +} + +DiffusionSvc::DiffusionSvc(const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_): + SingingVoiceConversion(ExecutionProvider_, DeviceID_, ThreadCount_) +{ + MoeVSClassName(L"MoeVoiceStudioDiffSingingVoiceConversion"); + + //Check Folder + if (_Config["Folder"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"folder\" (Model Folder)") + if (!_Config["Folder"].IsString()) + LibDLVoiceCodecThrow("[Error] Field \"folder\" (Model Folder) Must Be String") + const auto _folder = to_wide_string(_Config["Folder"].GetString()); + if (_folder.empty()) + LibDLVoiceCodecThrow("[Error] Field \"folder\" (Model Folder) Can Not Be Empty") + const std::wstring _path = GetCurrentFolder() + L"/Models/" + _folder + L"/" + _folder; + const auto cluster_folder = GetCurrentFolder() + L"/Models/" + _folder; + if (_Config["Hubert"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"Hubert\" (Hubert Folder)") + if (!_Config["Hubert"].IsString()) + LibDLVoiceCodecThrow("[Error] Field \"Hubert\" (Hubert Folder) Must Be String") + const std::wstring HuPath = to_wide_string(_Config["Hubert"].GetString()); + if (HuPath.empty()) + LibDLVoiceCodecThrow("[Error] Field \"Hubert\" (Hubert Folder) Can Not Be Empty") + + if (_Config["Hifigan"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"Hifigan\" (Hifigan Folder)") + if (!_Config["Hifigan"].IsString()) + LibDLVoiceCodecThrow("[Error] Field \"Hifigan\" (Hifigan Folder) Must Be String") + const std::wstring HifiganPath = to_wide_string(_Config["Hifigan"].GetString()); + if (HifiganPath.empty()) + LibDLVoiceCodecThrow("[Error] Field \"Hifigan\" (Hifigan Folder) Can Not Be Empty") + + std::map _PathDict; + _PathDict["Cluster"] = cluster_folder; + _PathDict["DiffHubert"] = GetCurrentFolder() + L"/hubert/" + HuPath + L".onnx"; + _PathDict["DiffHifigan"] = GetCurrentFolder() + L"/hifigan/" + HifiganPath + L".onnx"; + if(_waccess((_path + L"_encoder.onnx").c_str(), 0) != -1) + { + _PathDict["Encoder"] = _path + L"_encoder.onnx"; + _PathDict["DenoiseFn"] = _path + L"_denoise.onnx"; + _PathDict["NoisePredictor"] = _path + L"_pred.onnx"; + _PathDict["AfterProcess"] = _path + L"_after.onnx"; + if (_waccess((_path + L"_alpha.onnx").c_str(), 0) != -1) + _PathDict["Alphas"] = _path + L"_alpha.onnx"; + } + else + _PathDict["DiffSvc"] = _path + L"_DiffSvc.onnx"; + + if (_waccess((_path + L"_naive.onnx").c_str(), 0) != -1) + _PathDict["Naive"] = _path + L"_naive.onnx"; + + load(_PathDict, _Config, _ProgressCallback); +} + +DiffusionSvc::DiffusionSvc(const std::map& _PathDict, + const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_) : + SingingVoiceConversion(ExecutionProvider_, DeviceID_, ThreadCount_) +{ + MoeVSClassName(L"MoeVoiceStudioDiffSingingVoiceConversion"); + + load(_PathDict, _Config, _ProgressCallback); +} + +void DiffusionSvc::load(const std::map& _PathDict, const MJson& _Config, const ProgressCallback& _ProgressCallback) +{ + //Check SamplingRate + if (_Config["Rate"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"Rate\" (SamplingRate)") + if (_Config["Rate"].IsInt() || _Config["Rate"].IsInt64()) + _samplingRate = _Config["Rate"].GetInt(); + else + LibDLVoiceCodecThrow("[Error] Field \"Rate\" (SamplingRate) Must Be Int/Int64") + + logger.log(L"[Info] Current Sampling Rate is" + std::to_wstring(_samplingRate)); + + if (_Config["MelBins"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"MelBins\" (MelBins)") + if (_Config["MelBins"].IsInt() || _Config["MelBins"].IsInt64()) + melBins = _Config["MelBins"].GetInt(); + else + LibDLVoiceCodecThrow("[Error] Field \"MelBins\" (MelBins) Must Be Int/Int64") + + if (!(_Config["Hop"].IsInt() || _Config["Hop"].IsInt64())) + LibDLVoiceCodecThrow("[Error] Hop Must Be Int") + HopSize = _Config["Hop"].GetInt(); + + if (HopSize < 1) + LibDLVoiceCodecThrow("[Error] Hop Must > 0") + + if (!(_Config["HiddenSize"].IsInt() || _Config["HiddenSize"].IsInt64())) + logger.log(L"[Warn] Missing Field \"HiddenSize\", Use Default Value (256)"); + else + HiddenUnitKDims = _Config["HiddenSize"].GetInt(); + + if (_Config["Characters"].IsArray()) + SpeakerCount = (int64_t)_Config["Characters"].Size(); + + if (_Config["Volume"].IsBool()) + EnableVolume = _Config["Volume"].GetBool(); + else + logger.log(L"[Warn] Missing Field \"Volume\", Use Default Value (False)"); + + if (!_Config["CharaMix"].IsBool()) + logger.log(L"[Warn] Missing Field \"CharaMix\", Use Default Value (False)"); + else + EnableCharaMix = _Config["CharaMix"].GetBool(); + + if (!_Config["Diffusion"].IsBool()) + logger.log(L"[Warn] Missing Field \"Diffusion\", Use Default Value (False)"); + else if (_Config["Diffusion"].GetBool()) + DiffSvcVersion = L"DiffusionSvc"; + + if (_Config["Pndm"].IsInt()) + Pndms = _Config["Pndm"].GetInt(); + + if (_Config.HasMember("SpecMax") && _Config["SpecMax"].IsDouble()) + SpecMax = _Config["SpecMax"].GetFloat(); + else + logger.log(L"[Warn] Missing Field \"SpecMax\", Use Default Value (2)"); + + if (_Config.HasMember("SpecMin") && _Config["SpecMin"].IsDouble()) + SpecMin = _Config["SpecMin"].GetFloat(); + else + logger.log(L"[Warn] Missing Field \"SpecMin\", Use Default Value (-12)"); + + _callback = _ProgressCallback; + + if (_Config["Cluster"].IsString()) + { + const auto clus = to_wide_string(_Config["Cluster"].GetString()); + if (!(_Config["KMeansLength"].IsInt() || _Config["KMeansLength"].IsInt64())) + logger.log(L"[Warn] Missing Field \"KMeansLength\", Use Default Value (10000)"); + else + ClusterCenterSize = _Config["KMeansLength"].GetInt(); + try + { + Cluster = MoeVoiceStudioCluster::GetMoeVSCluster(clus, _PathDict.at("Cluster"), HiddenUnitKDims, ClusterCenterSize); + EnableCluster = true; + } + catch (std::exception& e) + { + logger.error(e.what()); + EnableCluster = false; + } + } + + //LoadModels + try + { + logger.log(L"[Info] loading DiffSvc Models"); + hubert = new Ort::Session(*env, _PathDict.at("DiffHubert").c_str(), *session_options); + if (_PathDict.find("Encoder")!= _PathDict.end() && _waccess(_PathDict.at("Encoder").c_str(), 0) != -1) + { + encoder = new Ort::Session(*env, _PathDict.at("Encoder").c_str(), *session_options); + denoise = new Ort::Session(*env, _PathDict.at("DenoiseFn").c_str(), *session_options); + pred = new Ort::Session(*env, _PathDict.at("NoisePredictor").c_str(), *session_options); + after = new Ort::Session(*env, _PathDict.at("AfterProcess").c_str(), *session_options); + if (_PathDict.find("Alphas") != _PathDict.end() && _waccess(_PathDict.at("Alphas").c_str(), 0) != -1) + alpha = new Ort::Session(*env, _PathDict.at("Alphas").c_str(), *session_options); + } + else + diffSvc = new Ort::Session(*env, _PathDict.at("DiffSvc").c_str(), *session_options); + + if (_PathDict.find("Naive") != _PathDict.end() && _waccess(_PathDict.at("Naive").c_str(), 0) != -1) + naive = new Ort::Session(*env, _PathDict.at("Naive").c_str(), *session_options); + + logger.log(L"[Info] DiffSvc Models loaded"); + } + catch (Ort::Exception& _exception) + { + Destory(); + LibDLVoiceCodecThrow(_exception.what()) + } + + if (_Config["TensorExtractor"].IsString()) + DiffSvcVersion = to_wide_string(_Config["TensorExtractor"].GetString()); + + if (_Config["MaxStep"].IsInt()) + MaxStep = _Config["MaxStep"].GetInt(); + + MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::Others _others_param; + _others_param.Memory = *memory_info; + + try + { + _TensorExtractor = GetTensorExtractor(DiffSvcVersion, 48000, _samplingRate, HopSize, EnableCharaMix, EnableVolume, HiddenUnitKDims, SpeakerCount, _others_param); + } + catch (std::exception& e) + { + Destory(); + LibDLVoiceCodecThrow(e.what()) + } +} + +std::vector DiffusionSvc::SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const +{ + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Start!"); + std::vector _data; + size_t total_audio_size = 0; + for (const auto& data_size : _Slice.Slices) + total_audio_size += data_size.OrgLen; + _data.reserve(size_t(double(total_audio_size) * 1.5)); + + auto speedup = (int64_t)_InferParams.Pndm; + auto step = (int64_t)_InferParams.Step; + + if (step > MaxStep) step = MaxStep; + if (speedup >= step) speedup = step / 5; + if (speedup == 0) speedup = 1; + const auto RealDiffSteps = step % speedup ? step / speedup + 1 : step / speedup; + if (diffSvc) + _callback(0, _Slice.Slices.size()); + else + _callback(0, _Slice.Slices.size() * RealDiffSteps); + size_t process = 0; + for (const auto& CurSlice : _Slice.Slices) + { + const auto InferDurTime = clock(); + const auto CurRtn = SliceInference(CurSlice, _InferParams, process); + _data.insert(_data.end(), CurRtn.data(), CurRtn.data() + CurRtn.size()); + if (CurSlice.IsNotMute) + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Segment[" + std::to_wstring(process) + L"] Finished! Segment Use Time: " + std::to_wstring(clock() - InferDurTime) + L"ms, Segment Duration: " + std::to_wstring((size_t)CurSlice.OrgLen * 1000ull / 48000ull) + L"ms"); + else + { + if (!diffSvc) + { + process += RealDiffSteps; + _callback(process, _Slice.Slices.size() * RealDiffSteps); + } + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Jump Empty Segment[" + std::to_wstring(process) + L"]!"); + } + if (diffSvc) + _callback(++process, _Slice.Slices.size()); + } + + logger.log(L"[Inferring] \"" + _Slice.Path + L"\" Finished"); + return _data; +} + +std::vector DiffusionSvc::SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) const +{ + std::mt19937 gen(int(_InferParams.Seed)); + std::normal_distribution normal(0, 1); + auto speedup = (int64_t)_InferParams.Pndm; + auto step = (int64_t)_InferParams.Step; + if (step > MaxStep) step = MaxStep; + if (speedup >= step) speedup = step / 5; + if (speedup == 0) speedup = 1; + + if (_Slice.IsNotMute) + { + auto RawWav = InferTools::InterpResample(_Slice.Audio, 48000, 16000, 32768.0f); + const auto src_audio_length = RawWav.size(); + bool NeedPadding = false; + if (_cur_execution_provider == ExecutionProviders::CUDA && !diffSvc) + { + NeedPadding = RawWav.size() % 16000; + const size_t WavPaddedSize = RawWav.size() / 16000 + 1; + if (NeedPadding) + RawWav.resize(WavPaddedSize * 16000, 0.f); + } + + const int64_t HubertInputShape[3] = { 1i64,1i64,(int64_t)RawWav.size() }; + std::vector HubertInputTensors, HubertOutPuts; + HubertInputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, RawWav.data(), RawWav.size(), HubertInputShape, 3)); + try { + HubertOutPuts = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + HubertInputTensors.data(), + HubertInputTensors.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what())) + } + const auto HubertSize = HubertOutPuts[0].GetTensorTypeAndShapeInfo().GetElementCount(); + const auto HubertOutPutData = HubertOutPuts[0].GetTensorMutableData(); + auto HubertOutPutShape = HubertOutPuts[0].GetTensorTypeAndShapeInfo().GetShape(); + HubertInputTensors.clear(); + if (HubertOutPutShape[2] != HiddenUnitKDims) + LibDLVoiceCodecThrow("HiddenUnitKDims UnMatch") + + std::vector srcHiddenUnits(HubertOutPutData, HubertOutPutData + HubertSize); + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + + const auto max_cluster_size = int64_t((size_t)HubertOutPutShape[1] * src_audio_length / RawWav.size()); + if (EnableCluster && _InferParams.ClusterRate > 0.001f) + { + const auto pts = Cluster->find(srcHiddenUnits.data(), long(SpeakerIdx), max_cluster_size); + for (int64_t indexs = 0; indexs < max_cluster_size * HiddenUnitKDims; ++indexs) + srcHiddenUnits[indexs] = srcHiddenUnits[indexs] * (1.f - _InferParams.ClusterRate) + pts[indexs] * _InferParams.ClusterRate; + } + std::vector finaOut; + std::vector DiffOut; + if (diffSvc) + { + const auto HubertLen = int64_t(HubertSize) / HiddenUnitKDims; + const int64_t F0Shape[] = { 1, int64_t(_Slice.Audio.size() * _samplingRate / 48000 / HopSize) }; + const int64_t HiddenUnitShape[] = { 1, HubertLen, HiddenUnitKDims }; + constexpr int64_t CharaEmbShape[] = { 1 }; + int64_t speedData[] = { Pndms }; + auto srcF0Data = InferTools::InterpFunc(_Slice.F0, long(_Slice.F0.size()), long(F0Shape[1])); + for (auto& it : srcF0Data) + it *= (float)pow(2.0, static_cast(_InferParams.Keys) / 12.0); + auto InterpedF0 = MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::GetInterpedF0log(srcF0Data, true); + auto alignment = MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::GetAligments(F0Shape[1], HubertLen); + std::vector TensorsInp; + + int64_t Chara[] = { SpeakerIdx }; + + TensorsInp.emplace_back(Ort::Value::CreateTensor(*memory_info, srcHiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + TensorsInp.emplace_back(Ort::Value::CreateTensor(*memory_info, alignment.data(), F0Shape[1], F0Shape, 2)); + TensorsInp.emplace_back(Ort::Value::CreateTensor(*memory_info, Chara, 1, CharaEmbShape, 1)); + TensorsInp.emplace_back(Ort::Value::CreateTensor(*memory_info, InterpedF0.data(), F0Shape[1], F0Shape, 2)); + + std::vector initial_noise(melBins * F0Shape[1], 0.0); + long long noise_shape[4] = { 1,1,melBins,F0Shape[1] }; + if (!naive) + { + for (auto& it : initial_noise) + it = normal(gen) * _InferParams.NoiseScale; + TensorsInp.emplace_back(Ort::Value::CreateTensor(*memory_info, initial_noise.data(), initial_noise.size(), noise_shape, 4)); + } + else + MoeVSNotImplementedError + + TensorsInp.emplace_back(Ort::Value::CreateTensor(*memory_info, speedData, 1, CharaEmbShape, 1)); + try + { + DiffOut = diffSvc->Run(Ort::RunOptions{ nullptr }, + DiffInput.data(), + TensorsInp.data(), + TensorsInp.size(), + DiffOutput.data(), + DiffOutput.size()); + } + catch (Ort::Exception& e2) + { + LibDLVoiceCodecThrow((std::string("Locate: Diff\n") + e2.what())) + } + try + { + finaOut = Vocoder->Run(Ort::RunOptions{ nullptr }, + nsfInput.data(), + DiffOut.data(), + Vocoder->GetInputCount(), + nsfOutput.data(), + nsfOutput.size()); + } + catch (Ort::Exception& e3) + { + LibDLVoiceCodecThrow((std::string("Locate: Nsf\n") + e3.what())) + } + } + else + { + MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::InferParams _Inference_Params; + _Inference_Params.AudioSize = _Slice.Audio.size(); + _Inference_Params.Chara = SpeakerIdx; + _Inference_Params.NoiseScale = _InferParams.NoiseScale; + _Inference_Params.DDSPNoiseScale = _InferParams.DDSPNoiseScale; + _Inference_Params.Seed = int(_InferParams.Seed); + _Inference_Params.upKeys = _InferParams.Keys; + + MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::Inputs InputTensors; + + if (_cur_execution_provider == ExecutionProviders::CUDA && NeedPadding) + { + auto CUDAF0 = _Slice.F0; + auto CUDAVolume = _Slice.Volume; + auto CUDASpeaker = _Slice.Speaker; + const auto src_src_audio_length = _Slice.Audio.size(); + const size_t WavPaddedSize = ((src_src_audio_length / 48000) + 1) * 48000; + const size_t AudioPadSize = WavPaddedSize - src_src_audio_length; + const size_t PaddedF0Size = CUDAF0.size() + (CUDAF0.size() * AudioPadSize / src_src_audio_length); + + if (!CUDAF0.empty()) CUDAF0.resize(PaddedF0Size, 0.f); + if (!CUDAVolume.empty()) CUDAVolume.resize(PaddedF0Size, 0.f); + for (auto iSpeaker : CUDASpeaker) + { + if (!iSpeaker.empty()) + iSpeaker.resize(PaddedF0Size, 0.f); + } + _Inference_Params.AudioSize = WavPaddedSize; + InputTensors = _TensorExtractor->Extract(srcHiddenUnits, CUDAF0, CUDAVolume, CUDASpeaker, _Inference_Params); + } + else + InputTensors = _TensorExtractor->Extract(srcHiddenUnits, _Slice.F0, _Slice.Volume, _Slice.Speaker, _Inference_Params); + + std::vector EncoderOut; + try { + EncoderOut = encoder->Run(Ort::RunOptions{ nullptr }, + InputTensors.InputNames, + InputTensors.Tensor.data(), + min(InputTensors.Tensor.size(), encoder->GetInputCount()), + InputTensors.OutputNames, + encoder->GetOutputCount()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: encoder\n") + e1.what())) + } + if (EncoderOut.size() == 1) + EncoderOut.emplace_back(Ort::Value::CreateTensor(*memory_info, InputTensors.Data.F0.data(), InputTensors.Data.FrameShape[1], InputTensors.Data.FrameShape.data(), 2)); + + std::vector DenoiseInTensors; + DenoiseInTensors.emplace_back(std::move(EncoderOut[0])); + + std::vector initial_noise(melBins * InputTensors.Data.FrameShape[1], 0.0); + long long noise_shape[4] = { 1,1,melBins,InputTensors.Data.FrameShape[1] }; + if (EncoderOut.size() == 3) + DenoiseInTensors.emplace_back(std::move(EncoderOut[2])); + else if (!naive) + { + for (auto& it : initial_noise) + it = normal(gen) * _InferParams.NoiseScale; + DenoiseInTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, initial_noise.data(), initial_noise.size(), noise_shape, 4)); + } + else + { + std::vector NaiveOut; + try { + NaiveOut = naive->Run(Ort::RunOptions{ nullptr }, + InputTensors.InputNames, + InputTensors.Tensor.data(), + min(InputTensors.Tensor.size(), naive->GetInputCount()), + naiveOutput.data(), + 1); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: naive\n") + e1.what())) + } + DenoiseInTensors.emplace_back(std::move(NaiveOut[0])); + } + + auto PredOut = MoeVSSampler::GetMoeVSSampler((!alpha ? L"Pndm" : _InferParams.Sampler), alpha, denoise, pred, melBins, _callback, memory_info)->Sample(DenoiseInTensors, step, speedup, _InferParams.NoiseScale, _InferParams.Seed, _Process); + + try + { + DiffOut = after->Run(Ort::RunOptions{ nullptr }, + afterInput.data(), + PredOut.data(), + PredOut.size(), + afterOutput.data(), + afterOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: pred\n") + e1.what())) + } + + DiffOut.emplace_back(std::move(EncoderOut[1])); + try + { + finaOut = Vocoder->Run(Ort::RunOptions{ nullptr }, + nsfInput.data(), + DiffOut.data(), + Vocoder->GetInputCount(), + nsfOutput.data(), + nsfOutput.size()); + } + catch (Ort::Exception& e3) + { + LibDLVoiceCodecThrow((std::string("Locate: Nsf\n") + e3.what())) + } + } + const auto shapeOut = finaOut[0].GetTensorTypeAndShapeInfo().GetShape(); + const auto dstWavLen = (_Slice.OrgLen * int64_t(_samplingRate)) / 48000; + std::vector TempVecWav(dstWavLen, 0); + if (shapeOut[2] < dstWavLen) + for (int64_t bbb = 0; bbb < shapeOut[2]; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.f); + else + for (int64_t bbb = 0; bbb < dstWavLen; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.f); + return TempVecWav; + } + const auto len = size_t(_Slice.OrgLen * int64_t(_samplingRate) / 48000); + return { len, 0i16, std::allocator() }; +} + +std::vector DiffusionSvc::Inference(std::wstring& _Paths, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const +{ + std::vector _Lens = GetOpenFileNameMoeVS(); + std::vector AudioFolders; + for (auto& path : _Lens) + { + path = std::regex_replace(path, std::wregex(L"\\\\"), L"/"); + auto PCMData = AudioPreprocess().codec(path, 48000); + auto SlicePos = SliceAudio(PCMData, _SlicerSettings); + auto Audio = GetAudioSlice(PCMData, SlicePos, _SlicerSettings); + Audio.Path = path; + PreProcessAudio(Audio); + std::vector _data = SliceInference(Audio, _InferParams); + + std::wstring OutFolder = GetCurrentFolder() + L"/Outputs/" + path.substr(path.rfind(L'/') + 1, path.rfind(L'.') - path.rfind(L'/') - 1); + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + OutFolder += L"-Params-(-NoiseScale=" + + std::to_wstring(_InferParams.NoiseScale) + + L"-Speaker=" + + (EnableCharaMix ? std::wstring(L"SpeakerMix") : std::to_wstring(SpeakerIdx)) + + L"-Seed=" + + std::to_wstring(_InferParams.Seed) + + L"-Sampler=" + + _InferParams.Sampler + + L"-F0Method=" + + _InferParams.F0Method + L")"; + if (_waccess((OutFolder + L".wav").c_str(), 0) != -1) + { + for (size_t idx = 0; idx < 99999999; ++idx) + if (_waccess((OutFolder + L" (" + std::to_wstring(idx) + L").wav").c_str(), 0) == -1) + { + OutFolder += L" (" + std::to_wstring(idx) + L").wav"; + break; + } + } + else + OutFolder += L".wav"; + AudioFolders.emplace_back(OutFolder); + InferTools::Wav::WritePCMData(_samplingRate, 1, _data, OutFolder); + } + return AudioFolders; +} + +std::vector DiffusionSvc::InferPCMData(const std::vector& PCMData, long srcSr, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const +{ + if (diffSvc || DiffSvcVersion != L"DiffusionSvc") + return PCMData; + + auto hubertin = InferTools::InterpResample(PCMData, srcSr, 16000); + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + std::mt19937 gen(int(_InferParams.Seed)); + std::normal_distribution normal(0, 1); + + const int64_t inputShape[3] = { 1i64,1i64,(int64_t)hubertin.size() }; + std::vector inputTensorshu; + inputTensorshu.emplace_back(Ort::Value::CreateTensor(*memory_info, hubertin.data(), hubertin.size(), inputShape, 3)); + std::vector hubertOut; + + const auto RealDiffSteps = _InferParams.Step % _InferParams.Pndm ? _InferParams.Step / _InferParams.Pndm + 1 : _InferParams.Step / _InferParams.Pndm; + _callback(0, RealDiffSteps); + + try { + hubertOut = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + inputTensorshu.data(), + inputTensorshu.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what())) + } + const auto HubertSize = hubertOut[0].GetTensorTypeAndShapeInfo().GetElementCount(); + const auto HubertOutPutData = hubertOut[0].GetTensorMutableData(); + const auto HubertOutPutShape = hubertOut[0].GetTensorTypeAndShapeInfo().GetShape(); + inputTensorshu.clear(); + if (HubertOutPutShape[2] != HiddenUnitKDims) + LibDLVoiceCodecThrow("HiddenUnitKDims UnMatch") + + std::vector HiddenUnits(HubertOutPutData, HubertOutPutData + HubertSize); + + if (EnableCluster && _InferParams.ClusterRate > 0.001f) + { + const auto clus_size = HubertOutPutShape[1]; + const auto pts = Cluster->find(HiddenUnits.data(), long(SpeakerIdx), clus_size); + for (size_t indexs = 0; indexs < HiddenUnits.size(); ++indexs) + HiddenUnits[indexs] = HiddenUnits[indexs] * (1.f - _InferParams.ClusterRate) + pts[indexs] * _InferParams.ClusterRate; + } + + const auto HubertLen = int64_t(HubertSize) / HiddenUnitKDims; + const int64_t F0Shape[] = { 1, int64_t(PCMData.size() / HopSize) }; + const int64_t HiddenUnitShape[] = { 1, HubertLen, HiddenUnitKDims }; + constexpr int64_t CharaEmbShape[] = { 1 }; + const int64_t CharaMixShape[] = { F0Shape[1], SpeakerCount }; + + const auto F0Extractor = MoeVSF0Extractor::GetF0Extractor(_InferParams.F0Method, _samplingRate, HopSize); + auto F0Data = F0Extractor->ExtractF0(PCMData, PCMData.size() / HopSize); + for (auto& ifo : F0Data) + ifo *= (float)pow(2.0, static_cast(_InferParams.Keys) / 12.0); + F0Data = _TensorExtractor->GetInterpedF0(InferTools::InterpFunc(F0Data, long(F0Data.size()), long(F0Shape[1]))); + std::vector Alignment = _TensorExtractor->GetAligments(F0Shape[1], HubertLen); + int64_t CharaEmb[] = { SpeakerIdx }; + + std::vector EncoderTensors; + + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + HiddenUnits.data(), + HubertSize, + HiddenUnitShape, + 3 + )); + + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + Alignment.data(), + F0Shape[1], + F0Shape, + 2 + )); + + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + F0Data.data(), + F0Shape[1], + F0Shape, + 2 + )); + + std::vector InputNamesEncoder; + std::vector Volume, SpkMap; + + if (EnableVolume) + { + InputNamesEncoder = { "hubert", "mel2ph", "f0", "volume", "spk_mix" }; + Volume = ExtractVolume(PCMData, HopSize); + if (abs(int64_t(Volume.size()) - int64_t(F0Data.size())) > 3) + Volume = InferTools::InterpFunc(ExtractVolume(PCMData, HopSize), long(Volume.size()), long(F0Shape[1])); + else + Volume.resize(F0Data.size(), 0.f); + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + Volume.data(), + F0Shape[1], + F0Shape, + 2 + )); + } + else + InputNamesEncoder = { "hubert", "mel2ph", "f0", "spk_mix" }; + + if (EnableCharaMix) + { + SpkMap = _TensorExtractor->GetCurrectSpkMixData(std::vector>(), F0Shape[1], SpeakerIdx); + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + SpkMap.data(), + SpkMap.size(), + CharaMixShape, + 2 + )); + } + else + { + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + CharaEmb, + 1, + CharaEmbShape, + 1 + )); + } + + const std::vector OutputNamesEncoder = { "mel_pred", "f0_pred", "init_noise" }; + + std::vector EncoderOut; + try { + EncoderOut = encoder->Run(Ort::RunOptions{ nullptr }, + InputNamesEncoder.data(), + EncoderTensors.data(), + min(EncoderTensors.size(), encoder->GetInputCount()), + OutputNamesEncoder.data(), + encoder->GetOutputCount()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: encoder\n") + e1.what())) + } + if (EncoderOut.size() == 1) + EncoderOut.emplace_back(Ort::Value::CreateTensor(*memory_info, F0Data.data(), F0Shape[1], F0Shape, 2)); + + std::vector DenoiseInTensors; + DenoiseInTensors.emplace_back(std::move(EncoderOut[0])); + + std::vector initial_noise(melBins * F0Shape[1], 0.0); + long long noise_shape[4] = { 1,1,melBins,F0Shape[1] }; + if (EncoderOut.size() == 3) + DenoiseInTensors.emplace_back(std::move(EncoderOut[2])); + else if (!naive) + { + for (auto& it : initial_noise) + it = normal(gen) * _InferParams.NoiseScale; + DenoiseInTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, initial_noise.data(), initial_noise.size(), noise_shape, 4)); + } + else + { + std::vector NaiveOut; + try { + NaiveOut = naive->Run(Ort::RunOptions{ nullptr }, + InputNamesEncoder.data(), + EncoderTensors.data(), + min(EncoderTensors.size(), naive->GetInputCount()), + naiveOutput.data(), + 1); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: naive\n") + e1.what())) + } + DenoiseInTensors.emplace_back(std::move(NaiveOut[0])); + } + + size_t process = 0; + + auto PredOut = MoeVSSampler::GetMoeVSSampler((!alpha ? L"Pndm" : _InferParams.Sampler), alpha, denoise, pred, melBins, _callback, memory_info)->Sample(DenoiseInTensors, (int64_t)_InferParams.Step, (int64_t)_InferParams.Pndm, _InferParams.NoiseScale, _InferParams.Seed, process); + + std::vector DiffOut, finaOut; + + try + { + DiffOut = after->Run(Ort::RunOptions{ nullptr }, + afterInput.data(), + PredOut.data(), + PredOut.size(), + afterOutput.data(), + afterOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: pred\n") + e1.what())) + } + + DiffOut.emplace_back(std::move(EncoderOut[1])); + try + { + finaOut = Vocoder->Run(Ort::RunOptions{ nullptr }, + nsfInput.data(), + DiffOut.data(), + Vocoder->GetInputCount(), + nsfOutput.data(), + nsfOutput.size()); + } + catch (Ort::Exception& e3) + { + LibDLVoiceCodecThrow((std::string("Locate: Nsf\n") + e3.what())) + } + + const auto dstWavLen = finaOut[0].GetTensorTypeAndShapeInfo().GetShape()[2]; + std::vector TempVecWav(dstWavLen, 0); + for (int64_t bbb = 0; bbb < dstWavLen; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.0f); + return TempVecWav; +} + +void DiffusionSvc::NormMel(std::vector& MelSpec) const +{ + for (auto& it : MelSpec) + it = (it - SpecMin) / (SpecMax - SpecMin) * 2 - 1; +} + +std::vector DiffusionSvc::ShallowDiffusionInference( + std::vector& _16KAudioHubert, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + std::pair, int64_t>& _Mel, + const std::vector& _SrcF0, + const std::vector& _SrcVolume, + const std::vector>& _SrcSpeakerMap, + size_t& Process, + int64_t SrcSize +) const +{ + if (diffSvc || DiffSvcVersion != L"DiffusionSvc") + LibDLVoiceCodecThrow("ShallowDiffusion Only Support DiffusionSvc Model") + std::vector InputNamesEncoder; + const auto _Mel_Size = _Mel.second; + + std::vector HubertInputTensors, HubertOutputTensors; + const int64_t HubertInputShape[3] = { 1i64,1i64,(int64_t)_16KAudioHubert.size() }; + HubertInputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, _16KAudioHubert.data(), _16KAudioHubert.size(), HubertInputShape, 3)); + try { + HubertOutputTensors = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + HubertInputTensors.data(), + HubertInputTensors.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what())) + } + + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + + const auto HubertLength = HubertOutputTensors[0].GetTensorTypeAndShapeInfo().GetShape()[1]; + const int64_t FrameShape[] = { 1, _Mel_Size }; + const int64_t CharaMixShape[] = { _Mel_Size, SpeakerCount }; + constexpr int64_t OneShape[] = { 1 }; + int64_t CharaEmb[] = { SpeakerIdx }; + + auto Alignment = _TensorExtractor->GetAligments(_Mel_Size, HubertLength); + Alignment.resize(FrameShape[1]); + auto F0Data = InferTools::InterpFunc(_SrcF0, long(_SrcF0.size()), long(FrameShape[1])); + std::vector Volume, SpkMap; + + std::vector EncoderTensors; + EncoderTensors.emplace_back(std::move(HubertOutputTensors[0])); + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + Alignment.data(), + FrameShape[1], + FrameShape, + 2 + )); + + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + F0Data.data(), + FrameShape[1], + FrameShape, + 2 + )); + + if (EnableVolume) + { + InputNamesEncoder = { "hubert", "mel2ph", "f0", "volume", "spk_mix" }; + Volume = InferTools::InterpFunc(_SrcVolume, long(_SrcVolume.size()), long(FrameShape[1])); + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + Volume.data(), + FrameShape[1], + FrameShape, + 2 + )); + } + else + InputNamesEncoder = { "hubert", "mel2ph", "f0", "spk_mix" }; + + if (EnableCharaMix) + { + SpkMap = _TensorExtractor->GetCurrectSpkMixData(_SrcSpeakerMap, FrameShape[1], CharaEmb[0]); + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + SpkMap.data(), + SpkMap.size(), + CharaMixShape, + 2 + )); + } + else + { + EncoderTensors.emplace_back(Ort::Value::CreateTensor( + *memory_info, + CharaEmb, + 1, + OneShape, + 1 + )); + } + + const std::vector OutputNamesEncoder = { "mel_pred" }; + + std::vector EncoderOut; + try { + EncoderOut = encoder->Run(Ort::RunOptions{ nullptr }, + InputNamesEncoder.data(), + EncoderTensors.data(), + min(EncoderTensors.size(), encoder->GetInputCount()), + OutputNamesEncoder.data(), + 1); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: encoder\n") + e1.what())) + } + + std::vector DenoiseInTensors; + DenoiseInTensors.emplace_back(std::move(EncoderOut[0])); + + long long noise_shape[4] = { 1,1,melBins,_Mel_Size }; + + NormMel(_Mel.first); + + /*std::mt19937 gen(int(_InferParams.Seed)); + std::normal_distribution normal(0, 1); + std::vector initial_noise(melBins * _Mel_Size, 0.0); + for (auto& it : initial_noise) + it = normal(gen) * _InferParams.NoiseScale; + DenoiseInTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, initial_noise.data(), initial_noise.size(), noise_shape, 4));*/ + + DenoiseInTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, _Mel.first.data(), _Mel.first.size(), noise_shape, 4)); + + auto PredOut = MoeVSSampler::GetMoeVSSampler((!alpha ? L"Pndm" : _InferParams.Sampler), alpha, denoise, pred, melBins, [](size_t, size_t) {}, memory_info)->Sample(DenoiseInTensors, (int64_t)_InferParams.Step, (int64_t)_InferParams.Pndm, _InferParams.NoiseScale, _InferParams.Seed, Process); + + std::vector DiffOut, finaOut; + try + { + DiffOut = after->Run(Ort::RunOptions{ nullptr }, + afterInput.data(), + PredOut.data(), + PredOut.size(), + afterOutput.data(), + afterOutput.size()); + } + catch (Ort::Exception& e1) + { + LibDLVoiceCodecThrow((std::string("Locate: pred\n") + e1.what())) + } + + DiffOut.emplace_back(std::move(EncoderTensors[2])); + try + { + finaOut = Vocoder->Run(Ort::RunOptions{ nullptr }, + nsfInput.data(), + DiffOut.data(), + Vocoder->GetInputCount(), + nsfOutput.data(), + nsfOutput.size()); + } + catch (Ort::Exception& e3) + { + LibDLVoiceCodecThrow((std::string("Locate: Nsf\n") + e3.what())) + } + + const auto shapeOut = finaOut[0].GetTensorTypeAndShapeInfo().GetShape(); + std::vector TempVecWav(SrcSize, 0); + if (shapeOut[2] < SrcSize) + for (int64_t bbb = 0; bbb < shapeOut[2]; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.f); + else + for (int64_t bbb = 0; bbb < SrcSize; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.f); + return TempVecWav; +} + +void StaticNormMel(std::vector& MelSpec, float SpecMin = -12, float SpecMax = 2) +{ + for (auto& it : MelSpec) + it = (it - SpecMin) / (SpecMax - SpecMin) * 2 - 1; +} + +std::vector VocoderInfer(std::vector& Mel, std::vector& F0, int64_t MelBins, int64_t MelSize, const Ort::MemoryInfo* Mem) +{ + const int64_t MelShape[] = { 1i64,MelBins,MelSize }; + const int64_t FrameShape[] = { 1,MelSize }; + std::vector Tensors; + Tensors.emplace_back(Ort::Value::CreateTensor( + *Mem, + Mel.data(), + Mel.size(), + MelShape, + 3) + ); + Tensors.emplace_back(Ort::Value::CreateTensor( + *Mem, + F0.data(), + FrameShape[1], + FrameShape, + 2) + ); + const std::vector nsfInput = { "c", "f0" }; + const std::vector nsfOutput = { "audio" }; + Tensors = Vocoder->Run(Ort::RunOptions{ nullptr }, + nsfInput.data(), + Tensors.data(), + Vocoder->GetInputCount(), + nsfOutput.data(), + nsfOutput.size()); + const auto AudioSize = Tensors[0].GetTensorTypeAndShapeInfo().GetShape()[2]; + std::vector Audio(AudioSize, 0); + for (int64_t it = 0; it < AudioSize; it++) + Audio[it] = static_cast(DiffusionSvc::Clamp(Tensors[0].GetTensorData()[it]) * 32766.0f); + return Audio; +} + +MoeVoiceStudioCoreEnd diff --git a/libsvc/Modules/src/Models/EnvManager.cpp b/libsvc/Modules/src/Models/EnvManager.cpp new file mode 100644 index 0000000..6ce8acc --- /dev/null +++ b/libsvc/Modules/src/Models/EnvManager.cpp @@ -0,0 +1,190 @@ +#include "../../header/Models/EnvManager.hpp" +#ifdef _WIN32 +#ifdef MOEVSDMLPROVIDER +#include +#endif +#endif +#include +#include "../../header/InferTools/F0Extractor/NetF0Predictors.hpp" +#include "../../header/Modules.hpp" +MoeVoiceStudioCoreEnvManagerHeader + +const char* logger_id = "MoeVoiceStudioCore"; + +void MoeVSOrtLoggingFn(void* param, OrtLoggingLevel severity, const char* category, const char* logid, const char* code_location, + const char* message) +{ + std::string ort_message = "["; + ort_message += category; + ort_message += "; In "; + ort_message += code_location; + ort_message += "; By"; + ort_message += logger_id; + ort_message += "] "; + ort_message += message; + logger.log(ort_message.c_str()); +} + +void MoeVoiceStudioEnv::Destory() +{ + MoeVSF0Extractor::EmptyCache(); + logger.log(L"[Info] Removing Env & Release Memory"); + delete GlobalOrtSessionOptions; + delete GlobalOrtEnv; + delete GlobalOrtMemoryInfo; + GlobalOrtSessionOptions = nullptr; + GlobalOrtEnv = nullptr; + GlobalOrtMemoryInfo = nullptr; + + if (cuda_option_v2) + Ort::GetApi().ReleaseCUDAProviderOptions(cuda_option_v2); + cuda_option_v2 = nullptr; + logger.log(L"[Info] Complete!"); +} + +void MoeVoiceStudioEnv::Load(unsigned ThreadCount, unsigned DeviceID, unsigned Provider) +{ + if (((Provider != CurProvider) || + (Provider == 0 && ThreadCount != CurThreadCount) || + ((Provider == 1 || Provider == 2) && DeviceID != CurDeviceID)) && + (MoeVSModuleManager::GetDiffusionSvcModel() || MoeVSModuleManager::GetVitsSvcModel() || MoeVSModuleManager::VocoderEnabled())) + LibDLVoiceCodecThrow("A Model Has Been Loaded, You Cannot Change Env When A Model Has Been Loaded"); + try + { + if (Provider != CurProvider) + Create(ThreadCount, DeviceID, Provider); + if (Provider == 0 && ThreadCount != CurThreadCount) + Create(ThreadCount, DeviceID, Provider); + if ((Provider == 1 || Provider == 2) && DeviceID != CurDeviceID) + Create(ThreadCount, DeviceID, Provider); + CurProvider = Provider; + } + catch(std::exception& e) + { + Destory(); + CurThreadCount = unsigned(-1); + CurDeviceID = unsigned(-1); + CurProvider = unsigned(-1); + logger.log(to_wide_string(e.what())); + LibDLVoiceCodecThrow(e.what()); + } +} + +void MoeVoiceStudioEnv::Create(unsigned ThreadCount_, unsigned DeviceID_, unsigned ExecutionProvider_) +{ + Destory(); + logger.log(L"[Info] Creating Env"); + + switch (ExecutionProvider_) + { + case 1: + { + const auto AvailableProviders = Ort::GetAvailableProviders(); + bool ret = true; + for (const auto& it : AvailableProviders) + if (it.find("CUDA") != std::string::npos) + ret = false; + if (ret) + LibDLVoiceCodecThrow("CUDA Provider Not Found"); + GlobalOrtSessionOptions = new Ort::SessionOptions; + +#ifdef MoeVSCUDAProviderV1 + OrtCUDAProviderOptions cuda_option; + cuda_option.device_id = int(DeviceID_); + cuda_option.do_copy_in_default_stream = false; + GlobalOrtSessionOptions->AppendExecutionProvider_CUDA(cuda_option); +#else + const OrtApi& ortApi = Ort::GetApi(); + if (cuda_option_v2) + ortApi.ReleaseCUDAProviderOptions(cuda_option_v2); + ortApi.CreateCUDAProviderOptions(&cuda_option_v2); + const std::vector keys{ + "device_id", + "gpu_mem_limit", + "arena_extend_strategy", + "cudnn_conv_algo_search", + "do_copy_in_default_stream", + "cudnn_conv_use_max_workspace", + "cudnn_conv1d_pad_to_nc1d", + "enable_cuda_graph", + "enable_skip_layer_norm_strict_mode" + }; + const std::vector values{ + std::to_string(DeviceID_).c_str(), + "2147483648", + "kNextPowerOfTwo", + "EXHAUSTIVE", + "1", + "1", + "1", + "0", + "0" + }; + ortApi.UpdateCUDAProviderOptions(cuda_option_v2, keys.data(), values.data(), keys.size()); + GlobalOrtSessionOptions->AppendExecutionProvider_CUDA_V2(*cuda_option_v2); + //ortApi.ReleaseCUDAProviderOptions(cuda_option_v2); +#endif + GlobalOrtEnv = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, logger_id, MoeVSOrtLoggingFn, nullptr); + GlobalOrtSessionOptions->SetGraphOptimizationLevel(ORT_ENABLE_ALL); + GlobalOrtSessionOptions->SetIntraOpNumThreads((int)std::thread::hardware_concurrency()); + GlobalOrtMemoryInfo = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)); + CurDeviceID = DeviceID_; + break; + } +#ifdef MOEVSDMLPROVIDER + case 2: + { + const auto AvailableProviders = Ort::GetAvailableProviders(); + std::string ret; + for (const auto& it : AvailableProviders) + if (it.find("Dml") != std::string::npos) + ret = it; + if (ret.empty()) + LibDLVoiceCodecThrow("DML Provider Not Found"); + const OrtApi& ortApi = Ort::GetApi(); + const OrtDmlApi* ortDmlApi = nullptr; + ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); + Ort::ThreadingOptions threading_options; + threading_options.SetGlobalInterOpNumThreads((int)std::thread::hardware_concurrency()); + GlobalOrtEnv = new Ort::Env(threading_options, MoeVSOrtLoggingFn, nullptr, ORT_LOGGING_LEVEL_WARNING, logger_id); + GlobalOrtEnv->DisableTelemetryEvents(); + GlobalOrtSessionOptions = new Ort::SessionOptions; + ortDmlApi->SessionOptionsAppendExecutionProvider_DML(*GlobalOrtSessionOptions, int(DeviceID_)); + GlobalOrtSessionOptions->SetGraphOptimizationLevel(ORT_ENABLE_ALL); + GlobalOrtSessionOptions->DisablePerSessionThreads(); + GlobalOrtSessionOptions->SetExecutionMode(ORT_SEQUENTIAL); + GlobalOrtSessionOptions->DisableMemPattern(); + GlobalOrtMemoryInfo = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemType::OrtMemTypeCPU)); + CurDeviceID = DeviceID_; + break; + } +#endif + default: + { + if (ThreadCount_ == 0) + ThreadCount_ = std::thread::hardware_concurrency(); + GlobalOrtSessionOptions = new Ort::SessionOptions; + GlobalOrtEnv = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, logger_id, MoeVSOrtLoggingFn, nullptr); + GlobalOrtSessionOptions->SetIntraOpNumThreads(static_cast(ThreadCount_)); + GlobalOrtSessionOptions->SetGraphOptimizationLevel(ORT_ENABLE_ALL); + GlobalOrtMemoryInfo = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)); + CurThreadCount = ThreadCount_; + break; + } + } + logger.log(L"[Info] Env Created"); +} + +bool MoeVoiceStudioEnv::IsEnabled() const +{ + return GlobalOrtEnv && GlobalOrtMemoryInfo && GlobalOrtSessionOptions; +} + +MoeVoiceStudioEnv GlobalMoeVSEnv; + +MoeVoiceStudioEnv& GetGlobalMoeVSEnv() +{ + return GlobalMoeVSEnv; +} + +MoeVoiceStudioCoreEnvManagerEnd \ No newline at end of file diff --git a/libsvc/Modules/src/Models/ModelBase.cpp b/libsvc/Modules/src/Models/ModelBase.cpp new file mode 100644 index 0000000..f63d04a --- /dev/null +++ b/libsvc/Modules/src/Models/ModelBase.cpp @@ -0,0 +1,213 @@ +#include "../../header/Models/ModelBase.hpp" +#include +#include "../../header/Models/EnvManager.hpp" +MoeVoiceStudioCoreHeader + +MoeVoiceStudioModule::MoeVoiceStudioModule(const ExecutionProviders& ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_) +{ + moevsenv::GetGlobalMoeVSEnv().Load(ThreadCount_, DeviceID_, (unsigned)ExecutionProvider_); + _cur_execution_provider = ExecutionProvider_; + env = moevsenv::GetGlobalMoeVSEnv().GetEnv(); + memory_info = moevsenv::GetGlobalMoeVSEnv().GetMemoryInfo(); + session_options = moevsenv::GetGlobalMoeVSEnv().GetSessionOptions(); +} + +MoeVoiceStudioModule::~MoeVoiceStudioModule() +{ + env = nullptr; + memory_info = nullptr; + session_options = nullptr; +} + +std::vector MoeVoiceStudioModule::CutLens(const std::wstring& input) +{ + std::vector _Lens; + std::wstring _tmpLen; + for (const auto& chari : input) + { + if ((chari == L'\n') || (chari == L'\r')) { + if (!_tmpLen.empty()) + { + _Lens.push_back(_tmpLen); + _tmpLen = L""; + } + } + else { + _tmpLen += chari; + } + } + return _Lens; +} + +std::vector MoeVoiceStudioModule::GetOpenFileNameMoeVS() +{ + constexpr long MaxPath = 8000; + std::vector OFNLIST; +#ifdef WIN32 + std::vector szFileName(MaxPath); + std::vector szTitleName(MaxPath); + OPENFILENAME ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lpstrFile = szFileName.data(); + ofn.nMaxFile = MaxPath; + ofn.lpstrFileTitle = szTitleName.data(); + ofn.nMaxFileTitle = MaxPath; + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = nullptr; + constexpr TCHAR szFilter[] = TEXT("Audio (*.wav;*.mp3;*.ogg;*.flac;*.aac)\0*.wav;*.mp3;*.ogg;*.flac;*.aac\0"); + ofn.lpstrFilter = szFilter; + ofn.lpstrTitle = nullptr; + ofn.lpstrDefExt = TEXT("wav"); + ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; + if (GetOpenFileName(&ofn)) + { + auto filePtr = szFileName.data(); + std::wstring preFix = filePtr; + filePtr += preFix.length() + 1; + if (!*filePtr) + OFNLIST.emplace_back(preFix); + else + { + preFix += L'\\'; + while (*filePtr != 0) + { + std::wstring thisPath(filePtr); + OFNLIST.emplace_back(preFix + thisPath); + filePtr += thisPath.length() + 1; + } + } + } + if(OFNLIST.empty()) + LibDLVoiceCodecThrow("Please Select Files"); + return OFNLIST; +#else +#endif +} + +std::vector MoeVoiceStudioModule::Inference(std::wstring& _Datas, + const MoeVSProjectSpace::MoeVSParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const +{ + MoeVSNotImplementedError; +} + +/* +int OnnxModule::InsertMessageToEmptyEditBox(std::wstring& _inputLens) +{ +#ifdef WIN32 + std::vector szFileName(MaxPath); + std::vector szTitleName(MaxPath); + OPENFILENAME ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lpstrFile = szFileName.data(); + ofn.nMaxFile = MaxPath; + ofn.lpstrFileTitle = szTitleName.data(); + ofn.nMaxFileTitle = MaxPath; + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = nullptr; + + constexpr TCHAR szFilter[] = TEXT("Audio (*.wav;*.mp3;*.ogg;*.flac;*.aac)\0*.wav;*.mp3;*.ogg;*.flac;*.aac\0"); + ofn.lpstrFilter = szFilter; + ofn.lpstrTitle = nullptr; + ofn.lpstrDefExt = TEXT("wav"); + ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; + if (GetOpenFileName(&ofn)) + { + auto filePtr = szFileName.data(); + std::wstring preFix = filePtr; + filePtr += preFix.length() + 1; + if (!*filePtr) + _inputLens = preFix; + else + { + preFix += L'\\'; + while (*filePtr != 0) + { + std::wstring thisPath(filePtr); + _inputLens += preFix + thisPath + L'\n'; + filePtr += thisPath.length() + 1; + } + } + } + else + return -2; + return 0; +#else +#endif +} + */ + +/* +void OnnxModule::ChangeDevice(Device _dev) +{ + if (_dev == device_) + return; + device_ = _dev; + delete session_options; + delete env; + delete memory_info; + env = nullptr; + session_options = nullptr; + memory_info = nullptr; + switch (_dev) + { + case Device::CUDA: + { + const auto AvailableProviders = Ort::GetAvailableProviders(); + bool ret = true; + for (const auto& it : AvailableProviders) + if (it.find("CUDA") != std::string::npos) + ret = false; + if (ret) + LibDLVoiceCodecThrow("CUDA Provider Not Found"); + OrtCUDAProviderOptions cuda_option; + cuda_option.device_id = int(__MoeVSGPUID); + session_options = new Ort::SessionOptions; + env = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, "OnnxModel"); + session_options->AppendExecutionProvider_CUDA(cuda_option); + session_options->SetGraphOptimizationLevel(ORT_ENABLE_BASIC); + session_options->SetIntraOpNumThreads(1); + memory_info = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)); + break; + } +#ifdef MOEVSDMLPROVIDER + case Device::DML: + { + const auto AvailableProviders = Ort::GetAvailableProviders(); + std::string ret; + for (const auto& it : AvailableProviders) + if (it.find("Dml") != std::string::npos) + ret = it; + if (ret.empty()) + LibDLVoiceCodecThrow("DML Provider Not Found"); + const OrtApi& ortApi = Ort::GetApi(); + const OrtDmlApi* ortDmlApi = nullptr; + ortApi.GetExecutionProviderApi("DML", ORT_API_VERSION, reinterpret_cast(&ortDmlApi)); + + const Ort::ThreadingOptions threadingOptions; + env = new Ort::Env(threadingOptions, ORT_LOGGING_LEVEL_VERBOSE, ""); + env->DisableTelemetryEvents(); + session_options = new Ort::SessionOptions; + ortDmlApi->SessionOptionsAppendExecutionProvider_DML(*session_options, int(__MoeVSGPUID)); + session_options->SetGraphOptimizationLevel(ORT_ENABLE_BASIC); + session_options->DisablePerSessionThreads(); + session_options->SetExecutionMode(ORT_SEQUENTIAL); + session_options->DisableMemPattern(); + memory_info = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemType::OrtMemTypeCPU)); + break; + } +#endif + default: + { + session_options = new Ort::SessionOptions; + env = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, "OnnxModel"); + session_options->SetIntraOpNumThreads(static_cast(__MoeVSNumThreads)); + session_options->SetGraphOptimizationLevel(ORT_ENABLE_ALL); + memory_info = new Ort::MemoryInfo(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)); + break; + } + } +} + */ + +MoeVoiceStudioCoreEnd \ No newline at end of file diff --git a/libsvc/Modules/src/Models/MoeVSProject.cpp b/libsvc/Modules/src/Models/MoeVSProject.cpp new file mode 100644 index 0000000..f894781 --- /dev/null +++ b/libsvc/Modules/src/Models/MoeVSProject.cpp @@ -0,0 +1,441 @@ +#include "../../header/Models/MoeVSProject.hpp" +#include "../../header/InferTools/inferTools.hpp" +#include + +namespace MoeVSProjectSpace +{ + MoeVSProject::MoeVSProject(const std::wstring& _path) + { + FileWrapper project_file(_path.c_str(), L"rb"); + if (!project_file.IsOpen()) + LibDLVoiceCodecThrow("File Doesn't Exists"); + fseek(project_file, 0, SEEK_SET); + + if (fread(&moevs_proj_header_, 1, sizeof(Header), project_file) != sizeof(Header)) + LibDLVoiceCodecThrow("Unexpected EOF"); + if (!(moevs_proj_header_.ChunkSymbol[0] == 'M' && moevs_proj_header_.ChunkSymbol[1] == 'O' && moevs_proj_header_.ChunkSymbol[2] == 'E' && moevs_proj_header_.ChunkSymbol[3] == 'V' && moevs_proj_header_.ChunkSymbol[4] == 'S' && moevs_proj_header_.ChunkSymbol[5] == 'P' && moevs_proj_header_.ChunkSymbol[6] == 'R' && moevs_proj_header_.ChunkSymbol[7] == 'J')) + LibDLVoiceCodecThrow("Unrecognized File"); + if (moevs_proj_header_.DataHeaderAmount == 0) + LibDLVoiceCodecThrow("Empty Project"); + + + data_pos_ = std::vector(moevs_proj_header_.DataHeaderAmount); + if (fread(data_pos_.data(), 1, sizeof(size_type) * (moevs_proj_header_.DataHeaderAmount), project_file) != sizeof(size_type) * (moevs_proj_header_.DataHeaderAmount)) + LibDLVoiceCodecThrow("Unexpected EOF"); + data_chunk_begin_ = sizeof(Header) + sizeof(size_type) * (moevs_proj_header_.DataHeaderAmount); + + + size_type _n_bytes = 0; + const size_type _data_begin = data_chunk_begin_; + fseek(project_file, long(_data_begin), SEEK_SET); + for (const auto& _pos_index : data_pos_) + { + Data _datas; + + //Header + if (fread(&_datas.Header, 1, sizeof(DataHeader), project_file) != sizeof(DataHeader)) + LibDLVoiceCodecThrow("Unexpected EOF"); + if (!(_datas.Header.ChunkSymbol[0] == 'D' && _datas.Header.ChunkSymbol[1] == 'A' && _datas.Header.ChunkSymbol[2] == 'T' && _datas.Header.ChunkSymbol[3] == 'A')) + LibDLVoiceCodecThrow("Unrecognized File"); + _datas.ParamData.Slices.resize(_datas.Header.OrgLenSize); + + //Audio + if (_datas.Header.OrgAudioOffsetPosSize != 0) { + _datas.Offset.OrgAudio = std::vector(_datas.Header.OrgAudioOffsetPosSize); + _n_bytes = sizeof(size_type) * _datas.Header.OrgAudioOffsetPosSize; + if (fread(_datas.Offset.OrgAudio.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + size_t _data_idx = 0; + for (const auto& j : _datas.Offset.OrgAudio) + { + if (!j) continue; + std::vector hs_vector(j); + _n_bytes = sizeof(int16_t) * j; + if (fread(hs_vector.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + _datas.ParamData.Slices[_data_idx].Audio = std::move(hs_vector); + _data_idx += 1; + } + } + + + //F0 + if (_datas.Header.F0OffsetPosSize != 0) + { + _datas.Offset.F0 = std::vector(_datas.Header.F0OffsetPosSize); + _n_bytes = sizeof(size_type) * _datas.Header.F0OffsetPosSize; + if (fread(_datas.Offset.F0.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + size_t _data_idx = 0; + for (const auto& j : _datas.Offset.F0) + { + if (!j) continue; + std::vector f0_vector(j); + _n_bytes = sizeof(float) * j; + if (fread(f0_vector.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + _datas.ParamData.Slices[_data_idx].F0 = std::move(f0_vector); + _data_idx += 1; + } + } + + + //Volume + if (_datas.Header.VolumeOffsetPosSize != 0) + { + _datas.Offset.Volume = std::vector(_datas.Header.VolumeOffsetPosSize); + _n_bytes = sizeof(size_type) * _datas.Header.VolumeOffsetPosSize; + if (fread(_datas.Offset.Volume.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + size_t _data_idx = 0; + for (const auto& j : _datas.Offset.Volume) + { + if (!j) continue; + std::vector Volume_vector(j); + _n_bytes = sizeof(float) * j; + if (fread(Volume_vector.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + _datas.ParamData.Slices[_data_idx].Volume = std::move(Volume_vector); + _data_idx += 1; + } + } + + + //ChrarcterMix + if (_datas.Header.CharacterOffsetPosSize != 0) + { + _datas.Offset.Speaker = std::vector(_datas.Header.CharacterOffsetPosSize); + _n_bytes = sizeof(size_type) * _datas.Header.CharacterOffsetPosSize; + if (fread(_datas.Offset.Speaker.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + size_t _data_idx = 0; + for (const auto& j : _datas.Offset.Speaker) + { + if (!j) continue; + + size_type NSpeaker = 0; + if (fread(&NSpeaker, 1, sizeof(size_type), project_file) != sizeof(size_type)) + LibDLVoiceCodecThrow("Unexpected EOF"); + if (!NSpeaker) continue; + + std::vector Speaker_vector(j); + _n_bytes = sizeof(float) * j; + if (fread(Speaker_vector.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + std::vector> SpkVec; + if (!Speaker_vector.empty()) + { + const auto frames = Speaker_vector.size() / NSpeaker; + for (size_t idxs = 0; idxs < NSpeaker; ++idxs) + SpkVec.emplace_back(Speaker_vector.data() + idxs * frames, Speaker_vector.data() + (idxs + 1) * frames); + } + _datas.ParamData.Slices[_data_idx].Speaker = std::move(SpkVec); + _data_idx += 1; + } + } + + + //OrgLen + if (_datas.Header.OrgLenSize != 0) + { + std::vector OrgLen(_datas.Header.OrgLenSize); + _n_bytes = sizeof(long) * _datas.Header.OrgLenSize; + if (fread(OrgLen.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + for (size_t _data_idx = 0; _data_idx < OrgLen.size(); ++_data_idx) + _datas.ParamData.Slices[_data_idx].OrgLen = OrgLen[_data_idx]; + } + + + //Symbol + if (_datas.Header.SymbolSize != 0) + { + std::vector BooleanVector(_datas.Header.SymbolSize); + _n_bytes = _datas.Header.SymbolSize; + if (fread(BooleanVector.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + for (size_t _data_idx = 0; _data_idx < BooleanVector.size(); ++_data_idx) + _datas.ParamData.Slices[_data_idx].IsNotMute = BooleanVector[_data_idx]; + } + + + //path + if (_datas.Header.PathSize != 0) + { + std::vector PathStr(_datas.Header.PathSize); + _n_bytes = _datas.Header.PathSize; + if (fread(PathStr.data(), 1, _n_bytes, project_file) != _n_bytes) + LibDLVoiceCodecThrow("Unexpected EOF"); + _datas.ParamData.Path = to_wide_string(PathStr.data()); + } + + data_.emplace_back(std::move(_datas)); + } + } + + MoeVSProject::MoeVSProject(const std::vector& _params) + { + moevs_proj_header_.DataHeaderAmount = size_type(_params.size()); + data_chunk_begin_ = sizeof(Header) + sizeof(size_type) * (moevs_proj_header_.DataHeaderAmount); + data_pos_.push_back(0); + size_type _offset = 0; + for (const auto& i : _params) + { + Data _data; + _data.ParamData = i; + for (const auto& SliceData : i.Slices) + { + _data.Offset.OrgAudio.push_back(SliceData.Audio.size()); + _data.Offset.F0.push_back(SliceData.F0.size()); + _data.Offset.Volume.push_back(SliceData.Volume.size()); + size_t size__ = 0; + for (auto& Speaker : SliceData.Speaker) + { + size__ += Speaker.size(); + } + _data.Offset.Speaker.push_back(size__); + } + _data.Header.OrgAudioOffsetPosSize = _data.Offset.OrgAudio.size(); + _data.Header.F0OffsetPosSize = _data.Offset.F0.size(); + _data.Header.VolumeOffsetPosSize = _data.Offset.Volume.size(); + _data.Header.CharacterOffsetPosSize = _data.Offset.Speaker.size(); + _data.Header.OrgLenSize = i.Slices.size(); + _data.Header.SymbolSize = i.Slices.size(); + const std::string bytePath = to_byte_string(i.Path); + _data.Header.PathSize = bytePath.length() + 1; + _offset += _data.Size(); + data_pos_.push_back(_offset); + data_.emplace_back(std::move(_data)); + } + data_pos_.pop_back(); + } + + void MoeVSProject::Write(const std::wstring& _path) const + { + FILE* project_file = nullptr; + _wfopen_s(&project_file, _path.c_str(), L"wb"); + if (!project_file) + LibDLVoiceCodecThrow("Cannot Create File"); + + fwrite(&moevs_proj_header_, 1, sizeof(Header), project_file); + fwrite(data_pos_.data(), 1, data_pos_.size() * sizeof(size_type), project_file); + + for (const auto& i : data_) + { + fwrite(&i.Header, 1, sizeof(DataHeader), project_file); + + + fwrite(i.Offset.OrgAudio.data(), 1, sizeof(size_type) * i.Offset.OrgAudio.size(), project_file); + for (const auto& orgaudio : i.ParamData.Slices) + fwrite(orgaudio.Audio.data(), 1, orgaudio.Audio.size() * sizeof(int16_t), project_file); + + + fwrite(i.Offset.F0.data(), 1, sizeof(size_type) * i.Offset.F0.size(), project_file); + for (const auto& f0 : i.ParamData.Slices) + fwrite(f0.F0.data(), 1, f0.F0.size() * sizeof(float), project_file); + + + if (i.Header.VolumeOffsetPosSize != 0) + { + fwrite(i.Offset.Volume.data(), 1, sizeof(size_type) * i.Offset.Volume.size(), project_file); + for (const auto& volume : i.ParamData.Slices) + fwrite(volume.Volume.data(), 1, volume.Volume.size() * sizeof(float), project_file); + } + + if (i.Header.CharacterOffsetPosSize != 0) + { + fwrite(i.Offset.Speaker.data(), 1, sizeof(size_type) * i.Offset.Speaker.size(), project_file); + for (const auto& Speaker : i.ParamData.Slices) + { + if (Speaker.Speaker.empty()) continue; + const size_type NSPK = Speaker.Speaker.size(); + fwrite(&NSPK, 1, sizeof(size_type), project_file); + for (const auto& Spkk : Speaker.Speaker) + fwrite(Spkk.data(), 1, Spkk.size() * sizeof(float), project_file); + } + } + + assert(i.ParamData.Slices.size() == i.Header.OrgLenSize); + std::vector CurOrgLen(i.Header.OrgLenSize); + for (size_t index = 0; index < i.ParamData.Slices.size(); ++index) + CurOrgLen[index] = i.ParamData.Slices[index].OrgLen; + fwrite(CurOrgLen.data(), 1, sizeof(long) * CurOrgLen.size(), project_file); + + assert(i.ParamData.Slices.size() == i.Header.SymbolSize); + std::vector BooleanVector(i.Header.SymbolSize); + for (size_t index = 0; index < i.ParamData.Slices.size(); ++index) + BooleanVector[index] = i.ParamData.Slices[index].IsNotMute ? 1 : 0; + fwrite(BooleanVector.data(), 1, i.Header.SymbolSize, project_file); + + const std::string bytePath = to_byte_string(i.ParamData.Path); + fwrite(bytePath.data(), 1, i.Header.PathSize, project_file); + } + fclose(project_file); + } + + std::wstring MoeVSTTSToken::Serialization() const + { + if (Text.empty()) + return L"\t\t\t{ }"; + std::wstring rtn = L"\t\t\t{\n"; + rtn += L"\t\t\t\t\"Text\": \"" + Text + L"\",\n"; + rtn += L"\t\t\t\t\"Phonemes\": " + wstring_vector_to_string(Phonemes) + L",\n"; + rtn += L"\t\t\t\t\"Tones\": " + vector_to_string(Tones) + L",\n"; + rtn += L"\t\t\t\t\"Durations\": " + vector_to_string(Durations) + L",\n"; + rtn += L"\t\t\t\t\"Language\": " + string_vector_to_string(Language) + L"\n\t\t\t}"; + return rtn; + } + + MoeVSTTSToken::MoeVSTTSToken(const MJsonValue& _JsonDocument, const MoeVSParams& _InitParams) + { + if (_JsonDocument.HasMember("Text") && _JsonDocument["Text"].IsString() && !_JsonDocument["Text"].Empty()) + Text = to_wide_string(_JsonDocument["Text"].GetString()); + else + LibDLVoiceCodecThrow("Field \"Text\" Should Not Be Empty") + + if (_JsonDocument.HasMember("Phonemes") && _JsonDocument["Phonemes"].IsArray()) + for (const auto& j : _JsonDocument["Phonemes"].GetArray()) + Phonemes.emplace_back(j.IsString() ? to_wide_string(j.GetString()) : L"[UNK]"); + + if (_JsonDocument.HasMember("Tones") && _JsonDocument["Tones"].IsArray()) + for (const auto& j : _JsonDocument["Tones"].GetArray()) + Tones.emplace_back(j.IsInt() ? j.GetInt() : 0); + + if (_JsonDocument.HasMember("Durations") && _JsonDocument["Durations"].IsArray()) + for (const auto& j : _JsonDocument["Durations"].GetArray()) + Durations.emplace_back(j.IsInt() ? j.GetInt() : 0); + + if (_JsonDocument.HasMember("Language") && _JsonDocument["Language"].IsArray()) + { + const auto LanguageArr = _JsonDocument["Language"].GetArray(); + if (!LanguageArr.empty() && LanguageArr[0].IsString()) + for (const auto& j : LanguageArr) + Language.emplace_back(j.GetString()); + } + } + + std::wstring MoeVSTTSSeq::Serialization() const + { + if (TextSeq.empty()) + return L""; + std::wstring rtn = L"\t{\n"; + rtn += L"\t\t\"TextSeq\": \"" + TextSeq + L"\",\n"; + rtn += L"\t\t\"SlicedTokens\": [\n"; + for (const auto& iter : SlicedTokens) + rtn += iter.Serialization() + L",\n"; + if (!SlicedTokens.empty()) + rtn.erase(rtn.end() - 2); + rtn += L"\t\t],\n"; + rtn += L"\t\t\"SpeakerMix\": " + vector_to_string(SpeakerMix) + L",\n"; + rtn += L"\t\t\"EmotionPrompt\": " + wstring_vector_to_string(EmotionPrompt) + L",\n"; + rtn += L"\t\t\"NoiseScale\": " + std::to_wstring(NoiseScale) + L",\n"; + rtn += L"\t\t\"LengthScale\": " + std::to_wstring(LengthScale) + L",\n"; + rtn += L"\t\t\"DurationPredictorNoiseScale\": " + std::to_wstring(DurationPredictorNoiseScale) + L",\n"; + rtn += L"\t\t\"FactorDpSdp\": " + std::to_wstring(FactorDpSdp) + L",\n"; + rtn += L"\t\t\"GateThreshold\": " + std::to_wstring(GateThreshold) + L",\n"; + rtn += L"\t\t\"MaxDecodeStep\": " + std::to_wstring(MaxDecodeStep) + L",\n"; + rtn += L"\t\t\"Seed\": " + std::to_wstring(Seed) + L",\n"; + rtn += L"\t\t\"SpeakerName\": \"" + SpeakerName + L"\",\n"; + rtn += L"\t\t\"RestTime\": " + std::to_wstring(RestTime) + L",\n"; + rtn += L"\t\t\"PlaceHolderSymbol\": \"" + PlaceHolderSymbol + L"\",\n"; + rtn += L"\t\t\"LanguageSymbol\": \"" + to_wide_string(LanguageSymbol) + L"\",\n"; + rtn += L"\t\t\"G2PAdditionalInfo\": \"" + AdditionalInfo + L"\"\n\t}"; + return rtn; + } + + MoeVSTTSSeq::MoeVSTTSSeq(const MJsonValue& _JsonDocument, const MoeVSParams& _InitParams) + { + if (_JsonDocument.HasMember("LanguageSymbol") && _JsonDocument["LanguageSymbol"].IsString() && !_JsonDocument["LanguageSymbol"].Empty()) + LanguageSymbol = _JsonDocument["LanguageSymbol"].GetString(); + else + LanguageSymbol = _InitParams.LanguageSymbol; + + if (_JsonDocument.HasMember("G2PAdditionalInfo") && _JsonDocument["G2PAdditionalInfo"].IsString() && !_JsonDocument["G2PAdditionalInfo"].Empty()) + AdditionalInfo = to_wide_string(_JsonDocument["G2PAdditionalInfo"].GetString()); + else + AdditionalInfo = _InitParams.AdditionalInfo; + + if (_JsonDocument.HasMember("PlaceHolderSymbol") && _JsonDocument["PlaceHolderSymbol"].IsString() && !_JsonDocument["PlaceHolderSymbol"].Empty()) + PlaceHolderSymbol = to_wide_string(_JsonDocument["PlaceHolderSymbol"].GetString()); + else + PlaceHolderSymbol = _InitParams.PlaceHolderSymbol; + + if (_JsonDocument.HasMember("TextSeq") && _JsonDocument["TextSeq"].IsString() && !_JsonDocument["TextSeq"].Empty()) + TextSeq = to_wide_string(_JsonDocument["TextSeq"].GetString()); + else + LibDLVoiceCodecThrow("Field \"TextSeq\" Should Not Be Empty") + + if (_JsonDocument.HasMember("SlicedTokens") && _JsonDocument["SlicedTokens"].IsArray()) + { + const auto TokensArrayObject = _JsonDocument["SlicedTokens"].GetArray(); + for (const auto& iter : TokensArrayObject) + SlicedTokens.emplace_back(iter, _InitParams); + } + + if (_JsonDocument.HasMember("SpeakerMix") && _JsonDocument["SpeakerMix"].IsArray()) + for (const auto& j : _JsonDocument["SpeakerMix"].GetArray()) + SpeakerMix.emplace_back(j.IsFloat() ? j.GetFloat() : 0.f); + else + SpeakerMix = _InitParams.SpeakerMix; + if (_JsonDocument.HasMember("EmotionPrompt") && _JsonDocument["EmotionPrompt"].IsArray()) + for (const auto& j : _JsonDocument["EmotionPrompt"].GetArray()) + EmotionPrompt.emplace_back(j.IsString() ? to_wide_string(j.GetString()) : std::wstring()); + else + EmotionPrompt = _InitParams.EmotionPrompt; + if (_JsonDocument.HasMember("NoiseScale") && _JsonDocument["NoiseScale"].IsFloat()) + NoiseScale = _JsonDocument["NoiseScale"].GetFloat(); + else + NoiseScale = _InitParams.NoiseScale; + if (_JsonDocument.HasMember("LengthScale") && _JsonDocument["LengthScale"].IsFloat()) + LengthScale = _JsonDocument["LengthScale"].GetFloat(); + else + LengthScale = _InitParams.LengthScale; + if (_JsonDocument.HasMember("RestTime") && _JsonDocument["RestTime"].IsFloat()) + RestTime = _JsonDocument["RestTime"].GetFloat(); + else + RestTime = _InitParams.RestTime; + if (_JsonDocument.HasMember("DurationPredictorNoiseScale") && _JsonDocument["DurationPredictorNoiseScale"].IsFloat()) + DurationPredictorNoiseScale = _JsonDocument["DurationPredictorNoiseScale"].GetFloat(); + else + DurationPredictorNoiseScale = _InitParams.DurationPredictorNoiseScale; + if (_JsonDocument.HasMember("FactorDpSdp") && _JsonDocument["FactorDpSdp"].IsFloat()) + FactorDpSdp = _JsonDocument["FactorDpSdp"].GetFloat(); + else + FactorDpSdp = _InitParams.FactorDpSdp; + if (_JsonDocument.HasMember("GateThreshold") && _JsonDocument["GateThreshold"].IsFloat()) + GateThreshold = _JsonDocument["GateThreshold"].GetFloat(); + else + GateThreshold = _InitParams.GateThreshold; + if (_JsonDocument.HasMember("MaxDecodeStep") && _JsonDocument["MaxDecodeStep"].IsFloat()) + MaxDecodeStep = _JsonDocument["MaxDecodeStep"].GetInt(); + else + MaxDecodeStep = _InitParams.MaxDecodeStep; + if (_JsonDocument.HasMember("Seed") && _JsonDocument["Seed"].IsInt()) + Seed = _JsonDocument["Seed"].GetInt(); + else + Seed = _InitParams.Seed; + if (_JsonDocument.HasMember("SpeakerName") && _JsonDocument["SpeakerName"].IsString()) + SpeakerName = to_wide_string(_JsonDocument["SpeakerName"].GetString()); + else + SpeakerName = _InitParams.SpeakerName; + + if (MaxDecodeStep < 2000) MaxDecodeStep = 2000; + if (MaxDecodeStep > 20000) MaxDecodeStep = 20000; + if (GateThreshold > 0.90f) GateThreshold = 0.90f; + if (GateThreshold < 0.2f) GateThreshold = 0.2f; + if (FactorDpSdp > 1.f) FactorDpSdp = 1.f; + if (FactorDpSdp < 0.f) FactorDpSdp = 0.f; + if (NoiseScale > 10.f) NoiseScale = 10.f; + if (NoiseScale < 0.f) NoiseScale = 0.f; + if (DurationPredictorNoiseScale > 10.f) DurationPredictorNoiseScale = 10.f; + if (DurationPredictorNoiseScale < 0.f) DurationPredictorNoiseScale = 0.f; + if (RestTime > 30.f) RestTime = 30.f; + if (LengthScale > 10.f) LengthScale = 10.f; + if (LengthScale < 0.1f) LengthScale = 0.1f; + } + + bool MoeVSTTSSeq::operator==(const MoeVSTTSSeq& right) const + { + return Serialization() == right.Serialization(); + } +} \ No newline at end of file diff --git a/libsvc/Modules/src/Models/SVC.cpp b/libsvc/Modules/src/Models/SVC.cpp new file mode 100644 index 0000000..70e750c --- /dev/null +++ b/libsvc/Modules/src/Models/SVC.cpp @@ -0,0 +1,122 @@ +#include "../../header/Models/SVC.hpp" +#include "../../header/InferTools/F0Extractor/F0ExtractorManager.hpp" + +MoeVoiceStudioCoreHeader + +SingingVoiceConversion::SingingVoiceConversion(const ExecutionProviders& ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_) : MoeVoiceStudioModule(ExecutionProvider_, DeviceID_, ThreadCount_) +{ + MoeVSClassName(L"MoeVoiceStudioSingingVoiceConversion"); +} + +SingingVoiceConversion::~SingingVoiceConversion() +{ + delete hubert; + hubert = nullptr; +} + +std::vector SingingVoiceConversion::Inference(std::wstring& _Paths, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const +{ + MoeVSNotImplementedError; +} + +std::vector SingingVoiceConversion::InferPCMData(const std::vector& PCMData, long srcSr, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const +{ + MoeVSNotImplementedError; +} + +std::vector SingingVoiceConversion::SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const +{ + MoeVSNotImplementedError; +} + +std::vector SingingVoiceConversion::SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) const +{ + MoeVSNotImplementedError; +} + +std::vector SingingVoiceConversion::ExtractVolume(const std::vector& OrgAudio, int hop_size) +{ + std::vector Audio; + Audio.reserve(OrgAudio.size() * 2); + Audio.insert(Audio.end(), hop_size, double(OrgAudio[0]) / 32768.); + for (const auto i : OrgAudio) + Audio.emplace_back((double)i / 32768.); + Audio.insert(Audio.end(), hop_size, double(OrgAudio[OrgAudio.size() - 1]) / 32768.); + const size_t n_frames = (OrgAudio.size() / hop_size) + 1; + std::vector volume(n_frames); + for (auto& i : Audio) + i = pow(i, 2); + int64_t index = 0; + for (auto& i : volume) + { + i = sqrt((float)InferTools::getAvg(Audio.begin()._Ptr + index * hop_size, Audio.begin()._Ptr + (index + 1) * hop_size)); + ++index; + } + return volume; +} + +std::vector SingingVoiceConversion::ExtractVolume(const std::vector& OrgAudio) const +{ + std::vector Audio; + Audio.reserve(OrgAudio.size() * 2); + Audio.insert(Audio.end(), HopSize, OrgAudio[0]); + Audio.insert(Audio.end(), OrgAudio.begin(), OrgAudio.end()); + Audio.insert(Audio.end(), HopSize, OrgAudio[OrgAudio.size() - 1]); + const size_t n_frames = (OrgAudio.size() / HopSize) + 1; + std::vector volume(n_frames); + for (auto& i : Audio) + i = pow(i, 2); + int64_t index = 0; + for (auto& i : volume) + { + i = sqrt((float)InferTools::getAvg(Audio.begin()._Ptr + index * HopSize, Audio.begin()._Ptr + (index + 1) * HopSize)); + ++index; + } + return volume; +} + +MoeVSProjectSpace::MoeVoiceStudioSvcData SingingVoiceConversion::GetAudioSlice(const std::vector& input, + const std::vector& _slice_pos, + const InferTools::SlicerSettings& _slicer) +{ + MoeVSProjectSpace::MoeVoiceStudioSvcData audio_slice; + for (size_t i = 1; i < _slice_pos.size(); i++) + { + MoeVSProjectSpace::MoeVoiceStudioSvcSlice _CurSlice; + const bool is_not_mute = abs(InferTools::getAvg((input.data() + _slice_pos[i - 1]), (input.data() + _slice_pos[i]))) > _slicer.Threshold; + _CurSlice.IsNotMute = is_not_mute; + _CurSlice.OrgLen = long(_slice_pos[i] - _slice_pos[i - 1]); + if (is_not_mute) + _CurSlice.Audio = { (input.data() + _slice_pos[i - 1]), (input.data() + _slice_pos[i]) }; + else + _CurSlice.Audio.clear(); + audio_slice.Slices.emplace_back(std::move(_CurSlice)); + } + return audio_slice; +} + +void SingingVoiceConversion::PreProcessAudio(MoeVSProjectSpace::MoeVoiceStudioSvcData& input, + int __SamplingRate, int __HopSize, const std::wstring& _f0_method) +{ + const auto F0Extractor = MoeVSF0Extractor::GetF0Extractor(_f0_method, __SamplingRate, __HopSize); + const auto num_slice = input.Slices.size(); + for (size_t i = 0; i < num_slice; ++i) + { + if (input.Slices[i].IsNotMute) + { + input.Slices[i].F0 = F0Extractor->ExtractF0(input.Slices[i].Audio, input.Slices[i].Audio.size() / __HopSize); + input.Slices[i].Volume = ExtractVolume(input.Slices[i].Audio, __HopSize); + } + else + { + input.Slices[i].F0.clear(); + input.Slices[i].Volume.clear(); + } + input.Slices[i].Speaker.clear(); + } +} + +MoeVoiceStudioCoreEnd \ No newline at end of file diff --git a/libsvc/Modules/src/Models/VitsSvc.cpp b/libsvc/Modules/src/Models/VitsSvc.cpp new file mode 100644 index 0000000..50c7648 --- /dev/null +++ b/libsvc/Modules/src/Models/VitsSvc.cpp @@ -0,0 +1,1320 @@ +#include "../../header/Models/VitsSvc.hpp" +#include "../../header/InferTools/AvCodec/AvCodeResample.h" +#include "../../header/InferTools/F0Extractor/F0ExtractorManager.hpp" +#include +#include "../../header/Logger/MoeSSLogger.hpp" +#include "../../header/InferTools/TensorExtractor/TensorExtractorManager.hpp" +#include + +MoeVoiceStudioCoreHeader +void VitsSvc::Destory() +{ + delete hubert; + hubert = nullptr; + + delete VitsSvcModel; + VitsSvcModel = nullptr; +} + +VitsSvc::~VitsSvc() +{ + logger.log(L"[Info] unloading VitsSvc Models"); + Destory(); + logger.log(L"[Info] VitsSvc Models unloaded"); +} + +VitsSvc::VitsSvc(const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_, + unsigned DeviceID_, unsigned ThreadCount_): + SingingVoiceConversion(ExecutionProvider_, DeviceID_, ThreadCount_) +{ + MoeVSClassName(L"MoeVoiceStudioVitsSingingVoiceConversion"); + + //Check Folder + if (_Config["Folder"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"folder\" (Model Folder)") + if (!_Config["Folder"].IsString()) + LibDLVoiceCodecThrow("[Error] Field \"folder\" (Model Folder) Must Be String") + const auto _folder = to_wide_string(_Config["Folder"].GetString()); + const auto cluster_folder = GetCurrentFolder() + L"/Models/" + _folder; + if (_folder.empty()) + LibDLVoiceCodecThrow("[Error] Field \"folder\" (Model Folder) Can Not Be Empty") + const std::wstring _path = GetCurrentFolder() + L"/Models/" + _folder + L"/" + _folder; + + if (_Config["Hubert"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"Hubert\" (Hubert Folder)") + if (!_Config["Hubert"].IsString()) + LibDLVoiceCodecThrow("[Error] Field \"Hubert\" (Hubert Folder) Must Be String") + const std::wstring HuPath = to_wide_string(_Config["Hubert"].GetString()); + if (HuPath.empty()) + LibDLVoiceCodecThrow("[Error] Field \"Hubert\" (Hubert Folder) Can Not Be Empty") + + std::map _PathDict; + _PathDict["Cluster"] = cluster_folder; + _PathDict["Hubert"] = (GetCurrentFolder() + L"/hubert/" + HuPath + L".onnx"); + _PathDict["RVC"] = (_path + L"_RVC.onnx"); + _PathDict["SoVits"] = (_path + L"_SoVits.onnx"); + if (_Config["ShallowDiffusion"].IsString()) + { + const std::wstring ShallowDiffusionConf = GetCurrentFolder() + L"/Models/" + to_wide_string(_Config["ShallowDiffusion"].GetString()) + L".json"; + _PathDict["ShallowDiffusionConfig"] = ShallowDiffusionConf; + if (_Config["MelOperator"].IsString()) + _PathDict["MelOperator"] = GetCurrentFolder() + L"/MelOps" + to_wide_string(_Config["MelOperator"].GetString()) + L".onnx"; + else + _PathDict["MelOperator"] = GetCurrentFolder() + L"/MelOps.onnx"; + } + + load(_PathDict, _Config, _ProgressCallback, ExecutionProvider_, DeviceID_, ThreadCount_, true); +} + +VitsSvc::VitsSvc(const std::map& _PathDict, const MJson& _Config, const ProgressCallback& _ProgressCallback, + ExecutionProviders ExecutionProvider_, + unsigned DeviceID_, unsigned ThreadCount_) : + SingingVoiceConversion(ExecutionProvider_, DeviceID_, ThreadCount_) +{ + MoeVSClassName(L"MoeVoiceStudioVitsSingingVoiceConversion"); + load(_PathDict, _Config, _ProgressCallback, ExecutionProvider_, DeviceID_, ThreadCount_, false); +} + +void VitsSvc::load(const std::map& _PathDict, const MJson& _Config, const ProgressCallback& _ProgressCallback, ExecutionProviders ExecutionProvider_, unsigned DeviceID_, unsigned ThreadCount_, bool MoeVoiceStudioFrontEnd) +{ + + //Check SamplingRate + if (_Config["Rate"].IsNull()) + LibDLVoiceCodecThrow("[Error] Missing field \"Rate\" (SamplingRate)") + if (_Config["Rate"].IsInt() || _Config["Rate"].IsInt64()) + _samplingRate = _Config["Rate"].GetInt(); + else + LibDLVoiceCodecThrow("[Error] Field \"Rate\" (SamplingRate) Must Be Int/Int64") + + logger.log(L"[Info] Current Sampling Rate is" + std::to_wstring(_samplingRate)); + + if (!_Config["SoVits3"].IsNull() && _Config["SoVits3"].GetBool()) + VitsSvcVersion = L"SoVits3.0"; + else if (!_Config["SoVits2"].IsNull() && _Config["SoVits2"].GetBool()) + VitsSvcVersion = L"SoVits2.0"; + else if (!_Config["SoVits2.0"].IsNull() && _Config["SoVits2.0"].GetBool()) + VitsSvcVersion = L"SoVits2.0"; + else if (!_Config["SoVits3.0"].IsNull() && _Config["SoVits3.0"].GetBool()) + VitsSvcVersion = L"SoVits3.0"; + else if (_Config["Type"].GetString() == std::string("RVC")) + VitsSvcVersion = L"RVC"; + + if (!_Config["SoVits4.0V2"].IsNull() && _Config["SoVits4.0V2"].GetBool()) + VitsSvcVersion = L"SoVits4.0-DDSP"; + +#ifdef MOEVSDMLPROVIDER + if (ExecutionProvider_ == ExecutionProviders::DML && VitsSvcVersion == L"SoVits4.0-DDSP") + LibDLVoiceCodecThrow("[Error] DirectXMl Not Support SoVits4.0V2, Please Use Cuda Or Cpu") +#endif + + if (!(_Config["Hop"].IsInt() || _Config["Hop"].IsInt64())) + LibDLVoiceCodecThrow("[Error] Hop Must Exist And Must Be Int") + HopSize = _Config["Hop"].GetInt(); + + if (!(_Config["HiddenSize"].IsInt() || _Config["HiddenSize"].IsInt64())) + logger.log(L"[Warn] Missing Field \"HiddenSize\", Use Default Value (256)"); + else + HiddenUnitKDims = _Config["HiddenSize"].GetInt(); + + if (!_Config["CharaMix"].IsBool()) + logger.log(L"[Warn] Missing Field \"CharaMix\", Use Default Value (False)"); + else + EnableCharaMix = _Config["CharaMix"].GetBool(); + + if (_Config["Cluster"].IsString()) + { + const auto clus = to_wide_string(_Config["Cluster"].GetString()); + if (!(_Config["KMeansLength"].IsInt() || _Config["KMeansLength"].IsInt64())) + logger.log(L"[Warn] Missing Field \"KMeansLength\", Use Default Value (10000)"); + else + ClusterCenterSize = _Config["KMeansLength"].GetInt(); + try + { + Cluster = MoeVoiceStudioCluster::GetMoeVSCluster(clus, _PathDict.at("Cluster"), HiddenUnitKDims, ClusterCenterSize); + EnableCluster = true; + } + catch (std::exception& e) + { + logger.error(e.what()); + EnableCluster = false; + } + } + + if (HopSize < 1) + LibDLVoiceCodecThrow("[Error] Hop Must > 0") + + if (_Config["Volume"].IsBool()) + EnableVolume = _Config["Volume"].GetBool(); + else + logger.log(L"[Warn] Missing Field \"Volume\", Use Default Value (False)"); + + if (_Config["Characters"].IsArray()) + SpeakerCount = int64_t(_Config["Characters"].Size()); + + _callback = _ProgressCallback; + + //LoadModels + try + { + logger.log(L"[Info] loading VitsSvcModel Models"); + hubert = new Ort::Session(*env, _PathDict.at("Hubert").c_str(), *session_options); + if (VitsSvcVersion == L"RVC") + VitsSvcModel = new Ort::Session(*env, _PathDict.at("RVC").c_str(), *session_options); + else + VitsSvcModel = new Ort::Session(*env, _PathDict.at("SoVits").c_str(), *session_options); + logger.log(L"[Info] VitsSvcModel Models loaded"); + } + catch (Ort::Exception& _exception) + { + Destory(); + LibDLVoiceCodecThrow(_exception.what()) + } + + if (VitsSvcModel->GetInputCount() == 4 && VitsSvcVersion != L"SoVits3.0") + VitsSvcVersion = L"SoVits2.0"; + + if (_Config["TensorExtractor"].IsString()) + VitsSvcVersion = to_wide_string(_Config["TensorExtractor"].GetString()); + + MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::Others _others_param; + _others_param.Memory = *memory_info; + try + { + _TensorExtractor = GetTensorExtractor(VitsSvcVersion, 48000, _samplingRate, HopSize, EnableCharaMix, EnableVolume, HiddenUnitKDims, SpeakerCount, _others_param); + } + catch (std::exception& e) + { + Destory(); + LibDLVoiceCodecThrow(e.what()) + } +} + +std::vector VitsSvc::SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const +{ + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Start!"); + std::vector _data; + size_t total_audio_size = 0; + for (const auto& data_size : _Slice.Slices) + total_audio_size += data_size.OrgLen; + _data.reserve(size_t(double(total_audio_size) * 1.5)); + _callback(0, _Slice.Slices.size()); + size_t process = 0; + for (auto& CurSlice : _Slice.Slices) + { + const auto InferDurTime = clock(); + const auto CurRtn = SliceInference(CurSlice, _InferParams, process); + _data.insert(_data.end(), CurRtn.data(), CurRtn.data() + CurRtn.size()); + if(CurSlice.IsNotMute) + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Segment[" + std::to_wstring(process) + L"] Finished! Segment Use Time: " + std::to_wstring(clock() - InferDurTime) + L"ms, Segment Duration: " + std::to_wstring((size_t)CurSlice.OrgLen * 1000ull / 48000ull) + L"ms"); + else + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Jump Empty Segment[" + std::to_wstring(process) + L"]!"); + _callback(++process, _Slice.Slices.size()); + } + logger.log(L"[Inferring] \"" + _Slice.Path + L"\" Finished"); + return _data; +} + +ShallowDiffusionData DataForDiffusion; + +ShallowDiffusionData& GetDataForShallowDiffusion() +{ + return DataForDiffusion; +} + +std::vector VitsSvc::SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) const +{ + DataForDiffusion._16KAudio.clear(); + DataForDiffusion.CUDAF0.clear(); + DataForDiffusion.CUDAVolume.clear(); + DataForDiffusion.CUDASpeaker.clear(); + DataForDiffusion.NeedPadding = false; + if (_Slice.IsNotMute) + { + DataForDiffusion._16KAudio = InferTools::InterpResample(_Slice.Audio, 48000, 16000, 32768.0f); + const auto src_audio_length = DataForDiffusion._16KAudio.size(); + bool NeedPadding = false; + if (_cur_execution_provider == ExecutionProviders::CUDA) + { + NeedPadding = DataForDiffusion._16KAudio.size() % 16000; + const size_t WavPaddedSize = DataForDiffusion._16KAudio.size() / 16000 + 1; + if (NeedPadding) + DataForDiffusion._16KAudio.resize(WavPaddedSize * 16000, 0.f); + } + + const int64_t HubertInputShape[3] = { 1i64,1i64,(int64_t)DataForDiffusion._16KAudio.size() }; + std::vector HubertInputTensors, HubertOutPuts; + HubertInputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, DataForDiffusion._16KAudio.data(), DataForDiffusion._16KAudio.size(), HubertInputShape, 3)); + try { + HubertOutPuts = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + HubertInputTensors.data(), + HubertInputTensors.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what())) + } + const auto HubertSize = HubertOutPuts[0].GetTensorTypeAndShapeInfo().GetElementCount(); + const auto HubertOutPutData = HubertOutPuts[0].GetTensorMutableData(); + auto HubertOutPutShape = HubertOutPuts[0].GetTensorTypeAndShapeInfo().GetShape(); + if (HubertOutPutShape[2] != HiddenUnitKDims) + LibDLVoiceCodecThrow("HiddenUnitKDims UnMatch") + + std::vector SrcHiddenUnits(HubertOutPutData, HubertOutPutData + HubertSize); + + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + + const auto max_cluster_size = int64_t((size_t)HubertOutPutShape[1] * src_audio_length / DataForDiffusion._16KAudio.size()); + if (EnableCluster && _InferParams.ClusterRate > 0.001f) + { + const auto pts = Cluster->find(SrcHiddenUnits.data(), long(SpeakerIdx), max_cluster_size); + for (int64_t indexs = 0; indexs < max_cluster_size * HiddenUnitKDims; ++indexs) + SrcHiddenUnits[indexs] = SrcHiddenUnits[indexs] * (1.f - _InferParams.ClusterRate) + pts[indexs] * _InferParams.ClusterRate; + } + + MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::InferParams _Inference_Params; + _Inference_Params.AudioSize = _Slice.Audio.size(); + _Inference_Params.Chara = SpeakerIdx; + _Inference_Params.NoiseScale = _InferParams.NoiseScale; + _Inference_Params.DDSPNoiseScale = _InferParams.DDSPNoiseScale; + _Inference_Params.Seed = int(_InferParams.Seed); + _Inference_Params.upKeys = _InferParams.Keys; + + MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::Inputs InputTensors; + + DataForDiffusion.NeedPadding = NeedPadding; + + if (_cur_execution_provider == ExecutionProviders::CUDA && NeedPadding) + { + DataForDiffusion.CUDAF0 = _Slice.F0; + DataForDiffusion.CUDAVolume = _Slice.Volume; + DataForDiffusion.CUDASpeaker = _Slice.Speaker; + const auto src_src_audio_length = _Slice.Audio.size(); + const size_t WavPaddedSize = ((src_src_audio_length / 48000) + 1) * 48000; + const size_t AudioPadSize = WavPaddedSize - src_src_audio_length; + const size_t PaddedF0Size = DataForDiffusion.CUDAF0.size() + (DataForDiffusion.CUDAF0.size() * AudioPadSize / src_src_audio_length); + + if (!DataForDiffusion.CUDAF0.empty()) DataForDiffusion.CUDAF0.resize(PaddedF0Size, 0.f); + if (!DataForDiffusion.CUDAVolume.empty()) DataForDiffusion.CUDAVolume.resize(PaddedF0Size, 0.f); + for (auto iSpeaker : DataForDiffusion.CUDASpeaker) + { + if (!iSpeaker.empty()) + iSpeaker.resize(PaddedF0Size, 0.f); + } + _Inference_Params.AudioSize = WavPaddedSize; + InputTensors = _TensorExtractor->Extract(SrcHiddenUnits, DataForDiffusion.CUDAF0, DataForDiffusion.CUDAVolume, DataForDiffusion.CUDASpeaker, _Inference_Params); + } + else + InputTensors = _TensorExtractor->Extract(SrcHiddenUnits, _Slice.F0, _Slice.Volume, _Slice.Speaker, _Inference_Params); + + + std::vector finaOut; + try + { + finaOut = VitsSvcModel->Run(Ort::RunOptions{ nullptr }, + InputTensors.InputNames, + InputTensors.Tensor.data(), + InputTensors.Tensor.size(), + soVitsOutput.data(), + soVitsOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: VitsSvc\n") + e.what())) + } + + const auto dstWavLen = (_Slice.OrgLen * int64_t(_samplingRate)) / 48000; + /*if (shallow_diffusion && stft_operator && _InferParams.UseShallowDiffusion) + { + auto PCMAudioBegin = finaOut[0].GetTensorData(); + auto PCMAudioEnd = PCMAudioBegin + finaOut[0].GetTensorTypeAndShapeInfo().GetElementCount(); + auto MelSpec = MelExtractor(PCMAudioBegin, PCMAudioEnd); + auto ShallowParam = _InferParams; + ShallowParam.SrcSamplingRate = _samplingRate; + auto ShallowDiffusionOutput = shallow_diffusion->ShallowDiffusionInference( + RawWav, + ShallowParam, + std::move(MelSpec[0]), + NeedPadding ? CUDAF0 : _Slice.F0, + NeedPadding ? CUDAVolume : _Slice.Volume, + NeedPadding ? CUDASpeaker : _Slice.Speaker + ); + ShallowDiffusionOutput.resize(dstWavLen, 0); + return ShallowDiffusionOutput; + }*/ + + const auto shapeOut = finaOut[0].GetTensorTypeAndShapeInfo().GetShape(); + std::vector TempVecWav = std::vector(dstWavLen, 0); + if (shapeOut[2] < dstWavLen) + for (int64_t bbb = 0; bbb < shapeOut[2]; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.0f); + else + for (int64_t bbb = 0; bbb < dstWavLen; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.0f); + return TempVecWav; + } + //Mute clips + const auto len = size_t(_Slice.OrgLen * int64_t(_samplingRate) / 48000); + return { len, 0i16, std::allocator() }; +} + +std::vector VitsSvc::Inference(std::wstring& _Paths, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, + const InferTools::SlicerSettings& _SlicerSettings) const +{ + std::vector _Lens = GetOpenFileNameMoeVS(); + std::vector AudioFolders; + for (auto& path : _Lens) + { + path = std::regex_replace(path, std::wregex(L"\\\\"), L"/"); + auto PCMData = AudioPreprocess().codec(path, 48000); + auto slicer_setting = _SlicerSettings; + slicer_setting.SamplingRate = 48000; + auto SlicePos = SliceAudio(PCMData, slicer_setting); + auto Audio = GetAudioSlice(PCMData, SlicePos, slicer_setting); + Audio.Path = path; + PreProcessAudio(Audio, 48000, 512, _InferParams.F0Method); + std::vector _data = SliceInference(Audio, _InferParams); + + std::wstring OutFolder = GetCurrentFolder() + L"/Outputs/" + path.substr(path.rfind(L'/') + 1, path.rfind(L'.') - path.rfind(L'/') - 1); + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + OutFolder += L"-Params-(-NoiseScale=" + + std::to_wstring(_InferParams.NoiseScale) + + L"-Speaker=" + + (EnableCharaMix ? std::wstring(L"SpeakerMix") : std::to_wstring(SpeakerIdx)) + + L"-Seed=" + + std::to_wstring(_InferParams.Seed) + + L"-F0Method=" + + _InferParams.F0Method + L")"; + if (_waccess((OutFolder + L".wav").c_str(), 0) != -1) + { + for (size_t idx = 0; idx < 99999999; ++idx) + if (_waccess((OutFolder + L" (" + std::to_wstring(idx) + L").wav").c_str(), 0) == -1) + { + OutFolder += L" (" + std::to_wstring(idx) + L").wav"; + break; + } + } + else + OutFolder += L".wav"; + AudioFolders.emplace_back(OutFolder); + _data = InferTools::InterpResample(_data, _samplingRate, 48000, 1i16); + InferTools::Wav::WritePCMData(48000, 1, _data, OutFolder); + } + return AudioFolders; +} + +std::vector VitsSvc::InferPCMData(const std::vector& PCMData, long srcSr, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) const +{ + auto hubertin = InferTools::InterpResample(PCMData, srcSr, 16000); + int64_t SpeakerIdx = _InferParams.SpeakerId; + if (SpeakerIdx >= SpeakerCount) + SpeakerIdx = SpeakerCount; + if (SpeakerIdx < 0) + SpeakerIdx = 0; + std::mt19937 gen(int(_InferParams.Seed)); + std::normal_distribution normal(0, 1); + float noise_scale = _InferParams.NoiseScale; + float ddsp_noise_scale = _InferParams.DDSPNoiseScale; + + const int64_t inputShape[3] = { 1i64,1i64,(int64_t)hubertin.size() }; + std::vector inputTensorshu; + inputTensorshu.emplace_back(Ort::Value::CreateTensor(*memory_info, hubertin.data(), hubertin.size(), inputShape, 3)); + std::vector hubertOut; + + try { + hubertOut = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + inputTensorshu.data(), + inputTensorshu.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what())) + } + auto HubertSize = hubertOut[0].GetTensorTypeAndShapeInfo().GetElementCount(); + auto HubertOutPutData = hubertOut[0].GetTensorMutableData(); + auto HubertOutPutShape = hubertOut[0].GetTensorTypeAndShapeInfo().GetShape(); + inputTensorshu.clear(); + if (HubertOutPutShape[2] != HiddenUnitKDims) + LibDLVoiceCodecThrow("HiddenUnitKDims UnMatch") + + std::vector HiddenUnitsSrc(HubertOutPutData, HubertOutPutData + HubertSize); + + if (EnableCluster && _InferParams.ClusterRate > 0.001f) + { + const auto clus_size = HubertOutPutShape[1]; + const auto pts = Cluster->find(HiddenUnitsSrc.data(), long(SpeakerIdx), clus_size); + for (size_t indexs = 0; indexs < HiddenUnitsSrc.size(); ++indexs) + HiddenUnitsSrc[indexs] = HiddenUnitsSrc[indexs] * (1.f - _InferParams.ClusterRate) + pts[indexs] * _InferParams.ClusterRate; + } + + const auto HubertLen = int64_t(HubertSize) / HiddenUnitKDims; + int64_t F0Shape[] = { 1, int64_t(PCMData.size() / HopSize) }; + int64_t HiddenUnitShape[] = { 1, HubertLen, HiddenUnitKDims }; + constexpr int64_t LengthShape[] = { 1 }; + int64_t CharaEmbShape[] = { 1 }; + int64_t CharaMixShape[] = { F0Shape[1], SpeakerCount }; + int64_t RandnShape[] = { 1, 192, F0Shape[1] }; + const int64_t IstftShape[] = { 1, 2048, F0Shape[1] }; + int64_t RandnCount = F0Shape[1] * 192; + const int64_t IstftCount = F0Shape[1] * 2048; + + std::vector RandnInput, IstftInput, UV, InterpedF0; + std::vector alignment; + int64_t XLength[1] = { HubertLen }; + std::vector Nsff0; + //auto SoVitsInput = soVitsInput; + int64_t Chara[] = { SpeakerIdx }; + std::vector charaMix; + + const auto F0Extractor = MoeVSF0Extractor::GetF0Extractor(_InferParams.F0Method, _samplingRate, HopSize); + + auto srcF0Data = F0Extractor->ExtractF0(PCMData, PCMData.size() / HopSize); + for (auto& ifo : srcF0Data) + ifo *= (float)pow(2.0, static_cast(_InferParams.Keys) / 12.0); + std::vector HiddenUnits; + std::vector F0Data; + + std::vector _Tensors; + std::vector SoVitsInput = soVitsInput; + + //Compatible with all versions + if (VitsSvcVersion == L"SoVits3.0") + { + int64_t upSample = _samplingRate / 16000; + HiddenUnits.reserve(HubertSize * (upSample + 1)); + for (int64_t itS = 0; itS < HiddenUnitShape[1]; ++itS) + for (int64_t itSS = 0; itSS < upSample; ++itSS) + HiddenUnits.insert(HiddenUnits.end(), HiddenUnitsSrc.begin() + itS * HiddenUnitKDims, HiddenUnitsSrc.begin() + (itS + 1) * HiddenUnitKDims); + HiddenUnitShape[1] *= upSample; + HubertSize *= upSample; + F0Data = _TensorExtractor->GetInterpedF0(InferTools::InterpFunc(srcF0Data, long(srcF0Data.size()), long(HiddenUnitShape[1]))); + F0Shape[1] = HiddenUnitShape[1]; + XLength[0] = HiddenUnitShape[1]; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, XLength, 1, LengthShape, 1)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, F0Data.data(), F0Data.size(), F0Shape, 2)); + } + else if (VitsSvcVersion == L"SoVits2.0") + { + HiddenUnits = std::move(HiddenUnitsSrc); + F0Shape[1] = HiddenUnitShape[1]; + F0Data = InferTools::InterpFunc(srcF0Data, long(srcF0Data.size()), long(HiddenUnitShape[1])); + XLength[0] = HiddenUnitShape[1]; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, XLength, 1, LengthShape, 1)); + Nsff0 = _TensorExtractor->GetNSFF0(F0Data); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Nsff0.data(), Nsff0.size(), F0Shape, 2)); + } + else if (VitsSvcVersion == L"RVC") + { + constexpr int64_t upSample = 2; + HiddenUnits.reserve(HubertSize * (upSample + 1)); + for (int64_t itS = 0; itS < HiddenUnitShape[1]; ++itS) + for (int64_t itSS = 0; itSS < upSample; ++itSS) + HiddenUnits.insert(HiddenUnits.end(), HiddenUnitsSrc.begin() + itS * HiddenUnitKDims, HiddenUnitsSrc.begin() + (itS + 1) * HiddenUnitKDims); + HiddenUnitShape[1] *= upSample; + HubertSize *= upSample; + F0Data = InferTools::InterpFunc(srcF0Data, long(srcF0Data.size()), long(HiddenUnitShape[1])); + F0Shape[1] = HiddenUnitShape[1]; + XLength[0] = HiddenUnitShape[1]; + RandnCount = 192 * F0Shape[1]; + RandnShape[2] = F0Shape[1]; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, XLength, 1, LengthShape, 1)); + InterpedF0 = _TensorExtractor->GetInterpedF0(F0Data); + Nsff0 = _TensorExtractor->GetNSFF0(InterpedF0); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Nsff0.data(), Nsff0.size(), F0Shape, 2)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, InterpedF0.data(), InterpedF0.size(), F0Shape, 2)); + SoVitsInput = RVCInput; + RandnInput = std::vector(RandnCount, 0.f); + for (auto& it : RandnInput) + it = normal(gen) * noise_scale; + } + else + { + HiddenUnits = std::move(HiddenUnitsSrc); + F0Data = InferTools::InterpFunc(srcF0Data, long(srcF0Data.size()), long(F0Shape[1])); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + InterpedF0 = _TensorExtractor->GetInterpedF0(F0Data); + alignment = _TensorExtractor->GetAligments(F0Shape[1], HubertLen); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, InterpedF0.data(), InterpedF0.size(), F0Shape, 2)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, alignment.data(), InterpedF0.size(), F0Shape, 2)); + if (VitsSvcVersion != L"SoVits4.0-DDSP") + { + UV = _TensorExtractor->GetUV(F0Data); + SoVitsInput = { "c", "f0", "mel2ph", "uv", "noise", "sid" }; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, UV.data(), UV.size(), F0Shape, 2)); + } + else + { + SoVitsInput = { "c", "f0", "mel2ph", "t_window", "noise", "sid" }; + IstftInput = std::vector(IstftCount, ddsp_noise_scale); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, IstftInput.data(), IstftInput.size(), IstftShape, 3)); + } + RandnInput = std::vector(RandnCount, 0.f); + for (auto& it : RandnInput) + it = normal(gen) * noise_scale; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, RandnInput.data(), RandnCount, RandnShape, 3)); + } + + + if (EnableCharaMix) + { + CharaMixShape[0] = F0Shape[1]; + std::vector charaMap(SpeakerCount, 0.f); + charaMap[SpeakerIdx] = 1.f; + charaMix.reserve((SpeakerCount + 1) * F0Shape[1]); + for (int64_t index = 0; index < F0Shape[1]; ++index) + charaMix.insert(charaMix.end(), charaMap.begin(), charaMap.end()); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, charaMix.data(), charaMix.size(), CharaMixShape, 2)); + } + else + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Chara, 1, CharaEmbShape, 1)); + + if (VitsSvcVersion == L"RVC") + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, RandnInput.data(), RandnCount, RandnShape, 3)); + + std::vector VolumeData; + + if (EnableVolume) + { + SoVitsInput.emplace_back("vol"); + VolumeData = ExtractVolume(PCMData, HopSize); + VolumeData.resize(F0Shape[1], 0.f); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, VolumeData.data(), F0Shape[1], F0Shape, 2)); + } + + std::vector finaOut; + + finaOut = VitsSvcModel->Run(Ort::RunOptions{ nullptr }, + SoVitsInput.data(), + _Tensors.data(), + _Tensors.size(), + soVitsOutput.data(), + soVitsOutput.size()); + + const auto dstWavLen = finaOut[0].GetTensorTypeAndShapeInfo().GetShape()[2]; + std::vector TempVecWav(dstWavLen, 0); + for (int64_t bbb = 0; bbb < dstWavLen; bbb++) + TempVecWav[bbb] = static_cast(Clamp(finaOut[0].GetTensorData()[bbb]) * 32766.0f); + if(VitsSvcVersion == L"RVC") + TempVecWav = InferTools::InterpResample(TempVecWav, _samplingRate, 48000, 1i16); + return TempVecWav; +} + +/*std::vector VitsSvc::MelExtractor(const float* PCMAudioBegin, const float* PCMAudioEnd) const +{ + std::vector _SRAudio{PCMAudioBegin, PCMAudioEnd}; + _SRAudio = InferTools::InterpResample(_SRAudio, _samplingRate, 16000, 1.f); + const auto _MelLength = int64_t(_SRAudio.size() * shallow_diffusion->GetSamplingRate() / 16000ull / shallow_diffusion->GetHopSize()); + const auto _SpecLength = (int64_t)ceil(double(_SRAudio.size()) / 16000. * 100.); + auto Alignment = _TensorExtractor->GetAligments(_MelLength, _SpecLength); + Alignment.resize(_MelLength); + std::vector AudioTensors; + const int64_t AudioInputShape[] = { 1,1,int64_t(_SRAudio.size()) }; + const int64_t AligShape[] = { 1,_MelLength }; + AudioTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, _SRAudio.data(), _SRAudio.size(), AudioInputShape, 3)); + AudioTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Alignment.data(), Alignment.size(), AligShape, 2)); + try + { + return stft_operator->Run(Ort::RunOptions{ nullptr }, + StftInput.data(), + AudioTensors.data(), + AudioTensors.size(), + StftOutput.data(), + StftOutput.size() + ); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: ShallowDiffusionStftOperator\n") + e.what()).c_str()); + } +}*/ + +//已弃用(旧MoeSS的推理函数) +#ifdef MOESSDFN +std::vector VitsSvc::InferBatch() const +{ + std::wstring RawPath; + int ret = InsertMessageToEmptyEditBox(RawPath); + if (ret == -1) + LibDLVoiceCodecThrow("TTS Does Not Support Automatic Completion"); + if (ret == -2) + LibDLVoiceCodecThrow("Please Select Files"); + RawPath += L'\n'; + std::vector _Lens = CutLens(RawPath); + const auto params = _get_init_params(); + auto tran = static_cast(params.keys); + auto threshold = static_cast(params.threshold); + auto minLen = static_cast(params.minLen); + auto frame_len = static_cast(params.frame_len); + auto frame_shift = static_cast(params.frame_shift); + if (frame_shift < 512 || frame_shift > 10240) + frame_shift = 512; + if (frame_len < 1024 || frame_len > 20480) + frame_len = 1024; + if (frame_shift > frame_len) + { + frame_shift = 512; + frame_len = 4 * 1024; + } + if (minLen < 3 || minLen > 30) + minLen = 3; + if (threshold < 10.0 || threshold > 2000.0) + threshold = 30.0; + int64_t charEmb = params.chara; + + std::mt19937 gen(int(params.seed)); + std::normal_distribution normal(0, 1); + float noise_scale = params.noise_scale; + float ddsp_noise_scale = params.noise_scale_w; + if (noise_scale > 50.0f) + noise_scale = 1.0f; + if (ddsp_noise_scale > 50.0f) + ddsp_noise_scale = 1.0f; + for (auto& path : _Lens) + { + logger.log(L"[Inferring] Inferring \"" + path + L'\"'); + size_t proc = 0; + if (path[0] == L'\"') + path = path.substr(1); + if (path[path.length() - 1] == L'\"') + path.pop_back(); + std::vector _data; + //Audio + logger.log(L"[Inferring] PreProcessing \"" + path + L"\" Encoder"); + auto RawWav = AudioPreprocess().codec(path, _samplingRate); + auto HubertWav = AudioPreprocess().codec(path, 16000); + auto info = cutWav(RawWav, threshold, minLen, static_cast(frame_len), static_cast(frame_shift)); + for (size_t i = 1; i < info.cutOffset.size(); ++i) + if ((info.cutOffset[i] - info.cutOffset[i - 1]) / RawWav.getHeader().bytesPerSec > 90) + LibDLVoiceCodecThrow("Reached max slice length, please change slicer param"); + + const auto LenFactor = ((double)16000 / (double)_samplingRate); + _callback(proc, info.cutTag.size()); + logger.log(L"[Inferring] Inferring \"" + path + L"\" Svc"); + for (size_t i = 1; i < info.cutOffset.size(); ++i) + { + if (info.cutTag[i - 1]) + { + auto featureInput = F0PreProcess(_samplingRate, (short)hop); + const auto len = (info.cutOffset[i] - info.cutOffset[i - 1]) / 2; + const auto srcPos = info.cutOffset[i - 1] / 2; + std::vector source(len, 0.0); + + for (unsigned long long j = 0; j < len; ++j) + source[j] = (double)RawWav[srcPos + j] / 32768.0; + + //hubertIn + const auto hubertInLen = (size_t)((double)len * LenFactor); + std::vector hubertin(hubertInLen, 0.0); + const auto startPos = (size_t)(((double)info.cutOffset[i - 1] / 2.0) * LenFactor); + for (size_t j = 0; j < hubertInLen; ++j) + hubertin[j] = (float)HubertWav[startPos + j] / 32768.0f; + + //hubert + const int64_t inputShape[3] = { 1i64,1i64,(int64_t)hubertInLen }; + std::vector inputTensors; + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, hubertin.data(), hubertInLen, inputShape, 3)); + std::vector hubertOut; + + try { + hubertOut = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + inputTensors.data(), + inputTensors.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what()).c_str()); + } + auto hubertOutData = hubertOut[0].GetTensorMutableData(); + auto hubertOutLen = hubertOut[0].GetTensorTypeAndShapeInfo().GetElementCount(); + auto hubertOutShape = hubertOut[0].GetTensorTypeAndShapeInfo().GetShape(); + + std::vector inputPitch1, alignment; + std::vector inputPitch2; + F0PreProcess::V4Return v4_return; + std::vector hubOutData(hubertOutData, hubertOutData + hubertOutLen); + if (SV3) + { + if (_samplingRate / 16000 == 2) + { + for (int64_t itS = hubertOutShape[1] - 1; itS + 1; --itS) + { + hubOutData.insert(hubOutData.begin() + itS * 256, hubOutData.begin() + itS * 256, hubOutData.begin() + (itS + 1) * 256); + } + hubertOutShape[1] *= 2; + hubertOutLen *= 2; + inputPitch2 = featureInput.GetF0AndOtherInput3(source.data(), (long long)len, (long long)hubertOutShape[1], (long long)tran); + } + else if (_samplingRate / 16000 == 3) + { + for (int64_t itS = hubertOutShape[1] - 1; itS + 1; --itS) + { + hubOutData.insert(hubOutData.begin() + itS * 256, hubOutData.begin() + itS * 256, hubOutData.begin() + (itS + 1) * 256); + hubOutData.insert(hubOutData.begin() + itS * 256, hubOutData.begin() + itS * 256, hubOutData.begin() + (itS + 1) * 256); + } + hubertOutShape[1] *= 3; + hubertOutLen *= 3; + inputPitch2 = featureInput.GetF0AndOtherInput3(source.data(), (long long)len, (long long)hubertOutShape[1], (long long)tran); + } + else + LibDLVoiceCodecThrow("SoVits3.0 Only Support Sr: 48K,32K"); + } + else if (SV4) + { + alignment = getAligments(len / hop, hubertOutShape[1]); + v4_return = featureInput.GetF0AndOtherInput4(source.data(), (long long)len, tran); + } + else if (_modelType == modelType::RVC) + { + for (int64_t itS = hubertOutShape[1] - 1; itS + 1; --itS) + { + hubOutData.insert(hubOutData.begin() + itS * 256, hubOutData.begin() + itS * 256, hubOutData.begin() + (itS + 1) * 256); + } + hubertOutShape[1] *= 2; + hubertOutLen *= 2; + auto f0data = featureInput.GetF0AndOtherInputR(source.data(), (long long)len, (long long)hubertOutShape[1], (long long)tran); + inputPitch1 = std::move(f0data.f0); + inputPitch2 = std::move(f0data.f0f); + } + else + { + inputPitch1 = featureInput.GetF0AndOtherInput(source.data(), (long long)len, (long long)hubertOutShape[1], (long long)tran); + } + + inputTensors.clear(); + auto SoVitsInput = soVitsInput; + //vits + constexpr long long ashape[1] = { 1 }; + long long ainput[1] = { hubertOutShape[1] }; + const long long bshape[2] = { 1, featureInput.getLen() }; + long long cdata[1] = { charEmb }; + constexpr long long cshape[1] = { 1 }; + const int64 zinputShape[3] = { 1,192,featureInput.getLen() }; + const int64 zinputCount = featureInput.getLen() * 192; + std::vector zinput(zinputCount, 0.0); + + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, hubOutData.data(), hubertOutLen, hubertOutShape.data(), 3)); + std::vector T_Window(2048 * featureInput.getLen(), ddsp_noise_scale); + + int64_t T_WindowShape[] = { 1, 2048, featureInput.getLen() }; + if (!SV4) + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, ainput, 1, ashape, 1)); + if (SV3) + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, inputPitch2.data(), featureInput.getLen(), bshape, 2)); + else if (SV4) + { + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, v4_return.f0.data(), featureInput.getLen(), bshape, 2)); + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, alignment.data(), featureInput.getLen(), bshape, 2)); + + if (!SVV2) + { + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, v4_return.uv.data(), featureInput.getLen(), bshape, 2)); + SoVitsInput = { "c", "f0", "mel2ph", "uv", "noise", "sid" }; + } + else + { + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, T_Window.data(), T_Window.size(), T_WindowShape, 3)); + SoVitsInput = { "c", "f0", "mel2ph", "t_window", "noise", "sid" }; + } + for (auto& it : zinput) + it = normal(gen) * noise_scale; + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, zinput.data(), zinputCount, zinputShape, 3)); + } + else + { + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, inputPitch1.data(), featureInput.getLen(), bshape, 2)); + if (_modelType == modelType::RVC) + { + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, inputPitch2.data(), featureInput.getLen(), bshape, 2)); + SoVitsInput = RVCInput; + for (auto& it : zinput) + it = normal(gen) * noise_scale; + } + } + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, cdata, 1, cshape, 1)); + std::vector finaOut; + if (_modelType == modelType::RVC) + inputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, zinput.data(), zinputCount, zinputShape, 3)); + try + { + finaOut = VitsSvcModel->Run(Ort::RunOptions{ nullptr }, + SoVitsInput.data(), + inputTensors.data(), + inputTensors.size(), + soVitsOutput.data(), + soVitsOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: sovits\n") + e.what()).c_str()); + } + + const auto shapeOut = finaOut[0].GetTensorTypeAndShapeInfo().GetShape(); + const auto dstWavLen = int64_t(len); + std::vector TempVecWav(dstWavLen, 0); + if (shapeOut[2] < dstWavLen) + { + for (int64_t bbb = 0; bbb < shapeOut[2]; bbb++) + TempVecWav[bbb] = static_cast(finaOut[0].GetTensorData()[bbb] * 32768.0f); + } + else + { + for (int64_t bbb = 0; bbb < dstWavLen; bbb++) + TempVecWav[bbb] = static_cast(finaOut[0].GetTensorData()[bbb] * 32768.0f); + } + _data.insert(_data.end(), TempVecWav.data(), TempVecWav.data() + (dstWavLen)); + } + else + { + const auto len = (info.cutOffset[i] - info.cutOffset[i - 1]) / 2; + const auto data = new int16_t[len]; + memset(data, 0, len * 2); + _data.insert(_data.end(), data, data + len); + delete[] data; + } + _callback(++proc, info.cutTag.size()); + } + logger.log(L"[Inferring] \"" + path + L"\" Finished"); + if (_Lens.size() == 1) + { + logger.log(L"[Info] Finished, Send To FrontEnd"); + return _data; + } + std::wstring outPutPath = GetCurrentFolder() + L"\\OutPuts\\" + path.substr(path.rfind(L'\\') + 1, path.rfind(L'.')) + L'-' + std::to_wstring(uint64_t(path.data())) + L".wav"; + logger.log(L"[Inferring] Write To \"" + outPutPath + L'\"'); + Wav(_samplingRate, long(_data.size()) * 2, _data.data()).Writef(outPutPath); + } + logger.log(L"[Info] Finished"); + return {}; +} +#endif + +/* +std::vector VitsSvc::InferSliceTensor(const MoeVSProjectSpace::MoeVSAudioSlice& _Slice, size_t SliceIdx, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, std::vector& _Tensors, + std::vector& SoVitsInput) const +{ + int64_t charEmb = _InferParams.SpeakerId; + float noise_scale = _InferParams.NoiseScale; + float ddsp_noise_scale = _InferParams.DDSPNoiseScale; + + auto RawWav = TensorPreprocess::MoeVoiceStudioTensorExtractor::InterpResample(_Slice.Audio[SliceIdx], 48000, 16000, 32768.0f); + const int64_t HubertInputShape[3] = { 1i64,1i64,(int64_t)RawWav.size() }; + std::vector HubertInputTensors, HubertOutPuts; + HubertInputTensors.emplace_back(Ort::Value::CreateTensor(*memory_info, RawWav.data(), RawWav.size(), HubertInputShape, 3)); + try { + HubertOutPuts = hubert->Run(Ort::RunOptions{ nullptr }, + hubertInput.data(), + HubertInputTensors.data(), + HubertInputTensors.size(), + hubertOutput.data(), + hubertOutput.size()); + } + catch (Ort::Exception& e) + { + LibDLVoiceCodecThrow((std::string("Locate: hubert\n") + e.what()).c_str()); + } + auto HubertSize = HubertOutPuts[0].GetTensorTypeAndShapeInfo().GetElementCount(); + auto HubertOutPutData = HubertOutPuts[0].GetTensorMutableData(); + auto HubertOutPutShape = HubertOutPuts[0].GetTensorTypeAndShapeInfo().GetShape(); + HubertInputTensors.clear(); + if (HubertOutPutShape[2] != HiddenUnitKDims) + LibDLVoiceCodecThrow("HiddenUnitKDims UnMatch"); + + std::vector srcHiddenUnits(HubertOutPutData, HubertOutPutData + HubertSize); + + if (EnableCluster && _InferParams.ClusterRate > 0.001f) + { + for (int64_t indexs = 0; indexs < HubertOutPutShape[1]; ++indexs) + { + const auto curbeg = srcHiddenUnits.data() + indexs * HubertOutPutShape[2]; + const auto curend = srcHiddenUnits.data() + (indexs + 1) * HubertOutPutShape[2]; + const auto hu = Cluster->find({ curbeg ,curend }, long(charEmb)); + for (int64_t ind = 0; ind < HubertOutPutShape[2]; ++ind) + *(curbeg + ind) = *(curbeg + ind) * (1.f - _InferParams.ClusterRate) + hu[ind] * _InferParams.ClusterRate; + } + } + + const auto HubertLen = int64_t(HubertSize) / HiddenUnitKDims; + int64_t F0Shape[] = { 1, int64_t(_Slice.Audio[SliceIdx].size() * _samplingRate / 48000 / HopSize) }; + int64_t HiddenUnitShape[] = { 1, HubertLen, HiddenUnitKDims }; + constexpr int64_t LengthShape[] = { 1 }; + int64_t CharaEmbShape[] = { 1 }; + int64_t CharaMixShape[] = { F0Shape[1], SpeakerCount }; + int64_t RandnShape[] = { 1, 192, F0Shape[1] }; + const int64_t IstftShape[] = { 1, 2048, F0Shape[1] }; + int64_t RandnCount = F0Shape[1] * 192; + const int64_t IstftCount = F0Shape[1] * 2048; + + std::vector RandnInput, IstftInput, UV, InterpedF0; + std::vector alignment; + int64_t XLength[1] = { HubertLen }; + std::vector Nsff0; + //auto SoVitsInput = soVitsInput; + int64_t Chara[] = { charEmb }; + std::vector charaMix; + + auto srcF0Data = _Slice.F0[SliceIdx]; + for (auto& ifo : srcF0Data) + ifo *= (float)pow(2.0, static_cast(_InferParams.Keys) / 12.0); + std::vector HiddenUnits; + std::vector F0Data; + + //Compatible with all versions + if (SoVits3) + { + int64_t upSample = _samplingRate / 16000; + HiddenUnits.reserve(HubertSize * (upSample + 1)); + for (int64_t itS = 0; itS < HiddenUnitShape[1]; ++itS) + for (int64_t itSS = 0; itSS < upSample; ++itSS) + HiddenUnits.insert(HiddenUnits.end(), srcHiddenUnits.begin() + itS * HiddenUnitKDims, srcHiddenUnits.begin() + (itS + 1) * HiddenUnitKDims); + HiddenUnitShape[1] *= upSample; + HubertSize *= upSample; + F0Data = GetInterpedF0(InterpFunc(srcF0Data, long(srcF0Data.size()), long(HiddenUnitShape[1]))); + F0Shape[1] = HiddenUnitShape[1]; + XLength[0] = HiddenUnitShape[1]; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, XLength, 1, LengthShape, 1)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, F0Data.data(), F0Data.size(), F0Shape, 2)); + } + else if (SoVits2) + { + HiddenUnits = std::move(srcHiddenUnits); + F0Shape[1] = HiddenUnitShape[1]; + F0Data = InterpFunc(srcF0Data, long(srcF0Data.size()), long(HiddenUnitShape[1])); + XLength[0] = HiddenUnitShape[1]; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, XLength, 1, LengthShape, 1)); + Nsff0 = GetNSFF0(F0Data); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Nsff0.data(), Nsff0.size(), F0Shape, 2)); + } + else if (_modelType == modelType::RVC) + { + constexpr int64_t upSample = 2; + HiddenUnits.reserve(HubertSize * (upSample + 1)); + for (int64_t itS = 0; itS < HiddenUnitShape[1]; ++itS) + for (int64_t itSS = 0; itSS < upSample; ++itSS) + HiddenUnits.insert(HiddenUnits.end(), srcHiddenUnits.begin() + itS * HiddenUnitKDims, srcHiddenUnits.begin() + (itS + 1) * HiddenUnitKDims); + HiddenUnitShape[1] *= upSample; + HubertSize *= upSample; + F0Data = InterpFunc(srcF0Data, long(srcF0Data.size()), long(HiddenUnitShape[1])); + F0Shape[1] = HiddenUnitShape[1]; + XLength[0] = HiddenUnitShape[1]; + RandnCount = 192 * F0Shape[1]; + RandnShape[2] = F0Shape[1]; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, XLength, 1, LengthShape, 1)); + InterpedF0 = GetInterpedF0(F0Data); + Nsff0 = GetNSFF0(InterpedF0); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Nsff0.data(), Nsff0.size(), F0Shape, 2)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, InterpedF0.data(), InterpedF0.size(), F0Shape, 2)); + SoVitsInput = RVCInput; + RandnInput = std::vector(RandnCount, 0.f); + for (auto& it : RandnInput) + it = normal(gen) * noise_scale; + } + else + { + HiddenUnits = std::move(srcHiddenUnits); + F0Data = InterpFunc(srcF0Data, long(srcF0Data.size()), long(F0Shape[1])); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, HiddenUnits.data(), HubertSize, HiddenUnitShape, 3)); + InterpedF0 = GetInterpedF0(F0Data); + alignment = GetAligments(F0Shape[1], HubertLen); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, InterpedF0.data(), InterpedF0.size(), F0Shape, 2)); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, alignment.data(), InterpedF0.size(), F0Shape, 2)); + if (!SoVitsDDSP) + { + UV = GetUV(F0Data); + SoVitsInput = { "c", "f0", "mel2ph", "uv", "noise", "sid" }; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, UV.data(), UV.size(), F0Shape, 2)); + } + else + { + SoVitsInput = { "c", "f0", "mel2ph", "t_window", "noise", "sid" }; + IstftInput = std::vector(IstftCount, ddsp_noise_scale); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, IstftInput.data(), IstftInput.size(), IstftShape, 3)); + } + RandnInput = std::vector(RandnCount, 0.f); + for (auto& it : RandnInput) + it = normal(gen) * noise_scale; + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, RandnInput.data(), RandnCount, RandnShape, 3)); + } + + + if (EnableCharaMix) + { + if (SpeakerCount > 1) + charaMix = GetCurrectSpkMixData(_Slice.Speaker[SliceIdx], F0Shape[1]); + CharaMixShape[0] = F0Shape[1]; + if (charaMix.empty()) + { + std::vector charaMap(SpeakerCount, 0.f); + charaMap[charEmb] = 1.f; + charaMix.reserve((SpeakerCount + 1) * F0Shape[1]); + for (int64_t index = 0; index < F0Shape[1]; ++index) + charaMix.insert(charaMix.end(), charaMap.begin(), charaMap.end()); + } + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, charaMix.data(), charaMix.size(), CharaMixShape, 2)); + } + else + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, Chara, 1, CharaEmbShape, 1)); + + if (_modelType == modelType::RVC) + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, RandnInput.data(), RandnCount, RandnShape, 3)); + + std::vector VolumeData; + + if (EnableVolume) + { + SoVitsInput.emplace_back("vol"); + VolumeData = InterpFunc(_Slice.Volume[SliceIdx], long(_Slice.Volume[SliceIdx].size()), long(F0Shape[1])); + _Tensors.emplace_back(Ort::Value::CreateTensor(*memory_info, VolumeData.data(), F0Shape[1], F0Shape, 2)); + } + + return VitsSvcModel->Run(Ort::RunOptions{ nullptr }, + SoVitsInput.data(), + _Tensors.data(), + _Tensors.size(), + soVitsOutput.data(), + soVitsOutput.size()); +} + */ + +#ifdef WIN32 +#ifdef MoeVSMui + +void VitsSvc::StartRT(Mui::Window::UIWindowBasic* window, const MoeVSProjectSpace::MoeVSSvcParams& _Params) +{ + if (RTSTAT) + return; + + std::wstring error; + + recoder = new MRecorder(); + recoder->InitRecorder(error, _samplingRate, 1, 16, Mui::_m_uint(_samplingRate * 0.5)); + recoder->Start(); + + audio_player = new Mui::Render::MDS_AudioPlayer(window); + audio_player->InitAudioPlayer(error); + + audio_stream = audio_player->CreateStreamPCM(recoder->GetFrameSize(), recoder->GetSampleRate(), recoder->GetChannels(), recoder->GetBitRate(), recoder->GetBlockAlign()); + audio_player->PlayTrack(audio_stream); + + emptyLen = InferPCMData(std::vector(recoder->GetFrameSize() / 4 * 3), _samplingRate, _Params).size(); + + std::thread RT_RECORD_THREAD = std::thread([&]() + { + logger.log(L"[RTInference] Recording Thread Start!"); + while (RTSTAT) + { + const auto PCM = recoder->GetStreamData(); + if (!PCM) continue; + rawInputBuffer.emplace_back((int16_t*)PCM->data, (int16_t*)(PCM->data + PCM->size)); + delete PCM; + } + logger.log(L"[RTInference] Recording Thread End!"); + }); + + std::thread RT_INPUT_CROSSFADE_THREAD = std::thread([&]() + { + logger.log(L"[RTInference] Input Crossfade Thread Start!"); + //const auto config = _get_init_params(); + while (RTSTAT) + { + if (rawInputBuffer.size() > 1) + { + std::vector pBuffer; + pBuffer.reserve(2 * rawInputBuffer[0].size()); + pBuffer.insert(pBuffer.end(), rawInputBuffer[0].begin(), rawInputBuffer[0].end()); + pBuffer.insert(pBuffer.end(), rawInputBuffer[1].begin(), + rawInputBuffer[1].begin() + int64_t(rawInputBuffer[1].size()) / 2); + inputBuffer.emplace_back(std::move(pBuffer)); + rawInputBuffer.pop_front(); + } + if (rawInputBuffer.size() > 100) + { + logger.log(L"[RTInferenceWarn] Raw Input Buffer Is Too Large"); + } + } + logger.log(L"[RTInference] Input Crossfade Thread End!"); + }); + + std::thread RT_INFERENCE_THREAD = std::thread([&]() + { + logger.log(L"[RTInference] Inferencing Thread Start!"); + const auto PCMSIZE = recoder->GetFrameSize() / 2; + while (RTSTAT) + { + //const auto configs = _get_init_params(); + if (!inputBuffer.empty()) + { + try + { + bool zeroVector = true; + for(const auto& i16data : inputBuffer[0]) + { + if(i16data> 1600) + { + zeroVector = false; + break; + } + } + if(zeroVector) + rawOutputBuffer.emplace_back(std::vector(emptyLen, 0)); + else + { + auto RtRES = InferPCMData(inputBuffer[0], _samplingRate, _Params); + rawOutputBuffer.emplace_back(std::move(RtRES)); + } + inputBuffer.pop_front(); + } + catch (std::exception& e) + { + RTSTAT = false; + inputBuffer.clear(); + outputBuffer.clear(); + rawInputBuffer.clear(); + rawOutputBuffer.clear(); + LibDLVoiceCodecThrow(e.what()); + } + } + if (inputBuffer.size() > 100) + { + logger.log(L"[RTInferenceWarn] Input Buffer Is Too Large"); + } + } + logger.log(L"[RTInference] Inferencing Thread End!"); + }); + + std::thread RT_OUTPUT_CROSSFADE_THREAD = std::thread([&]() + { + logger.log(L"[RTInference] OutPut Crossfade Thread Start!"); + while (RTSTAT) + { + if (rawOutputBuffer.size() > 1) + { + const auto cross_fade_size = int64_t(rawOutputBuffer[0].size()) / 3; + const auto PCMSIZE = cross_fade_size * 2; + for (int64_t i = 0; i < cross_fade_size; ++i) + { + rawOutputBuffer[0][PCMSIZE + i] = int16_t(((double)rawOutputBuffer[0][PCMSIZE + i] * (1.0 - (double(i) / double(cross_fade_size)))) + ((double)rawOutputBuffer[1][i] * (double(i) / double(cross_fade_size)))); + rawOutputBuffer[1][PCMSIZE + i] = int16_t(((double)rawOutputBuffer[0][PCMSIZE + i] * (1.0 - (double(i) / double(cross_fade_size)))) + ((double)rawOutputBuffer[1][i] * (double(i) / double(cross_fade_size)))); + } + std::vector pBuffer(rawOutputBuffer[0].data() + cross_fade_size / 2, rawOutputBuffer[0].data() + 2 * cross_fade_size + cross_fade_size / 2); + outputBuffer.emplace_back(std::move(pBuffer)); + rawOutputBuffer.pop_front(); + } + } + logger.log(L"[RTInference] OutPut Crossfade Thread End!"); + }); + + std::thread RT_OUTPUT_THREAD = std::thread([&]() + { + logger.log(L"[RTInference] OutPut Thread Start!"); + const auto PCMSIZE = recoder->GetFrameSize(); + std::vector pBuffer(PCMSIZE); + Mui::_m_size lastSize = 0; + while (RTSTAT) + { + if (!outputBuffer.empty()) + { + //audio_player->WriteStreamPCM(audio_stream, (Mui::_m_byte*)outputBuffer.front().data(), Mui::_m_size(RTAudioSize)); + //outputBuffer.pop_front(); + auto queue_data = (Mui::_m_byte*)outputBuffer[0].data(); + auto queue_dataSize = Mui::_m_size(outputBuffer[0].size() * 2); + while (queue_dataSize + lastSize >= PCMSIZE) + { + //每次从队列中拿走帧大小的数据 + const auto dataSize = PCMSIZE - lastSize; + memcpy(pBuffer.data() + lastSize, queue_data, dataSize); + //调用WriteStream 播放 + audio_player->WriteStreamPCM(audio_stream, pBuffer.data(), PCMSIZE); + //指针偏移 + queue_data += dataSize; + queue_dataSize -= dataSize; + lastSize = 0; + } + //如果剩余数据不足PCMLength + if (queue_dataSize > 0) + { + //检查本次queue能否把缓冲区填满 + if (lastSize + queue_dataSize >= PCMSIZE) + { + //计算剩余空间 + const auto dataSize = PCMSIZE - lastSize; + //复制数据到buffer + memcpy(pBuffer.data() + lastSize, queue_data, dataSize); + audio_player->WriteStreamPCM(audio_stream, pBuffer.data(), PCMSIZE); + queue_data += dataSize; + queue_dataSize -= dataSize; + lastSize = 0; + } + //如果剩余空间仍然不足PCMLength,将剩余数据复制到buffer中 等待下次循环 + if (queue_dataSize > 0) + { + memcpy(pBuffer.data() + lastSize, queue_data, queue_dataSize); + lastSize += queue_dataSize; + } + } + logger.log(L"OUTPUTBUFFERLEN:" + std::to_wstring(outputBuffer.size())); + logger.log(L"INPUTBUFFERLEN:" + std::to_wstring(inputBuffer.size())); + outputBuffer.pop_front(); + } + } + logger.log(L"[RTInference] OutPut Thread End!"); + }); + + logger.log(L"[RTInference] Start RTInference!"); + for (uint64_t idx = 0; idx < 1; ++idx) + rawInputBuffer.emplace_back(recoder->GetFrameSize() / 2, 0); + RTSTAT = true; + RT_RECORD_THREAD.detach(); + RT_INPUT_CROSSFADE_THREAD.detach(); + RT_INFERENCE_THREAD.detach(); + RT_OUTPUT_CROSSFADE_THREAD.detach(); + RT_OUTPUT_THREAD.detach(); + while (true); +} + +void VitsSvc::EndRT() +{ + recoder->Stop(); + audio_player->StopTrack(audio_stream); + audio_player->DeleteTrack(audio_stream); + delete audio_stream; + delete audio_player; + delete recoder; + audio_stream = nullptr; + audio_player = nullptr; + recoder = nullptr; + if (RTSTAT) + RTSTAT = false; + inputBuffer.clear(); + outputBuffer.clear(); + rawInputBuffer.clear(); + rawOutputBuffer.clear(); +} +#endif +#endif + +MoeVoiceStudioCoreEnd \ No newline at end of file diff --git a/libsvc/Modules/src/Modules.cpp b/libsvc/Modules/src/Modules.cpp new file mode 100644 index 0000000..a31e1da --- /dev/null +++ b/libsvc/Modules/src/Modules.cpp @@ -0,0 +1,359 @@ +#include "../header/Modules.hpp" +#include "../header/InferTools/F0Extractor/DioF0Extractor.hpp" +#include "../header/InferTools/F0Extractor/F0ExtractorManager.hpp" +#include "../header/InferTools/TensorExtractor/MoeVSCoreTensorExtractor.hpp" +#include "../header/InferTools/Cluster/MoeVSClusterManager.hpp" +#include "../header/InferTools/Cluster/MoeVSKmeansCluster.hpp" +#include "../header/InferTools/F0Extractor/HarvestF0Extractor.hpp" +#include "../header/InferTools/Sampler/MoeVSSamplerManager.hpp" +#include "../header/InferTools/Sampler/MoeVSSamplers.hpp" +#include "../header/InferTools/F0Extractor/NetF0Predictors.hpp" +#include "../header/InferTools/Stft/stft.hpp" +#ifdef MoeVoiceStudioIndexCluster +#ifdef max +#undef max +#endif +#include "../header/InferTools/Cluster/MoeVSIndexCluster.hpp" +#endif + +#define MoeVSRegisterF0Constructor(__RegisterName, __ClassName) MoeVSF0Extractor::RegisterF0Extractor(__RegisterName, \ + [](int32_t sampling_rate, int32_t hop_size, \ + int32_t n_f0_bins, double max_f0, double min_f0) \ + -> MoeVSF0Extractor::F0Extractor \ + { \ + return new MoeVSF0Extractor::__ClassName(sampling_rate, hop_size, n_f0_bins, max_f0, min_f0); \ + }) + +#define MoeVSRegisterTensorConstructor(__RegisterName, __ClassName) MoeVSTensorPreprocess::RegisterTensorExtractor(__RegisterName,\ + [](uint64_t _srcsr, uint64_t _sr, uint64_t _hop, \ + bool _smix, bool _volume, uint64_t _hidden_size, \ + uint64_t _nspeaker, \ + const MoeVSTensorPreprocess::MoeVoiceStudioTensorExtractor::Others& _other) \ + ->MoeVSTensorPreprocess::TensorExtractor \ + { \ + return new MoeVSTensorPreprocess::__ClassName(_srcsr, _sr, _hop, _smix, _volume, \ + _hidden_size, _nspeaker, _other); \ + }) + +#define MoeVSRegisterCluster(__RegisterName, __ClassName) MoeVoiceStudioCluster::RegisterMoeVSCluster(__RegisterName,\ + [](const std::wstring& _path, size_t hidden_size, size_t KmeansLen) \ + ->MoeVoiceStudioCluster::MoeVSCluster \ + { \ + return new MoeVoiceStudioCluster::__ClassName(_path, hidden_size, KmeansLen); \ + }) + +#define MoeVSRegisterSampler(__RegisterName, __ClassName) MoeVSSampler::RegisterMoeVSSampler(__RegisterName, \ + [](Ort::Session* alpha, Ort::Session* dfn, Ort::Session* pred, int64_t Mel_Bins, \ + const MoeVSSampler::MoeVSBaseSampler::ProgressCallback& _ProgressCallback, \ + Ort::MemoryInfo* memory) -> MoeVSSampler::MoeVSSampler \ + { \ + return new MoeVSSampler::__ClassName(alpha, dfn, pred, Mel_Bins, _ProgressCallback, memory); \ + }) + +namespace MoeVSModuleManager +{ + bool MoeVoiceStudioCoreInitStat = false; + + MoeVoiceStudioCore::VitsSvc* GlobalVitsSvcModel = nullptr; + + MoeVoiceStudioCore::DiffusionSvc* GlobalDiffusionSvcModel = nullptr; + + MoeVoiceStudioCore::MoeVoiceStudioModule::ProgressCallback ProgessBar; + + int64_t SpeakerCount = 0; + int64_t SamplingRate = 32000; + int32_t VocoderHopSize = 512; + int32_t VocoderMelBins = 128; + int64_t VitsSamplingRate = 32000; + + int64_t& GetSamplingRate() + { + return SamplingRate; + } + + int64_t& GetSpeakerCount() + { + return SpeakerCount; + } + + int32_t& GetVocoderHopSize() + { + return VocoderHopSize; + } + + int32_t& GetVocoderMelBins() + { + return VocoderMelBins; + } + + void MoeVoiceStudioCoreInitSetup() + { + if (MoeVoiceStudioCoreInitStat) + return; + MoeVSRegisterF0Constructor(L"Dio", DioF0Extractor); + MoeVSRegisterF0Constructor(L"Harvest", HarvestF0Extractor); + MoeVSRegisterF0Constructor(L"RMVPE", RMVPEF0Extractor); + MoeVSRegisterF0Constructor(L"FCPE", MELPEF0Extractor); + MoeVSRegisterTensorConstructor(L"SoVits2.0", SoVits2TensorExtractor); + MoeVSRegisterTensorConstructor(L"SoVits3.0", SoVits3TensorExtractor); + MoeVSRegisterTensorConstructor(L"SoVits4.0", SoVits4TensorExtractor); + MoeVSRegisterTensorConstructor(L"SoVits4.0-DDSP", SoVits4DDSPTensorExtractor); + MoeVSRegisterTensorConstructor(L"RVC", RVCTensorExtractor); + MoeVSRegisterTensorConstructor(L"DiffSvc", DiffSvcTensorExtractor); + MoeVSRegisterTensorConstructor(L"DiffusionSvc", DiffusionSvcTensorExtractor); + MoeVSRegisterCluster(L"KMeans", KMeansCluster); +#ifdef MoeVoiceStudioIndexCluster + MoeVSRegisterCluster(L"Index", IndexCluster); +#endif + MoeVSRegisterSampler(L"Pndm", PndmSampler); + MoeVSRegisterSampler(L"DDim", DDimSampler); + MoeVoiceStudioCoreInitStat = true; + } + + MoeVoiceStudioCore::VitsSvc* GetVitsSvcModel() + { + return GlobalVitsSvcModel; + } + + MoeVoiceStudioCore::DiffusionSvc* GetDiffusionSvcModel() + { + return GlobalDiffusionSvcModel; + } + + void UnloadVitsSvcModel() + { + delete GlobalVitsSvcModel; + GlobalVitsSvcModel = nullptr; + } + + void UnloadDiffusionSvcModel() + { + delete GlobalDiffusionSvcModel; + GlobalDiffusionSvcModel = nullptr; + } + + void LoadVitsSvcModel(const MJson& Config, + const MoeVoiceStudioCore::MoeVoiceStudioModule::ProgressCallback& Callback, + int ProviderID, int NumThread, int DeviceID) + { + ProgessBar = Callback; + UnloadVitsSvcModel(); + if (Config["Type"].GetString() == "DiffSvc") + LibDLVoiceCodecThrow("Trying To Load Diffusion Model As VitsSvc Model!") + + GlobalVitsSvcModel = new MoeVoiceStudioCore::VitsSvc( + Config, Callback, + MoeVoiceStudioCore::MoeVoiceStudioModule::ExecutionProviders(ProviderID), + DeviceID, NumThread + ); + + VitsSamplingRate = GlobalVitsSvcModel->GetSamplingRate(); + SamplingRate = VitsSamplingRate; + } + + void LoadDiffusionSvcModel(const MJson& Config, + const MoeVoiceStudioCore::MoeVoiceStudioModule::ProgressCallback& Callback, + int ProviderID, int NumThread, int DeviceID) + { + ProgessBar = Callback; + UnloadDiffusionSvcModel(); + if (Config["Type"].GetString() == "DiffSvc") + GlobalDiffusionSvcModel = new MoeVoiceStudioCore::DiffusionSvc( + Config, Callback, + MoeVoiceStudioCore::MoeVoiceStudioModule::ExecutionProviders(ProviderID), + DeviceID, NumThread + ); + else + LibDLVoiceCodecThrow("Trying To Load VitsSvc Model As Diffusion Model!") + + if(VocoderEnabled()) + if (!GetVitsSvcModel() || (!GlobalDiffusionSvcModel->OldVersion() && GlobalDiffusionSvcModel->GetDiffSvcVer() == L"DiffusionSvc")) + SamplingRate = GlobalDiffusionSvcModel->GetSamplingRate(); + } + + void LoadVocoderModel(const std::wstring& VocoderPath) + { + MoeVoiceStudioCore::LoadVocoderModel(VocoderPath); + } + + void UnloadVocoderModel() + { + MoeVoiceStudioCore::UnLoadVocoderModel(); + } + + bool VocoderEnabled() + { + return MoeVoiceStudioCore::VocoderEnabled(); + } + + std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcData& _Slice, + const MoeVSProjectSpace::MoeVSSvcParams& _InferParams) + { + const bool DiffusionModelEnabled = GlobalDiffusionSvcModel && VocoderEnabled(); + std::vector RtnAudio; + size_t TotalAudioSize = 0; + for (const auto& data_size : _Slice.Slices) + TotalAudioSize += data_size.OrgLen; + RtnAudio.reserve(size_t(double(TotalAudioSize) * 1.5)); + + //VitsSteps + int64_t GlobalSteps = 0; + if (GlobalVitsSvcModel) + GlobalSteps = 1; + + //DiffusionSteps + auto SkipDiffusionStep = (int64_t)_InferParams.Pndm; + auto DiffusionTotalStep = (int64_t)_InferParams.Step; + + if (DiffusionModelEnabled && DiffusionTotalStep > GlobalDiffusionSvcModel->GetMaxStep()) + DiffusionTotalStep = GlobalDiffusionSvcModel->GetMaxStep(); + if (SkipDiffusionStep >= DiffusionTotalStep) + SkipDiffusionStep = DiffusionTotalStep / 5; + if (SkipDiffusionStep == 0) + SkipDiffusionStep = 1; + const auto RealDiffSteps = DiffusionTotalStep % SkipDiffusionStep ? DiffusionTotalStep / SkipDiffusionStep + 1 : DiffusionTotalStep / SkipDiffusionStep; + if((DiffusionModelEnabled && !GlobalVitsSvcModel) || (DiffusionModelEnabled && ShallowDiffusionEnabled())) + { + if (GlobalDiffusionSvcModel->OldVersion()) + GlobalSteps += 1; + else + GlobalSteps += RealDiffSteps; + } + + //TotalSteps + const int64_t TotalSteps = GlobalSteps * int64_t(_Slice.Slices.size()); + size_t ProgressVal = 0; + size_t SliceIndex = 0; + ProgessBar(0, TotalSteps); + for (const auto& CurSlice : _Slice.Slices) + { + const auto InferBeginTime = clock(); + const auto CurRtn = SliceInference(CurSlice, _InferParams, ProgressVal); + RtnAudio.insert(RtnAudio.end(), CurRtn.data(), CurRtn.data() + CurRtn.size()); + if (CurSlice.IsNotMute) + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Segment[" + std::to_wstring(SliceIndex++) + L"] Finished! Segment Use Time: " + std::to_wstring(clock() - InferBeginTime) + L"ms, Segment Duration: " + std::to_wstring((size_t)CurSlice.OrgLen * 1000ull / 48000ull) + L"ms"); + else + { + if (DiffusionModelEnabled && !GlobalDiffusionSvcModel->OldVersion()) + { + ProgressVal += RealDiffSteps; + ProgessBar(ProgressVal, TotalSteps); + } + logger.log(L"[Inferring] Inferring \"" + _Slice.Path + L"\", Jump Empty Segment[" + std::to_wstring(SliceIndex++) + L"]!"); + } + if (DiffusionModelEnabled && GlobalDiffusionSvcModel->OldVersion()) + ProgessBar(++ProgressVal, TotalSteps); + if (GlobalVitsSvcModel) + ProgessBar(++ProgressVal, TotalSteps); + } + + logger.log(L"[Inferring] \"" + _Slice.Path + L"\" Finished"); + + return RtnAudio; + } + + int CurStftSr = -1, CurHopSize = -1, CurMelBins = -1; + + DlCodecStft::Mel* MelOperator = nullptr; + + void ReloadMelOps(int SamplingRate_I64, int Hopsize_I64, int MelBins_I64) + { + if (CurStftSr != SamplingRate_I64 || CurHopSize != Hopsize_I64 || CurMelBins != MelBins_I64) + { + delete MelOperator; + CurStftSr = SamplingRate_I64; + CurHopSize = Hopsize_I64; + CurMelBins = MelBins_I64; + MelOperator = new DlCodecStft::Mel(Hopsize_I64 * 4, CurHopSize, CurStftSr, CurMelBins); + } + } + + std::vector Enhancer(std::vector& Mel, const std::vector& F0, size_t MelSize) + { + auto Rf0 = F0; + if (Rf0.size() != MelSize) + Rf0 = InferTools::InterpFunc(Rf0, (long)Rf0.size(), (long)MelSize); + return MoeVoiceStudioCore::VocoderInfer( + Mel, + Rf0, + VocoderMelBins, + (int64_t)MelSize, + GlobalVitsSvcModel->GetMemoryInfo() + ); + } + + std::vector SliceInference(const MoeVSProjectSpace::MoeVoiceStudioSvcSlice& _Slice, const MoeVSProjectSpace::MoeVSSvcParams& _InferParams, size_t& _Process) + { + const bool DiffusionModelEnabled = GlobalDiffusionSvcModel && VocoderEnabled(); + int64_t SamplingRate_I64 = VitsSamplingRate; + SamplingRate = VitsSamplingRate; + if ((DiffusionModelEnabled && !GlobalVitsSvcModel) || (_InferParams.UseShallowDiffusion && DiffusionModelEnabled && ShallowDiffusionEnabled())) + { + SamplingRate_I64 = GlobalDiffusionSvcModel->GetSamplingRate(); + SamplingRate = GlobalDiffusionSvcModel->GetSamplingRate(); + } + if (!_Slice.IsNotMute) + return { size_t(_Slice.OrgLen * SamplingRate_I64 / 48000), 0i16, std::allocator() }; + std::vector RtnAudio; + RtnAudio.reserve(size_t(double(_Slice.Audio.size()) * 1.5)); + + if (GlobalVitsSvcModel) + { + auto BgnTime = clock(); + RtnAudio = GlobalVitsSvcModel->SliceInference(_Slice, _InferParams, _Process); + logger.log(("[Inference] Slice Vits Use Time " + std::to_string(clock() - BgnTime) + "ms").c_str()); + if(_InferParams.UseShallowDiffusion && DiffusionModelEnabled && !GlobalDiffusionSvcModel->OldVersion() && GlobalDiffusionSvcModel->GetDiffSvcVer() == L"DiffusionSvc") + { + ReloadMelOps( + (int)SamplingRate_I64, + GlobalDiffusionSvcModel->GetHopSize(), + (int)GlobalDiffusionSvcModel->GetMelBins() + ); + + const auto TempAudio = InferTools::InterpResample(RtnAudio, (long)VitsSamplingRate, CurStftSr, 32767.); + auto Mel = MelOperator->operator()(TempAudio); + auto& ShallData = MoeVoiceStudioCore::GetDataForShallowDiffusion(); + BgnTime = clock(); + RtnAudio = GlobalDiffusionSvcModel->ShallowDiffusionInference( + ShallData._16KAudio, _InferParams, Mel, + ShallData.NeedPadding ? ShallData.CUDAF0 : _Slice.F0, + ShallData.NeedPadding ? ShallData.CUDAVolume : _Slice.Volume, + ShallData.NeedPadding ? ShallData.CUDASpeaker : _Slice.Speaker, + _Process, + (int64_t)TempAudio.size() + ); + logger.log(("[Inference] Slice Diffusion Use Time " + std::to_string(clock() - BgnTime) + "ms").c_str()); + } + if(_InferParams.UseShallowDiffusion && VocoderEnabled() && !GetDiffusionSvcModel()) + { + ReloadMelOps( + (int)SamplingRate_I64, + VocoderHopSize, + VocoderMelBins + ); + const auto TempAudio = InferTools::InterpResample(RtnAudio, (long)SamplingRate_I64, (long)SamplingRate_I64, 32767.); + auto Mel = MelOperator->operator()(TempAudio); + const auto& ShallData = MoeVoiceStudioCore::GetDataForShallowDiffusion(); + const auto SrcSize = RtnAudio.size(); + RtnAudio = Enhancer(Mel.first, ShallData.NeedPadding ? ShallData.CUDAF0 : _Slice.F0, (size_t)Mel.second); + RtnAudio.resize(SrcSize, 0); + } + } + else if (DiffusionModelEnabled) + { + const auto BgnTime = clock(); + RtnAudio = GlobalDiffusionSvcModel->SliceInference(_Slice, _InferParams, _Process); + logger.log(("[Inference] Slice Diffusion Use Time " + std::to_string(clock() - BgnTime) + "ms").c_str()); + } + else + LibDLVoiceCodecThrow("You Must Load A Model To Inference!"); + + return RtnAudio; + } + + bool ShallowDiffusionEnabled() + { + const bool DiffusionModelEnabled = GlobalDiffusionSvcModel && VocoderEnabled(); + return DiffusionModelEnabled && !GlobalDiffusionSvcModel->OldVersion() && GlobalDiffusionSvcModel->GetDiffSvcVer() == L"DiffusionSvc"; + } +} \ No newline at end of file diff --git a/libsvc/Modules/src/StringPreprocess.cpp b/libsvc/Modules/src/StringPreprocess.cpp new file mode 100644 index 0000000..1023808 --- /dev/null +++ b/libsvc/Modules/src/StringPreprocess.cpp @@ -0,0 +1,76 @@ +#include "../header/StringPreprocess.hpp" +#ifdef _WIN32 +#include +#else +#error +#endif + +std::string to_byte_string(const std::wstring& input) +{ + std::vector ByteString(input.length() * 6); + WideCharToMultiByte( + CP_UTF8, + 0, + input.c_str(), + int(input.length()), + ByteString.data(), + int(ByteString.size()), + nullptr, + nullptr + ); + return ByteString.data(); +} + +std::string to_ansi_string(const std::wstring& input) +{ + std::vector ByteString(input.length() * 6); + WideCharToMultiByte( + CP_ACP, + 0, + input.c_str(), + int(input.length()), + ByteString.data(), + int(ByteString.size()), + nullptr, + nullptr + ); + return ByteString.data(); +} + +std::wstring to_wide_string(const std::string& input) +{ + std::vector WideString(input.length() * 2); + MultiByteToWideChar( + CP_UTF8, + 0, + input.c_str(), + int(input.length()), + WideString.data(), + int(WideString.size()) + ); + return WideString.data(); +} + +std::wstring string_vector_to_string(const std::vector& vector) +{ + std::wstring vecstr = L"["; + for (const auto& it : vector) + if (!it.empty()) + vecstr += L'\"' + to_wide_string(it) + L"\", "; + if (vecstr.length() > 2) + vecstr = vecstr.substr(0, vecstr.length() - 2); + vecstr += L']'; + return vecstr; +} + +std::wstring wstring_vector_to_string(const std::vector& vector) +{ + std::wstring vecstr = L"["; + for (const auto& it : vector) + if (!it.empty()) + vecstr += L'\"' + it + L"\", "; + if (vecstr.length() > 2) + vecstr = vecstr.substr(0, vecstr.length() - 2); + vecstr += L']'; + return vecstr; +} \ No newline at end of file diff --git a/libsvc/dllmain.cpp b/libsvc/dllmain.cpp new file mode 100644 index 0000000..bbb8edc --- /dev/null +++ b/libsvc/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "Windows.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/libsvc/libsvc.vcxproj b/libsvc/libsvc.vcxproj new file mode 100644 index 0000000..f1b59ca --- /dev/null +++ b/libsvc/libsvc.vcxproj @@ -0,0 +1,250 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {dc31109b-1984-41db-bd9f-4261675c3962} + libsvc + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)libsvc\Modules\Lib\World\src\world;$(SolutionDir)Lib\ffmpeg-4.2.1\include;$(SolutionDir)libsvc\Modules\Lib\MJson;$(SolutionDir)Lib\OnnxRuntimeDmlProvider\build\native\include;$(SolutionDir)Lib\faiss;$(SolutionDir)Lib\openblas\include;$(SolutionDir)Lib\stft;$(IncludePath) + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)libsvc\Modules\Lib\World\src\world;$(SolutionDir)Lib\ffmpeg-4.2.1\include;$(SolutionDir)libsvc\Modules\Lib\MJson;$(SolutionDir)Lib\OnnxRuntimeDmlProvider\build\native\include;$(SolutionDir)Lib\faiss;$(SolutionDir)Lib\openblas\include;$(SolutionDir)Lib\stft;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;LIBSVC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;LIBSVC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;LIBSVC_EXPORTS;_WINDOWS;_USRDLL;MOEVSDMLPROVIDER;MoeVoiceStudioIndexCluster;LibSvcDll;YYJSON_EXPORTS;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp17 + $(SolutionDir)Lib;%(AdditionalIncludeDirectories) + MultiThreadedDebug + + + Windows + true + false + $(SolutionDir)Lib\ffmpeg-4.2.1\Lib;$(SolutionDir)Lib\OnnxRuntimeDmlProvider\runtimes\win-x64\native;$(SolutionDir)Lib\;$(SolutionDir)Lib\openblas\lib;$(SolutionDir)Lib\stft;%(AdditionalLibraryDirectories) + avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;onnxruntime.lib;fftw3.lib;libopenblas.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;LIBSVC_EXPORTS;_WINDOWS;_USRDLL;MOEVSDMLPROVIDER;MoeVoiceStudioIndexCluster;LibSvcDll;YYJSON_EXPORTS;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp17 + $(SolutionDir)Lib;%(AdditionalIncludeDirectories) + MultiThreaded + + + Windows + true + true + true + false + $(SolutionDir)Lib\ffmpeg-4.2.1\Lib;$(SolutionDir)Lib\OnnxRuntimeDmlProvider\runtimes\win-x64\native;$(SolutionDir)Lib\;$(SolutionDir)Lib\openblas\lib;$(SolutionDir)Lib\stft;%(AdditionalLibraryDirectories) + avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;onnxruntime.lib;fftw3.lib;libopenblas.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsvc/libsvc.vcxproj.filters b/libsvc/libsvc.vcxproj.filters new file mode 100644 index 0000000..b158735 --- /dev/null +++ b/libsvc/libsvc.vcxproj.filters @@ -0,0 +1,363 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {fb0b5757-fc04-4d88-8597-2d94fe79e2cc} + + + {54cbaedb-ecbf-4fb3-87b5-36ebc8c6cfce} + + + {9b0ef72f-b6a6-40a5-b235-1976820fe35c} + + + {aad5c051-397f-436b-9b11-fec3ca01aa9c} + + + {6cfaac44-b900-49e1-8fc9-35f8f9ab6e18} + + + {9e2f1339-c577-45aa-a509-5dddb4d9d542} + + + {eb24584c-3751-4dbe-823e-90d25c93772e} + + + {c4adc16a-e759-4f1c-b5e6-3d76f4505e02} + + + {c7deefd2-d55c-4b5a-80d8-1cac74f545cc} + + + {e99697a0-cd74-43bc-ae4c-bfa1299311f4} + + + {df45b238-81cd-49bf-bf84-ec6e97b35b83} + + + {d98ecef8-77c5-4e4a-915c-4d71f0ea665d} + + + {4a90fefb-d821-42c0-aba2-b31ba87ffaaa} + + + {8c7cf0e0-76ea-40a0-82de-2e020b44c65f} + + + {c579373a-26a1-4979-b8ba-93b8aee12708} + + + {94e998a7-0897-4c87-9ee0-1426f372b6b0} + + + {3d85cc85-c9e7-4d29-92a2-57b044c10fc8} + + + {7ce28dec-70cd-445a-9a99-b11ebfc0635e} + + + {65e1efd6-d194-4317-abb3-fa252719bef8} + + + {d60edd1b-50d6-4880-a09e-6f1a1d8b4f70} + + + {cfa6e269-3e22-43ed-8f28-fd50b9bf9d18} + + + {965d3ac6-1a15-45bf-890e-af0cf1e0e3c4} + + + {01dc10ac-ad67-4d10-b7a7-92cb14cf5fe3} + + + {c1060cbd-4688-4a7d-88f7-d81f38533cb8} + + + {7e0d0066-c7ce-4445-a80c-d93cda1fc5fe} + + + {6c8a8d59-2b2b-4b2f-8dbb-3ccc3f802e9a} + + + {bc3612f1-00b0-4d01-97b0-2eb66f7b33be} + + + {3405ef93-0ba8-495b-9110-22b821411ce9} + + + + + 头文件\Modules\Models + + + 头文件\Modules\Models + + + 头文件\Modules\Models + + + 头文件\Modules\Models + + + 头文件\Modules\Models + + + 头文件\Modules\Models + + + 头文件\Modules + + + 头文件\Modules + + + 头文件\Modules\Logger + + + 头文件\Modules\InferTools\AVCodec + + + 头文件\Modules\InferTools\Cluster + + + 头文件\Modules\InferTools\Cluster + + + 头文件\Modules\InferTools\Cluster + + + 头文件\Modules\InferTools\Cluster + + + 头文件\Modules\InferTools\DataStruct + + + 头文件\Modules\InferTools\F0Extractor + + + 头文件\Modules\InferTools\F0Extractor + + + 头文件\Modules\InferTools\F0Extractor + + + 头文件\Modules\InferTools\F0Extractor + + + 头文件\Modules\InferTools\F0Extractor + + + 头文件\Modules\InferTools\Sampler + + + 头文件\Modules\InferTools\Sampler + + + 头文件\Modules\InferTools\Sampler + + + 头文件\Modules\InferTools\Stft + + + 头文件\Modules\InferTools\TensorExtractor + + + 头文件\Modules\InferTools\TensorExtractor + + + 头文件\Modules\InferTools\TensorExtractor + + + 头文件\Modules\InferTools + + + 头文件 + + + 头文件\Lib\Json + + + 头文件\Lib\Json + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + 头文件\Lib\World + + + + + 源文件 + + + 源文件\Modules\Models + + + 源文件\Modules\Models + + + 源文件\Modules\Models + + + 源文件\Modules\Models + + + 源文件\Modules\Models + + + 源文件\Modules\Models + + + 源文件\Modules\Logger + + + 源文件\Modules\InferTools + + + 源文件\Modules\InferTools\TensorExtractor + + + 源文件\Modules\InferTools\TensorExtractor + + + 源文件\Modules\InferTools\TensorExtractor + + + 源文件\Modules\InferTools\Stft + + + 源文件\Modules\InferTools\Sampler + + + 源文件\Modules\InferTools\Sampler + + + 源文件\Modules\InferTools\Sampler + + + 源文件\Modules\InferTools\F0Extractor + + + 源文件\Modules\InferTools\F0Extractor + + + 源文件\Modules\InferTools\F0Extractor + + + 源文件\Modules\InferTools\F0Extractor + + + 源文件\Modules\InferTools\F0Extractor + + + 源文件\Modules\InferTools\DataStruct + + + 源文件\Modules\InferTools\Cluster + + + 源文件\Modules\InferTools\Cluster + + + 源文件\Modules\InferTools\Cluster + + + 源文件\Modules\InferTools\Cluster + + + 源文件\Modules\InferTools\AVCodec + + + 源文件\Modules + + + 源文件\Modules + + + 源文件\Lib\Json + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\World + + + 源文件\Lib\Json + + + \ No newline at end of file diff --git a/libsvc/libsvc.vcxproj.user b/libsvc/libsvc.vcxproj.user new file mode 100644 index 0000000..dc63f8a --- /dev/null +++ b/libsvc/libsvc.vcxproj.user @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file