react-native-davoice-tts 1.0.410 → 1.0.411

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 +20 -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 +67 -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 +30 -0
  11. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +433 -58
  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 +60 -0
  17. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +433 -58
  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 +433 -58
  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 +39 -0
  30. package/speech/index.ts +73 -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
+ sBeBhBzj/r1GrbXqcLsAirfQIKQ=
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
+ NZOQSk+oyJ0wjXl83FP/8CfQY9Q=
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
+ 5Ssm9whRQvwOrYUSWUeqAmJlBCk=
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
+ IUrwKe5bYNqrgkU5fQDMrzwCF2A=
34
34
  </data>
35
35
  <key>Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
36
36
  <data>
37
- GWj4rqzBJFHhghEkrYmW4vHZBTQ=
37
+ NZOQSk+oyJ0wjXl83FP/8CfQY9Q=
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
+ mGZKyR1CWLd+ITTHoFSKKVlTcxQ=
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
+ cA4vZqE2K7PeK7Oc4Josd+Vt/Ls=
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
+ sBeBhBzj/r1GrbXqcLsAirfQIKQ=
67
67
  </data>
68
68
  <key>hash2</key>
69
69
  <data>
70
- 8AwLIvn9Cc6MkqCoTSwxxt0PdUyRSH33aZgxBH42d2c=
70
+ WIZa1mRKiVZ+bP5gKEKczEKDF8R9zEhTU3SFieC0fO8=
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
+ NZOQSk+oyJ0wjXl83FP/8CfQY9Q=
78
78
  </data>
79
79
  <key>hash2</key>
80
80
  <data>
81
- IUrltZXg4lQ05e1Tp7k/jFjfoqqHkai2OOvbmlkaAs4=
81
+ dImQrwOEdDV/E4EJqL/m2QqEGw0dPMKHybTSk2TKAkQ=
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
+ 5Ssm9whRQvwOrYUSWUeqAmJlBCk=
100
100
  </data>
101
101
  <key>hash2</key>
102
102
  <data>
103
- HMDb7i6UKre/ujb7siIgBLS8+Fz0defkXEApVC4vLIM=
103
+ dtLnnJQR8LoDEVnD1tg06i/kKRwcaXwxJ/jHdnDmhEI=
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
+ IUrwKe5bYNqrgkU5fQDMrzwCF2A=
122
122
  </data>
123
123
  <key>hash2</key>
124
124
  <data>
125
- n3EI7Sk02U7mWJOkNn1NaMtD4zbkEEFIN9TXL19EBYM=
125
+ S/nm0VmvBX9ZvqiP8S6Y9LBt62/or63J0jrUsMBV5X0=
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
+ NZOQSk+oyJ0wjXl83FP/8CfQY9Q=
133
133
  </data>
134
134
  <key>hash2</key>
135
135
  <data>
136
- IUrltZXg4lQ05e1Tp7k/jFjfoqqHkai2OOvbmlkaAs4=
136
+ dImQrwOEdDV/E4EJqL/m2QqEGw0dPMKHybTSk2TKAkQ=
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
+ mGZKyR1CWLd+ITTHoFSKKVlTcxQ=
155
155
  </data>
156
156
  <key>hash2</key>
157
157
  <data>
158
- kedZaxsmXt+0P9wn0DUIyKHC6sUL1OmlaOtGVaL3k3k=
158
+ Iqp+5eTycIlB+l9STROeFAxcZ/W9bSUDLqAMYDkMbCw=
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
+ cA4vZqE2K7PeK7Oc4Josd+Vt/Ls=
177
177
  </data>
178
178
  <key>hash2</key>
179
179
  <data>
180
- 89+0RSxFC9KOOhvMzQN3ina8oFsBJ1uob3XKWjnaNfc=
180
+ QAjZ2CvyRKa72Kv5Ki3OOS/Q6EuERdLQwHK9/M4yO6Y=
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.411",
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,18 @@ 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
+ /** Length of the reference clip that was embedded. */
46
+ seconds: number;
47
+ /** Non-empty when less than 4 s of speech remained or the cut fell inside speech. */
48
+ warnings: string[];
49
+ /** What the preparation did (silence trimmed, pauses shortened, cut point). */
50
+ report: string[];
51
+ };
40
52
  /** iOS AVAudioSession routing entry shared by STT and TTS. */
41
53
  export type PreferredAudioInput = 'bluetoothHighQualityMic' | 'builtInMic' | 'none';
42
54
  export type RouteConfigEntry = {
@@ -155,6 +167,32 @@ declare class Speech {
155
167
  * Resolves [] for voices without variants or when unavailable.
156
168
  */
157
169
  getVoiceAbilities(name: string): Promise<string[]>;
170
+ /** Also look for voices in the folder `path`: <name>.emd files made by
171
+ * createVoiceClone() (or .npy). They are listed by getAllVoices() after
172
+ * the bundled voices and selected with changeVoice() like them; a bundled
173
+ * voice wins over a file with the same name. Call again to pick up new files;
174
+ * null or "" removes them. Resolves the external voice names found.
175
+ */
176
+ setExternalVoicesPath(path: string | null): Promise<string[]>;
177
+ /** Creates a voice clone from reference audio and saves it as a
178
+ * scrambled .emd at `outputPath`.
179
+ *
180
+ * - inputAudio: path/URI of a wav, mp3, m4a/aac, ogg, flac, ... recording. The
181
+ * best ~6 s of speech is used: silence trimmed, long pauses shortened, cut at
182
+ * a gap between words (never inside a word).
183
+ * - outputPath: the .emd file to write (".emd" is added when missing), or a
184
+ * folder ending in "/" (-> <folder>/<input name>.emd). The file name is the
185
+ * voice name.
186
+ * - embedder: the language's voice encoder embedder_<code>.dm, e.g.
187
+ * require('./assets/models/embedder_en.dm') or a path; it is unscrambled in
188
+ * memory only.
189
+ *
190
+ * Resolves { path, voice, seconds, warnings, report }; warnings is non-empty
191
+ * when less than 4 s of speech remained or the cut had to fall inside speech.
192
+ * When the .emd lands in the folder given to setExternalVoicesPath(), the new
193
+ * voice is usable right away.
194
+ */
195
+ createVoiceClone(inputAudio: ModelRef, outputPath: string, embedder: ModelRef): Promise<VoiceCloneResult>;
158
196
  /** Reference voice, matched case-insensitively without .wav.
159
197
  * Returns 0 on match; -1 on fallback/unavailability. Missing names select the
160
198
  * first WAV (alphabetical order). Already generated audio keeps its voice.
@@ -176,6 +214,7 @@ declare class Speech {
176
214
  private hasCompletedFullInit;
177
215
  private hasCompletedTtsInit;
178
216
  private iosTtsOnly;
217
+ private externalVoicesPath;
179
218
  private logCall;
180
219
  private resolveModelExt;
181
220
  private resolveModelToPath;
package/speech/index.ts CHANGED
@@ -137,6 +137,19 @@ 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
+ /** Length of the reference clip that was embedded. */
146
+ seconds: number;
147
+ /** Non-empty when less than 4 s of speech remained or the cut fell inside speech. */
148
+ warnings: string[];
149
+ /** What the preparation did (silence trimmed, pauses shortened, cut point). */
150
+ report: string[];
151
+ };
152
+
140
153
  /** iOS AVAudioSession routing entry shared by STT and TTS. */
141
154
  export type PreferredAudioInput =
142
155
  | 'bluetoothHighQualityMic'
@@ -337,6 +350,65 @@ class Speech {
337
350
  }
338
351
  }
339
352
 
353
+ /** Also look for voices in the folder `path`: <name>.emd files made by
354
+ * createVoiceClone() (or .npy). They are listed by getAllVoices() after
355
+ * the bundled voices and selected with changeVoice() like them; a bundled
356
+ * voice wins over a file with the same name. Call again to pick up new files;
357
+ * null or "" removes them. Resolves the external voice names found.
358
+ */
359
+ async setExternalVoicesPath(path: string | null): Promise<string[]> {
360
+ const bridge = Platform.OS === 'ios' && !this.iosTtsOnly && this.hasCompletedFullInit
361
+ ? NativeSpeech : NativeTTS;
362
+ if ((Platform.OS !== 'android' && Platform.OS !== 'ios') || typeof bridge?.setExternalVoicesPath !== 'function') return [];
363
+ this.externalVoicesPath = path || null;
364
+ try {
365
+ const names = await bridge.setExternalVoicesPath(path || null);
366
+ return Array.isArray(names) ? names.filter((v: unknown): v is string => typeof v === 'string') : [];
367
+ } catch {
368
+ return [];
369
+ }
370
+ }
371
+
372
+ /** Creates a voice clone from reference audio and saves it as a
373
+ * scrambled .emd at `outputPath`.
374
+ *
375
+ * - inputAudio: path/URI of a wav, mp3, m4a/aac, ogg, flac, ... recording. The
376
+ * best ~6 s of speech is used: silence trimmed, long pauses shortened, cut at
377
+ * a gap between words (never inside a word).
378
+ * - outputPath: the .emd file to write (".emd" is added when missing), or a
379
+ * folder ending in "/" (-> <folder>/<input name>.emd). The file name is the
380
+ * voice name.
381
+ * - embedder: the language's voice encoder embedder_<code>.dm, e.g.
382
+ * require('./assets/models/embedder_en.dm') or a path; it is unscrambled in
383
+ * memory only.
384
+ *
385
+ * Resolves { path, voice, seconds, warnings, report }; warnings is non-empty
386
+ * when less than 4 s of speech remained or the cut had to fall inside speech.
387
+ * When the .emd lands in the folder given to setExternalVoicesPath(), the new
388
+ * voice is usable right away.
389
+ */
390
+ async createVoiceClone(inputAudio: ModelRef, outputPath: string, embedder: ModelRef): Promise<VoiceCloneResult> {
391
+ const bridge = (typeof NativeTTS?.createVoiceClone === 'function') ? NativeTTS : NativeSpeech;
392
+ if (typeof bridge?.createVoiceClone !== 'function') {
393
+ throw new Error('createVoiceClone is not available on this platform');
394
+ }
395
+ const result = await bridge.createVoiceClone(
396
+ this.resolveModelToPath(inputAudio), String(outputPath), this.resolveModelToPath(embedder));
397
+ const folder = this.externalVoicesPath;
398
+ if (folder && typeof result?.path === 'string') {
399
+ const strip = (p: string) => p.replace(/^file:\/\//, '').replace(/\/+$/, '');
400
+ const parent = result.path.substring(0, result.path.lastIndexOf('/'));
401
+ if (strip(parent) === strip(folder)) await this.setExternalVoicesPath(folder);
402
+ }
403
+ return {
404
+ path: String(result?.path ?? ''),
405
+ voice: String(result?.voice ?? ''),
406
+ seconds: Number(result?.seconds ?? 0),
407
+ warnings: Array.isArray(result?.warnings) ? result.warnings.map(String) : [],
408
+ report: Array.isArray(result?.report) ? result.report.map(String) : [],
409
+ };
410
+ }
411
+
340
412
  /** Reference voice, matched case-insensitively without .wav.
341
413
  * Returns 0 on match; -1 on fallback/unavailability. Missing names select the
342
414
  * first WAV (alphabetical order). Already generated audio keeps its voice.
@@ -423,6 +495,7 @@ class Speech {
423
495
  private hasCompletedFullInit = false;
424
496
  private hasCompletedTtsInit = false;
425
497
  private iosTtsOnly = false; // when true, use NativeTTS directly on iOS
498
+ private externalVoicesPath: string | null = null; // setExternalVoicesPath()
426
499
 
427
500
 
428
501
  private logCall(name: string, payload?: any) {