react-native-davoice-tts 1.0.410 → 1.0.412

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 (30) hide show
  1. package/README.md +19 -0
  2. package/TTSRNBridge.podspec +1 -1
  3. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar +0 -0
  4. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.md5 +1 -1
  5. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.sha1 +1 -1
  6. package/android/src/main/java/com/davoice/tts/rn/DaVoiceTTSBridge.java +64 -0
  7. package/ios/SpeechBridge/SpeechBridge.m +40 -0
  8. package/ios/TTSRNBridge/DaVoiceTTSBridge.m +40 -0
  9. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/DavoiceTTS +0 -0
  10. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +28 -0
  11. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +505 -116
  12. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.private.swiftinterface +4 -0
  13. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftinterface +4 -0
  15. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/DavoiceTTS +0 -0
  16. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +56 -0
  17. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +505 -116
  18. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +4 -0
  19. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  20. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface +4 -0
  21. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json +505 -116
  22. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +4 -0
  23. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  24. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +4 -0
  25. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeDirectory +0 -0
  26. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeRequirements-1 +0 -0
  27. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeResources +33 -33
  28. package/package.json +1 -1
  29. package/speech/index.d.ts +33 -0
  30. package/speech/index.ts +65 -0
@@ -161,6 +161,10 @@ public enum AudioPlaybackHook {
161
161
  }
162
162
  @objc final public func getAllVoices() -> [Swift.String]
163
163
  @objc final public func getVoiceAbilities(_ name: Swift.String) -> [Swift.String]
164
+ #if compiler(>=5.3) && $NonescapableTypes
165
+ @objc(setExternalVoicesPath:completion:) final public func setExternalVoicesPath(_ path: Swift.String?, completion: (([Swift.String]) -> Swift.Void)? = nil)
166
+ #endif
167
+ @objc public static func createVoiceClone(_ inputAudioPath: Swift.String, outputPath: Swift.String, embedderPath: Swift.String) throws -> [Swift.String : Any]
164
168
  @objc(changeVoice:completion:) final public func changeVoice(_ name: Swift.String, completion: @escaping (Swift.Int) -> Swift.Void)
165
169
  @objc final public var onLastUtteranceFinished: (() -> Swift.Void)?
166
170
  @objc deinit
@@ -161,6 +161,10 @@ public enum AudioPlaybackHook {
161
161
  }
162
162
  @objc final public func getAllVoices() -> [Swift.String]
163
163
  @objc final public func getVoiceAbilities(_ name: Swift.String) -> [Swift.String]
164
+ #if compiler(>=5.3) && $NonescapableTypes
165
+ @objc(setExternalVoicesPath:completion:) final public func setExternalVoicesPath(_ path: Swift.String?, completion: (([Swift.String]) -> Swift.Void)? = nil)
166
+ #endif
167
+ @objc public static func createVoiceClone(_ inputAudioPath: Swift.String, outputPath: Swift.String, embedderPath: Swift.String) throws -> [Swift.String : Any]
164
168
  @objc(changeVoice:completion:) final public func changeVoice(_ name: Swift.String, completion: @escaping (Swift.Int) -> Swift.Void)
165
169
  @objc final public var onLastUtteranceFinished: (() -> Swift.Void)?
166
170
  @objc deinit
@@ -6,7 +6,7 @@
6
6
  <dict>
7
7
  <key>Headers/DavoiceTTS-Swift.h</key>
8
8
  <data>
9
- aXTVWBRj3rHjoLELB1SKEpSKlVI=
9
+ CayZ/2UHTVx8d3crJ59EEwyTj9M=
10
10
  </data>
11
11
  <key>Info.plist</key>
12
12
  <data>
@@ -14,43 +14,43 @@
14
14
  </data>
15
15
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
16
16
  <data>
17
- GWj4rqzBJFHhghEkrYmW4vHZBTQ=
17
+ KWvzlkvZwNqGIqaxH0B1gfHtqic=
18
18
  </data>
19
19
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
20
20
  <data>
21
- ksSF6xaCJF6G3MwPOHiS/nveH7o=
21
+ +SjLLdC9wzcmmVKdK3OspIIfl4c=
22
22
  </data>
23
23
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftdoc</key>
24
24
  <data>
25
- +fcSy/bltR5s4BKnsFh0ZW8LoDg=
25
+ AtalCan8W74vSROBG2Y+VszUbo0=
26
26
  </data>
27
27
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface</key>
28
28
  <data>
29
- ksSF6xaCJF6G3MwPOHiS/nveH7o=
29
+ +SjLLdC9wzcmmVKdK3OspIIfl4c=
30
30
  </data>
31
31
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftmodule</key>
32
32
  <data>
33
- mH9jIRkv1+Mb2wr01gTzH/Lq4KI=
33
+ LFOxWYSLyaxM56QHAmqcH8h0ACo=
34
34
  </data>
35
35
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
36
36
  <data>
37
- GWj4rqzBJFHhghEkrYmW4vHZBTQ=
37
+ KWvzlkvZwNqGIqaxH0B1gfHtqic=
38
38
  </data>
39
39
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
40
40
  <data>
41
- ZT1wxKJLUV/5yDXgkr1hiFY6Kkw=
41
+ CCVbMysPRrLBmlDQ1ev8miGvbX4=
42
42
  </data>
43
43
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
44
44
  <data>
45
- fI/NjOu5fRs+DuF5Nsf2eQwCFZg=
45
+ Y+jBdqRgb8hjAkzEQ+dx4dPeJaE=
46
46
  </data>
47
47
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
48
48
  <data>
49
- ZT1wxKJLUV/5yDXgkr1hiFY6Kkw=
49
+ CCVbMysPRrLBmlDQ1ev8miGvbX4=
50
50
  </data>
51
51
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
52
52
  <data>
53
- WKflcCZyLnNxAl4KY0kr/rkp3Yw=
53
+ IN1uxp6gFqpaRyEzBkr1vqmlddQ=
54
54
  </data>
55
55
  <key>Modules/module.modulemap</key>
56
56
  <data>
@@ -63,121 +63,121 @@
63
63
  <dict>
64
64
  <key>hash</key>
65
65
  <data>
66
- aXTVWBRj3rHjoLELB1SKEpSKlVI=
66
+ CayZ/2UHTVx8d3crJ59EEwyTj9M=
67
67
  </data>
68
68
  <key>hash2</key>
69
69
  <data>
70
- 8AwLIvn9Cc6MkqCoTSwxxt0PdUyRSH33aZgxBH42d2c=
70
+ jJHyYm5Bz+b6+Gr1OBaA2Vwm5CfUUg2QAD0t3khjh6E=
71
71
  </data>
72
72
  </dict>
73
73
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
74
74
  <dict>
75
75
  <key>hash</key>
76
76
  <data>
77
- GWj4rqzBJFHhghEkrYmW4vHZBTQ=
77
+ KWvzlkvZwNqGIqaxH0B1gfHtqic=
78
78
  </data>
79
79
  <key>hash2</key>
80
80
  <data>
81
- IUrltZXg4lQ05e1Tp7k/jFjfoqqHkai2OOvbmlkaAs4=
81
+ IJy/ubRByJPxcw2/2tr0kKPcmzOcUWVMjpHnQGhsbA8=
82
82
  </data>
83
83
  </dict>
84
84
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
85
85
  <dict>
86
86
  <key>hash</key>
87
87
  <data>
88
- ksSF6xaCJF6G3MwPOHiS/nveH7o=
88
+ +SjLLdC9wzcmmVKdK3OspIIfl4c=
89
89
  </data>
90
90
  <key>hash2</key>
91
91
  <data>
92
- SCUrsTaAxwGLnaArK6cNSD5hYMgb029jIt3i0+iOelg=
92
+ e9/3r3eXLtlmcPkmnRU0xYdk/hzgUy0Rpv8GzYPXK2o=
93
93
  </data>
94
94
  </dict>
95
95
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftdoc</key>
96
96
  <dict>
97
97
  <key>hash</key>
98
98
  <data>
99
- +fcSy/bltR5s4BKnsFh0ZW8LoDg=
99
+ AtalCan8W74vSROBG2Y+VszUbo0=
100
100
  </data>
101
101
  <key>hash2</key>
102
102
  <data>
103
- HMDb7i6UKre/ujb7siIgBLS8+Fz0defkXEApVC4vLIM=
103
+ eU2dx0sPZRWhE+65rFlT50UaJiM1ds4VmlV8NpEvyh8=
104
104
  </data>
105
105
  </dict>
106
106
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface</key>
107
107
  <dict>
108
108
  <key>hash</key>
109
109
  <data>
110
- ksSF6xaCJF6G3MwPOHiS/nveH7o=
110
+ +SjLLdC9wzcmmVKdK3OspIIfl4c=
111
111
  </data>
112
112
  <key>hash2</key>
113
113
  <data>
114
- SCUrsTaAxwGLnaArK6cNSD5hYMgb029jIt3i0+iOelg=
114
+ e9/3r3eXLtlmcPkmnRU0xYdk/hzgUy0Rpv8GzYPXK2o=
115
115
  </data>
116
116
  </dict>
117
117
  <key>Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftmodule</key>
118
118
  <dict>
119
119
  <key>hash</key>
120
120
  <data>
121
- mH9jIRkv1+Mb2wr01gTzH/Lq4KI=
121
+ LFOxWYSLyaxM56QHAmqcH8h0ACo=
122
122
  </data>
123
123
  <key>hash2</key>
124
124
  <data>
125
- n3EI7Sk02U7mWJOkNn1NaMtD4zbkEEFIN9TXL19EBYM=
125
+ b0HdHL72+oq00ajrgipy9IW6xmrsIPmAWUhTKbxRxAc=
126
126
  </data>
127
127
  </dict>
128
128
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
129
129
  <dict>
130
130
  <key>hash</key>
131
131
  <data>
132
- GWj4rqzBJFHhghEkrYmW4vHZBTQ=
132
+ KWvzlkvZwNqGIqaxH0B1gfHtqic=
133
133
  </data>
134
134
  <key>hash2</key>
135
135
  <data>
136
- IUrltZXg4lQ05e1Tp7k/jFjfoqqHkai2OOvbmlkaAs4=
136
+ IJy/ubRByJPxcw2/2tr0kKPcmzOcUWVMjpHnQGhsbA8=
137
137
  </data>
138
138
  </dict>
139
139
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
140
140
  <dict>
141
141
  <key>hash</key>
142
142
  <data>
143
- ZT1wxKJLUV/5yDXgkr1hiFY6Kkw=
143
+ CCVbMysPRrLBmlDQ1ev8miGvbX4=
144
144
  </data>
145
145
  <key>hash2</key>
146
146
  <data>
147
- nywtFQgfcnZrO0U914JYYA2M7Do7Fpqy+mFpzteM43w=
147
+ zs5TSJY3M0yp73wG3kcfebXGiEGjaBbjDvBJN1eDcEg=
148
148
  </data>
149
149
  </dict>
150
150
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
151
151
  <dict>
152
152
  <key>hash</key>
153
153
  <data>
154
- fI/NjOu5fRs+DuF5Nsf2eQwCFZg=
154
+ Y+jBdqRgb8hjAkzEQ+dx4dPeJaE=
155
155
  </data>
156
156
  <key>hash2</key>
157
157
  <data>
158
- kedZaxsmXt+0P9wn0DUIyKHC6sUL1OmlaOtGVaL3k3k=
158
+ MY670aR0rAe1lIS0ea8ReB4Vgq8WweloNM+OAYzY6oc=
159
159
  </data>
160
160
  </dict>
161
161
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
162
162
  <dict>
163
163
  <key>hash</key>
164
164
  <data>
165
- ZT1wxKJLUV/5yDXgkr1hiFY6Kkw=
165
+ CCVbMysPRrLBmlDQ1ev8miGvbX4=
166
166
  </data>
167
167
  <key>hash2</key>
168
168
  <data>
169
- nywtFQgfcnZrO0U914JYYA2M7Do7Fpqy+mFpzteM43w=
169
+ zs5TSJY3M0yp73wG3kcfebXGiEGjaBbjDvBJN1eDcEg=
170
170
  </data>
171
171
  </dict>
172
172
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
173
173
  <dict>
174
174
  <key>hash</key>
175
175
  <data>
176
- WKflcCZyLnNxAl4KY0kr/rkp3Yw=
176
+ IN1uxp6gFqpaRyEzBkr1vqmlddQ=
177
177
  </data>
178
178
  <key>hash2</key>
179
179
  <data>
180
- 89+0RSxFC9KOOhvMzQN3ina8oFsBJ1uob3XKWjnaNfc=
180
+ 8NrcKhKsP3QhU/mgYfgIokKzZlFrHsKSAcjtPOK7b/8=
181
181
  </data>
182
182
  </dict>
183
183
  <key>Modules/module.modulemap</key>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-davoice-tts",
3
- "version": "1.0.410",
3
+ "version": "1.0.412",
4
4
  "description": "tts library for React Native",
5
5
  "main": "tts/index.js",
6
6
  "types": "tts/index.d.ts",
package/speech/index.d.ts CHANGED
@@ -37,6 +37,14 @@ export type NewSpeechWAVEvent = {
37
37
  };
38
38
  export type RNAssetLike = any;
39
39
  export type ModelRef = string | RNAssetLike;
40
+ /** createVoiceClone() result. */
41
+ export type VoiceCloneResult = {
42
+ /** The .emd written (its file name without .emd is the voice name). */
43
+ path: string;
44
+ voice: string;
45
+ /** Non-empty when the user should record again (e.g. too little speech). */
46
+ warnings: string[];
47
+ };
40
48
  /** iOS AVAudioSession routing entry shared by STT and TTS. */
41
49
  export type PreferredAudioInput = 'bluetoothHighQualityMic' | 'builtInMic' | 'none';
42
50
  export type RouteConfigEntry = {
@@ -155,6 +163,30 @@ declare class Speech {
155
163
  * Resolves [] for voices without variants or when unavailable.
156
164
  */
157
165
  getVoiceAbilities(name: string): Promise<string[]>;
166
+ /** Also look for voices in the folder `path`: <name>.emd files made by
167
+ * createVoiceClone() (or .npy). They are listed by getAllVoices() after
168
+ * the bundled voices and selected with changeVoice() like them; a bundled
169
+ * voice wins over a file with the same name. Call again to pick up new files;
170
+ * null or "" removes them. Resolves the external voice names found.
171
+ */
172
+ setExternalVoicesPath(path: string | null): Promise<string[]>;
173
+ /** Voice cloning: creates a voice from a recording and saves it as a
174
+ * scrambled .emd at `outputPath`.
175
+ *
176
+ * - inputAudio: path/URI of a recording of one speaker (wav, mp3, m4a/aac,
177
+ * ogg, flac, ...). It is prepared for cloning automatically.
178
+ * - outputPath: the .emd file to write (".emd" is added when missing), or a
179
+ * folder ending in "/" (-> <folder>/<input name>.emd). The file name is the
180
+ * voice name.
181
+ * - embedder: the language's voice encoder embedder_<code>.dm, e.g.
182
+ * require('./assets/models/embedder_en.dm') or a path; it is unscrambled in
183
+ * memory only.
184
+ *
185
+ * Resolves { path, voice, warnings }; when warnings is non-empty, ask the user
186
+ * to record again. When the .emd lands in the folder given to setExternalVoicesPath(), the new
187
+ * voice is usable right away.
188
+ */
189
+ createVoiceClone(inputAudio: ModelRef, outputPath: string, embedder: ModelRef): Promise<VoiceCloneResult>;
158
190
  /** Reference voice, matched case-insensitively without .wav.
159
191
  * Returns 0 on match; -1 on fallback/unavailability. Missing names select the
160
192
  * first WAV (alphabetical order). Already generated audio keeps its voice.
@@ -176,6 +208,7 @@ declare class Speech {
176
208
  private hasCompletedFullInit;
177
209
  private hasCompletedTtsInit;
178
210
  private iosTtsOnly;
211
+ private externalVoicesPath;
179
212
  private logCall;
180
213
  private resolveModelExt;
181
214
  private resolveModelToPath;
package/speech/index.ts CHANGED
@@ -137,6 +137,15 @@ export type NewSpeechWAVEvent = { path: string };
137
137
  export type RNAssetLike = any; // keep permissive (Metro asset numbers/objects vary)
138
138
  export type ModelRef = string | RNAssetLike;
139
139
 
140
+ /** createVoiceClone() result. */
141
+ export type VoiceCloneResult = {
142
+ /** The .emd written (its file name without .emd is the voice name). */
143
+ path: string;
144
+ voice: string;
145
+ /** Non-empty when the user should record again (e.g. too little speech). */
146
+ warnings: string[];
147
+ };
148
+
140
149
  /** iOS AVAudioSession routing entry shared by STT and TTS. */
141
150
  export type PreferredAudioInput =
142
151
  | 'bluetoothHighQualityMic'
@@ -337,6 +346,61 @@ class Speech {
337
346
  }
338
347
  }
339
348
 
349
+ /** Also look for voices in the folder `path`: <name>.emd files made by
350
+ * createVoiceClone() (or .npy). They are listed by getAllVoices() after
351
+ * the bundled voices and selected with changeVoice() like them; a bundled
352
+ * voice wins over a file with the same name. Call again to pick up new files;
353
+ * null or "" removes them. Resolves the external voice names found.
354
+ */
355
+ async setExternalVoicesPath(path: string | null): Promise<string[]> {
356
+ const bridge = Platform.OS === 'ios' && !this.iosTtsOnly && this.hasCompletedFullInit
357
+ ? NativeSpeech : NativeTTS;
358
+ if ((Platform.OS !== 'android' && Platform.OS !== 'ios') || typeof bridge?.setExternalVoicesPath !== 'function') return [];
359
+ this.externalVoicesPath = path || null;
360
+ try {
361
+ const names = await bridge.setExternalVoicesPath(path || null);
362
+ return Array.isArray(names) ? names.filter((v: unknown): v is string => typeof v === 'string') : [];
363
+ } catch {
364
+ return [];
365
+ }
366
+ }
367
+
368
+ /** Voice cloning: creates a voice from a recording and saves it as a
369
+ * scrambled .emd at `outputPath`.
370
+ *
371
+ * - inputAudio: path/URI of a recording of one speaker (wav, mp3, m4a/aac,
372
+ * ogg, flac, ...). It is prepared for cloning automatically.
373
+ * - outputPath: the .emd file to write (".emd" is added when missing), or a
374
+ * folder ending in "/" (-> <folder>/<input name>.emd). The file name is the
375
+ * voice name.
376
+ * - embedder: the language's voice encoder embedder_<code>.dm, e.g.
377
+ * require('./assets/models/embedder_en.dm') or a path; it is unscrambled in
378
+ * memory only.
379
+ *
380
+ * Resolves { path, voice, warnings }; when warnings is non-empty, ask the user
381
+ * to record again. When the .emd lands in the folder given to setExternalVoicesPath(), the new
382
+ * voice is usable right away.
383
+ */
384
+ async createVoiceClone(inputAudio: ModelRef, outputPath: string, embedder: ModelRef): Promise<VoiceCloneResult> {
385
+ const bridge = (typeof NativeTTS?.createVoiceClone === 'function') ? NativeTTS : NativeSpeech;
386
+ if (typeof bridge?.createVoiceClone !== 'function') {
387
+ throw new Error('createVoiceClone is not available on this platform');
388
+ }
389
+ const result = await bridge.createVoiceClone(
390
+ this.resolveModelToPath(inputAudio), String(outputPath), this.resolveModelToPath(embedder));
391
+ const folder = this.externalVoicesPath;
392
+ if (folder && typeof result?.path === 'string') {
393
+ const strip = (p: string) => p.replace(/^file:\/\//, '').replace(/\/+$/, '');
394
+ const parent = result.path.substring(0, result.path.lastIndexOf('/'));
395
+ if (strip(parent) === strip(folder)) await this.setExternalVoicesPath(folder);
396
+ }
397
+ return {
398
+ path: String(result?.path ?? ''),
399
+ voice: String(result?.voice ?? ''),
400
+ warnings: Array.isArray(result?.warnings) ? result.warnings.map(String) : [],
401
+ };
402
+ }
403
+
340
404
  /** Reference voice, matched case-insensitively without .wav.
341
405
  * Returns 0 on match; -1 on fallback/unavailability. Missing names select the
342
406
  * first WAV (alphabetical order). Already generated audio keeps its voice.
@@ -423,6 +487,7 @@ class Speech {
423
487
  private hasCompletedFullInit = false;
424
488
  private hasCompletedTtsInit = false;
425
489
  private iosTtsOnly = false; // when true, use NativeTTS directly on iOS
490
+ private externalVoicesPath: string | null = null; // setExternalVoicesPath()
426
491
 
427
492
 
428
493
  private logCall(name: string, payload?: any) {