echogarden 0.12.2 → 1.0.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 (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 +9 -10
  12. package/dist/alignment/SpeechAlignment.js +136 -105
  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 +88 -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 +93 -19
  222. package/docs/Engines.md +28 -16
  223. package/docs/Licenses.md +4 -1
  224. package/docs/Options.md +158 -78
  225. package/docs/Releases.md +262 -0
  226. package/docs/Server.md +7 -7
  227. package/docs/Tasklist.md +95 -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 +204 -119
  235. package/src/api/API.ts +14 -13
  236. package/src/api/APIOptions.ts +12 -11
  237. package/src/api/Alignment.ts +147 -90
  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
@@ -1,27 +1,29 @@
1
- import path from "node:path"
1
+ import path from 'node:path'
2
2
 
3
- import { deepClone, extendDeep } from "../utilities/ObjectUtilities.js"
3
+ import { deepClone, extendDeep } from '../utilities/ObjectUtilities.js'
4
4
 
5
- import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js"
5
+ import * as FFMpegTranscoder from '../codecs/FFMpegTranscoder.js'
6
6
 
7
- import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr, yieldToEventLoop, delay, runOperationWithRetries } from "../utilities/Utilities.js"
8
- import { RawAudio, concatAudioSegments, downmixToMono, encodeWaveBuffer, getAudioPeakDecibels, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, trimAudioEnd, trimAudioStart } from "../audio/AudioUtilities.js"
9
- import { Logger } from "../utilities/Logger.js"
7
+ import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr, yieldToEventLoop, delay, runOperationWithRetries } from '../utilities/Utilities.js'
8
+ import { RawAudio, attenuateIfClipping, concatAudioSegments, downmixToMono, encodeRawAudioToWave, getSamplePeakDecibels, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, trimAudioEnd, trimAudioStart } from '../audio/AudioUtilities.js'
9
+ import { Logger } from '../utilities/Logger.js'
10
10
 
11
- import { isWordOrSymbolWord, splitToParagraphs, splitToSentences } from "../nlp/Segmentation.js"
12
- import { type RubberbandOptions } from "../dsp/Rubberband.js"
13
- import { loadLexiconsForLanguage } from "../nlp/Lexicon.js"
11
+ import { isWordOrSymbolWord, splitToParagraphs, splitToSentences } from '../nlp/Segmentation.js'
12
+ import { type RubberbandOptions } from '../dsp/Rubberband.js'
13
+ import { loadLexiconsForLanguage } from '../nlp/Lexicon.js'
14
14
 
15
- import * as API from "./API.js"
16
- import { Timeline, TimelineEntry, addTimeOffsetToTimeline, multiplyTimelineByFactor } from "../utilities/Timeline.js"
17
- import { getAppDataDir, ensureDir, existsSync, isFileIsUpToDate, readAndParseJsonFile, writeFileSafe } from "../utilities/FileSystem.js"
18
- import { formatLanguageCodeWithName, getShortLanguageCode, normalizeLanguageCode, defaultDialectForLanguageCode } from "../utilities/Locale.js"
19
- import { loadPackage } from "../utilities/PackageManager.js"
20
- import { EngineMetadata, appName } from "./Common.js"
21
- import { shouldCancelCurrentTask } from "../server/Worker.js"
22
- import chalk from "chalk"
23
- import { SubtitlesConfig, defaultSubtitlesBaseConfig } from "../subtitles/Subtitles.js"
24
- import { type EspeakOptions } from "../synthesis/EspeakTTS.js"
15
+ import * as API from './API.js'
16
+ import { Timeline, TimelineEntry, addTimeOffsetToTimeline, multiplyTimelineByFactor } from '../utilities/Timeline.js'
17
+ import { getAppDataDir, ensureDir, existsSync, isFileIsUpToDate, readAndParseJsonFile, writeFileSafe } from '../utilities/FileSystem.js'
18
+ import { formatLanguageCodeWithName, getShortLanguageCode, normalizeLanguageCode, defaultDialectForLanguageCode } from '../utilities/Locale.js'
19
+ import { loadPackage } from '../utilities/PackageManager.js'
20
+ import { EngineMetadata, appName } from './Common.js'
21
+ import { shouldCancelCurrentTask } from '../server/Worker.js'
22
+ import chalk from 'chalk'
23
+ import { SubtitlesConfig, defaultSubtitlesBaseConfig } from '../subtitles/Subtitles.js'
24
+ import { type EspeakOptions } from '../synthesis/EspeakTTS.js'
25
+ import { OpenAICloudTTSOptions, defaultOpenAICloudTTSOptions } from '../synthesis/OpenAICloudTTS.js'
26
+ import { ElevenlabsTTSOptions, defaultElevenlabsTTSOptions } from '../synthesis/ElevenlabsTTS.js'
25
27
 
26
28
  const log = logToStderr
27
29
 
@@ -51,7 +53,7 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
51
53
  options = extendDeep(defaultSynthesisOptions, options)
52
54
 
53
55
  if (!options.language && !options.voice) {
54
- logger.start("No language or voice specified. Detecting language")
56
+ logger.start('No language or voice specified. Detecting language')
55
57
 
56
58
  let segmentsPlainText = segments
57
59
 
@@ -63,7 +65,7 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
63
65
  }
64
66
  }
65
67
 
66
- const { detectedLanguage } = await API.detectTextLanguage(segmentsPlainText.join("\n\n"), options.languageDetection || {})
68
+ const { detectedLanguage } = await API.detectTextLanguage(segmentsPlainText.join('\n\n'), options.languageDetection || {})
67
69
 
68
70
  options.language = detectedLanguage
69
71
 
@@ -86,7 +88,7 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
86
88
  const { bestMatchingVoice } = await requestVoiceList(options)
87
89
 
88
90
  if (!bestMatchingVoice) {
89
- throw new Error("No matching voice found")
91
+ throw new Error('No matching voice found')
90
92
  }
91
93
 
92
94
  options.voice = bestMatchingVoice.name
@@ -110,12 +112,12 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
110
112
  for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
111
113
  const segmentText = segments[segmentIndex].trim()
112
114
 
113
- logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}: "${segmentText}"`)
115
+ logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}: '${segmentText}'`)
114
116
 
115
117
  const segmentStartTime = timeOffset
116
118
 
117
119
  const segmentEntry: TimelineEntry = {
118
- type: "segment",
120
+ type: 'segment',
119
121
  text: segmentText,
120
122
  startTime: timeOffset,
121
123
  endTime: -1,
@@ -124,12 +126,12 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
124
126
 
125
127
  let sentences: string[]
126
128
 
127
- if ((options.splitToSentences || options.engine == "vits") && !options.ssml) {
129
+ if ((options.splitToSentences || options.engine == 'vits') && !options.ssml) {
128
130
  sentences = splitToSentences(segmentText, options.language!)
129
- sentences = sentences.filter(sentence => sentence.trim() != "")
131
+ sentences = sentences.filter(sentence => sentence.trim() != '')
130
132
 
131
133
  if (sentences.length == 0) {
132
- sentences = [""]
134
+ sentences = ['']
133
135
  }
134
136
  } else {
135
137
  sentences = [segmentText]
@@ -142,8 +144,8 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
142
144
  await yieldToEventLoop()
143
145
 
144
146
  if (shouldCancelCurrentTask()) {
145
- //log("\n\n\n\n\nCANCELED\n\n\n\n")
146
- throw new Error("Canceled")
147
+ //log('\n\n\n\n\nCANCELED\n\n\n\n')
148
+ throw new Error('Canceled')
147
149
  }
148
150
 
149
151
  const sentenceText = sentences[sentenceIndex].trim()
@@ -175,14 +177,14 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
175
177
  const sentenceEndTime = timeOffset - endPause
176
178
 
177
179
  segmentEntry.timeline!.push({
178
- type: "sentence",
180
+ type: 'sentence',
179
181
  text: sentenceText,
180
182
  startTime: sentenceStartTime,
181
183
  endTime: sentenceEndTime,
182
184
  timeline: sentenceTimelineWithOffset
183
185
  })
184
186
 
185
- peakDecibelsSoFar = Math.max(peakDecibelsSoFar, getAudioPeakDecibels(sentenceRawAudio.audioChannels))
187
+ peakDecibelsSoFar = Math.max(peakDecibelsSoFar, getSamplePeakDecibels(sentenceRawAudio.audioChannels))
186
188
 
187
189
  const sentenceAudio = await convertToTargetCodecIfNeeded(sentenceRawAudio)
188
190
 
@@ -245,7 +247,9 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
245
247
  resultRawAudio = { audioChannels: joinedAudioBuffers, sampleRate: segmentsRawAudio[0].sampleRate }
246
248
 
247
249
  if (options.postProcessing!.normalizeAudio) {
248
- resultRawAudio = normalizeAudioLevel(resultRawAudio, options.postProcessing!.targetPeakDb, options.postProcessing!.maxIncreaseDb)
250
+ resultRawAudio = normalizeAudioLevel(resultRawAudio, options.postProcessing!.targetPeak, options.postProcessing!.maxGainIncrease)
251
+ } else {
252
+ resultRawAudio = attenuateIfClipping(resultRawAudio)
249
253
  }
250
254
  } else {
251
255
  resultRawAudio = getEmptyRawAudio(1, 24000)
@@ -259,8 +263,8 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
259
263
  if (targetCodec) {
260
264
  logger.start(`Convert to ${targetCodec} codec`)
261
265
 
262
- if (targetCodec == "wav") {
263
- output = encodeWaveBuffer(rawAudio)
266
+ if (targetCodec == 'wav') {
267
+ output = encodeRawAudioToWave(rawAudio)
264
268
  } else {
265
269
  const ffmpegOptions = FFMpegTranscoder.getDefaultFFMpegOptionsForSpeech(targetCodec, options.outputAudioFormat?.bitrate)
266
270
  output = await FFMpegTranscoder.encodeFromChannels(rawAudio, ffmpegOptions)
@@ -293,9 +297,10 @@ export interface SynthesisResult {
293
297
 
294
298
  async function synthesizeSegment(text: string, options: SynthesisOptions) {
295
299
  const logger = new Logger()
300
+
296
301
  const startTimestamp = logger.getTimestamp()
297
302
 
298
- logger.start("Prepare for synthesis")
303
+ logger.start('Prepare for synthesis')
299
304
 
300
305
  const simplifiedText = simplifyPunctuationCharacters(text)
301
306
 
@@ -306,7 +311,7 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
306
311
  const { bestMatchingVoice } = await requestVoiceList(options)
307
312
 
308
313
  if (!bestMatchingVoice) {
309
- throw new Error("No matching voice found")
314
+ throw new Error('No matching voice found')
310
315
  }
311
316
 
312
317
  const selectedVoice = bestMatchingVoice
@@ -338,18 +343,18 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
338
343
  let shouldPostprocessPitch = false
339
344
 
340
345
  switch (engine) {
341
- case "vits": {
346
+ case 'vits': {
342
347
  if (inputIsSSML) {
343
348
  throw new Error(`The VITS engine doesn't currently support SSML inputs`)
344
349
  }
345
350
 
346
351
  let vitsLanguage = language
347
352
 
348
- if (vitsLanguage == "en") {
349
- vitsLanguage = "en-us"
353
+ if (vitsLanguage == 'en') {
354
+ vitsLanguage = 'en-us'
350
355
  }
351
356
 
352
- const vitsTTS = await import("../synthesis/VitsTTS.js")
357
+ const vitsTTS = await import('../synthesis/VitsTTS.js')
353
358
 
354
359
  const lengthScale = 1 / speed
355
360
 
@@ -360,7 +365,7 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
360
365
  if (speakerId != undefined) {
361
366
  if (selectedVoice.speakerCount == undefined) {
362
367
  if (speakerId != 0) {
363
- throw new Error("Selected VITS model has only one speaker. Speaker ID must be 0 if specified.")
368
+ throw new Error('Selected VITS model has only one speaker. Speaker ID must be 0 if specified.')
364
369
  }
365
370
  } else if (speakerId < 0 || speakerId >= selectedVoice.speakerCount) {
366
371
  throw new Error(`Selected VITS model has ${selectedVoice.speakerCount} voices. Speaker ID should be in the range ${0} to ${selectedVoice.speakerCount - 1}`)
@@ -385,18 +390,18 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
385
390
  break
386
391
  }
387
392
 
388
- case "pico": {
393
+ case 'pico': {
389
394
  if (inputIsSSML) {
390
395
  throw new Error(`The SVOX Pico engine doesn't currently support SSML inputs`)
391
396
  }
392
397
 
393
- const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js")
398
+ const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js')
394
399
 
395
400
  const picoSpeed = Math.round(speed * 1.0 * 100)
396
401
  const picoPitch = Math.round(pitch * 1.0 * 100)
397
402
  const picoVolume = 35.0
398
403
 
399
- const preparedText = `<speed level="${picoSpeed}"><pitch level="${picoPitch}"><volume level="${picoVolume}">${simplifiedText}</volume></pitch></speed>`
404
+ const preparedText = `<speed level='${picoSpeed}'><pitch level='${picoPitch}'><volume level='${picoVolume}'>${simplifiedText}</volume></pitch></speed>`
400
405
 
401
406
  logger.end()
402
407
 
@@ -412,12 +417,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
412
417
  break
413
418
  }
414
419
 
415
- case "flite": {
420
+ case 'flite': {
416
421
  if (inputIsSSML) {
417
422
  throw new Error(`The Flite engine doesn't currently support SSML inputs`)
418
423
  }
419
424
 
420
- const FliteTTS = await import("../synthesis/FliteTTS.js")
425
+ const FliteTTS = await import('../synthesis/FliteTTS.js')
421
426
 
422
427
  logger.end()
423
428
 
@@ -430,8 +435,8 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
430
435
  break
431
436
  }
432
437
 
433
- case "espeak": {
434
- const EspeakTTS = await import("../synthesis/EspeakTTS.js")
438
+ case 'espeak': {
439
+ const EspeakTTS = await import('../synthesis/EspeakTTS.js')
435
440
 
436
441
  const engineOptions = options.espeak!
437
442
 
@@ -441,6 +446,7 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
441
446
  const espeakPitch = engineOptions.pitch || options.pitch! * 50
442
447
  const espeakPitchRange = engineOptions.pitchRange || options.pitchVariation! * 50
443
448
  const espeakUseKlatt = engineOptions.useKlatt || false
449
+ const espeakInsertSeparators = engineOptions.insertSeparators || false
444
450
 
445
451
  const espeakOptions: EspeakOptions = {
446
452
  voice: espeakVoice,
@@ -448,7 +454,8 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
448
454
  rate: espeakRate,
449
455
  pitch: espeakPitch,
450
456
  pitchRange: espeakPitchRange,
451
- useKlatt: espeakUseKlatt
457
+ useKlatt: espeakUseKlatt,
458
+ insertSeparators: espeakInsertSeparators,
452
459
  }
453
460
 
454
461
  if (inputIsSSML) {
@@ -471,12 +478,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
471
478
  break
472
479
  }
473
480
 
474
- case "sam": {
481
+ case 'sam': {
475
482
  if (inputIsSSML) {
476
483
  throw new Error(`The SAM engine doesn't support SSML inputs`)
477
484
  }
478
485
 
479
- const SamTTS = await import("../synthesis/SamTTS.js")
486
+ const SamTTS = await import('../synthesis/SamTTS.js')
480
487
 
481
488
  const engineOptions = options.sam!
482
489
 
@@ -494,12 +501,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
494
501
  break
495
502
  }
496
503
 
497
- case "sapi": {
504
+ case 'sapi': {
498
505
  if (inputIsSSML) {
499
506
  throw new Error(`The SAPI engine doesn't currently support SSML inputs`)
500
507
  }
501
508
 
502
- const SapiTTS = await import("../synthesis/SapiTTS.js")
509
+ const SapiTTS = await import('../synthesis/SapiTTS.js')
503
510
 
504
511
  await SapiTTS.AssertSAPIAvailable(false)
505
512
 
@@ -520,12 +527,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
520
527
  break
521
528
  }
522
529
 
523
- case "msspeech": {
530
+ case 'msspeech': {
524
531
  if (inputIsSSML) {
525
532
  throw new Error(`The MSSpeech engine doesn't currently support SSML inputs`)
526
533
  }
527
534
 
528
- const SapiTTS = await import("../synthesis/SapiTTS.js")
535
+ const SapiTTS = await import('../synthesis/SapiTTS.js')
529
536
 
530
537
  await SapiTTS.AssertSAPIAvailable(true)
531
538
 
@@ -546,12 +553,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
546
553
  break
547
554
  }
548
555
 
549
- case "coqui-server": {
556
+ case 'coqui-server': {
550
557
  if (inputIsSSML) {
551
558
  throw new Error(`The Coqui Server engine doesn't support SSML inputs`)
552
559
  }
553
560
 
554
- const CoquiServerTTS = await import("../synthesis/CoquiServerTTS.js")
561
+ const CoquiServerTTS = await import('../synthesis/CoquiServerTTS.js')
555
562
 
556
563
  const engineOptions = options.coquiServer!
557
564
 
@@ -574,15 +581,15 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
574
581
  break
575
582
  }
576
583
 
577
- case "google-cloud": {
578
- const GoogleCloudTTS = await import("../synthesis/GoogleCloudTTS.js")
584
+ case 'google-cloud': {
585
+ const GoogleCloudTTS = await import('../synthesis/GoogleCloudTTS.js')
579
586
 
580
587
  const engineOptions = options.googleCloud!
581
588
 
582
589
  const apiKey = engineOptions.apiKey
583
590
 
584
591
  if (!apiKey) {
585
- throw new Error(`No API key given`)
592
+ throw new Error(`No Google Cloud API key provided`)
586
593
  }
587
594
 
588
595
  let pitchDeltaSemitones: number
@@ -607,21 +614,21 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
607
614
  break
608
615
  }
609
616
 
610
- case "microsoft-azure": {
611
- const AzureCognitiveServicesTTS = await import("../synthesis/AzureCognitiveServicesTTS.js")
617
+ case 'microsoft-azure': {
618
+ const AzureCognitiveServicesTTS = await import('../synthesis/AzureCognitiveServicesTTS.js')
612
619
 
613
620
  const engineOptions = options.microsoftAzure!
614
621
 
615
622
  const subscriptionKey = engineOptions.subscriptionKey
616
623
 
617
624
  if (!subscriptionKey) {
618
- throw new Error(`No subscription key given`)
625
+ throw new Error(`No Microsoft Azure subscription key provided`)
619
626
  }
620
627
 
621
628
  const serviceRegion = engineOptions!.serviceRegion
622
629
 
623
630
  if (!serviceRegion) {
624
- throw new Error(`No service region given`)
631
+ throw new Error(`No Microsoft Azure service region provided`)
625
632
  }
626
633
 
627
634
  let ssmlPitch: string
@@ -648,27 +655,27 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
648
655
  break
649
656
  }
650
657
 
651
- case "amazon-polly": {
652
- const AwsPollyTTS = await import("../synthesis/AwsPollyTTS.js")
658
+ case 'amazon-polly': {
659
+ const AwsPollyTTS = await import('../synthesis/AwsPollyTTS.js')
653
660
 
654
661
  const engineOptions = options.amazonPolly!
655
662
 
656
663
  const region = engineOptions.region
657
664
 
658
665
  if (!region) {
659
- throw new Error(`No region given`)
666
+ throw new Error(`No Amazon Polly region provided`)
660
667
  }
661
668
 
662
669
  const accessKeyId = engineOptions.accessKeyId
663
670
 
664
671
  if (!accessKeyId) {
665
- throw new Error(`No access key id given`)
672
+ throw new Error(`No Amazon Polly access key id provided`)
666
673
  }
667
674
 
668
675
  const secretAccessKey = engineOptions.secretAccessKey
669
676
 
670
677
  if (!secretAccessKey) {
671
- throw new Error(`No secret access key given`)
678
+ throw new Error(`No Amazon Polly secret access key provided`)
672
679
  }
673
680
 
674
681
  const pollyEngine = engineOptions.pollyEngine
@@ -686,29 +693,44 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
686
693
  break
687
694
  }
688
695
 
689
- case "elevenlabs": {
696
+ case 'openai-cloud': {
697
+ const OpenAICloudTTS = await import('../synthesis/OpenAICloudTTS.js')
698
+
699
+ const openAICloudTTSOptions = options.openAICloud!
700
+
701
+ if (!openAICloudTTSOptions.apiKey) {
702
+ throw new Error(`No API key given`)
703
+ }
704
+
705
+ logger.end();
706
+
707
+ synthesizedAudio = await OpenAICloudTTS.synthesize(text, voice, speed, openAICloudTTSOptions)
708
+
709
+ shouldPostprocessSpeed = false
710
+ shouldPostprocessPitch = true
711
+
712
+ break
713
+ }
714
+
715
+ case 'elevenlabs': {
690
716
  if (inputIsSSML) {
691
717
  throw new Error(`The Elevenlabs engine doesn't support SSML inputs`)
692
718
  }
693
719
 
694
- const ElevenLabsTTS = await import("../synthesis/ElevenLabsTTS.js")
720
+ const ElevenLabsTTS = await import('../synthesis/ElevenlabsTTS.js')
695
721
 
696
722
  const engineOptions = options.elevenlabs!
697
723
 
698
- const apiKey = engineOptions.apiKey
699
-
700
- if (!apiKey) {
701
- throw new Error(`No ElevenLabs API key given`)
724
+ if (!engineOptions.apiKey) {
725
+ throw new Error(`No ElevenLabs API key provided`)
702
726
  }
703
727
 
704
- const voiceId = (selectedVoice as any)["elevenLabsVoiceId"]
705
- const modelId = (selectedVoice as any)["elevenLabsModelId"]
706
- const stability = engineOptions.stability!
707
- const similarityBoost = engineOptions.similarityBoost!
728
+ const voiceId = (selectedVoice as any)['elevenLabsVoiceId']
729
+ const modelId = (selectedVoice as any)['elevenLabsModelId']
708
730
 
709
731
  logger.end()
710
732
 
711
- const { rawAudio } = await ElevenLabsTTS.synthesize(text, voiceId, apiKey, modelId, stability, similarityBoost)
733
+ const { rawAudio } = await ElevenLabsTTS.synthesize(text, voiceId, modelId, engineOptions)
712
734
 
713
735
  synthesizedAudio = rawAudio
714
736
 
@@ -718,12 +740,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
718
740
  break
719
741
  }
720
742
 
721
- case "google-translate": {
743
+ case 'google-translate': {
722
744
  if (inputIsSSML) {
723
745
  throw new Error(`The Google Translate engine doesn't support SSML inputs`)
724
746
  }
725
747
 
726
- const GoogleTranslateTTS = await import("../synthesis/GoogleTranslateTTS.js")
748
+ const GoogleTranslateTTS = await import('../synthesis/GoogleTranslateTTS.js')
727
749
 
728
750
  logger.end()
729
751
 
@@ -745,23 +767,23 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
745
767
  break
746
768
  }
747
769
 
748
- case "microsoft-edge": {
770
+ case 'microsoft-edge': {
749
771
  if (inputIsSSML) {
750
772
  throw new Error(`The Microsoft Edge engine doesn't support SSML inputs`)
751
773
  }
752
774
 
753
- const MicrosoftEdgeTTS = await import("../synthesis/MicrosoftEdgeTTS.js")
775
+ const MicrosoftEdgeTTS = await import('../synthesis/MicrosoftEdgeTTS.js')
754
776
 
755
777
  const engineOptions = options.microsoftEdge!
756
778
 
757
779
  const trustedClientToken = engineOptions.trustedClientToken
758
780
 
759
781
  if (!trustedClientToken) {
760
- throw new Error("No trusted client token provided.")
782
+ throw new Error('No Microsoft Edge trusted client token provided')
761
783
  }
762
784
 
763
- if (await sha256AsHex(trustedClientToken) != "558d7c6a7f7db444895946fe23a54ad172fd6d159f46cb34dd4db21bb27c07d7") {
764
- throw new Error("Trusted client token is incorrect.")
785
+ if (await sha256AsHex(trustedClientToken) != '558d7c6a7f7db444895946fe23a54ad172fd6d159f46cb34dd4db21bb27c07d7') {
786
+ throw new Error('Trusted client token is incorrect.')
765
787
  }
766
788
 
767
789
  let ssmlPitch: string
@@ -791,12 +813,12 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
791
813
  break
792
814
  }
793
815
 
794
- case "streamlabs-polly": {
816
+ case 'streamlabs-polly': {
795
817
  if (inputIsSSML) {
796
818
  throw new Error(`The Streamlabs Polly Engine engine doesn't support SSML inputs`)
797
819
  }
798
820
 
799
- const StreamlabsPollyTTS = await import("../synthesis/StreamlabsPollyTTS.js")
821
+ const StreamlabsPollyTTS = await import('../synthesis/StreamlabsPollyTTS.js')
800
822
 
801
823
  logger.end()
802
824
 
@@ -820,11 +842,13 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
820
842
  }
821
843
  }
822
844
 
823
- logger.start("Postprocess synthesized audio")
845
+ logger.start('Postprocess synthesized audio')
824
846
  synthesizedAudio = downmixToMono(synthesizedAudio)
825
847
 
826
848
  if (options.postProcessing!.normalizeAudio) {
827
- synthesizedAudio = normalizeAudioLevel(synthesizedAudio, options.postProcessing!.targetPeakDb!, options.postProcessing!.maxIncreaseDb!)
849
+ synthesizedAudio = normalizeAudioLevel(synthesizedAudio, options.postProcessing!.targetPeak!, options.postProcessing!.maxGainIncrease!)
850
+ } else {
851
+ synthesizedAudio = attenuateIfClipping(synthesizedAudio)
828
852
  }
829
853
 
830
854
  const preTrimSampleCount = synthesizedAudio.audioChannels[0].length
@@ -838,7 +862,7 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
838
862
  }
839
863
 
840
864
  if (!timeline) {
841
- logger.start("Align synthesized audio with text")
865
+ logger.start('Align synthesized audio with text')
842
866
 
843
867
  let plainText = text
844
868
 
@@ -880,17 +904,17 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
880
904
  }
881
905
 
882
906
  if ((timeStretchFactor != undefined && timeStretchFactor != 1.0) || (pitchShiftFactor != undefined && pitchShiftFactor != 1.0)) {
883
- logger.start("Apply time and pitch shifting")
907
+ logger.start('Apply time and pitch shifting')
884
908
 
885
909
  timeStretchFactor = timeStretchFactor || 1.0
886
910
  pitchShiftFactor = pitchShiftFactor || 1.0
887
911
 
888
912
  const timePitchShiftingMethod = postProcessingOptions.timePitchShiftingMethod
889
913
 
890
- if (timePitchShiftingMethod == "sonic") {
914
+ if (timePitchShiftingMethod == 'sonic') {
891
915
  const sonic = await import('../dsp/Sonic.js')
892
916
  synthesizedAudio = await sonic.stretchTimePitch(synthesizedAudio, timeStretchFactor, pitchShiftFactor)
893
- } else if (timePitchShiftingMethod == "rubberband") {
917
+ } else if (timePitchShiftingMethod == 'rubberband') {
894
918
  const rubberband = await import('../dsp/Rubberband.js')
895
919
 
896
920
  const rubberbandOptions: RubberbandOptions = extendDeep(rubberband.defaultRubberbandOptions, postProcessingOptions.rubberband || {})
@@ -928,10 +952,10 @@ function convertSpeedScaleToSSMLValueString(rate: number) {
928
952
 
929
953
  function convertPitchScaleToSSMLValueString(pitch: number, voiceGender: VoiceGender) {
930
954
  let fundementalFrequency
931
- if (voiceGender == "male") {
955
+ if (voiceGender == 'male') {
932
956
  // Use an estimate of the average male voice fundemental frequency
933
957
  fundementalFrequency = 120
934
- } else if (voiceGender == "female") {
958
+ } else if (voiceGender == 'female') {
935
959
  // Use an estimate of the average female voice fundemental frequency
936
960
  fundementalFrequency = 210
937
961
  } else {
@@ -949,9 +973,9 @@ function convertPitchScaleToSSMLValueString(pitch: number, voiceGender: VoiceGen
949
973
  }
950
974
  }
951
975
 
952
- export type SynthesisEngine = "vits" | "pico" | "flite" | "espeak" | "sam" | "sapi" | "msspeech" | "coqui-server" | "google-cloud" | "microsoft-azure" | "amazon-polly" | "elevenlabs" | "google-translate" | "microsoft-edge" | "streamlabs-polly"
976
+ export type SynthesisEngine = 'vits' | 'pico' | 'flite' | 'espeak' | 'sam' | 'sapi' | 'msspeech' | 'coqui-server' | 'google-cloud' | 'microsoft-azure' | 'amazon-polly' | 'openai-cloud' | 'elevenlabs' | 'google-translate' | 'microsoft-edge' | 'streamlabs-polly'
953
977
 
954
- export type TimePitchShiftingMethod = "sonic" | "rubberband"
978
+ export type TimePitchShiftingMethod = 'sonic' | 'rubberband'
955
979
 
956
980
  export interface SynthesisOptions {
957
981
  engine?: SynthesisEngine
@@ -979,8 +1003,8 @@ export interface SynthesisOptions {
979
1003
 
980
1004
  postProcessing?: {
981
1005
  normalizeAudio?: boolean
982
- targetPeakDb?: number
983
- maxIncreaseDb?: number
1006
+ targetPeak?: number
1007
+ maxGainIncrease?: number
984
1008
 
985
1009
  speed?: number
986
1010
  pitch?: number
@@ -990,7 +1014,7 @@ export interface SynthesisOptions {
990
1014
  }
991
1015
 
992
1016
  outputAudioFormat?: {
993
- codec?: "wav" | "mp3" | "opus" | "m4a" | "ogg" | "flac"
1017
+ codec?: 'wav' | 'mp3' | 'opus' | 'm4a' | 'ogg' | 'flac'
994
1018
  bitrate?: number
995
1019
  }
996
1020
 
@@ -1012,7 +1036,9 @@ export interface SynthesisOptions {
1012
1036
  rate?: number
1013
1037
  pitch?: number
1014
1038
  pitchRange?: number
1039
+
1015
1040
  useKlatt?: boolean
1041
+ insertSeparators?: boolean
1016
1042
  }
1017
1043
 
1018
1044
  sam?: {
@@ -1055,15 +1081,13 @@ export interface SynthesisOptions {
1055
1081
  region?: string
1056
1082
  accessKeyId?: string
1057
1083
  secretAccessKey?: string
1058
- pollyEngine?: "standard" | "neural"
1084
+ pollyEngine?: 'standard' | 'neural'
1059
1085
  lexiconNames?: string[]
1060
1086
  }
1061
1087
 
1062
- elevenlabs?: {
1063
- apiKey?: string
1064
- stability?: number
1065
- similarityBoost?: number
1066
- },
1088
+ openAICloud?: OpenAICloudTTSOptions
1089
+
1090
+ elevenlabs?: ElevenlabsTTSOptions,
1067
1091
 
1068
1092
  googleTranslate?: {
1069
1093
  tld?: string
@@ -1105,7 +1129,7 @@ export const defaultSynthesisOptions: SynthesisOptions = {
1105
1129
  },
1106
1130
 
1107
1131
  alignment: {
1108
- engine: "dtw",
1132
+ engine: 'dtw',
1109
1133
 
1110
1134
  dtw: {
1111
1135
  granularity: 'high'
@@ -1114,13 +1138,13 @@ export const defaultSynthesisOptions: SynthesisOptions = {
1114
1138
 
1115
1139
  postProcessing: {
1116
1140
  normalizeAudio: true,
1117
- targetPeakDb: -3,
1118
- maxIncreaseDb: 30,
1141
+ targetPeak: -3,
1142
+ maxGainIncrease: 30,
1119
1143
 
1120
1144
  speed: undefined,
1121
1145
  pitch: undefined,
1122
1146
 
1123
- timePitchShiftingMethod: "sonic",
1147
+ timePitchShiftingMethod: 'sonic',
1124
1148
  rubberband: {
1125
1149
  }
1126
1150
  },
@@ -1164,7 +1188,7 @@ export const defaultSynthesisOptions: SynthesisOptions = {
1164
1188
  },
1165
1189
 
1166
1190
  coquiServer: {
1167
- serverUrl: "http://[::1]:5002",
1191
+ serverUrl: 'http://[::1]:5002',
1168
1192
  speakerId: null
1169
1193
  },
1170
1194
 
@@ -1192,14 +1216,12 @@ export const defaultSynthesisOptions: SynthesisOptions = {
1192
1216
  lexiconNames: undefined,
1193
1217
  },
1194
1218
 
1195
- elevenlabs: {
1196
- apiKey: undefined,
1197
- stability: 0.5,
1198
- similarityBoost: 0.5,
1199
- },
1219
+ openAICloud: defaultOpenAICloudTTSOptions,
1220
+
1221
+ elevenlabs: defaultElevenlabsTTSOptions,
1200
1222
 
1201
1223
  googleTranslate: {
1202
- tld: "us"
1224
+ tld: 'us'
1203
1225
  },
1204
1226
 
1205
1227
  microsoftEdge: {
@@ -1236,8 +1258,8 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1236
1258
  let voiceList: SynthesisVoice[] = []
1237
1259
 
1238
1260
  switch (options.engine) {
1239
- case "espeak": {
1240
- const EspeakTTS = await import("../synthesis/EspeakTTS.js")
1261
+ case 'espeak': {
1262
+ const EspeakTTS = await import('../synthesis/EspeakTTS.js')
1241
1263
 
1242
1264
  const voices = await EspeakTTS.listVoices()
1243
1265
 
@@ -1256,41 +1278,41 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1256
1278
  return {
1257
1279
  name: voice.identifier,
1258
1280
  languages,
1259
- gender: "male"
1281
+ gender: 'male'
1260
1282
  }
1261
1283
  })
1262
1284
 
1263
1285
  break
1264
1286
  }
1265
1287
 
1266
- case "flite": {
1267
- const FliteTTS = await import("../synthesis/FliteTTS.js")
1288
+ case 'flite': {
1289
+ const FliteTTS = await import('../synthesis/FliteTTS.js')
1268
1290
 
1269
1291
  voiceList = deepClone(FliteTTS.voiceList)
1270
1292
 
1271
1293
  break
1272
1294
  }
1273
1295
 
1274
- case "pico": {
1275
- const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js")
1296
+ case 'pico': {
1297
+ const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js')
1276
1298
 
1277
1299
  voiceList = SvoxPicoTTS.voiceList
1278
1300
 
1279
1301
  break
1280
1302
  }
1281
1303
 
1282
- case "sam": {
1304
+ case 'sam': {
1283
1305
  voiceList.push({
1284
- name: "sam",
1285
- languages: ["en-US", "en"],
1286
- gender: "male"
1306
+ name: 'sam',
1307
+ languages: ['en-US', 'en'],
1308
+ gender: 'male'
1287
1309
  })
1288
1310
 
1289
1311
  break
1290
1312
  }
1291
1313
 
1292
- case "vits": {
1293
- const VitsTTS = await import("../synthesis/VitsTTS.js")
1314
+ case 'vits': {
1315
+ const VitsTTS = await import('../synthesis/VitsTTS.js')
1294
1316
 
1295
1317
  voiceList = VitsTTS.voiceList.map(entry => {
1296
1318
  return { ...entry, packageName: `vits-${entry.name}` }
@@ -1299,8 +1321,8 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1299
1321
  break
1300
1322
  }
1301
1323
 
1302
- case "sapi": {
1303
- const SapiTTS = await import("../synthesis/SapiTTS.js")
1324
+ case 'sapi': {
1325
+ const SapiTTS = await import('../synthesis/SapiTTS.js')
1304
1326
 
1305
1327
  await SapiTTS.AssertSAPIAvailable(false)
1306
1328
 
@@ -1309,8 +1331,8 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1309
1331
  break
1310
1332
  }
1311
1333
 
1312
- case "msspeech": {
1313
- const SapiTTS = await import("../synthesis/SapiTTS.js")
1334
+ case 'msspeech': {
1335
+ const SapiTTS = await import('../synthesis/SapiTTS.js')
1314
1336
 
1315
1337
  await SapiTTS.AssertSAPIAvailable(true)
1316
1338
 
@@ -1319,23 +1341,23 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1319
1341
  break
1320
1342
  }
1321
1343
 
1322
- case "coqui-server": {
1344
+ case 'coqui-server': {
1323
1345
  voiceList = [{
1324
- name: "coqui",
1325
- languages: ["en-US"],
1326
- gender: "unknown"
1346
+ name: 'coqui',
1347
+ languages: ['en-US'],
1348
+ gender: 'unknown'
1327
1349
  }]
1328
1350
 
1329
1351
  break
1330
1352
  }
1331
1353
 
1332
- case "google-cloud": {
1333
- const GoogleCloudTTS = await import("../synthesis/GoogleCloudTTS.js")
1354
+ case 'google-cloud': {
1355
+ const GoogleCloudTTS = await import('../synthesis/GoogleCloudTTS.js')
1334
1356
 
1335
1357
  const apiKey = options.googleCloud!.apiKey
1336
1358
 
1337
1359
  if (!apiKey) {
1338
- throw new Error(`No API key given`)
1360
+ throw new Error(`No Google Cloud API key provided`)
1339
1361
  }
1340
1362
 
1341
1363
  const voices = await GoogleCloudTTS.getVoiceList(apiKey)
@@ -1343,25 +1365,25 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1343
1365
  voiceList = voices.map(voice => ({
1344
1366
  name: voice.name,
1345
1367
  languages: [normalizeLanguageCode(voice.languageCodes[0]), getShortLanguageCode(voice.languageCodes[0])],
1346
- gender: voice.ssmlGender.toLowerCase() as ("male" | "female"),
1368
+ gender: voice.ssmlGender.toLowerCase() as ('male' | 'female'),
1347
1369
  }))
1348
1370
 
1349
1371
  break
1350
1372
  }
1351
1373
 
1352
- case "microsoft-azure": {
1353
- const AzureCognitiveServicesTTS = await import("../synthesis/AzureCognitiveServicesTTS.js")
1374
+ case 'microsoft-azure': {
1375
+ const AzureCognitiveServicesTTS = await import('../synthesis/AzureCognitiveServicesTTS.js')
1354
1376
 
1355
1377
  const subscriptionKey = options.microsoftAzure!.subscriptionKey
1356
1378
 
1357
1379
  if (!subscriptionKey) {
1358
- throw new Error(`No subscription key given`)
1380
+ throw new Error(`No Microsoft Azure subscription key provided`)
1359
1381
  }
1360
1382
 
1361
1383
  const serviceRegion = options.microsoftAzure!.serviceRegion
1362
1384
 
1363
1385
  if (!serviceRegion) {
1364
- throw new Error(`No service region given`)
1386
+ throw new Error(`No Microsoft Azure service region provided`)
1365
1387
  }
1366
1388
 
1367
1389
  const voices = await AzureCognitiveServicesTTS.getVoiceList(subscriptionKey, serviceRegion)
@@ -1370,32 +1392,32 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1370
1392
  voiceList.push({
1371
1393
  name: voice.name,
1372
1394
  languages: [normalizeLanguageCode(voice.locale), getShortLanguageCode(voice.locale)],
1373
- gender: voice.gender == 1 ? "female" : "male"
1395
+ gender: voice.gender == 1 ? 'female' : 'male'
1374
1396
  })
1375
1397
  }
1376
1398
 
1377
1399
  break
1378
1400
  }
1379
1401
 
1380
- case "amazon-polly": {
1381
- const AwsPollyTTS = await import("../synthesis/AwsPollyTTS.js")
1402
+ case 'amazon-polly': {
1403
+ const AwsPollyTTS = await import('../synthesis/AwsPollyTTS.js')
1382
1404
 
1383
1405
  const region = options.amazonPolly!.region
1384
1406
 
1385
1407
  if (!region) {
1386
- throw new Error(`No region given`)
1408
+ throw new Error(`No Amazon Polly region provided`)
1387
1409
  }
1388
1410
 
1389
1411
  const accessKeyId = options.amazonPolly!.accessKeyId
1390
1412
 
1391
1413
  if (!accessKeyId) {
1392
- throw new Error(`No access key id given`)
1414
+ throw new Error(`No Amazon Polly access key id provided`)
1393
1415
  }
1394
1416
 
1395
1417
  const secretAccessKey = options.amazonPolly!.secretAccessKey
1396
1418
 
1397
1419
  if (!secretAccessKey) {
1398
- throw new Error(`No secret access key given`)
1420
+ throw new Error(`No Amazon Polly secret access key provided`)
1399
1421
  }
1400
1422
 
1401
1423
  const voices = await AwsPollyTTS.getVoiceList(region, accessKeyId, secretAccessKey)
@@ -1416,22 +1438,30 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1416
1438
  voiceList.push({
1417
1439
  name: voice.Id!,
1418
1440
  languages: languageCodes,
1419
- gender: voice.Gender!.toLowerCase() as ("male" | "female")
1441
+ gender: voice.Gender!.toLowerCase() as ('male' | 'female')
1420
1442
  })
1421
1443
  }
1422
1444
 
1423
1445
  break
1424
1446
  }
1425
1447
 
1426
- case "elevenlabs": {
1427
- const ElevenLabsTTS = await import("../synthesis/ElevenLabsTTS.js")
1448
+ case 'openai-cloud': {
1449
+ const OpenAICloudTTS = await import('../synthesis/OpenAICloudTTS.js')
1450
+
1451
+ voiceList = OpenAICloudTTS.voiceList
1452
+
1453
+ break
1454
+ }
1455
+
1456
+ case 'elevenlabs': {
1457
+ const ElevenLabsTTS = await import('../synthesis/ElevenlabsTTS.js')
1428
1458
 
1429
1459
  const engineOptions = options.elevenlabs!
1430
1460
 
1431
1461
  const apiKey = engineOptions.apiKey
1432
1462
 
1433
1463
  if (!apiKey) {
1434
- throw new Error(`No Elevenlabs API key given`)
1464
+ throw new Error(`No Elevenlabs API key provided`)
1435
1465
  }
1436
1466
 
1437
1467
  voiceList = await ElevenLabsTTS.getVoiceList(apiKey)
@@ -1439,29 +1469,29 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1439
1469
  break
1440
1470
  }
1441
1471
 
1442
- case "google-translate": {
1443
- const GoogleTranslateTTS = await import("../synthesis/GoogleTranslateTTS.js")
1472
+ case 'google-translate': {
1473
+ const GoogleTranslateTTS = await import('../synthesis/GoogleTranslateTTS.js')
1444
1474
 
1445
1475
  const langLookup = GoogleTranslateTTS.supportedLanguageLookup
1446
1476
 
1447
1477
  for (const langCode in langLookup) {
1448
1478
  voiceList.push({
1449
1479
  name: langLookup[langCode],
1450
- languages: langCode.includes("-") ? [normalizeLanguageCode(langCode), getShortLanguageCode(langCode)] : [normalizeLanguageCode(langCode)],
1451
- gender: "unknown"
1480
+ languages: langCode.includes('-') ? [normalizeLanguageCode(langCode), getShortLanguageCode(langCode)] : [normalizeLanguageCode(langCode)],
1481
+ gender: 'unknown'
1452
1482
  })
1453
1483
  }
1454
1484
 
1455
1485
  break
1456
1486
  }
1457
1487
 
1458
- case "microsoft-edge": {
1459
- const MicrosoftEdgeTTS = await import("../synthesis/MicrosoftEdgeTTS.js")
1488
+ case 'microsoft-edge': {
1489
+ const MicrosoftEdgeTTS = await import('../synthesis/MicrosoftEdgeTTS.js')
1460
1490
 
1461
1491
  const trustedClientToken = options.microsoftEdge?.trustedClientToken
1462
1492
 
1463
1493
  if (!trustedClientToken) {
1464
- throw new Error("No trusted client token provided")
1494
+ throw new Error('No Microsoft Edge trusted client token provided')
1465
1495
  }
1466
1496
 
1467
1497
  const voices =
@@ -1472,14 +1502,14 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1472
1502
  voiceList = voices.map((voice: any) => ({
1473
1503
  name: voice.Name,
1474
1504
  languages: [normalizeLanguageCode(voice.Locale), getShortLanguageCode(voice.Locale)],
1475
- gender: voice.Gender == "Male" ? "male" : "female",
1505
+ gender: voice.Gender == 'Male' ? 'male' : 'female',
1476
1506
  }))
1477
1507
 
1478
1508
  break
1479
1509
  }
1480
1510
 
1481
- case "streamlabs-polly": {
1482
- const StreamlabsPollyTTS = await import("../synthesis/StreamlabsPollyTTS.js")
1511
+ case 'streamlabs-polly': {
1512
+ const StreamlabsPollyTTS = await import('../synthesis/StreamlabsPollyTTS.js')
1483
1513
 
1484
1514
  voiceList = StreamlabsPollyTTS.voiceList
1485
1515
 
@@ -1503,12 +1533,12 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1503
1533
  voiceList = await loadVoiceList()
1504
1534
  }
1505
1535
 
1506
- const languageCode = normalizeLanguageCode(options.language || "")
1536
+ const languageCode = normalizeLanguageCode(options.language || '')
1507
1537
 
1508
1538
  if (languageCode) {
1509
1539
  let filteredVoiceList = voiceList.filter(voice => voice.languages.includes(languageCode))
1510
1540
 
1511
- if (filteredVoiceList.length == 0 && languageCode.includes("-")) {
1541
+ if (filteredVoiceList.length == 0 && languageCode.includes('-')) {
1512
1542
  const shortLanguageCode = getShortLanguageCode(languageCode)
1513
1543
 
1514
1544
  filteredVoiceList = voiceList.filter(voice => voice.languages.includes(shortLanguageCode))
@@ -1519,7 +1549,7 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
1519
1549
 
1520
1550
  if (options.voiceGender) {
1521
1551
  const genderLowercase = options.voiceGender.toLowerCase()
1522
- voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender == "unknown")
1552
+ voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender == 'unknown')
1523
1553
  }
1524
1554
 
1525
1555
  if (options.voice) {
@@ -1568,31 +1598,31 @@ export interface RequestVoiceListResult {
1568
1598
  export async function selectBestOfflineEngineForLanguage(language: string): Promise<SynthesisEngine> {
1569
1599
  language = normalizeLanguageCode(language)
1570
1600
 
1571
- const VitsTTS = await import("../synthesis/VitsTTS.js")
1601
+ const VitsTTS = await import('../synthesis/VitsTTS.js')
1572
1602
 
1573
1603
  const vitsLanguages = getAllLangCodesFromVoiceList(VitsTTS.voiceList)
1574
1604
 
1575
1605
  if (vitsLanguages.includes(language)) {
1576
- return "vits"
1606
+ return 'vits'
1577
1607
  }
1578
1608
 
1579
- const FliteTTS = await import("../synthesis/FliteTTS.js")
1609
+ const FliteTTS = await import('../synthesis/FliteTTS.js')
1580
1610
 
1581
1611
  const fliteLanguages = getAllLangCodesFromVoiceList(FliteTTS.voiceList)
1582
1612
 
1583
1613
  if (fliteLanguages.includes(language)) {
1584
- return "flite"
1614
+ return 'flite'
1585
1615
  }
1586
1616
 
1587
- const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js")
1617
+ const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js')
1588
1618
 
1589
1619
  const picoLanguages = getAllLangCodesFromVoiceList(SvoxPicoTTS.voiceList)
1590
1620
 
1591
1621
  if (picoLanguages.includes(language)) {
1592
- return "pico"
1622
+ return 'pico'
1593
1623
  }
1594
1624
 
1595
- return "espeak"
1625
+ return 'espeak'
1596
1626
  }
1597
1627
 
1598
1628
  export function getAllLangCodesFromVoiceList(voiceList: SynthesisVoice[]) {
@@ -1649,7 +1679,7 @@ export interface SynthesisVoice {
1649
1679
  packageName?: string
1650
1680
  }
1651
1681
 
1652
- export type VoiceGender = "male" | "female" | "unknown"
1682
+ export type VoiceGender = 'male' | 'female' | 'unknown'
1653
1683
 
1654
1684
  export const synthesisEngines: EngineMetadata[] = [
1655
1685
  {
@@ -1673,13 +1703,13 @@ export const synthesisEngines: EngineMetadata[] = [
1673
1703
  {
1674
1704
  id: 'espeak',
1675
1705
  name: 'eSpeak NG',
1676
- description: 'A lightweight "robot" sounding formant-based synthesizer.',
1706
+ description: `A lightweight 'robot' sounding formant-based synthesizer.`,
1677
1707
  type: 'local'
1678
1708
  },
1679
1709
  {
1680
1710
  id: 'sam',
1681
1711
  name: 'SAM (Software Automatic Mouth)',
1682
- description: 'A classic "robot" speech synthesizer from 1982.',
1712
+ description: `A classic 'robot' speech synthesizer from 1982.`,
1683
1713
  type: 'local'
1684
1714
  },
1685
1715
  {
@@ -1718,6 +1748,12 @@ export const synthesisEngines: EngineMetadata[] = [
1718
1748
  description: 'Amazon Polly (also: AWS Polly) cloud text-to-speech.',
1719
1749
  type: 'cloud'
1720
1750
  },
1751
+ {
1752
+ id: 'openai-cloud',
1753
+ name: 'OpenAI Cloud',
1754
+ description: 'OpenAI cloud text-to-speech.',
1755
+ type: 'cloud'
1756
+ },
1721
1757
  {
1722
1758
  id: 'elevenlabs',
1723
1759
  name: 'Elevenlabs',