react-native-nitro-onnx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/NOTICE +241 -0
  2. package/NitroOnnxSpeech.podspec +51 -0
  3. package/Package.swift +52 -0
  4. package/README.md +398 -0
  5. package/android/CMakeLists.txt +117 -0
  6. package/android/build.gradle +65 -0
  7. package/android/src/main/AndroidManifest.xml +6 -0
  8. package/android/src/main/assets/silero_vad.onnx +0 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +24 -0
  10. package/android/src/main/java/com/margelo/nitro/onnx/speech/OnnxSpeechPackage.kt +67 -0
  11. package/assets/silero_vad.onnx +0 -0
  12. package/cpp/AndroidPthreadCompat.cpp +16 -0
  13. package/cpp/AsrEngine.cpp +277 -0
  14. package/cpp/AsrEngine.hpp +110 -0
  15. package/cpp/AudioFileReader.cpp +177 -0
  16. package/cpp/AudioFileReader.hpp +26 -0
  17. package/cpp/AudioUtils.cpp +28 -0
  18. package/cpp/AudioUtils.hpp +38 -0
  19. package/cpp/ModelSingleton.hpp +51 -0
  20. package/cpp/NitroOnnxSpeech.cpp +68 -0
  21. package/cpp/NitroOnnxSpeech.hpp +42 -0
  22. package/cpp/OfflineAsr.cpp +81 -0
  23. package/cpp/OfflineAsr.hpp +33 -0
  24. package/cpp/ResourceDir.cpp +29 -0
  25. package/cpp/ResourceDir.hpp +18 -0
  26. package/cpp/SpeakerEngine.cpp +170 -0
  27. package/cpp/SpeakerEngine.hpp +70 -0
  28. package/cpp/SpeakerManager.cpp +93 -0
  29. package/cpp/SpeakerManager.hpp +43 -0
  30. package/cpp/StreamingAsr.cpp +123 -0
  31. package/cpp/StreamingAsr.hpp +55 -0
  32. package/cpp/ThreadPool.cpp +41 -0
  33. package/cpp/ThreadPool.hpp +62 -0
  34. package/cpp/Tts.cpp +132 -0
  35. package/cpp/Tts.hpp +38 -0
  36. package/cpp/TtsEngine.cpp +222 -0
  37. package/cpp/TtsEngine.hpp +77 -0
  38. package/cpp/Vad.cpp +126 -0
  39. package/cpp/Vad.hpp +62 -0
  40. package/cpp/VadEngine.cpp +217 -0
  41. package/cpp/VadEngine.hpp +126 -0
  42. package/ios/OnnxSpeechInitializer.mm +37 -0
  43. package/ios/PrivacyInfo.xcprivacy +14 -0
  44. package/lib/index.d.ts +11 -0
  45. package/lib/index.d.ts.map +1 -0
  46. package/lib/index.js +19 -0
  47. package/lib/index.js.map +1 -0
  48. package/lib/specs/OnnxSpeech.nitro.d.ts +264 -0
  49. package/lib/specs/OnnxSpeech.nitro.d.ts.map +1 -0
  50. package/lib/specs/OnnxSpeech.nitro.js +6 -0
  51. package/lib/specs/OnnxSpeech.nitro.js.map +1 -0
  52. package/nitro.json +19 -0
  53. package/nitrogen/generated/.gitattributes +1 -0
  54. package/nitrogen/generated/android/NitroOnnxSpeech+autolinking.cmake +86 -0
  55. package/nitrogen/generated/android/NitroOnnxSpeech+autolinking.gradle +27 -0
  56. package/nitrogen/generated/android/NitroOnnxSpeechOnLoad.cpp +49 -0
  57. package/nitrogen/generated/android/NitroOnnxSpeechOnLoad.hpp +34 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onnx/speech/NitroOnnxSpeechOnLoad.kt +35 -0
  59. package/nitrogen/generated/ios/NitroOnnxSpeech+autolinking.rb +62 -0
  60. package/nitrogen/generated/ios/NitroOnnxSpeech-Swift-Cxx-Bridge.cpp +17 -0
  61. package/nitrogen/generated/ios/NitroOnnxSpeech-Swift-Cxx-Bridge.hpp +27 -0
  62. package/nitrogen/generated/ios/NitroOnnxSpeech-Swift-Cxx-Umbrella.hpp +38 -0
  63. package/nitrogen/generated/ios/NitroOnnxSpeechAutolinking.mm +35 -0
  64. package/nitrogen/generated/ios/NitroOnnxSpeechAutolinking.swift +16 -0
  65. package/nitrogen/generated/shared/c++/AsrModelConfig.hpp +142 -0
  66. package/nitrogen/generated/shared/c++/AsrModelType.hpp +112 -0
  67. package/nitrogen/generated/shared/c++/AsrResult.hpp +105 -0
  68. package/nitrogen/generated/shared/c++/HybridOfflineAsrSpec.cpp +25 -0
  69. package/nitrogen/generated/shared/c++/HybridOfflineAsrSpec.hpp +73 -0
  70. package/nitrogen/generated/shared/c++/HybridOnnxSpeechSpec.cpp +27 -0
  71. package/nitrogen/generated/shared/c++/HybridOnnxSpeechSpec.hpp +82 -0
  72. package/nitrogen/generated/shared/c++/HybridSpeakerManagerSpec.cpp +28 -0
  73. package/nitrogen/generated/shared/c++/HybridSpeakerManagerSpec.hpp +77 -0
  74. package/nitrogen/generated/shared/c++/HybridStreamingAsrSpec.cpp +32 -0
  75. package/nitrogen/generated/shared/c++/HybridStreamingAsrSpec.hpp +81 -0
  76. package/nitrogen/generated/shared/c++/HybridTtsSpec.cpp +26 -0
  77. package/nitrogen/generated/shared/c++/HybridTtsSpec.hpp +74 -0
  78. package/nitrogen/generated/shared/c++/HybridVadSpec.cpp +31 -0
  79. package/nitrogen/generated/shared/c++/HybridVadSpec.hpp +81 -0
  80. package/nitrogen/generated/shared/c++/RegisteredSpeaker.hpp +91 -0
  81. package/nitrogen/generated/shared/c++/SpeakerEmbeddingConfig.hpp +91 -0
  82. package/nitrogen/generated/shared/c++/TtsModelConfig.hpp +178 -0
  83. package/nitrogen/generated/shared/c++/TtsModelType.hpp +88 -0
  84. package/nitrogen/generated/shared/c++/TtsResult.hpp +91 -0
  85. package/nitrogen/generated/shared/c++/VadConfig.hpp +100 -0
  86. package/nitrogen/generated/shared/c++/VadSegment.hpp +91 -0
  87. package/package.json +62 -0
  88. package/scripts/prepare-sherpa-onnx.js +236 -0
  89. package/scripts/test-cpp.js +24 -0
  90. package/src/index.ts +45 -0
  91. package/src/specs/OnnxSpeech.nitro.ts +326 -0
@@ -0,0 +1,43 @@
1
+ // ------------------------------------------------------------------------------
2
+ // SpeakerManager.hpp
3
+ // ------------------------------------------------------------------------------
4
+ #pragma once
5
+
6
+ #include "SpeakerEngine.hpp"
7
+
8
+ #include <NitroModules/Promise.hpp>
9
+ #include <NitroModules/HybridObject.hpp>
10
+ #include <HybridSpeakerManagerSpec.hpp>
11
+
12
+ #include <memory>
13
+
14
+ namespace margelo::nitro::onnx::speech {
15
+
16
+ class SpeakerManager : public HybridSpeakerManagerSpec {
17
+ public:
18
+ static constexpr auto TAG = "SpeakerManager";
19
+
20
+ SpeakerManager(std::shared_ptr<ThreadPool> threadPool, std::string cacheDir);
21
+ ~SpeakerManager() override;
22
+
23
+ std::shared_ptr<Promise<void>> load(const SpeakerEmbeddingConfig& config) override;
24
+ bool isLoaded() override;
25
+ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> computeEmbedding(
26
+ const std::shared_ptr<ArrayBuffer>& samples) override;
27
+ std::shared_ptr<Promise<RegisteredSpeaker>> registerSpeaker(
28
+ const std::string& id,
29
+ const std::string& name,
30
+ const std::shared_ptr<ArrayBuffer>& embedding) override;
31
+ std::shared_ptr<Promise<RegisteredSpeaker>> registerSpeakerFromFile(
32
+ const std::string& id,
33
+ const std::string& name,
34
+ const std::string& path) override;
35
+ std::shared_ptr<Promise<std::vector<RegisteredSpeaker>>> listSpeakers() override;
36
+ std::shared_ptr<Promise<void>> removeSpeaker(const std::string& id) override;
37
+ std::shared_ptr<Promise<void>> unload() override;
38
+
39
+ private:
40
+ SpeakerEngine engine_;
41
+ };
42
+
43
+ } // namespace margelo::nitro::onnx::speech
@@ -0,0 +1,123 @@
1
+ // ------------------------------------------------------------------------------
2
+ // StreamingAsr.cpp
3
+ // ------------------------------------------------------------------------------
4
+ #include "StreamingAsr.hpp"
5
+
6
+ #include "AudioUtils.hpp"
7
+
8
+ #include <optional>
9
+
10
+ namespace margelo::nitro::onnx::speech {
11
+
12
+ namespace {
13
+
14
+ AsrResult toAsrResult(const AsrEngineResult& native) {
15
+ std::vector<double> timestamps;
16
+ timestamps.reserve(native.timestamps.size());
17
+ for (float t : native.timestamps) {
18
+ timestamps.push_back(static_cast<double>(t));
19
+ }
20
+ return AsrResult(
21
+ native.text,
22
+ std::optional<double>(static_cast<double>(native.score)),
23
+ std::optional<double>(static_cast<double>(native.startMs)),
24
+ std::optional<double>(static_cast<double>(native.endMs)),
25
+ timestamps.empty() ? std::nullopt : std::optional<std::vector<double>>(timestamps),
26
+ native.json.empty() ? std::nullopt : std::optional<std::string>(native.json));
27
+ }
28
+
29
+ } // namespace
30
+
31
+ StreamingAsr::StreamingAsr(std::shared_ptr<ThreadPool> threadPool)
32
+ : HybridObject(TAG), engine_(std::move(threadPool)) {}
33
+
34
+ StreamingAsr::~StreamingAsr() {
35
+ engine_.unload();
36
+ }
37
+
38
+ std::shared_ptr<Promise<void>> StreamingAsr::load(const AsrModelConfig& config) {
39
+ return Promise<void>::async([this, config]() {
40
+ AsrEngineConfig native;
41
+ native.type = config.type;
42
+ native.modelDir = config.modelDir;
43
+ native.tokensPath = config.tokensPath;
44
+ native.encoder = config.encoder.value_or("");
45
+ native.decoder = config.decoder.value_or("");
46
+ native.joiner = config.joiner.value_or("");
47
+ native.numThreads = static_cast<int32_t>(config.numThreads.value_or(2));
48
+ native.decodingMethod = config.decodingMethod.value_or("greedy_search");
49
+ native.maxActivePaths = static_cast<int32_t>(config.maxActivePaths.value_or(4));
50
+ engine_.load(native, shared_cast<StreamingAsr>());
51
+ });
52
+ }
53
+
54
+ bool StreamingAsr::isLoaded() {
55
+ return engine_.isLoaded();
56
+ }
57
+
58
+ std::shared_ptr<Promise<void>> StreamingAsr::acceptWaveform(
59
+ const std::shared_ptr<ArrayBuffer>& samples) {
60
+ return Promise<void>::async([this, samples]() {
61
+ auto floatSamples = bytesToFloatVector(samples->data(), samples->size());
62
+ engine_.acceptWaveform(floatSamples);
63
+ });
64
+ }
65
+
66
+ std::shared_ptr<Promise<AsrResult>> StreamingAsr::finalize() {
67
+ return Promise<AsrResult>::async([this]() { return toAsrResult(engine_.finalize()); });
68
+ }
69
+
70
+ std::shared_ptr<Promise<void>> StreamingAsr::reset() {
71
+ return Promise<void>::async([this]() { engine_.reset(); });
72
+ }
73
+
74
+ std::shared_ptr<Promise<void>> StreamingAsr::unload() {
75
+ return Promise<void>::async([this]() { engine_.unload(); });
76
+ }
77
+
78
+ std::optional<std::function<void(const AsrResult& /* result */)>> StreamingAsr::getOnPartialResult() {
79
+ return onPartialResult_;
80
+ }
81
+
82
+ void StreamingAsr::setOnPartialResult(
83
+ const std::optional<std::function<void(const AsrResult& /* result */)>>& onPartialResult) {
84
+ onPartialResult_ = onPartialResult;
85
+ }
86
+
87
+ std::optional<std::function<void(const AsrResult& /* result */)>> StreamingAsr::getOnFinalResult() {
88
+ return onFinalResult_;
89
+ }
90
+
91
+ void StreamingAsr::setOnFinalResult(
92
+ const std::optional<std::function<void(const AsrResult& /* result */)>>& onFinalResult) {
93
+ onFinalResult_ = onFinalResult;
94
+ }
95
+
96
+ std::optional<std::function<void(const std::string& /* error */)>> StreamingAsr::getOnError() {
97
+ return onError_;
98
+ }
99
+
100
+ void StreamingAsr::setOnError(
101
+ const std::optional<std::function<void(const std::string& /* error */)>>& onError) {
102
+ onError_ = onError;
103
+ }
104
+
105
+ void StreamingAsr::onPartialResult(const AsrEngineResult& result) {
106
+ if (onPartialResult_.has_value()) {
107
+ onPartialResult_.value()(toAsrResult(result));
108
+ }
109
+ }
110
+
111
+ void StreamingAsr::onFinalResult(const AsrEngineResult& result) {
112
+ if (onFinalResult_.has_value()) {
113
+ onFinalResult_.value()(toAsrResult(result));
114
+ }
115
+ }
116
+
117
+ void StreamingAsr::onError(const std::string& error) {
118
+ if (onError_.has_value()) {
119
+ onError_.value()(error);
120
+ }
121
+ }
122
+
123
+ } // namespace margelo::nitro::onnx::speech
@@ -0,0 +1,55 @@
1
+ // ------------------------------------------------------------------------------
2
+ // StreamingAsr.hpp
3
+ // ------------------------------------------------------------------------------
4
+ #pragma once
5
+
6
+ #include "AsrEngine.hpp"
7
+
8
+ #include <NitroModules/Promise.hpp>
9
+ #include <NitroModules/HybridObject.hpp>
10
+ #include <HybridStreamingAsrSpec.hpp>
11
+
12
+ #include <functional>
13
+ #include <memory>
14
+ #include <optional>
15
+
16
+ namespace margelo::nitro::onnx::speech {
17
+
18
+ class StreamingAsr : public HybridStreamingAsrSpec,
19
+ public StreamingAsrListener {
20
+ public:
21
+ static constexpr auto TAG = "StreamingAsr";
22
+
23
+ explicit StreamingAsr(std::shared_ptr<ThreadPool> threadPool);
24
+ ~StreamingAsr() override;
25
+
26
+ std::shared_ptr<Promise<void>> load(const AsrModelConfig& config) override;
27
+ bool isLoaded() override;
28
+ std::shared_ptr<Promise<void>> acceptWaveform(const std::shared_ptr<ArrayBuffer>& samples) override;
29
+ std::shared_ptr<Promise<AsrResult>> finalize() override;
30
+ std::shared_ptr<Promise<void>> reset() override;
31
+ std::shared_ptr<Promise<void>> unload() override;
32
+
33
+ // Nitro event callback getters/setters.
34
+ std::optional<std::function<void(const AsrResult& /* result */)>> getOnPartialResult() override;
35
+ void setOnPartialResult(
36
+ const std::optional<std::function<void(const AsrResult& /* result */)>>& onPartialResult) override;
37
+ std::optional<std::function<void(const AsrResult& /* result */)>> getOnFinalResult() override;
38
+ void setOnFinalResult(
39
+ const std::optional<std::function<void(const AsrResult& /* result */)>>& onFinalResult) override;
40
+ std::optional<std::function<void(const std::string& /* error */)>> getOnError() override;
41
+ void setOnError(
42
+ const std::optional<std::function<void(const std::string& /* error */)>>& onError) override;
43
+
44
+ void onPartialResult(const AsrEngineResult& result) override;
45
+ void onFinalResult(const AsrEngineResult& result) override;
46
+ void onError(const std::string& error) override;
47
+
48
+ private:
49
+ StreamingAsrEngine engine_;
50
+ std::optional<std::function<void(const AsrResult&)>> onPartialResult_;
51
+ std::optional<std::function<void(const AsrResult&)>> onFinalResult_;
52
+ std::optional<std::function<void(const std::string&)>> onError_;
53
+ };
54
+
55
+ } // namespace margelo::nitro::onnx::speech
@@ -0,0 +1,41 @@
1
+ // ------------------------------------------------------------------------------
2
+ // ThreadPool.cpp
3
+ // ------------------------------------------------------------------------------
4
+ #include "ThreadPool.hpp"
5
+
6
+ namespace margelo::nitro::onnx::speech {
7
+
8
+ ThreadPool::ThreadPool(size_t threadCount) {
9
+ for (size_t i = 0; i < threadCount; ++i) {
10
+ workers_.emplace_back([this]() {
11
+ for (;;) {
12
+ std::function<void()> task;
13
+ {
14
+ std::unique_lock<std::mutex> lock(queueMutex_);
15
+ condition_.wait(lock, [this]() { return stop_ || !tasks_.empty(); });
16
+ if (stop_ && tasks_.empty()) {
17
+ return;
18
+ }
19
+ task = std::move(tasks_.front());
20
+ tasks_.pop();
21
+ }
22
+ task();
23
+ }
24
+ });
25
+ }
26
+ }
27
+
28
+ ThreadPool::~ThreadPool() {
29
+ {
30
+ std::unique_lock<std::mutex> lock(queueMutex_);
31
+ stop_ = true;
32
+ }
33
+ condition_.notify_all();
34
+ for (std::thread& worker : workers_) {
35
+ if (worker.joinable()) {
36
+ worker.join();
37
+ }
38
+ }
39
+ }
40
+
41
+ } // namespace margelo::nitro::onnx::speech
@@ -0,0 +1,62 @@
1
+ // ------------------------------------------------------------------------------
2
+ // ThreadPool.hpp
3
+ // A fixed-size thread pool used to run all heavy native inference tasks.
4
+ // Keeping inference off the JS thread is a core requirement of this module.
5
+ // ------------------------------------------------------------------------------
6
+ #pragma once
7
+
8
+ #include <atomic>
9
+ #include <condition_variable>
10
+ #include <functional>
11
+ #include <future>
12
+ #include <mutex>
13
+ #include <queue>
14
+ #include <thread>
15
+ #include <vector>
16
+
17
+ namespace margelo::nitro::onnx::speech {
18
+
19
+ /**
20
+ * Minimal thread pool for background inference work.
21
+ * Tasks are enqueued as std::function<void()> and executed by worker threads.
22
+ */
23
+ class ThreadPool final {
24
+ public:
25
+ explicit ThreadPool(size_t threadCount = std::thread::hardware_concurrency());
26
+ ~ThreadPool();
27
+
28
+ ThreadPool(const ThreadPool&) = delete;
29
+ ThreadPool& operator=(const ThreadPool&) = delete;
30
+
31
+ /**
32
+ * Schedule a task on the pool and obtain a future for the result.
33
+ * @tparam F Callable type.
34
+ * @tparam Args Argument types.
35
+ * @return std::future for the callable result.
36
+ */
37
+ template <typename F, typename... Args>
38
+ auto enqueue(F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>> {
39
+ using ReturnType = std::invoke_result_t<F, Args...>;
40
+ auto task = std::make_shared<std::packaged_task<ReturnType()>>(
41
+ std::bind(std::forward<F>(f), std::forward<Args>(args)...));
42
+ std::future<ReturnType> result = task->get_future();
43
+ {
44
+ std::unique_lock<std::mutex> lock(queueMutex_);
45
+ if (stop_) {
46
+ throw std::runtime_error("Cannot enqueue on stopped ThreadPool");
47
+ }
48
+ tasks_.emplace([task]() { (*task)(); });
49
+ }
50
+ condition_.notify_one();
51
+ return result;
52
+ }
53
+
54
+ private:
55
+ std::vector<std::thread> workers_;
56
+ std::queue<std::function<void()>> tasks_;
57
+ std::mutex queueMutex_;
58
+ std::condition_variable condition_;
59
+ std::atomic<bool> stop_{false};
60
+ };
61
+
62
+ } // namespace margelo::nitro::onnx::speech
package/cpp/Tts.cpp ADDED
@@ -0,0 +1,132 @@
1
+ // ------------------------------------------------------------------------------
2
+ // TtsImpl.cpp
3
+ // ------------------------------------------------------------------------------
4
+ #include "Tts.hpp"
5
+
6
+ #include "AudioUtils.hpp"
7
+
8
+ #include <NitroModules/ArrayBuffer.hpp>
9
+ #include <fstream>
10
+ #include <stdexcept>
11
+
12
+ namespace margelo::nitro::onnx::speech {
13
+
14
+ namespace {
15
+
16
+ TtsResult toTtsResult(const TtsEngineResult& native) {
17
+ std::vector<uint8_t> bytes = floatVectorToBytes(native.samples);
18
+ return TtsResult(
19
+ ArrayBuffer::move(std::move(bytes)),
20
+ static_cast<double>(native.sampleRate),
21
+ native.durationMs);
22
+ }
23
+
24
+ } // namespace
25
+
26
+ Tts::Tts(std::shared_ptr<ThreadPool> threadPool)
27
+ : HybridObject(TAG), engine_(std::move(threadPool)) {}
28
+
29
+ Tts::~Tts() {
30
+ engine_.unload();
31
+ }
32
+
33
+ std::shared_ptr<Promise<void>> Tts::load(const TtsModelConfig& config) {
34
+ return Promise<void>::async([this, config]() {
35
+ TtsEngineConfig native;
36
+ native.type = static_cast<TtsModelType>(config.type);
37
+ native.modelDir = config.modelDir;
38
+ native.model = config.model.value_or("");
39
+ native.acousticModel = config.acousticModel.value_or("");
40
+ native.vocoder = config.vocoder.value_or("");
41
+ native.tokens = config.tokens.value_or("");
42
+ native.lexicon = config.lexicon.value_or("");
43
+ native.voices = config.voices.value_or("");
44
+ native.espeakNgData = config.espeakNgData.value_or("");
45
+ native.dictDir = config.dictDir.value_or("");
46
+ native.lmMain = config.lmMain.value_or("");
47
+ native.lmFlow = config.lmFlow.value_or("");
48
+ native.textConditioner = config.textConditioner.value_or("");
49
+ native.pocketEncoder = config.pocketEncoder.value_or("");
50
+ native.pocketDecoder = config.pocketDecoder.value_or("");
51
+ native.vocabJson = config.vocabJson.value_or("");
52
+ native.tokenScoresJson = config.tokenScoresJson.value_or("");
53
+ native.zipvoiceEncoder = config.zipvoiceEncoder.value_or("");
54
+ native.zipvoiceDecoder = config.zipvoiceDecoder.value_or("");
55
+ native.config = config.config.value_or("");
56
+ native.numThreads = static_cast<int32_t>(config.numThreads.value_or(2));
57
+ native.outputSampleRate = static_cast<int32_t>(config.outputSampleRate.value_or(16000.0));
58
+ native.speakerId = static_cast<int32_t>(config.speakerId.value_or(0.0));
59
+ native.speed = static_cast<float>(config.speed.value_or(1.0));
60
+ engine_.load(native);
61
+ });
62
+ }
63
+
64
+ bool Tts::isLoaded() {
65
+ return engine_.isLoaded();
66
+ }
67
+
68
+ std::shared_ptr<Promise<TtsResult>> Tts::synthesize(const std::string& text, std::optional<double> speed) {
69
+ return Promise<TtsResult>::async([this, text, speed]() {
70
+ float spd = speed.has_value() ? static_cast<float>(speed.value()) : -1.0f;
71
+ return toTtsResult(engine_.synthesize(text, -1, spd));
72
+ });
73
+ }
74
+
75
+ std::shared_ptr<Promise<TtsResult>> Tts::synthesizeWithSpeaker(
76
+ const std::string& text,
77
+ const std::string& speakerId,
78
+ std::optional<double> speed) {
79
+ return Promise<TtsResult>::async([this, text, speakerId, speed]() {
80
+ int32_t sid = std::stoi(speakerId);
81
+ float spd = speed.has_value() ? static_cast<float>(speed.value()) : -1.0f;
82
+ return toTtsResult(engine_.synthesize(text, sid, spd));
83
+ });
84
+ }
85
+
86
+ std::shared_ptr<Promise<void>> Tts::saveWav(const TtsResult& result, const std::string& path) {
87
+ return Promise<void>::async([result, path]() {
88
+ const auto sampleRate = static_cast<uint32_t>(result.sampleRate);
89
+ const uint16_t numChannels = 1;
90
+ const uint16_t bitsPerSample = 32;
91
+ const uint16_t audioFormat = 3; // IEEE float
92
+
93
+ const auto* sampleData = reinterpret_cast<const uint8_t*>(result.samples->data());
94
+ const uint32_t dataByteCount = static_cast<uint32_t>(result.samples->size());
95
+
96
+ const uint32_t byteRate = sampleRate * numChannels * bitsPerSample / 8;
97
+ const uint16_t blockAlign = numChannels * bitsPerSample / 8;
98
+ const uint32_t chunkSize = 36 + dataByteCount;
99
+
100
+ std::ofstream file(path, std::ios::binary);
101
+ if (!file) {
102
+ throw std::runtime_error("Cannot open file for writing: " + path);
103
+ }
104
+
105
+ // RIFF header
106
+ file.write("RIFF", 4);
107
+ file.write(reinterpret_cast<const char*>(&chunkSize), 4);
108
+ file.write("WAVE", 4);
109
+
110
+ // fmt chunk
111
+ file.write("fmt ", 4);
112
+ uint32_t fmtSize = 16;
113
+ file.write(reinterpret_cast<const char*>(&fmtSize), 4);
114
+ file.write(reinterpret_cast<const char*>(&audioFormat), 2);
115
+ file.write(reinterpret_cast<const char*>(&numChannels), 2);
116
+ file.write(reinterpret_cast<const char*>(&sampleRate), 4);
117
+ file.write(reinterpret_cast<const char*>(&byteRate), 4);
118
+ file.write(reinterpret_cast<const char*>(&blockAlign), 2);
119
+ file.write(reinterpret_cast<const char*>(&bitsPerSample), 2);
120
+
121
+ // data chunk
122
+ file.write("data", 4);
123
+ file.write(reinterpret_cast<const char*>(&dataByteCount), 4);
124
+ file.write(reinterpret_cast<const char*>(sampleData), dataByteCount);
125
+ });
126
+ }
127
+
128
+ std::shared_ptr<Promise<void>> Tts::unload() {
129
+ return Promise<void>::async([this]() { engine_.unload(); });
130
+ }
131
+
132
+ } // namespace margelo::nitro::onnx::speech
package/cpp/Tts.hpp ADDED
@@ -0,0 +1,38 @@
1
+ // ------------------------------------------------------------------------------
2
+ // TtsImpl.hpp
3
+ // ------------------------------------------------------------------------------
4
+ #pragma once
5
+
6
+ #include "TtsEngine.hpp"
7
+
8
+ #include <NitroModules/Promise.hpp>
9
+ #include <NitroModules/HybridObject.hpp>
10
+ #include <HybridTtsSpec.hpp>
11
+
12
+ #include <memory>
13
+ #include <optional>
14
+
15
+ namespace margelo::nitro::onnx::speech {
16
+
17
+ class Tts : public HybridTtsSpec {
18
+ public:
19
+ static constexpr auto TAG = "Tts";
20
+
21
+ explicit Tts(std::shared_ptr<ThreadPool> threadPool);
22
+ ~Tts() override;
23
+
24
+ std::shared_ptr<Promise<void>> load(const TtsModelConfig& config) override;
25
+ bool isLoaded() override;
26
+ std::shared_ptr<Promise<TtsResult>> synthesize(const std::string& text, std::optional<double> speed) override;
27
+ std::shared_ptr<Promise<TtsResult>> synthesizeWithSpeaker(
28
+ const std::string& text,
29
+ const std::string& speakerId,
30
+ std::optional<double> speed) override;
31
+ std::shared_ptr<Promise<void>> saveWav(const TtsResult& result, const std::string& path) override;
32
+ std::shared_ptr<Promise<void>> unload() override;
33
+
34
+ private:
35
+ TtsEngine engine_;
36
+ };
37
+
38
+ } // namespace margelo::nitro::onnx::speech