echogarden 0.12.2 → 1.0.1

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 (334) hide show
  1. package/README.md +15 -14
  2. package/data/schemas/options.json +398 -111
  3. package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
  4. package/dist/alignment/DTWMfccSequenceAlignment.js +8 -8
  5. package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
  6. package/dist/alignment/DTWSequenceAlignment.js +1 -1
  7. package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
  8. package/dist/alignment/DTWSequenceAlignmentWindowed.js +2 -2
  9. package/dist/alignment/LevenshteinSequenceAlignment.d.ts +1 -1
  10. package/dist/alignment/LevenshteinSequenceAlignment.js +1 -1
  11. package/dist/alignment/SpeechAlignment.d.ts +11 -6
  12. package/dist/alignment/SpeechAlignment.js +113 -71
  13. package/dist/alignment/SpeechAlignment.js.map +1 -1
  14. package/dist/api/API.d.ts +13 -12
  15. package/dist/api/API.js +14 -13
  16. package/dist/api/API.js.map +1 -1
  17. package/dist/api/APIOptions.d.ts +5 -4
  18. package/dist/api/Alignment.d.ts +15 -9
  19. package/dist/api/Alignment.js +92 -74
  20. package/dist/api/Alignment.js.map +1 -1
  21. package/dist/api/Common.js +1 -1
  22. package/dist/api/Denoising.d.ts +6 -6
  23. package/dist/api/Denoising.js +23 -23
  24. package/dist/api/Denoising.js.map +1 -1
  25. package/dist/api/LanguageDetection.d.ts +19 -12
  26. package/dist/api/LanguageDetection.js +88 -38
  27. package/dist/api/LanguageDetection.js.map +1 -1
  28. package/dist/api/Recognition.d.ts +16 -6
  29. package/dist/api/Recognition.js +129 -55
  30. package/dist/api/Recognition.js.map +1 -1
  31. package/dist/api/SourceSeparation.d.ts +17 -0
  32. package/dist/api/SourceSeparation.js +61 -0
  33. package/dist/api/SourceSeparation.js.map +1 -0
  34. package/dist/api/Synthesis.d.ts +18 -18
  35. package/dist/api/Synthesis.js +191 -164
  36. package/dist/api/Synthesis.js.map +1 -1
  37. package/dist/api/Translation.d.ts +19 -8
  38. package/dist/api/Translation.js +132 -35
  39. package/dist/api/Translation.js.map +1 -1
  40. package/dist/api/Vad.d.ts +10 -5
  41. package/dist/api/Vad.js +76 -38
  42. package/dist/api/Vad.js.map +1 -1
  43. package/dist/audio/AudioBufferConversion.d.ts +1 -1
  44. package/dist/audio/AudioBufferConversion.js +4 -4
  45. package/dist/audio/AudioPlayer.d.ts +1 -1
  46. package/dist/audio/AudioPlayer.js +26 -26
  47. package/dist/audio/AudioPlayer.js.map +1 -1
  48. package/dist/audio/AudioRecorder.d.ts +1 -1
  49. package/dist/audio/AudioRecorder.js +5 -5
  50. package/dist/audio/AudioUtilities.d.ts +13 -9
  51. package/dist/audio/AudioUtilities.js +86 -24
  52. package/dist/audio/AudioUtilities.js.map +1 -1
  53. package/dist/cli/CLI.d.ts +3 -3
  54. package/dist/cli/CLI.js +271 -162
  55. package/dist/cli/CLI.js.map +1 -1
  56. package/dist/cli/CLIConfigFile.js +8 -8
  57. package/dist/cli/CLILauncher.js +6 -6
  58. package/dist/cli/CLIOptionsSchema.js +2 -2
  59. package/dist/cli/CLIParser.js +5 -5
  60. package/dist/cli/CLIStarter.js +4 -4
  61. package/dist/codecs/FFMpegTranscoder.d.ts +2 -2
  62. package/dist/codecs/FFMpegTranscoder.js +37 -37
  63. package/dist/codecs/FFMpegTranscoder.js.map +1 -1
  64. package/dist/codecs/TIMITCodec.js +5 -5
  65. package/dist/codecs/WaveCodec.d.ts +1 -1
  66. package/dist/codecs/WaveCodec.js +22 -22
  67. package/dist/denoising/RNNoise.d.ts +1 -1
  68. package/dist/denoising/RNNoise.js +9 -9
  69. package/dist/dsp/BiquadFilter.d.ts +3 -2
  70. package/dist/dsp/BiquadFilter.js +18 -11
  71. package/dist/dsp/BiquadFilter.js.map +1 -1
  72. package/dist/dsp/DecayingPeakEstimator.d.ts +16 -0
  73. package/dist/dsp/DecayingPeakEstimator.js +23 -0
  74. package/dist/dsp/DecayingPeakEstimator.js.map +1 -0
  75. package/dist/dsp/FFT.d.ts +8 -4
  76. package/dist/dsp/FFT.js +76 -30
  77. package/dist/dsp/FFT.js.map +1 -1
  78. package/dist/dsp/KWeightingFilter.d.ts +9 -0
  79. package/dist/dsp/KWeightingFilter.js +40 -0
  80. package/dist/dsp/KWeightingFilter.js.map +1 -0
  81. package/dist/dsp/LoudnessEstimator.d.ts +21 -0
  82. package/dist/dsp/LoudnessEstimator.js +47 -0
  83. package/dist/dsp/LoudnessEstimator.js.map +1 -0
  84. package/dist/dsp/MFCC.d.ts +2 -2
  85. package/dist/dsp/MFCC.js +15 -15
  86. package/dist/dsp/MelSpectogram.d.ts +1 -1
  87. package/dist/dsp/MelSpectogram.js +6 -6
  88. package/dist/dsp/Rubberband.d.ts +11 -11
  89. package/dist/dsp/Rubberband.js +27 -27
  90. package/dist/dsp/Sonic.d.ts +1 -1
  91. package/dist/dsp/Sonic.js +3 -3
  92. package/dist/dsp/SpeexResampler.d.ts +1 -1
  93. package/dist/dsp/SpeexResampler.js +2 -2
  94. package/dist/math/VectorMath.d.ts +12 -8
  95. package/dist/math/VectorMath.js +35 -32
  96. package/dist/math/VectorMath.js.map +1 -1
  97. package/dist/nlp/ChineseSegmentation.js +2 -2
  98. package/dist/nlp/CompromiseNLP.js +3 -3
  99. package/dist/nlp/EspeakPhonemizer.js +30 -30
  100. package/dist/nlp/IPA.js +20 -20
  101. package/dist/nlp/JapaneseSegmentation.js +6 -6
  102. package/dist/nlp/Lexicon.d.ts +1 -1
  103. package/dist/nlp/Lexicon.js +7 -7
  104. package/dist/nlp/Segmentation.d.ts +3 -0
  105. package/dist/nlp/Segmentation.js +21 -14
  106. package/dist/nlp/Segmentation.js.map +1 -1
  107. package/dist/nlp/TextNormalizer.js +16 -16
  108. package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -2
  109. package/dist/recognition/AmazonTranscribeSTT.js +13 -14
  110. package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
  111. package/dist/recognition/AzureCognitiveServicesSTT.js +5 -6
  112. package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
  113. package/dist/recognition/GoogleCloudSTT.d.ts +3 -3
  114. package/dist/recognition/GoogleCloudSTT.js +18 -18
  115. package/dist/recognition/OpenAICloudSTT.d.ts +19 -0
  116. package/dist/recognition/OpenAICloudSTT.js +81 -0
  117. package/dist/recognition/OpenAICloudSTT.js.map +1 -0
  118. package/dist/recognition/SileroSTT.d.ts +2 -2
  119. package/dist/recognition/SileroSTT.js +25 -25
  120. package/dist/recognition/VoskSTT.d.ts +2 -2
  121. package/dist/recognition/VoskSTT.js +8 -8
  122. package/dist/recognition/WhisperCppSTT.d.ts +88 -0
  123. package/dist/recognition/WhisperCppSTT.js +332 -0
  124. package/dist/recognition/WhisperCppSTT.js.map +1 -0
  125. package/dist/recognition/WhisperSTT.d.ts +49 -25
  126. package/dist/recognition/WhisperSTT.js +626 -481
  127. package/dist/recognition/WhisperSTT.js.map +1 -1
  128. package/dist/server/Client.d.ts +1 -1
  129. package/dist/server/Client.js +22 -22
  130. package/dist/server/Server.js +9 -9
  131. package/dist/server/Server.js.map +1 -1
  132. package/dist/server/Worker.d.ts +22 -22
  133. package/dist/server/Worker.js +36 -36
  134. package/dist/server/Worker.js.map +1 -1
  135. package/dist/server/WorkerStarter.js +2 -2
  136. package/dist/source-separation/MDXNetSourceSeparation.d.ts +11 -0
  137. package/dist/source-separation/MDXNetSourceSeparation.js +161 -0
  138. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -0
  139. package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
  140. package/dist/speech-language-detection/SileroLanguageDetection.js +7 -7
  141. package/dist/subtitles/Subtitles.d.ts +10 -0
  142. package/dist/subtitles/Subtitles.js +2 -2
  143. package/dist/subtitles/Subtitles.js.map +1 -1
  144. package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
  145. package/dist/synthesis/AwsPollyTTS.js +12 -12
  146. package/dist/synthesis/AzureCognitiveServicesTTS.js +7 -7
  147. package/dist/synthesis/CoquiServerTTS.js +10 -10
  148. package/dist/synthesis/CoquiServerTTS.js.map +1 -1
  149. package/dist/synthesis/ElevenlabsTTS.d.ts +23 -0
  150. package/dist/synthesis/ElevenlabsTTS.js +103 -0
  151. package/dist/synthesis/ElevenlabsTTS.js.map +1 -0
  152. package/dist/synthesis/EspeakTTS.d.ts +6 -5
  153. package/dist/synthesis/EspeakTTS.js +81 -69
  154. package/dist/synthesis/EspeakTTS.js.map +1 -1
  155. package/dist/synthesis/FliteTTS.d.ts +3 -3
  156. package/dist/synthesis/FliteTTS.js +154 -154
  157. package/dist/synthesis/FliteTTS.js.map +1 -1
  158. package/dist/synthesis/GoogleCloudTTS.d.ts +3 -3
  159. package/dist/synthesis/GoogleCloudTTS.js +17 -17
  160. package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
  161. package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
  162. package/dist/synthesis/GoogleTranslateTTS.js +103 -103
  163. package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -2
  164. package/dist/synthesis/MicrosoftEdgeTTS.js +74 -74
  165. package/dist/synthesis/OpenAICloudTTS.d.ts +13 -0
  166. package/dist/synthesis/OpenAICloudTTS.js +169 -0
  167. package/dist/synthesis/OpenAICloudTTS.js.map +1 -0
  168. package/dist/synthesis/SamTTS.js +3 -3
  169. package/dist/synthesis/SapiTTS.d.ts +3 -3
  170. package/dist/synthesis/SapiTTS.js +26 -26
  171. package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
  172. package/dist/synthesis/StreamlabsPollyTTS.js +27 -27
  173. package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
  174. package/dist/synthesis/SvoxPicoTTS.js +65 -65
  175. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  176. package/dist/synthesis/VitsTTS.d.ts +3 -3
  177. package/dist/synthesis/VitsTTS.js +378 -378
  178. package/dist/synthesis/VitsTTS.js.map +1 -1
  179. package/dist/tests/Test.js +2 -2
  180. package/dist/utilities/Compression.d.ts +5 -0
  181. package/dist/utilities/Compression.js +29 -13
  182. package/dist/utilities/Compression.js.map +1 -1
  183. package/dist/utilities/FileDownloader.d.ts +1 -1
  184. package/dist/utilities/FileDownloader.js +16 -16
  185. package/dist/utilities/FileSystem.js +7 -7
  186. package/dist/utilities/Locale.d.ts +7 -7
  187. package/dist/utilities/Locale.js +15 -15
  188. package/dist/utilities/Logger.js +3 -3
  189. package/dist/utilities/ObjectUtilities.js +19 -19
  190. package/dist/utilities/OpenPromise.js +2 -2
  191. package/dist/utilities/OpenPromise.js.map +1 -1
  192. package/dist/utilities/PackageManager.js +31 -0
  193. package/dist/utilities/PackageManager.js.map +1 -1
  194. package/dist/utilities/PathUtilities.js +8 -8
  195. package/dist/utilities/RandomGenerator.js +2 -2
  196. package/dist/utilities/SmoothEstimator.d.ts +8 -0
  197. package/dist/utilities/SmoothEstimator.js +25 -0
  198. package/dist/utilities/SmoothEstimator.js.map +1 -0
  199. package/dist/utilities/TarballMaker.js +8 -8
  200. package/dist/utilities/Timeline.d.ts +3 -2
  201. package/dist/utilities/Timeline.js +11 -11
  202. package/dist/utilities/Timeline.js.map +1 -1
  203. package/dist/utilities/Timer.js +4 -4
  204. package/dist/utilities/Utilities.d.ts +4 -0
  205. package/dist/utilities/Utilities.js +38 -15
  206. package/dist/utilities/Utilities.js.map +1 -1
  207. package/dist/utilities/WasmMemoryManager.js +7 -7
  208. package/dist/utilities/WebReader.js +23 -23
  209. package/dist/utilities/WikipediaReader.js +2 -2
  210. package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +28 -0
  211. package/dist/voice-activity-detection/AdaptiveGateVAD.js +138 -0
  212. package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -0
  213. package/dist/voice-activity-detection/SileroVAD.d.ts +1 -1
  214. package/dist/voice-activity-detection/SileroVAD.js +5 -5
  215. package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
  216. package/dist/voice-activity-detection/WebRtcVAD.d.ts +1 -1
  217. package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
  218. package/docs/API.md +29 -11
  219. package/docs/CLI.md +31 -7
  220. package/docs/Contributing.md +38 -0
  221. package/docs/Development.md +91 -19
  222. package/docs/Engines.md +28 -16
  223. package/docs/Licenses.md +4 -1
  224. package/docs/Options.md +159 -79
  225. package/docs/Releases.md +262 -0
  226. package/docs/Server.md +7 -7
  227. package/docs/Tasklist.md +96 -76
  228. package/docs/Technical.md +4 -4
  229. package/package.json +13 -14
  230. package/src/alignment/DTWMfccSequenceAlignment.ts +9 -9
  231. package/src/alignment/DTWSequenceAlignment.ts +2 -2
  232. package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
  233. package/src/alignment/LevenshteinSequenceAlignment.ts +2 -2
  234. package/src/alignment/SpeechAlignment.ts +195 -86
  235. package/src/api/API.ts +14 -13
  236. package/src/api/APIOptions.ts +12 -11
  237. package/src/api/Alignment.ts +148 -92
  238. package/src/api/Common.ts +1 -1
  239. package/src/api/Denoising.ts +28 -28
  240. package/src/api/LanguageDetection.ts +135 -48
  241. package/src/api/Recognition.ts +198 -59
  242. package/src/api/SourceSeparation.ts +99 -0
  243. package/src/api/Synthesis.ts +217 -181
  244. package/src/api/Translation.ts +193 -40
  245. package/src/api/Vad.ts +110 -41
  246. package/src/audio/AudioBufferConversion.ts +4 -4
  247. package/src/audio/AudioPlayer.ts +27 -27
  248. package/src/audio/AudioRecorder.ts +5 -5
  249. package/src/audio/AudioUtilities.ts +107 -24
  250. package/src/cli/CLI.ts +313 -164
  251. package/src/cli/CLIConfigFile.ts +8 -8
  252. package/src/cli/CLILauncher.ts +6 -6
  253. package/src/cli/CLIOptionsSchema.ts +2 -2
  254. package/src/cli/CLIParser.ts +5 -5
  255. package/src/cli/CLIStarter.ts +4 -4
  256. package/src/codecs/FFMpegTranscoder.ts +38 -38
  257. package/src/codecs/TIMITCodec.ts +5 -5
  258. package/src/codecs/WaveCodec.ts +22 -22
  259. package/src/denoising/RNNoise.ts +9 -9
  260. package/src/dsp/BiquadFilter.ts +19 -11
  261. package/src/dsp/DecayingPeakEstimator.ts +35 -0
  262. package/src/dsp/FFT.ts +103 -35
  263. package/src/dsp/KWeightingFilter.ts +43 -0
  264. package/src/dsp/LoudnessEstimator.ts +74 -0
  265. package/src/dsp/MFCC.ts +15 -15
  266. package/src/dsp/MelSpectogram.ts +7 -7
  267. package/src/dsp/Rubberband.ts +38 -38
  268. package/src/dsp/Sonic.ts +4 -4
  269. package/src/dsp/SpeexResampler.ts +2 -2
  270. package/src/math/VectorMath.ts +42 -33
  271. package/src/nlp/ChineseSegmentation.ts +3 -3
  272. package/src/nlp/CompromiseNLP.ts +3 -3
  273. package/src/nlp/EspeakPhonemizer.ts +30 -30
  274. package/src/nlp/IPA.ts +20 -20
  275. package/src/nlp/JapaneseSegmentation.ts +6 -6
  276. package/src/nlp/Lexicon.ts +8 -8
  277. package/src/nlp/Segmentation.ts +23 -14
  278. package/src/nlp/TextNormalizer.ts +16 -16
  279. package/src/recognition/AmazonTranscribeSTT.ts +16 -17
  280. package/src/recognition/AzureCognitiveServicesSTT.ts +8 -6
  281. package/src/recognition/GoogleCloudSTT.ts +21 -21
  282. package/src/recognition/OpenAICloudSTT.ts +142 -0
  283. package/src/recognition/SileroSTT.ts +26 -26
  284. package/src/recognition/VoskSTT.ts +10 -10
  285. package/src/recognition/WhisperCppSTT.ts +555 -0
  286. package/src/recognition/WhisperSTT.ts +760 -507
  287. package/src/server/Client.ts +23 -23
  288. package/src/server/Server.ts +9 -9
  289. package/src/server/Worker.ts +53 -53
  290. package/src/server/WorkerStarter.ts +2 -2
  291. package/src/source-separation/MDXNetSourceSeparation.ts +228 -0
  292. package/src/speech-language-detection/SileroLanguageDetection.ts +8 -8
  293. package/src/subtitles/Subtitles.ts +3 -3
  294. package/src/synthesis/AwsPollyTTS.ts +14 -14
  295. package/src/synthesis/AzureCognitiveServicesTTS.ts +10 -10
  296. package/src/synthesis/CoquiServerTTS.ts +10 -10
  297. package/src/synthesis/ElevenlabsTTS.ts +137 -0
  298. package/src/synthesis/EspeakTTS.ts +90 -71
  299. package/src/synthesis/FliteTTS.ts +157 -157
  300. package/src/synthesis/GoogleCloudTTS.ts +19 -19
  301. package/src/synthesis/GoogleTranslateTTS.ts +104 -104
  302. package/src/synthesis/MicrosoftEdgeTTS.ts +80 -80
  303. package/src/synthesis/OpenAICloudTTS.ts +196 -0
  304. package/src/synthesis/SamTTS.ts +3 -3
  305. package/src/synthesis/SapiTTS.ts +29 -29
  306. package/src/synthesis/StreamlabsPollyTTS.ts +29 -29
  307. package/src/synthesis/SvoxPicoTTS.ts +67 -67
  308. package/src/synthesis/VitsTTS.ts +380 -380
  309. package/src/tests/Test.ts +4 -4
  310. package/src/utilities/Compression.ts +34 -13
  311. package/src/utilities/FileDownloader.ts +19 -19
  312. package/src/utilities/FileSystem.ts +7 -7
  313. package/src/utilities/Locale.ts +22 -22
  314. package/src/utilities/Logger.ts +4 -4
  315. package/src/utilities/ObjectUtilities.ts +19 -19
  316. package/src/utilities/OpenPromise.ts +2 -2
  317. package/src/utilities/PackageManager.ts +40 -0
  318. package/src/utilities/PathUtilities.ts +8 -8
  319. package/src/utilities/RandomGenerator.ts +3 -3
  320. package/src/utilities/SmoothEstimator.ts +35 -0
  321. package/src/utilities/TarballMaker.ts +9 -9
  322. package/src/utilities/Timeline.ts +15 -13
  323. package/src/utilities/Timer.ts +4 -4
  324. package/src/utilities/Utilities.ts +49 -15
  325. package/src/utilities/WasmMemoryManager.ts +7 -7
  326. package/src/utilities/WebReader.ts +23 -23
  327. package/src/utilities/WikipediaReader.ts +2 -2
  328. package/src/voice-activity-detection/AdaptiveGateVAD.ts +202 -0
  329. package/src/voice-activity-detection/SileroVAD.ts +5 -5
  330. package/src/voice-activity-detection/WebRtcVAD.ts +5 -5
  331. package/dist/synthesis/ElevenLabsTTS.d.ts +0 -8
  332. package/dist/synthesis/ElevenLabsTTS.js +0 -82
  333. package/dist/synthesis/ElevenLabsTTS.js.map +0 -1
  334. package/src/synthesis/ElevenLabsTTS.ts +0 -104
package/docs/Technical.md CHANGED
@@ -16,7 +16,7 @@ Packages are downloaded as `.tar.gz` files, and are extracted to `[data-folder]/
16
16
  * `Users/User/Library/Application Support/echogarden` on macOS
17
17
  * `/home/user/.local/share/echogarden` on Linux
18
18
 
19
- By using downloadable packages, the installed size is made significantly smaller and the installation faster. The total size of all available packages is currently about 16.2GB (11.4GB compressed).
19
+ By using downloadable packages, the installed size is made significantly smaller and the installation faster. The total size of all available packages is currently about 50GB (may not be up-to-date).
20
20
 
21
21
  The packages are currently hosted and downloaded from a dedicated [Hugging Face repository](https://huggingface.co/echogarden/echogarden-packages).
22
22
 
@@ -24,14 +24,14 @@ The packages are currently hosted and downloaded from a dedicated [Hugging Face
24
24
 
25
25
  The base installed (uncompressed) size, including dependencies, is around 270MB. This is without any models or voices, which are downloaded as needed.
26
26
 
27
- Currently, the biggest contributors to the size are:
27
+ Currently, the largest contributors to the size are:
28
28
 
29
29
  * `onnxruntime-node` (NAPI): 92MB
30
30
  * `kuromoji` (JavaScript) 40MB
31
31
  * `flite-wasi` (WASI): 20MB
32
32
  * `espeak-ng-emscripten` (WASM): 18MB
33
33
 
34
- `onnxruntime-node` is big because it bundles pre-compiled binaries for multiple platforms. `kuromoji` is large because of its dictionary files and some unessential test code it bundles. The other three packages include large WASM binaries.
34
+ `onnxruntime-node` is large because it bundles pre-compiled binaries for multiple platforms. `kuromoji` is large because of its dictionary files and some unessential test code it bundles. The other three packages include large WASM binaries.
35
35
 
36
36
  So, yes, in the future it may be possible to reduce the core installed size by dynamically installing some of these dependencies, or using modified, "slimmed-down" versions of some packages.
37
37
 
@@ -51,7 +51,7 @@ However, it is a lot of work, and only a subset of the engines can be supported
51
51
  * Possibly lots of issues with inconsistent browser support and browser security constraints
52
52
  * Not future-proof. Due to changing restrictions of browsers, the runtime environment is not guaranteed be reliably reproducible in the future, meaning that it may need continuous maintenance to ensure it keeps working on the newest browsers
53
53
 
54
- It remains to be seen if this sort of work would feel justified somehow. I designed the tool to make the local installation extremely easy and issue-free. I guess it could look "impressive" to be able to run it in a browser, and may be come as a nice "toy" or "tech-demo", and could get some attention, but it may eventually turn out to be simpler and more practical to just install a local instance and connect to it from the browser via a WebSocket API.
54
+ It remains to be seen if this sort of work would feel justified somehow. I designed the tool to make the local installation extremely easy and issue-free. I guess it could look "impressive" to be able to run it in a browser, and may become as a nice "toy" or "tech-demo", and could get some attention, but it may eventually turn out to be simpler and more practical to just install a local instance and connect to it from the browser via a WebSocket API.
55
55
 
56
56
  A TTS-only browser extension is in development. It registers Echogarden's voices on the browser's Web Speech API using the [`chrome.ttsEngine`](https://developer.chrome.com/docs/extensions/reference/ttsEngine/) extension API and communicates with it using the WebSocket API
57
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echogarden",
3
- "version": "0.12.2",
3
+ "version": "1.0.1",
4
4
  "description": "An integrated speech toolset, providing a variety of synthesis, recognition, alignment, and other processing tools, designed to be directly accessible to end-users.",
5
5
  "author": "Rotem Dan",
6
6
  "license": "GPL-3.0",
@@ -24,7 +24,7 @@
24
24
  "url": "https://github.com/echogarden-project/echogarden/issues"
25
25
  },
26
26
  "engines": {
27
- "node": ">=16"
27
+ "node": ">=18"
28
28
  },
29
29
  "os": [
30
30
  "win32",
@@ -54,8 +54,8 @@
54
54
  "echogarden": "./dist/cli/CLILauncher.js"
55
55
  },
56
56
  "dependencies": {
57
- "@aws-sdk/client-polly": "^3.535.0",
58
- "@aws-sdk/client-transcribe-streaming": "^3.535.0",
57
+ "@aws-sdk/client-polly": "^3.554.0",
58
+ "@aws-sdk/client-transcribe-streaming": "^3.554.0",
59
59
  "@echogarden/espeak-ng-emscripten": "^0.1.2",
60
60
  "@echogarden/fasttext-wasm": "^0.1.0",
61
61
  "@echogarden/flite-wasi": "^0.1.1",
@@ -72,10 +72,10 @@
72
72
  "chalk": "^5.3.0",
73
73
  "cldr-segmentation": "^2.2.0",
74
74
  "command-exists": "^1.2.9",
75
- "compromise": "^14.12.0",
75
+ "compromise": "^14.13.0",
76
76
  "compromise-dates": "^3.5.0",
77
77
  "fs-extra": "^11.2.0",
78
- "gaxios": "^6.3.0",
78
+ "gaxios": "^6.4.0",
79
79
  "graceful-fs": "^4.2.11",
80
80
  "html-escaper": "^3.0.3",
81
81
  "html-to-text": "^9.0.5",
@@ -90,9 +90,11 @@
90
90
  "ndarray": "^1.0.19",
91
91
  "ndarray-ops": "^1.2.2",
92
92
  "onnxruntime-node": "^1.17.0",
93
+ "openai": "^4.33.0",
93
94
  "sam-js": "^0.2.1",
94
95
  "strip-ansi": "^7.1.0",
95
- "tar": "^6.2.0",
96
+ "tar": "^7.0.0",
97
+ "tiktoken": "^1.0.13",
96
98
  "tinyld": "^1.3.4",
97
99
  "ws": "^8.16.0",
98
100
  "wtf_wikipedia": "^10.3.0"
@@ -121,14 +123,11 @@
121
123
  "@types/msgpack-lite": "^0.1.11",
122
124
  "@types/ndarray": "^1.0.14",
123
125
  "@types/ndarray-ops": "^1.2.7",
124
- "@types/node": "^20.11.28",
126
+ "@types/node": "^20.12.7",
125
127
  "@types/recursive-readdir": "^2.2.4",
126
- "@types/tar": "^6.1.11",
128
+ "@types/tar": "^6.1.12",
127
129
  "@types/ws": "^8.5.10",
128
- "@typescript-eslint/eslint-plugin": "^7.2.0",
129
- "@typescript-eslint/parser": "^7.2.0",
130
- "eslint": "^8.57.0",
131
- "ts-json-schema-generator": "^1.5.0",
132
- "typescript": "^5.4.2"
130
+ "ts-json-schema-generator": "^1.5.1",
131
+ "typescript": "^5.4.5"
133
132
  }
134
133
  }
@@ -1,21 +1,21 @@
1
- import { cosineDistancePrecomputedMagnitudes, createVectorForIntegerRange, eucledianDistance, magnitude } from "../math/VectorMath.js"
2
- import { logToStderr } from "../utilities/Utilities.js"
3
- import { alignDTWWindowed } from "./DTWSequenceAlignmentWindowed.js"
1
+ import { cosineDistancePrecomputedMagnitudes, createVectorForIntegerRange, euclidianDistance, magnitude } from '../math/VectorMath.js'
2
+ import { logToStderr } from '../utilities/Utilities.js'
3
+ import { alignDTWWindowed } from './DTWSequenceAlignmentWindowed.js'
4
4
 
5
5
  const log = logToStderr
6
6
 
7
- export async function alignMFCC_DTW(mfccFrames1: number[][], mfccFrames2: number[][], windowLength: number, distanceFunction: "eucledian" | "cosine" = "eucledian", centerIndexes?: number[]) {
8
- if (distanceFunction == "eucledian") {
7
+ export async function alignMFCC_DTW(mfccFrames1: number[][], mfccFrames2: number[][], windowLength: number, distanceFunction: 'euclidian' | 'cosine' = 'euclidian', centerIndexes?: number[]) {
8
+ if (distanceFunction == 'euclidian') {
9
9
  const { path } = alignDTWWindowed(
10
10
  mfccFrames1,
11
11
  mfccFrames2,
12
- eucledianDistance,
12
+ euclidianDistance,
13
13
  windowLength,
14
14
  centerIndexes
15
15
  )
16
16
 
17
17
  return path
18
- } else if (distanceFunction == "cosine") {
18
+ } else if (distanceFunction == 'cosine') {
19
19
  const indexes1 = createVectorForIntegerRange(0, mfccFrames1.length)
20
20
  const indexes2 = createVectorForIntegerRange(0, mfccFrames2.length)
21
21
 
@@ -32,12 +32,12 @@ export async function alignMFCC_DTW(mfccFrames1: number[][], mfccFrames2: number
32
32
 
33
33
  return path
34
34
  } else {
35
- throw new Error("Invalid distance function")
35
+ throw new Error('Invalid distance function')
36
36
  }
37
37
  }
38
38
 
39
39
  export function getCostMatrixMemorySizeMB(sequence1Length: number, sequence2Length: number, windowLength: number) {
40
40
  const costMatrixMemorySizeMB = sequence1Length * Math.min(sequence2Length, windowLength) * 4 / 1000000
41
-
41
+
42
42
  return costMatrixMemorySizeMB
43
43
  }
@@ -1,5 +1,5 @@
1
- import { logToStderr } from "../utilities/Utilities.js"
2
- import { AlignmentPath } from "./SpeechAlignment.js"
1
+ import { logToStderr } from '../utilities/Utilities.js'
2
+ import { AlignmentPath } from './SpeechAlignment.js'
3
3
 
4
4
  const log = logToStderr
5
5
 
@@ -1,11 +1,11 @@
1
- import { logToStderr } from "../utilities/Utilities.js"
2
- import { AlignmentPath } from "./SpeechAlignment.js"
1
+ import { logToStderr } from '../utilities/Utilities.js'
2
+ import { AlignmentPath } from './SpeechAlignment.js'
3
3
 
4
4
  const log = logToStderr
5
5
 
6
6
  export function alignDTWWindowed<T, U>(sequence1: T[], sequence2: U[], costFunction: (a: T, b: U) => number, windowMaxLength: number, centerIndexes?: number[]) {
7
7
  if (windowMaxLength < 2) {
8
- throw new Error("Window length must be greater or equal to 2")
8
+ throw new Error('Window length must be greater or equal to 2')
9
9
  }
10
10
 
11
11
  if (sequence1.length == 0 || sequence2.length == 0) {
@@ -1,5 +1,5 @@
1
- import { logToStderr } from "../utilities/Utilities.js"
2
- import { AlignmentPath } from "./SpeechAlignment.js"
1
+ import { logToStderr } from '../utilities/Utilities.js'
2
+ import { AlignmentPath } from './SpeechAlignment.js'
3
3
 
4
4
  const log = logToStderr
5
5
 
@@ -1,16 +1,26 @@
1
- import { clip } from "../utilities/Utilities.js"
1
+ import { clip } from '../utilities/Utilities.js'
2
+
3
+ import * as API from '../api/API.js'
4
+
5
+ import { computeMFCCs, extendDefaultMfccOptions, MfccOptions } from '../dsp/MFCC.js'
6
+ import { alignMFCC_DTW, getCostMatrixMemorySizeMB } from './DTWMfccSequenceAlignment.js'
7
+ import { Logger } from '../utilities/Logger.js'
8
+ import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
9
+ import { downmixToMonoAndNormalize, getEndingSilentSampleCount, getRawAudioDuration, getStartingSilentSampleCount, RawAudio } from '../audio/AudioUtilities.js'
10
+ import chalk from 'chalk'
11
+ import { synthesize } from '../api/API.js'
12
+ import { resampleAudioSpeex } from '../dsp/SpeexResampler.js'
13
+ import { deepClone } from '../utilities/ObjectUtilities.js'
14
+ import { zeroIfNaN } from '../math/VectorMath.js'
15
+ import { EspeakOptions } from '../synthesis/EspeakTTS.js'
16
+
17
+ export async function alignUsingDtw(
18
+ sourceRawAudio: RawAudio,
19
+ referenceRawAudio: RawAudio,
20
+ referenceTimeline: Timeline,
21
+ granularities: DtwGranularity[],
22
+ windowDurations: number[]) {
2
23
 
3
- import * as API from "../api/API.js"
4
-
5
- import { computeMFCCs, extendDefaultMfccOptions, MfccOptions } from "../dsp/MFCC.js"
6
- import { alignMFCC_DTW, getCostMatrixMemorySizeMB } from "./DTWMfccSequenceAlignment.js"
7
- import { Logger } from "../utilities/Logger.js"
8
- import { Timeline, TimelineEntry } from "../utilities/Timeline.js"
9
- import { getEndingSilentSampleCount, getRawAudioDuration, getStartingSilentSampleCount, RawAudio } from "../audio/AudioUtilities.js"
10
- import { type EspeakOptions } from "../synthesis/EspeakTTS.js"
11
- import chalk from "chalk"
12
-
13
- export async function alignUsingDtw(sourceRawAudio: RawAudio, referenceRawAudio: RawAudio, referenceTimeline: Timeline, granularities: DtwGranularity[], windowDurations: number[]) {
14
24
  const logger = new Logger()
15
25
 
16
26
  if (windowDurations.length == 0) {
@@ -38,11 +48,11 @@ export async function alignUsingDtw(sourceRawAudio: RawAudio, referenceRawAudio:
38
48
  framesPerSecond = 1 / mfccOptions.hopDuration!
39
49
 
40
50
  // Compute reference MFCCs
41
- logger.start("Compute reference MFCC features")
51
+ logger.start('Compute reference MFCC features')
42
52
  const referenceMfccs = await computeMFCCs(referenceRawAudio, mfccOptions)
43
53
 
44
54
  // Compute source MFCCs
45
- logger.start("Compute source MFCC features")
55
+ logger.start('Compute source MFCC features')
46
56
  const sourceMfccs = await computeMFCCs(sourceRawAudio, mfccOptions)
47
57
  logger.end()
48
58
 
@@ -52,12 +62,12 @@ export async function alignUsingDtw(sourceRawAudio: RawAudio, referenceRawAudio:
52
62
  if (passIndex == 0) {
53
63
  const minRecommendedWindowDuration = 0.2 * rawAudioDuration
54
64
 
55
- if (windowDuration < minRecommendedWindowDuration ) {
65
+ if (windowDuration < minRecommendedWindowDuration) {
56
66
  logger.logTitledMessage('Warning', `Maximum DTW window duration is set to ${windowDuration.toFixed(1)}s, which is smaller than 20% of the source audio duration of ${rawAudioDuration.toFixed(1)}s. This may lead to suboptimal results in some cases. Consider increasing window duration if needed.`, chalk.yellowBright)
57
67
  }
58
68
  }
59
69
 
60
- logger.start("Align MFCC features using DTW")
70
+ logger.start('Align MFCC features using DTW')
61
71
  const dtwWindowLength = Math.floor(windowDuration * framesPerSecond)
62
72
 
63
73
  let centerIndexes: number[] | undefined
@@ -85,18 +95,18 @@ export async function alignUsingDtw(sourceRawAudio: RawAudio, referenceRawAudio:
85
95
  logger.end()
86
96
  }
87
97
 
88
- logger.start("\nConvert path to timeline")
98
+ logger.start('\nConvert path to timeline')
89
99
 
90
100
  function getMappedTimelineEntry(timelineEntry: TimelineEntry, recurse = true): TimelineEntry {
91
101
  const referenceStartFrameIndex = Math.floor(timelineEntry.startTime * framesPerSecond)
92
102
  const referenceEndFrameIndex = Math.floor(timelineEntry.endTime * framesPerSecond)
93
103
 
94
104
  if (referenceStartFrameIndex < 0 || referenceEndFrameIndex < 0) {
95
- throw new Error("Unexpected: encountered a negative timestamp in timeline")
105
+ throw new Error('Unexpected: encountered a negative timestamp in timeline')
96
106
  }
97
107
 
98
- const mappedStartFrameIndex = getMappedFrameIndexForPath(referenceStartFrameIndex, compactedPath, "first")
99
- const mappedEndFrameIndex = getMappedFrameIndexForPath(referenceEndFrameIndex, compactedPath, "first")
108
+ const mappedStartFrameIndex = getMappedFrameIndexForPath(referenceStartFrameIndex, compactedPath, 'first')
109
+ const mappedEndFrameIndex = getMappedFrameIndexForPath(referenceEndFrameIndex, compactedPath, 'first')
100
110
 
101
111
  let innerTimeline: Timeline | undefined
102
112
 
@@ -141,7 +151,16 @@ export async function alignUsingDtw(sourceRawAudio: RawAudio, referenceRawAudio:
141
151
  return mappedTimeline
142
152
  }
143
153
 
144
- export async function alignUsingDtwWithRecognition(sourceRawAudio: RawAudio, referenceRawAudio: RawAudio, referenceTimeline: Timeline, recognitionTimeline: Timeline, granularities: DtwGranularity[], windowDurations: number[], espeakOptions: EspeakOptions, phoneAlignmentMethod: API.PhoneAlignmentMethod = "interpolation") {
154
+ export async function alignUsingDtwWithRecognitionReference(
155
+ sourceRawAudio: RawAudio,
156
+ referenceRawAudio: RawAudio,
157
+ referenceTimeline: Timeline,
158
+ recognitionTimeline: Timeline,
159
+ granularities: DtwGranularity[],
160
+ windowDurations: number[],
161
+ espeakOptions: EspeakOptions,
162
+ phoneAlignmentMethod: API.PhoneAlignmentMethod = 'interpolation') {
163
+
145
164
  const logger = new Logger()
146
165
 
147
166
  if (recognitionTimeline.length == 0) {
@@ -167,57 +186,89 @@ export async function alignUsingDtwWithRecognition(sourceRawAudio: RawAudio, ref
167
186
  logger.start("Synthesize recognized transcript with eSpeak")
168
187
  const recognizedWords = recognitionTimeline.map(entry => entry.text)
169
188
 
170
- const { rawAudio: synthesizedRecognizedTranscriptRawAudio, timeline: synthesizedRecognitionTimeline } = await createAlignmentReferenceUsingEspeakForFragments(recognizedWords, espeakOptions)
189
+ const {
190
+ rawAudio: synthesizedRecognizedTranscriptRawAudio,
191
+ timeline: synthesizedRecognitionTimeline
192
+ } = await createAlignmentReferenceUsingEspeakForFragments(recognizedWords, espeakOptions)
171
193
 
172
194
  let recognitionTimelineWithPhones: Timeline
173
195
 
174
- if (phoneAlignmentMethod == "interpolation") {
196
+ if (phoneAlignmentMethod == 'interpolation') {
175
197
  // Add phone timelines by interpolating from reference words
176
- logger.start("Interpolate phone timing")
198
+ logger.start('Interpolate phone timing')
177
199
 
178
- recognitionTimelineWithPhones = await interpolatePhoneTimelines(recognitionTimeline, synthesizedRecognitionTimeline)
179
- } else if (phoneAlignmentMethod == "dtw") {
180
- logger.start("Align phone timing")
200
+ recognitionTimelineWithPhones = await interpolatePhoneTimelines(
201
+ recognitionTimeline,
202
+ synthesizedRecognitionTimeline
203
+ )
204
+ } else if (phoneAlignmentMethod == 'dtw') {
205
+ logger.start('Align phone timing')
181
206
 
182
207
  // Add phone timelines by aligning each individual recognized word with the corresponding word
183
208
  // in the reference timeline
184
- recognitionTimelineWithPhones = await alignPhoneTimelines(sourceRawAudio, recognitionTimeline, synthesizedRecognizedTranscriptRawAudio, synthesizedRecognitionTimeline)
185
- } else if (phoneAlignmentMethod == "dtw-knn") {
186
- logger.start("Align phone timing")
187
- throw new Error("Not implemented")
209
+ recognitionTimelineWithPhones = await alignPhoneTimelines(
210
+ sourceRawAudio,
211
+ recognitionTimeline,
212
+ synthesizedRecognizedTranscriptRawAudio,
213
+ synthesizedRecognitionTimeline,
214
+ 60)
188
215
  } else {
189
216
  throw new Error(`Unknown phone alignment method: ${phoneAlignmentMethod}`)
190
217
  }
191
218
 
192
- logger.start("Map from the synthesized recognized timeline to the recognized timeline")
193
219
  // Create a mapping from the synthesized recognized timeline to the recognized timeline
220
+ logger.start("Map from the synthesized recognized timeline to the recognized timeline")
221
+
222
+ type SynthesizedToRecognizedTimeMappingEntry = {
223
+ synthesized: number
224
+ recognized: number
225
+ }
226
+
194
227
  type SynthesizedToRecognizedTimeMapping = SynthesizedToRecognizedTimeMappingEntry[]
195
- type SynthesizedToRecognizedTimeMappingEntry = { synthesized: number, recognized: number }
196
228
 
197
229
  const synthesizedToRecognizedTimeMapping: SynthesizedToRecognizedTimeMapping = []
198
230
 
199
- for (let i = 0; i < synthesizedRecognitionTimeline.length; i++) {
200
- const synthesizedTimelineEntry = synthesizedRecognitionTimeline[i]
201
- const recognitionTimelineEntry = recognitionTimelineWithPhones[i]
231
+ for (let wordEntryIndex = 0; wordEntryIndex < synthesizedRecognitionTimeline.length; wordEntryIndex++) {
232
+ const synthesizedTimelineEntry = synthesizedRecognitionTimeline[wordEntryIndex]
233
+ const recognitionTimelineEntry = recognitionTimelineWithPhones[wordEntryIndex]
202
234
 
203
- synthesizedToRecognizedTimeMapping.push({ synthesized: synthesizedTimelineEntry.startTime, recognized: recognitionTimelineEntry.startTime })
235
+ synthesizedToRecognizedTimeMapping.push({
236
+ synthesized: synthesizedTimelineEntry.startTime,
237
+ recognized: recognitionTimelineEntry.startTime
238
+ })
204
239
 
205
240
  if (synthesizedTimelineEntry.timeline) {
206
- for (let j = 0; j < synthesizedTimelineEntry.timeline.length; j++) {
207
- const synthesizedPhoneTimelineEntry = synthesizedTimelineEntry.timeline[j]
208
- const recognitionPhoneTimelineEntry = recognitionTimelineEntry.timeline![j]
209
-
210
- synthesizedToRecognizedTimeMapping.push({ synthesized: synthesizedPhoneTimelineEntry.startTime, recognized: recognitionPhoneTimelineEntry.startTime })
211
- synthesizedToRecognizedTimeMapping.push({ synthesized: synthesizedPhoneTimelineEntry.endTime, recognized: recognitionPhoneTimelineEntry.endTime })
241
+ for (let tokenEntryIndex = 0; tokenEntryIndex < synthesizedTimelineEntry.timeline.length; tokenEntryIndex++) {
242
+ const synthesizedPhoneTimelineEntry = synthesizedTimelineEntry.timeline[tokenEntryIndex]
243
+ const recognitionPhoneTimelineEntry = recognitionTimelineEntry.timeline![tokenEntryIndex]
244
+
245
+ synthesizedToRecognizedTimeMapping.push({
246
+ synthesized: synthesizedPhoneTimelineEntry.startTime,
247
+ recognized: recognitionPhoneTimelineEntry.startTime
248
+ })
249
+
250
+ synthesizedToRecognizedTimeMapping.push({
251
+ synthesized: synthesizedPhoneTimelineEntry.endTime,
252
+ recognized: recognitionPhoneTimelineEntry.endTime
253
+ })
212
254
  }
213
255
  }
214
256
 
215
- synthesizedToRecognizedTimeMapping.push({ synthesized: synthesizedTimelineEntry.endTime, recognized: recognitionTimelineEntry.endTime })
257
+ synthesizedToRecognizedTimeMapping.push({
258
+ synthesized: synthesizedTimelineEntry.endTime,
259
+ recognized: recognitionTimelineEntry.endTime
260
+ })
216
261
  }
217
262
 
218
- logger.start("Align the synthesized recognized transcript with the synthesized ground-truth transcript")
219
263
  // Align the synthesized recognized transcript to the synthesized reference transcript
220
- const alignedSynthesizedRecognitionTimeline = await alignUsingDtw(synthesizedRecognizedTranscriptRawAudio, referenceRawAudio, referenceTimeline, granularities, windowDurations)
264
+ logger.start("Align the synthesized recognized transcript with the synthesized ground-truth transcript")
265
+
266
+ const alignedSynthesizedRecognitionTimeline = await alignUsingDtw(
267
+ synthesizedRecognizedTranscriptRawAudio,
268
+ referenceRawAudio,
269
+ referenceTimeline,
270
+ granularities,
271
+ windowDurations)
221
272
 
222
273
  let currentSynthesizedToRecognizedMappingIndex = 0
223
274
 
@@ -234,7 +285,7 @@ export async function alignUsingDtwWithRecognition(sourceRawAudio: RawAudio, ref
234
285
  }
235
286
 
236
287
  if (left > right) {
237
- throw new Error("left is larger than right!")
288
+ throw new Error("Left is larger than right!")
238
289
  }
239
290
 
240
291
  if (Math.abs(synthesizedTime - left) < Math.abs(synthesizedTime - right)) {
@@ -273,44 +324,59 @@ export async function alignUsingDtwWithRecognition(sourceRawAudio: RawAudio, ref
273
324
  export async function interpolatePhoneTimelines(sourceTimeline: Timeline, referenceTimeline: Timeline) {
274
325
  const interpolatedTimeline: Timeline = []
275
326
 
276
- for (let i = 0; i < sourceTimeline.length; i++) {
277
- const referenceEntry = referenceTimeline[i]
327
+ for (let wordEntryIndex = 0; wordEntryIndex < sourceTimeline.length; wordEntryIndex++) {
328
+ const referenceEntry = referenceTimeline[wordEntryIndex]
278
329
 
279
- const interpolatedEntry = { ...sourceTimeline[i] }
330
+ const interpolatedEntry = deepClone(sourceTimeline[wordEntryIndex])
280
331
  interpolatedTimeline.push(interpolatedEntry)
281
332
 
282
- if (interpolatedEntry.type != "word") {
333
+ if (interpolatedEntry.type != 'word') {
283
334
  continue
284
335
  }
285
336
 
286
337
  const interpolatedEntryDuration = interpolatedEntry.endTime - interpolatedEntry.startTime
287
338
  const synthesisEntryDuration = referenceEntry.endTime - referenceEntry.startTime
288
339
 
289
- interpolatedEntry.timeline = []
340
+ function mapEntry(targetEntry: TimelineEntry): TimelineEntry {
341
+ const targetStartTimePercentageRelativeToWord =
342
+ (targetEntry.startTime - referenceEntry.startTime) / synthesisEntryDuration
290
343
 
291
- for (const phoneEntry of referenceEntry.timeline!) {
292
- const phoneStartTimePercentageRelativeToWord =
293
- (phoneEntry.startTime - referenceEntry.startTime) / synthesisEntryDuration
344
+ const targetEndTimePercentageRelativeToWord =
345
+ (targetEntry.endTime - referenceEntry.startTime) / synthesisEntryDuration
294
346
 
295
- const phoneEndTimePercentageRelativeToWord =
296
- (phoneEntry.endTime - referenceEntry.startTime) / synthesisEntryDuration
347
+ const interpolatedStartTime =
348
+ interpolatedEntry.startTime + (zeroIfNaN(targetStartTimePercentageRelativeToWord) * interpolatedEntryDuration)
297
349
 
298
- const interpolatedPhoneStartTime = interpolatedEntry.startTime + (phoneStartTimePercentageRelativeToWord * interpolatedEntryDuration)
299
- const interpolatedPhoneEndTime = interpolatedEntry.startTime + (phoneEndTimePercentageRelativeToWord * interpolatedEntryDuration)
350
+ const interpolatedEndTime =
351
+ interpolatedEntry.startTime + (zeroIfNaN(targetEndTimePercentageRelativeToWord) * interpolatedEntryDuration)
300
352
 
301
- interpolatedEntry.timeline.push({
302
- ...phoneEntry,
353
+ return {
354
+ ...targetEntry,
303
355
 
304
- startTime: interpolatedPhoneStartTime,
305
- endTime: interpolatedPhoneEndTime
306
- })
356
+ startTime: interpolatedStartTime,
357
+ endTime: interpolatedEndTime
358
+ }
359
+ }
360
+
361
+ const interpolatedPhoneEntries: Timeline = []
362
+
363
+ for (const phoneEntry of (referenceEntry.timeline || [])) {
364
+ interpolatedPhoneEntries.push(mapEntry(phoneEntry))
307
365
  }
366
+
367
+ interpolatedEntry.timeline = interpolatedPhoneEntries
308
368
  }
309
369
 
310
370
  return interpolatedTimeline
311
371
  }
312
372
 
313
- export async function alignPhoneTimelines(sourceRawAudio: RawAudio, sourceWordTimeline: Timeline, referenceRawAudio: RawAudio, referenceTimeline: Timeline) {
373
+ export async function alignPhoneTimelines(
374
+ sourceRawAudio: RawAudio,
375
+ sourceWordTimeline: Timeline,
376
+ referenceRawAudio: RawAudio,
377
+ referenceTimeline: Timeline,
378
+ windowDuration: number) {
379
+
314
380
  const mfccOptions: MfccOptions = extendDefaultMfccOptions({ zeroFirstCoefficient: true })
315
381
 
316
382
  const framesPerSecond = 1 / mfccOptions.hopDuration!
@@ -323,10 +389,9 @@ export async function alignPhoneTimelines(sourceRawAudio: RawAudio, sourceWordTi
323
389
  for (let i = 0; i < referenceTimeline.length; i++) {
324
390
  const referenceWordEntry = referenceTimeline[i]
325
391
 
326
- const alignedWordEntry = { ...sourceWordTimeline[i] }
327
- alignedWordTimeline.push(alignedWordEntry)
392
+ const alignedWordEntry = deepClone(sourceWordTimeline[i])
328
393
 
329
- if (alignedWordEntry.type != "word") {
394
+ if (alignedWordEntry.type != 'word') {
330
395
  continue
331
396
  }
332
397
 
@@ -351,27 +416,36 @@ export async function alignPhoneTimelines(sourceRawAudio: RawAudio, sourceWordTi
351
416
  const sourceWordMfccs = sourceMfccs.slice(alignedWordStartFrameIndex, alignedWordEndFrameIndex)
352
417
 
353
418
  // Compute DTW path
354
- const rawPath = await alignMFCC_DTW(referenceWordMfccs, sourceWordMfccs, 60)
419
+ const rawPath = await alignMFCC_DTW(referenceWordMfccs, sourceWordMfccs, windowDuration * framesPerSecond)
355
420
  const compactedPath = compactPath(rawPath)
356
421
 
357
- // Add phone timeline using the mapped time information
358
- alignedWordEntry.timeline = []
422
+ function mapEntry(referenceEntry: TimelineEntry): TimelineEntry {
423
+ const referenceStartFrameOffset = Math.floor((referenceEntry.startTime - referenceWordEntry.startTime) * framesPerSecond)
424
+ const alignedStartFrameOffset = getMappedFrameIndexForPath(referenceStartFrameOffset, compactedPath)
425
+ const alignedStartTime = alignedWordEntry.startTime + (alignedStartFrameOffset / framesPerSecond)
359
426
 
360
- for (const referencePhoneEntry of referenceWordEntry.timeline!) {
361
- const referencePhoneStartFrameOffset = Math.floor((referencePhoneEntry.startTime - referenceWordEntry.startTime) * framesPerSecond)
362
- const alignedPhoneStartFrameOffset = getMappedFrameIndexForPath(referencePhoneStartFrameOffset, compactedPath)
363
- const alignedPhoneStartTime = alignedWordEntry.startTime + (alignedPhoneStartFrameOffset / framesPerSecond)
427
+ const referenceEndFrameOffset = Math.floor((referenceEntry.endTime - referenceWordEntry.startTime) * framesPerSecond)
428
+ const alignedEndFrameOffset = getMappedFrameIndexForPath(referenceEndFrameOffset, compactedPath)
429
+ const alignedEndTime = alignedWordEntry.startTime + (alignedEndFrameOffset / framesPerSecond)
364
430
 
365
- const referencePhoneEndFrameOffset = Math.floor((referencePhoneEntry.endTime - referenceWordEntry.startTime) * framesPerSecond)
366
- const alignedPhoneEndFrameOffset = getMappedFrameIndexForPath(referencePhoneEndFrameOffset, compactedPath)
367
- const alignedPhoneEndTime = alignedWordEntry.startTime + (alignedPhoneEndFrameOffset / framesPerSecond)
431
+ return {
432
+ ...referenceEntry,
368
433
 
369
- alignedWordEntry.timeline.push({
370
- ...referencePhoneEntry,
371
- startTime: alignedPhoneStartTime,
372
- endTime: alignedPhoneEndTime
373
- })
434
+ startTime: alignedStartTime,
435
+ endTime: alignedEndTime
436
+ }
374
437
  }
438
+
439
+ // Add phone timeline using the mapped time information
440
+ const alignedPhoneTimeline: Timeline = []
441
+
442
+ for (const referencePhoneEntry of (referenceWordEntry.timeline || [])) {
443
+ alignedPhoneTimeline.push(mapEntry(referencePhoneEntry))
444
+ }
445
+
446
+ alignedWordEntry.timeline = alignedPhoneTimeline
447
+
448
+ alignedWordTimeline.push(alignedWordEntry)
375
449
  }
376
450
 
377
451
  return alignedWordTimeline
@@ -385,7 +459,7 @@ export async function createAlignmentReferenceUsingEspeakForFragments(fragments:
385
459
 
386
460
  progressLogger.start("Create alignment reference with eSpeak")
387
461
 
388
- const result = await Espeak.synthesizeFragments(fragments, espeakOptions, insertSeparators)
462
+ const result = await Espeak.synthesizeFragments(fragments, espeakOptions)
389
463
 
390
464
  result.timeline = result.timeline.flatMap(clause => clause.timeline!)
391
465
 
@@ -398,6 +472,41 @@ export async function createAlignmentReferenceUsingEspeakForFragments(fragments:
398
472
  return result
399
473
  }
400
474
 
475
+ export async function createAlignmentReferenceUsingEspeak(transcript: string, language: string, plaintextOptions?: API.PlainTextOptions, customLexiconPaths?: string[], insertSeparators?: boolean) {
476
+ const logger = new Logger()
477
+
478
+ logger.start('Create alignment reference with eSpeak')
479
+
480
+ const synthesisOptions: API.SynthesisOptions = {
481
+ engine: 'espeak',
482
+ language,
483
+
484
+ plainText: plaintextOptions,
485
+ customLexiconPaths: customLexiconPaths,
486
+
487
+ espeak: {
488
+ useKlatt: false,
489
+ insertSeparators,
490
+ }
491
+ }
492
+
493
+ let {
494
+ audio: referenceRawAudio,
495
+ timeline: segmentTimeline,
496
+ voice: espeakVoice
497
+ } = await synthesize(transcript, synthesisOptions)
498
+
499
+ const sentenceTimeline = segmentTimeline.flatMap(entry => entry.timeline!)
500
+ const wordTimeline = sentenceTimeline.flatMap(entry => entry.timeline!)
501
+
502
+ referenceRawAudio = await resampleAudioSpeex(referenceRawAudio as RawAudio, 16000)
503
+ referenceRawAudio = downmixToMonoAndNormalize(referenceRawAudio)
504
+
505
+ logger.end()
506
+
507
+ return { referenceRawAudio, referenceTimeline: wordTimeline, espeakVoice }
508
+ }
509
+
401
510
  function compactPath(path: AlignmentPath) {
402
511
  const compactedPath: CompactedPath = []
403
512
 
@@ -414,7 +523,7 @@ function compactPath(path: AlignmentPath) {
414
523
  return compactedPath
415
524
  }
416
525
 
417
- function getMappedFrameIndexForPath(referenceFrameIndex: number, compactedPath: CompactedPath, mappingKind: "first" | "last" = "first") {
526
+ function getMappedFrameIndexForPath(referenceFrameIndex: number, compactedPath: CompactedPath, mappingKind: 'first' | 'last' = 'first') {
418
527
  if (compactedPath.length == 0) {
419
528
  return 0
420
529
  }
@@ -425,7 +534,7 @@ function getMappedFrameIndexForPath(referenceFrameIndex: number, compactedPath:
425
534
 
426
535
  let mappedFrameIndex: number
427
536
 
428
- if (mappingKind == "first") {
537
+ if (mappingKind == 'first') {
429
538
  mappedFrameIndex = compactedPathEntry.first
430
539
  } else {
431
540
  mappedFrameIndex = compactedPathEntry.last