echogarden 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/README.md +59 -1
  2. package/data/lexicons/heteronyms.json +674 -0
  3. package/data/schemas/options.json +1057 -0
  4. package/data/tables/lcid-table.json +7427 -0
  5. package/dist/alignment/DTWMfccSequenceAlignment.d.ts +2 -0
  6. package/dist/alignment/DTWMfccSequenceAlignment.js +26 -0
  7. package/dist/alignment/DTWMfccSequenceAlignment.js.map +1 -0
  8. package/dist/alignment/DTWSequenceAlignment.d.ts +5 -0
  9. package/dist/alignment/DTWSequenceAlignment.js +99 -0
  10. package/dist/alignment/DTWSequenceAlignment.js.map +1 -0
  11. package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +5 -0
  12. package/dist/alignment/DTWSequenceAlignmentWindowed.js +161 -0
  13. package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -0
  14. package/dist/alignment/LevenshteinSequenceAlignment.d.ts +5 -0
  15. package/dist/alignment/LevenshteinSequenceAlignment.js +102 -0
  16. package/dist/alignment/LevenshteinSequenceAlignment.js.map +1 -0
  17. package/dist/alignment/SpeechAlignment.d.ts +27 -0
  18. package/dist/alignment/SpeechAlignment.js +266 -0
  19. package/dist/alignment/SpeechAlignment.js.map +1 -0
  20. package/dist/api/API.d.ts +8 -0
  21. package/dist/api/API.js +10 -0
  22. package/dist/api/API.js.map +1 -0
  23. package/dist/api/APIOptions.d.ts +12 -0
  24. package/dist/api/APIOptions.js +2 -0
  25. package/dist/api/APIOptions.js.map +1 -0
  26. package/dist/api/Alignment.d.ts +32 -0
  27. package/dist/api/Alignment.js +134 -0
  28. package/dist/api/Alignment.js.map +1 -0
  29. package/dist/api/Denoising.d.ts +14 -0
  30. package/dist/api/Denoising.js +66 -0
  31. package/dist/api/Denoising.js.map +1 -0
  32. package/dist/api/Globals.d.ts +1 -0
  33. package/dist/api/Globals.js +2 -0
  34. package/dist/api/Globals.js.map +1 -0
  35. package/dist/api/LanguageDetection.d.ts +49 -0
  36. package/dist/api/LanguageDetection.js +118 -0
  37. package/dist/api/LanguageDetection.js.map +1 -0
  38. package/dist/api/Recognition.d.ts +49 -0
  39. package/dist/api/Recognition.js +162 -0
  40. package/dist/api/Recognition.js.map +1 -0
  41. package/dist/api/Synthesis.d.ts +125 -0
  42. package/dist/api/Synthesis.js +892 -0
  43. package/dist/api/Synthesis.js.map +1 -0
  44. package/dist/api/Translation.d.ts +25 -0
  45. package/dist/api/Translation.js +69 -0
  46. package/dist/api/Translation.js.map +1 -0
  47. package/dist/api/Vad.d.ts +25 -0
  48. package/dist/api/Vad.js +88 -0
  49. package/dist/api/Vad.js.map +1 -0
  50. package/dist/audio/AudioBufferConversion.d.ts +15 -0
  51. package/dist/audio/AudioBufferConversion.js +228 -0
  52. package/dist/audio/AudioBufferConversion.js.map +1 -0
  53. package/dist/audio/AudioPlayer.d.ts +9 -0
  54. package/dist/audio/AudioPlayer.js +232 -0
  55. package/dist/audio/AudioPlayer.js.map +1 -0
  56. package/dist/audio/AudioRecorder.d.ts +3 -0
  57. package/dist/audio/AudioRecorder.js +68 -0
  58. package/dist/audio/AudioRecorder.js.map +1 -0
  59. package/dist/audio/AudioUtilities.d.ts +48 -0
  60. package/dist/audio/AudioUtilities.js +209 -0
  61. package/dist/audio/AudioUtilities.js.map +1 -0
  62. package/dist/audio/SoxPath.d.ts +1 -0
  63. package/dist/audio/SoxPath.js +19 -0
  64. package/dist/audio/SoxPath.js.map +1 -0
  65. package/dist/cli/CLI.d.ts +8 -0
  66. package/dist/cli/CLI.js +860 -0
  67. package/dist/cli/CLI.js.map +1 -0
  68. package/dist/cli/CLIConfigFile.d.ts +3 -0
  69. package/dist/cli/CLIConfigFile.js +62 -0
  70. package/dist/cli/CLIConfigFile.js.map +1 -0
  71. package/dist/cli/CLILauncher.d.ts +2 -0
  72. package/dist/cli/CLILauncher.js +22 -0
  73. package/dist/cli/CLILauncher.js.map +1 -0
  74. package/dist/cli/CLIOptionsSchema.d.ts +5 -0
  75. package/dist/cli/CLIOptionsSchema.js +36 -0
  76. package/dist/cli/CLIOptionsSchema.js.map +1 -0
  77. package/dist/cli/CLIParser.d.ts +6 -0
  78. package/dist/cli/CLIParser.js +34 -0
  79. package/dist/cli/CLIParser.js.map +1 -0
  80. package/dist/cli/CLIStarter.d.ts +1 -0
  81. package/dist/cli/CLIStarter.js +3 -0
  82. package/dist/cli/CLIStarter.js.map +1 -0
  83. package/dist/codecs/FFMpegTranscoder.d.ts +20 -0
  84. package/dist/codecs/FFMpegTranscoder.js +169 -0
  85. package/dist/codecs/FFMpegTranscoder.js.map +1 -0
  86. package/dist/codecs/TIMITCodec.d.ts +9 -0
  87. package/dist/codecs/TIMITCodec.js +14 -0
  88. package/dist/codecs/TIMITCodec.js.map +1 -0
  89. package/dist/codecs/WaveCodec.d.ts +19 -0
  90. package/dist/codecs/WaveCodec.js +208 -0
  91. package/dist/codecs/WaveCodec.js.map +1 -0
  92. package/dist/denoising/RNNoise.d.ts +6 -0
  93. package/dist/denoising/RNNoise.js +68 -0
  94. package/dist/denoising/RNNoise.js.map +1 -0
  95. package/dist/dsp/BiquadFilter.d.ts +26 -0
  96. package/dist/dsp/BiquadFilter.js +399 -0
  97. package/dist/dsp/BiquadFilter.js.map +1 -0
  98. package/dist/dsp/FFT.d.ts +9 -0
  99. package/dist/dsp/FFT.js +135 -0
  100. package/dist/dsp/FFT.js.map +1 -0
  101. package/dist/dsp/MFCC.d.ts +25 -0
  102. package/dist/dsp/MFCC.js +162 -0
  103. package/dist/dsp/MFCC.js.map +1 -0
  104. package/dist/dsp/MelSpectogram.d.ts +19 -0
  105. package/dist/dsp/MelSpectogram.js +102 -0
  106. package/dist/dsp/MelSpectogram.js.map +1 -0
  107. package/dist/dsp/Rubberband.d.ts +52 -0
  108. package/dist/dsp/Rubberband.js +186 -0
  109. package/dist/dsp/Rubberband.js.map +1 -0
  110. package/dist/dsp/Sonic.d.ts +2 -0
  111. package/dist/dsp/Sonic.js +39 -0
  112. package/dist/dsp/Sonic.js.map +1 -0
  113. package/dist/dsp/SpeexResampler.d.ts +3 -0
  114. package/dist/dsp/SpeexResampler.js +53 -0
  115. package/dist/dsp/SpeexResampler.js.map +1 -0
  116. package/dist/math/VectorMath.d.ts +70 -0
  117. package/dist/math/VectorMath.js +564 -0
  118. package/dist/math/VectorMath.js.map +1 -0
  119. package/dist/nlp/ChineseSegmentation.d.ts +1 -0
  120. package/dist/nlp/ChineseSegmentation.js +53 -0
  121. package/dist/nlp/ChineseSegmentation.js.map +1 -0
  122. package/dist/nlp/CompromiseNLP.d.ts +15 -0
  123. package/dist/nlp/CompromiseNLP.js +66 -0
  124. package/dist/nlp/CompromiseNLP.js.map +1 -0
  125. package/dist/nlp/EspeakPhonemizer.d.ts +4 -0
  126. package/dist/nlp/EspeakPhonemizer.js +133 -0
  127. package/dist/nlp/EspeakPhonemizer.js.map +1 -0
  128. package/dist/nlp/IPA.d.ts +19 -0
  129. package/dist/nlp/IPA.js +113 -0
  130. package/dist/nlp/IPA.js.map +1 -0
  131. package/dist/nlp/JapaneseSegmentation.d.ts +1 -0
  132. package/dist/nlp/JapaneseSegmentation.js +40 -0
  133. package/dist/nlp/JapaneseSegmentation.js.map +1 -0
  134. package/dist/nlp/Lexicon.d.ts +17 -0
  135. package/dist/nlp/Lexicon.js +6 -0
  136. package/dist/nlp/Lexicon.js.map +1 -0
  137. package/dist/nlp/PhoneConversion.d.ts +6 -0
  138. package/dist/nlp/PhoneConversion.js +467 -0
  139. package/dist/nlp/PhoneConversion.js.map +1 -0
  140. package/dist/nlp/Segmentation.d.ts +41 -0
  141. package/dist/nlp/Segmentation.js +158 -0
  142. package/dist/nlp/Segmentation.js.map +1 -0
  143. package/dist/nlp/TextNormalizer.d.ts +4 -0
  144. package/dist/nlp/TextNormalizer.js +69 -0
  145. package/dist/nlp/TextNormalizer.js.map +1 -0
  146. package/dist/recognition/AmazonTranscribeSTT.d.ts +6 -0
  147. package/dist/recognition/AmazonTranscribeSTT.js +79 -0
  148. package/dist/recognition/AmazonTranscribeSTT.js.map +1 -0
  149. package/dist/recognition/AzureCognitiveServicesSTT.d.ts +7 -0
  150. package/dist/recognition/AzureCognitiveServicesSTT.js +51 -0
  151. package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -0
  152. package/dist/recognition/GoogleCloudSTT.d.ts +7 -0
  153. package/dist/recognition/GoogleCloudSTT.js +66 -0
  154. package/dist/recognition/GoogleCloudSTT.js.map +1 -0
  155. package/dist/recognition/SileroSTT.d.ts +9 -0
  156. package/dist/recognition/SileroSTT.js +125 -0
  157. package/dist/recognition/SileroSTT.js.map +1 -0
  158. package/dist/recognition/VoskSTT.d.ts +10 -0
  159. package/dist/recognition/VoskSTT.js +78 -0
  160. package/dist/recognition/VoskSTT.js.map +1 -0
  161. package/dist/recognition/WhisperSTT.d.ts +69 -0
  162. package/dist/recognition/WhisperSTT.js +977 -0
  163. package/dist/recognition/WhisperSTT.js.map +1 -0
  164. package/dist/server/Server.d.ts +1 -0
  165. package/dist/server/Server.js +18 -0
  166. package/dist/server/Server.js.map +1 -0
  167. package/dist/speech-language-detection/SileroLanguageDetection.d.ts +9 -0
  168. package/dist/speech-language-detection/SileroLanguageDetection.js +47 -0
  169. package/dist/speech-language-detection/SileroLanguageDetection.js.map +1 -0
  170. package/dist/subtitles/Subtitles.d.ts +25 -0
  171. package/dist/subtitles/Subtitles.js +286 -0
  172. package/dist/subtitles/Subtitles.js.map +1 -0
  173. package/dist/synthesis/AwsPollyTTS.d.ts +7 -0
  174. package/dist/synthesis/AwsPollyTTS.js +51 -0
  175. package/dist/synthesis/AwsPollyTTS.js.map +1 -0
  176. package/dist/synthesis/AzureCognitiveServicesTTS.d.ts +9 -0
  177. package/dist/synthesis/AzureCognitiveServicesTTS.js +103 -0
  178. package/dist/synthesis/AzureCognitiveServicesTTS.js.map +1 -0
  179. package/dist/synthesis/CoquiServerTTS.d.ts +6 -0
  180. package/dist/synthesis/CoquiServerTTS.js +22 -0
  181. package/dist/synthesis/CoquiServerTTS.js.map +1 -0
  182. package/dist/synthesis/ElevenLabsTTS.d.ts +8 -0
  183. package/dist/synthesis/ElevenLabsTTS.js +48 -0
  184. package/dist/synthesis/ElevenLabsTTS.js.map +1 -0
  185. package/dist/synthesis/EspeakTTS.d.ts +46 -0
  186. package/dist/synthesis/EspeakTTS.js +353 -0
  187. package/dist/synthesis/EspeakTTS.js.map +1 -0
  188. package/dist/synthesis/FliteTTS.d.ts +17 -0
  189. package/dist/synthesis/FliteTTS.js +326 -0
  190. package/dist/synthesis/FliteTTS.js.map +1 -0
  191. package/dist/synthesis/GoogleCloudTTS.d.ts +16 -0
  192. package/dist/synthesis/GoogleCloudTTS.js +72 -0
  193. package/dist/synthesis/GoogleCloudTTS.js.map +1 -0
  194. package/dist/synthesis/GoogleTranslateTTS.d.ts +13 -0
  195. package/dist/synthesis/GoogleTranslateTTS.js +177 -0
  196. package/dist/synthesis/GoogleTranslateTTS.js.map +1 -0
  197. package/dist/synthesis/MicrosoftEdgeTTS.d.ts +10 -0
  198. package/dist/synthesis/MicrosoftEdgeTTS.js +216 -0
  199. package/dist/synthesis/MicrosoftEdgeTTS.js.map +1 -0
  200. package/dist/synthesis/SamTTS.d.ts +4 -0
  201. package/dist/synthesis/SamTTS.js +21 -0
  202. package/dist/synthesis/SamTTS.js.map +1 -0
  203. package/dist/synthesis/SapiTTS.d.ts +9 -0
  204. package/dist/synthesis/SapiTTS.js +211 -0
  205. package/dist/synthesis/SapiTTS.js.map +1 -0
  206. package/dist/synthesis/StreamlabsPollyTTS.d.ts +12 -0
  207. package/dist/synthesis/StreamlabsPollyTTS.js +88 -0
  208. package/dist/synthesis/StreamlabsPollyTTS.js.map +1 -0
  209. package/dist/synthesis/SvoxPicoTTS.d.ts +11 -0
  210. package/dist/synthesis/SvoxPicoTTS.js +236 -0
  211. package/dist/synthesis/SvoxPicoTTS.js.map +1 -0
  212. package/dist/synthesis/VitsTTS.d.ts +27 -0
  213. package/dist/synthesis/VitsTTS.js +359 -0
  214. package/dist/synthesis/VitsTTS.js.map +1 -0
  215. package/dist/tests/Test.d.ts +1 -0
  216. package/dist/tests/Test.js +10 -0
  217. package/dist/tests/Test.js.map +1 -0
  218. package/dist/text-language-detection/FastTextLanguageDetection.d.ts +2 -0
  219. package/dist/text-language-detection/FastTextLanguageDetection.js +38 -0
  220. package/dist/text-language-detection/FastTextLanguageDetection.js.map +1 -0
  221. package/dist/text-language-detection/TinyLDLanguageDetection.d.ts +2 -0
  222. package/dist/text-language-detection/TinyLDLanguageDetection.js +12 -0
  223. package/dist/text-language-detection/TinyLDLanguageDetection.js.map +1 -0
  224. package/dist/utilities/BinaryArrayConversion.d.ts +15 -0
  225. package/dist/utilities/BinaryArrayConversion.js +115 -0
  226. package/dist/utilities/BinaryArrayConversion.js.map +1 -0
  227. package/dist/utilities/Compression.d.ts +4 -0
  228. package/dist/utilities/Compression.js +67 -0
  229. package/dist/utilities/Compression.js.map +1 -0
  230. package/dist/utilities/FileDownloader.d.ts +3 -0
  231. package/dist/utilities/FileDownloader.js +147 -0
  232. package/dist/utilities/FileDownloader.js.map +1 -0
  233. package/dist/utilities/FileSystem.d.ts +30 -0
  234. package/dist/utilities/FileSystem.js +141 -0
  235. package/dist/utilities/FileSystem.js.map +1 -0
  236. package/dist/utilities/Hashing.d.ts +10 -0
  237. package/dist/utilities/Hashing.js +169 -0
  238. package/dist/utilities/Hashing.js.map +1 -0
  239. package/dist/utilities/Locale.d.ts +9 -0
  240. package/dist/utilities/Locale.js +65 -0
  241. package/dist/utilities/Locale.js.map +1 -0
  242. package/dist/utilities/Logger.d.ts +11 -0
  243. package/dist/utilities/Logger.js +49 -0
  244. package/dist/utilities/Logger.js.map +1 -0
  245. package/dist/utilities/NdArrayUtilities.d.ts +3 -0
  246. package/dist/utilities/NdArrayUtilities.js +22 -0
  247. package/dist/utilities/NdArrayUtilities.js.map +1 -0
  248. package/dist/utilities/ObjectUtilities.d.ts +4 -0
  249. package/dist/utilities/ObjectUtilities.js +132 -0
  250. package/dist/utilities/ObjectUtilities.js.map +1 -0
  251. package/dist/utilities/OpenPromise.d.ts +6 -0
  252. package/dist/utilities/OpenPromise.js +12 -0
  253. package/dist/utilities/OpenPromise.js.map +1 -0
  254. package/dist/utilities/PackageManager.d.ts +4 -0
  255. package/dist/utilities/PackageManager.js +46 -0
  256. package/dist/utilities/PackageManager.js.map +1 -0
  257. package/dist/utilities/RandomGenerator.d.ts +35 -0
  258. package/dist/utilities/RandomGenerator.js +149 -0
  259. package/dist/utilities/RandomGenerator.js.map +1 -0
  260. package/dist/utilities/TarballMaker.d.ts +4 -0
  261. package/dist/utilities/TarballMaker.js +50 -0
  262. package/dist/utilities/TarballMaker.js.map +1 -0
  263. package/dist/utilities/Timeline.d.ts +20 -0
  264. package/dist/utilities/Timeline.js +110 -0
  265. package/dist/utilities/Timeline.js.map +1 -0
  266. package/dist/utilities/Timer.d.ts +13 -0
  267. package/dist/utilities/Timer.js +70 -0
  268. package/dist/utilities/Timer.js.map +1 -0
  269. package/dist/utilities/Utilities.d.ts +68 -0
  270. package/dist/utilities/Utilities.js +305 -0
  271. package/dist/utilities/Utilities.js.map +1 -0
  272. package/dist/utilities/WasmMemoryManager.d.ts +142 -0
  273. package/dist/utilities/WasmMemoryManager.js +407 -0
  274. package/dist/utilities/WasmMemoryManager.js.map +1 -0
  275. package/dist/utilities/WebReader.d.ts +1 -0
  276. package/dist/utilities/WebReader.js +47 -0
  277. package/dist/utilities/WebReader.js.map +1 -0
  278. package/dist/utilities/WikipediaReader.d.ts +1 -0
  279. package/dist/utilities/WikipediaReader.js +31 -0
  280. package/dist/utilities/WikipediaReader.js.map +1 -0
  281. package/dist/voice-activity-detection/SileroVAD.d.ts +13 -0
  282. package/dist/voice-activity-detection/SileroVAD.js +58 -0
  283. package/dist/voice-activity-detection/SileroVAD.js.map +1 -0
  284. package/dist/voice-activity-detection/WebRtcVAD.d.ts +3 -0
  285. package/dist/voice-activity-detection/WebRtcVAD.js +53 -0
  286. package/dist/voice-activity-detection/WebRtcVAD.js.map +1 -0
  287. package/docs/CLI.md +234 -0
  288. package/docs/Development.md +3 -0
  289. package/docs/Engines.md +80 -0
  290. package/docs/Licenses.md +37 -0
  291. package/docs/Options.md +192 -0
  292. package/docs/Roadmap.md +16 -0
  293. package/docs/Technical.md +72 -0
  294. package/package.json +119 -19
  295. package/cli.js +0 -3
@@ -0,0 +1,892 @@
1
+ import path from "node:path";
2
+ import { deepClone, extendDeep } from "../utilities/ObjectUtilities.js";
3
+ import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js";
4
+ import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr } from "../utilities/Utilities.js";
5
+ import { concatAudioSegments, downmixToMono, getAudioPeakDecibels, getEmptyRawAudio, normalizeAudioLevel, trimAudioEnd, trimAudioStart } from "../audio/AudioUtilities.js";
6
+ import { Logger } from "../utilities/Logger.js";
7
+ import { splitToSentences } from "../nlp/Segmentation.js";
8
+ import { loadLexiconFile } from "../nlp/Lexicon.js";
9
+ import * as API from "./API.js";
10
+ import { addTimeOffsetToTimeline, multiplyTimelineByFactor } from "../utilities/Timeline.js";
11
+ import { getAppDataDir, ensureDir, existsSync, isFileIsUpToDate, readAndParseJsonFile, resolveToModuleRootDir, writeFileSafe } from "../utilities/FileSystem.js";
12
+ import { formatLanguageCodeWithName, getShortLanguageCode, normalizeLanguageCode, shortLanguageCodeToLong } from "../utilities/Locale.js";
13
+ import { loadPackage } from "../utilities/PackageManager.js";
14
+ import { appName } from "./Globals.js";
15
+ const log = logToStderr;
16
+ /////////////////////////////////////////////////////////////////////////////////////////////
17
+ // Synthesis
18
+ /////////////////////////////////////////////////////////////////////////////////////////////
19
+ export async function synthesizeSegments(segments, options, onSegment, onSentence) {
20
+ const logger = new Logger();
21
+ options = extendDeep(defaultSynthesisOptions, options);
22
+ if (!options.language && !options.voice) {
23
+ logger.start("No language or voice specified. Detecting language");
24
+ const { detectedLanguage } = await API.detectTextLanguage(segments.join("\n"), {});
25
+ options.language = detectedLanguage;
26
+ logger.end();
27
+ logger.log(`Language detected: ${formatLanguageCodeWithName(detectedLanguage)}`);
28
+ }
29
+ if (!options.engine) {
30
+ if (options.voice) {
31
+ throw new Error(`Voice '${options.voice}' was specified but no engine was specified.`);
32
+ }
33
+ options.engine = selectBestOfflineEngineForLanguage(options.language);
34
+ logger.log(`No engine specified, setting engine to '${options.engine}'`);
35
+ }
36
+ logger.start(`Get voice list for ${options.engine}`);
37
+ const { bestMatchingVoice } = await requestVoiceList(options);
38
+ if (!bestMatchingVoice) {
39
+ throw new Error("No matching voice found");
40
+ }
41
+ options.voice = bestMatchingVoice.name;
42
+ if (!options.language) {
43
+ options.language = bestMatchingVoice.languages[0];
44
+ }
45
+ logger.end();
46
+ logger.log(`Selected voice: '${options.voice}'`);
47
+ const segmentsAudio = [];
48
+ const segmentsTimelines = [];
49
+ const timeline = [];
50
+ let peakDecibelsSoFar = -100;
51
+ let timeOffset = 0;
52
+ for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
53
+ logger.log(`\nSynthesizing segment ${segmentIndex + 1}/${segments.length}..`);
54
+ const segmentText = segments[segmentIndex].trim();
55
+ const segmentStartTime = timeOffset;
56
+ const segmentEntry = {
57
+ type: "segment",
58
+ text: segmentText,
59
+ startTime: timeOffset,
60
+ endTime: -1,
61
+ timeline: []
62
+ };
63
+ let sentences;
64
+ if (options.splitToSentences || options.engine == "vits") {
65
+ sentences = splitToSentences(segmentText, options.language);
66
+ }
67
+ else {
68
+ sentences = [segmentText];
69
+ }
70
+ const sentencesAudio = [];
71
+ const sentencesTimelines = [];
72
+ for (let sentenceIndex = 0; sentenceIndex < sentences.length; sentenceIndex++) {
73
+ logger.log(`\nSynthesizing sentence ${sentenceIndex + 1}/${sentences.length}..`);
74
+ const sentenceText = sentences[sentenceIndex].trim();
75
+ const sentenceStartTime = timeOffset;
76
+ let sentencetSynthesisOptions = { postProcessing: { normalizeAudio: false } };
77
+ sentencetSynthesisOptions = extendDeep(options, sentencetSynthesisOptions);
78
+ const { synthesizedAudio: sentenceAudio, timeline: sentenceTimeline } = await synthesizeSegment(sentenceText, sentencetSynthesisOptions);
79
+ const endPause = sentenceIndex == sentences.length - 1 ? options.segmentEndPause : options.sentenceEndPause;
80
+ sentenceAudio.audioChannels[0] = trimAudioEnd(sentenceAudio.audioChannels[0], endPause * sentenceAudio.sampleRate, -40);
81
+ sentencesAudio.push(sentenceAudio);
82
+ if (sentenceTimeline.length > 0) {
83
+ sentencesTimelines.push(sentenceTimeline);
84
+ }
85
+ const sentenceAudioLength = sentenceAudio.audioChannels[0].length / sentenceAudio.sampleRate;
86
+ timeOffset += sentenceAudioLength;
87
+ const sentenceTimelineWithOffset = addTimeOffsetToTimeline(sentenceTimeline, sentenceStartTime);
88
+ segmentEntry.timeline.push({
89
+ type: "sentence",
90
+ text: sentenceText,
91
+ startTime: sentenceStartTime,
92
+ endTime: timeOffset,
93
+ timeline: sentenceTimelineWithOffset
94
+ });
95
+ peakDecibelsSoFar = Math.max(peakDecibelsSoFar, getAudioPeakDecibels(sentenceAudio.audioChannels));
96
+ if (onSentence) {
97
+ await onSentence({
98
+ index: sentenceIndex,
99
+ total: sentences.length,
100
+ audio: sentenceAudio,
101
+ timeline: sentenceTimeline,
102
+ transcript: sentenceText,
103
+ language: options.language,
104
+ peakDecibelsSoFar
105
+ });
106
+ }
107
+ }
108
+ segmentEntry.endTime = timeOffset;
109
+ logger.end();
110
+ logger.start(`Merge and postprocess sentences`);
111
+ let segmentAudio;
112
+ if (sentencesAudio.length > 0) {
113
+ const joinedAudioBuffers = concatAudioSegments(sentencesAudio.map(part => part.audioChannels));
114
+ segmentAudio = { audioChannels: joinedAudioBuffers, sampleRate: sentencesAudio[0].sampleRate };
115
+ }
116
+ else {
117
+ segmentAudio = getEmptyRawAudio(1, 24000);
118
+ }
119
+ segmentsAudio.push(segmentAudio);
120
+ timeline.push(segmentEntry);
121
+ const segmentTimelineWithoutOffset = addTimeOffsetToTimeline(segmentEntry.timeline, -segmentStartTime);
122
+ segmentsTimelines.push(segmentTimelineWithoutOffset);
123
+ logger.end();
124
+ if (onSegment) {
125
+ await onSegment({
126
+ index: segmentIndex,
127
+ total: segments.length,
128
+ audio: segmentAudio,
129
+ timeline: segmentTimelineWithoutOffset,
130
+ transcript: segmentText,
131
+ language: options.language,
132
+ peakDecibelsSoFar
133
+ });
134
+ }
135
+ }
136
+ logger.start(`Merge and postprocess segments`);
137
+ let resultAudio;
138
+ if (segmentsAudio.length > 0) {
139
+ const joinedAudioBuffers = concatAudioSegments(segmentsAudio.map(part => part.audioChannels));
140
+ resultAudio = { audioChannels: joinedAudioBuffers, sampleRate: segmentsAudio[0].sampleRate };
141
+ if (options.postProcessing.normalizeAudio) {
142
+ resultAudio = normalizeAudioLevel(resultAudio, options.postProcessing.targetPeakDb, options.postProcessing.maxIncreaseDb);
143
+ }
144
+ }
145
+ else {
146
+ resultAudio = getEmptyRawAudio(1, 24000);
147
+ }
148
+ logger.end();
149
+ return {
150
+ synthesizedAudio: resultAudio,
151
+ timeline,
152
+ segmentsAudio,
153
+ segmentsTimelines,
154
+ };
155
+ }
156
+ async function synthesizeSegment(text, options) {
157
+ const logger = new Logger();
158
+ const startTimestamp = logger.getTimestamp();
159
+ logger.start("Prepare for synthesis");
160
+ const simplifiedText = simplifyPunctuationCharacters(text);
161
+ const engine = options.engine;
162
+ logger.start(`Get voice list for ${engine}`);
163
+ const { bestMatchingVoice } = await requestVoiceList(options);
164
+ if (!bestMatchingVoice) {
165
+ throw new Error("No matching voice found");
166
+ }
167
+ const selectedVoice = bestMatchingVoice;
168
+ let voicePackagePath;
169
+ if (selectedVoice.packageName) {
170
+ logger.end();
171
+ voicePackagePath = await loadPackage(selectedVoice.packageName);
172
+ }
173
+ logger.start(`Initialize ${engine} module`);
174
+ const voice = selectedVoice.name;
175
+ const language = options.language ? normalizeLanguageCode(options.language) : selectedVoice.languages[0];
176
+ const voiceGender = selectedVoice.gender;
177
+ const speed = clip(options.speed, 0.1, 10.0);
178
+ const pitch = clip(options.pitch, 0.1, 10.0);
179
+ const ssmlEnabled = options.ssml;
180
+ let synthesizedAudio;
181
+ let timeline;
182
+ let shouldPostprocessSpeed = false;
183
+ let shouldPostprocessPitch = false;
184
+ switch (engine) {
185
+ case "vits": {
186
+ let vitsLanguage = language;
187
+ if (vitsLanguage == "en") {
188
+ vitsLanguage = "en-us";
189
+ }
190
+ const vitsTTS = await import("../synthesis/VitsTTS.js");
191
+ const lengthScale = 1 / speed;
192
+ const engineOptions = options.vits;
193
+ const speakerId = engineOptions.speakerId;
194
+ if (speakerId != undefined) {
195
+ if (selectedVoice.speakerCount == undefined) {
196
+ if (speakerId != 0) {
197
+ throw new Error("Selected VITS model has only one speaker. Speaker ID must be 0 if specified.");
198
+ }
199
+ }
200
+ else if (speakerId < 0 || speakerId >= selectedVoice.speakerCount) {
201
+ throw new Error(`Selected VITS model has ${selectedVoice.speakerCount} voices. Speaker ID should be in the range ${0} to ${selectedVoice.speakerCount - 1}`);
202
+ }
203
+ }
204
+ const heteronymsLexicon = await loadLexiconFile(resolveToModuleRootDir("data/lexicons/heteronyms.json"));
205
+ const lexicons = [heteronymsLexicon];
206
+ const modelPath = voicePackagePath;
207
+ logger.end();
208
+ const { rawAudio, timeline: outTimeline } = await vitsTTS.synthesizeSentence(text, voice, modelPath, lengthScale, speakerId, lexicons);
209
+ synthesizedAudio = rawAudio;
210
+ timeline = outTimeline;
211
+ shouldPostprocessPitch = true;
212
+ logger.end();
213
+ break;
214
+ }
215
+ case "pico": {
216
+ const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js");
217
+ const picoSpeed = Math.round(speed * 1.0 * 100);
218
+ const picoPitch = Math.round(pitch * 1.0 * 100);
219
+ const picoVolume = 35.0;
220
+ const preparedText = `<speed level="${picoSpeed}"><pitch level="${picoPitch}"><volume level="${picoVolume}">${simplifiedText}</volume></pitch></speed>`;
221
+ logger.end();
222
+ const { textAnalysisFilename, signalGenerationFilename } = SvoxPicoTTS.getResourceFilenamesForLanguage(language);
223
+ const resourceFilePath = path.resolve(voicePackagePath, textAnalysisFilename);
224
+ const signalGenerationFilePath = path.resolve(voicePackagePath, signalGenerationFilename);
225
+ const { rawAudio } = await SvoxPicoTTS.synthesize(preparedText, resourceFilePath, signalGenerationFilePath);
226
+ synthesizedAudio = rawAudio;
227
+ break;
228
+ }
229
+ case "flite": {
230
+ const FliteTTS = await import("../synthesis/FliteTTS.js");
231
+ logger.end();
232
+ const { rawAudio, events } = await FliteTTS.synthesize(simplifiedText, voice, voicePackagePath, speed);
233
+ synthesizedAudio = rawAudio;
234
+ shouldPostprocessPitch = true;
235
+ break;
236
+ }
237
+ case "espeak": {
238
+ const EspeakTTS = await import("../synthesis/EspeakTTS.js");
239
+ const engineOptions = options.espeak;
240
+ await EspeakTTS.setVoice(voice);
241
+ await EspeakTTS.setRate(engineOptions.rate || speed * 150);
242
+ await EspeakTTS.setPitch(engineOptions.pitch || pitch * 50);
243
+ await EspeakTTS.setPitchRange(engineOptions.pitchRange || options.pitchVariation * 50);
244
+ logger.end();
245
+ const { rawAudio, events } = await EspeakTTS.synthesize(simplifiedText, ssmlEnabled);
246
+ synthesizedAudio = rawAudio;
247
+ break;
248
+ }
249
+ case "sam": {
250
+ const SamTTS = await import("../synthesis/SamTTS.js");
251
+ const engineOptions = options.sam;
252
+ const samPitch = clip(engineOptions.pitch || Math.round((1 / pitch) * 64), 0, 255);
253
+ const samSpeed = clip(engineOptions.speed || Math.round((1 / speed) * 72), 0, 255);
254
+ const samMouth = clip(engineOptions.mouth, 0, 255);
255
+ const samThroat = clip(engineOptions.throat, 0, 255);
256
+ logger.end();
257
+ const { rawAudio } = await SamTTS.synthesize(simplifiedText, samPitch, samSpeed, samMouth, samThroat);
258
+ synthesizedAudio = rawAudio;
259
+ break;
260
+ }
261
+ case "sapi": {
262
+ const SapiTTS = await import("../synthesis/SapiTTS.js");
263
+ await SapiTTS.AssertSAPIAvailable(false);
264
+ const engineOptions = options.sapi;
265
+ const sapiRate = engineOptions.rate || 0;
266
+ logger.end();
267
+ const { rawAudio, timeline: outTimeline } = await SapiTTS.synthesize(text, voice, sapiRate, false);
268
+ synthesizedAudio = rawAudio;
269
+ timeline = outTimeline;
270
+ shouldPostprocessSpeed = true;
271
+ shouldPostprocessPitch = true;
272
+ break;
273
+ }
274
+ case "msspeech": {
275
+ const SapiTTS = await import("../synthesis/SapiTTS.js");
276
+ await SapiTTS.AssertSAPIAvailable(true);
277
+ const engineOptions = options.msspeech;
278
+ const sapiRate = engineOptions.rate || 0;
279
+ logger.end();
280
+ const { rawAudio, timeline: outTimeline } = await SapiTTS.synthesize(text, voice, sapiRate, true);
281
+ synthesizedAudio = rawAudio;
282
+ timeline = outTimeline;
283
+ shouldPostprocessSpeed = true;
284
+ shouldPostprocessPitch = true;
285
+ break;
286
+ }
287
+ case "coqui-server": {
288
+ const CoquiServerTTS = await import("../synthesis/CoquiServerTTS.js");
289
+ const engineOptions = options.coquiServer;
290
+ const speakerId = engineOptions.speakerId;
291
+ const serverUrl = engineOptions.serverUrl;
292
+ if (!serverUrl) {
293
+ throw new Error(`'coqui-server' requires a server URL`);
294
+ }
295
+ logger.end();
296
+ const { rawAudio } = await CoquiServerTTS.synthesize(simplifiedText, speakerId, serverUrl);
297
+ synthesizedAudio = rawAudio;
298
+ shouldPostprocessSpeed = true;
299
+ shouldPostprocessPitch = true;
300
+ break;
301
+ }
302
+ case "google-cloud": {
303
+ const GoogleCloudTTS = await import("../synthesis/GoogleCloudTTS.js");
304
+ const engineOptions = options.googleCloud;
305
+ const apiKey = engineOptions.apiKey;
306
+ if (!apiKey) {
307
+ throw new Error(`No API key given`);
308
+ }
309
+ let pitchDeltaSemitones;
310
+ // 1 semitone up = multiply by 1.05946
311
+ // 1 semitone down = divide by 1.05946
312
+ if (engineOptions.pitchDeltaSemitones != undefined) {
313
+ pitchDeltaSemitones = engineOptions.pitchDeltaSemitones;
314
+ }
315
+ else if (pitch >= 1.0) {
316
+ pitchDeltaSemitones = Math.round(17.3132 * Math.log(pitch));
317
+ }
318
+ else {
319
+ pitchDeltaSemitones = Math.round(-17.3132 * Math.log(1 / pitch));
320
+ }
321
+ logger.end();
322
+ const { audioData, timepoints } = await GoogleCloudTTS.synthesize(simplifiedText, apiKey, language, voice, speed, pitchDeltaSemitones, 0, ssmlEnabled);
323
+ const rawAudio = await FFMpegTranscoder.decodeToChannels(audioData);
324
+ synthesizedAudio = rawAudio;
325
+ break;
326
+ }
327
+ case "microsoft-azure": {
328
+ const AzureCognitiveServicesTTS = await import("../synthesis/AzureCognitiveServicesTTS.js");
329
+ const engineOptions = options.microsoftAzure;
330
+ const subscriptionKey = engineOptions.subscriptionKey;
331
+ if (!subscriptionKey) {
332
+ throw new Error(`No subscription key given`);
333
+ }
334
+ const serviceRegion = engineOptions.serviceRegion;
335
+ if (!serviceRegion) {
336
+ throw new Error(`No service region given`);
337
+ }
338
+ let ssmlPitch;
339
+ if (engineOptions.pitchDeltaHz != undefined) {
340
+ if (engineOptions.pitchDeltaHz >= 0) {
341
+ ssmlPitch = `+${Math.abs(engineOptions.pitchDeltaHz)}Hz`;
342
+ }
343
+ else {
344
+ ssmlPitch = `-${Math.abs(engineOptions.pitchDeltaHz)}Hz`;
345
+ }
346
+ }
347
+ else {
348
+ ssmlPitch = convertPitchScaleToSSMLValueString(pitch, voiceGender);
349
+ }
350
+ const ssmlRate = convertSpeedScaleToSSMLValueString(speed);
351
+ logger.end();
352
+ const { rawAudio, timeline: outTimeline } = await AzureCognitiveServicesTTS.synthesize(text, subscriptionKey, serviceRegion, language, voice, ssmlEnabled, ssmlPitch, ssmlRate);
353
+ synthesizedAudio = rawAudio;
354
+ timeline = outTimeline;
355
+ break;
356
+ }
357
+ case "amazon-polly": {
358
+ const AwsPollyTTS = await import("../synthesis/AwsPollyTTS.js");
359
+ const engineOptions = options.awsPolly;
360
+ const region = engineOptions.region;
361
+ if (!region) {
362
+ throw new Error(`No region given`);
363
+ }
364
+ const accessKeyId = engineOptions.accessKeyId;
365
+ if (!accessKeyId) {
366
+ throw new Error(`No access key id given`);
367
+ }
368
+ const secretAccessKey = engineOptions.secretAccessKey;
369
+ if (!secretAccessKey) {
370
+ throw new Error(`No secret access key given`);
371
+ }
372
+ const pollyEngine = engineOptions.pollyEngine;
373
+ const lexiconNames = engineOptions.lexiconNames;
374
+ logger.end();
375
+ const { rawAudio } = await AwsPollyTTS.synthesize(simplifiedText, undefined, voice, region, accessKeyId, secretAccessKey, pollyEngine, ssmlEnabled, lexiconNames);
376
+ synthesizedAudio = rawAudio;
377
+ shouldPostprocessSpeed = true;
378
+ shouldPostprocessPitch = true;
379
+ break;
380
+ }
381
+ case "elevenlabs": {
382
+ const ElevenLabsTTS = await import("../synthesis/ElevenLabsTTS.js");
383
+ const engineOptions = options.elevenlabs;
384
+ const apiKey = engineOptions.apiKey;
385
+ if (!apiKey) {
386
+ throw new Error(`No ElevenLabs API key given`);
387
+ }
388
+ const voiceId = selectedVoice["elevenLabsVoiceId"];
389
+ const stability = engineOptions.stability;
390
+ const similarityBoost = engineOptions.similarityBoost;
391
+ logger.end();
392
+ const { rawAudio } = await ElevenLabsTTS.synthesize(simplifiedText, voiceId, apiKey, stability, similarityBoost);
393
+ synthesizedAudio = rawAudio;
394
+ shouldPostprocessSpeed = true;
395
+ shouldPostprocessPitch = true;
396
+ break;
397
+ }
398
+ case "google-translate": {
399
+ const GoogleTranslateTTS = await import("../synthesis/GoogleTranslateTTS.js");
400
+ logger.end();
401
+ const { rawAudio, timeline: segmentTimeline } = await GoogleTranslateTTS.synthesizeLongText(text, language, options.googleTranslate?.tld, options.sentenceEndPause, options.segmentEndPause);
402
+ synthesizedAudio = rawAudio;
403
+ logger.start(`Generate word-level timestamps by individually aligning segments`);
404
+ const alignmentOptions = extendDeep(options.alignment, { language });
405
+ timeline = await API.alignSegments(synthesizedAudio, segmentTimeline, alignmentOptions);
406
+ shouldPostprocessSpeed = true;
407
+ shouldPostprocessPitch = true;
408
+ break;
409
+ }
410
+ case "microsoft-edge": {
411
+ const MicrosoftEdgeTTS = await import("../synthesis/MicrosoftEdgeTTS.js");
412
+ const engineOptions = options.microsoftEdge;
413
+ const trustedClientToken = engineOptions.trustedClientToken;
414
+ if (!trustedClientToken) {
415
+ throw new Error("No trusted client token provided.");
416
+ }
417
+ if (await sha256AsHex(trustedClientToken) != "558d7c6a7f7db444895946fe23a54ad172fd6d159f46cb34dd4db21bb27c07d7") {
418
+ throw new Error("Trusted client token is incorrect.");
419
+ }
420
+ let ssmlPitch;
421
+ if (engineOptions.pitchDeltaHz != undefined) {
422
+ if (engineOptions.pitchDeltaHz >= 0) {
423
+ ssmlPitch = `+${Math.abs(engineOptions.pitchDeltaHz)}Hz`;
424
+ }
425
+ else {
426
+ ssmlPitch = `-${Math.abs(engineOptions.pitchDeltaHz)}Hz`;
427
+ }
428
+ }
429
+ else {
430
+ ssmlPitch = convertPitchScaleToSSMLValueString(pitch, voiceGender);
431
+ }
432
+ const ssmlRate = convertSpeedScaleToSSMLValueString(speed);
433
+ logger.end();
434
+ const { rawAudio, timeline: edgeTimeline } = await MicrosoftEdgeTTS.synthesize(text, trustedClientToken, voice, ssmlPitch, ssmlRate);
435
+ synthesizedAudio = rawAudio;
436
+ timeline = edgeTimeline;
437
+ break;
438
+ }
439
+ case "streamlabs-polly": {
440
+ const StreamlabsPollyTTS = await import("../synthesis/StreamlabsPollyTTS.js");
441
+ logger.end();
442
+ const { rawAudio, timeline: segmentTimeline } = await StreamlabsPollyTTS.synthesizeLongText(text, voice, language, options.sentenceEndPause, options.segmentEndPause);
443
+ synthesizedAudio = rawAudio;
444
+ logger.start(`Generate word-level timestamps by individually aligning segments`);
445
+ const alignmentOptions = extendDeep(options.alignment, { language });
446
+ timeline = await API.alignSegments(synthesizedAudio, segmentTimeline, alignmentOptions);
447
+ shouldPostprocessSpeed = true;
448
+ shouldPostprocessPitch = true;
449
+ break;
450
+ }
451
+ default: {
452
+ throw new Error(`Engine '${options.engine}' is not supported`);
453
+ }
454
+ }
455
+ logger.start("Postprocess synthesized audio");
456
+ synthesizedAudio = downmixToMono(synthesizedAudio);
457
+ if (options.postProcessing.normalizeAudio) {
458
+ synthesizedAudio = normalizeAudioLevel(synthesizedAudio, options.postProcessing.targetPeakDb, options.postProcessing.maxIncreaseDb);
459
+ }
460
+ const preTrimSampleCount = synthesizedAudio.audioChannels[0].length;
461
+ synthesizedAudio.audioChannels[0] = trimAudioStart(synthesizedAudio.audioChannels[0], 0, -40);
462
+ if (timeline) {
463
+ const oldDuration = preTrimSampleCount / synthesizedAudio.sampleRate;
464
+ const newDuration = synthesizedAudio.audioChannels[0].length / synthesizedAudio.sampleRate;
465
+ timeline = addTimeOffsetToTimeline(timeline, newDuration - oldDuration);
466
+ }
467
+ if (!timeline) {
468
+ logger.start("Align synthesized audio with text");
469
+ let plainText = text;
470
+ if (ssmlEnabled) {
471
+ plainText = await convertHtmlToText(text);
472
+ }
473
+ const alignmentOptions = options.alignment;
474
+ alignmentOptions.language = language;
475
+ const { wordTimeline } = await API.align(synthesizedAudio, plainText, alignmentOptions);
476
+ timeline = wordTimeline;
477
+ logger.end();
478
+ }
479
+ const postProcessingOptions = options.postProcessing;
480
+ let timeStretchFactor = postProcessingOptions.speed;
481
+ if (shouldPostprocessSpeed && timeStretchFactor == undefined) {
482
+ timeStretchFactor = speed;
483
+ }
484
+ let pitchShiftFactor = postProcessingOptions.pitch;
485
+ if (shouldPostprocessPitch && pitchShiftFactor == undefined) {
486
+ pitchShiftFactor = pitch;
487
+ }
488
+ if ((timeStretchFactor != undefined && timeStretchFactor != 1.0) || (pitchShiftFactor != undefined && pitchShiftFactor != 1.0)) {
489
+ logger.start("Apply time and pitch shifting");
490
+ timeStretchFactor = timeStretchFactor || 1.0;
491
+ pitchShiftFactor = pitchShiftFactor || 1.0;
492
+ const timePitchShiftingMethod = postProcessingOptions.timePitchShiftingMethod;
493
+ if (timePitchShiftingMethod == "sonic") {
494
+ const sonic = await import('../dsp/Sonic.js');
495
+ synthesizedAudio = await sonic.stretchTimePitch(synthesizedAudio, timeStretchFactor, pitchShiftFactor);
496
+ }
497
+ else if (timePitchShiftingMethod == "rubberband") {
498
+ const rubberband = await import('../dsp/Rubberband.js');
499
+ const rubberbandOptions = extendDeep(rubberband.defaultRubberbandOptions, postProcessingOptions.rubberband);
500
+ synthesizedAudio = await rubberband.stretchTimePitch(synthesizedAudio, timeStretchFactor, pitchShiftFactor, rubberbandOptions);
501
+ }
502
+ else {
503
+ throw new Error(`'${timePitchShiftingMethod}' is not a valid time and pitch shifting method`);
504
+ }
505
+ if (timeStretchFactor != 1.0 && timeline) {
506
+ timeline = multiplyTimelineByFactor(timeline, 1 / timeStretchFactor);
507
+ }
508
+ }
509
+ if (timeline) {
510
+ timeline = timeline.filter(entry => entry.text.trim() != "");
511
+ }
512
+ logger.end();
513
+ logger.logDuration(`Total synthesis time`, startTimestamp);
514
+ return { synthesizedAudio, timeline };
515
+ }
516
+ function convertSpeedScaleToSSMLValueString(rate) {
517
+ if (rate >= 1.0) {
518
+ const ratePercentage = Math.floor((rate - 1) * 100);
519
+ return `+${ratePercentage}%`;
520
+ }
521
+ else {
522
+ const ratePercentage = Math.floor(((1 / rate) - 1) * 100);
523
+ return `-${ratePercentage}%`;
524
+ }
525
+ }
526
+ function convertPitchScaleToSSMLValueString(pitch, voiceGender) {
527
+ let fundementalFrequency;
528
+ if (voiceGender == "male") {
529
+ // Use an estimate of the average male voice fundemental frequency
530
+ fundementalFrequency = 120;
531
+ }
532
+ else if (voiceGender == "female") {
533
+ // Use an estimate of the average female voice fundemental frequency
534
+ fundementalFrequency = 210;
535
+ }
536
+ else {
537
+ // (shouldn't occur since all voices should have a gender specified)
538
+ // Use the average of male and female voice frequency
539
+ fundementalFrequency = 165;
540
+ }
541
+ if (pitch >= 1.0) {
542
+ const pitchDeltaHertz = Math.floor(pitch * fundementalFrequency) - fundementalFrequency;
543
+ return `+${pitchDeltaHertz}Hz`;
544
+ }
545
+ else {
546
+ const pitchDeltaHertz = fundementalFrequency - Math.floor(pitch * fundementalFrequency);
547
+ return `-${pitchDeltaHertz}Hz`;
548
+ }
549
+ }
550
+ export const defaultSynthesisOptions = {
551
+ engine: undefined,
552
+ language: undefined,
553
+ voice: undefined,
554
+ voiceGender: undefined,
555
+ speed: 1.0,
556
+ pitch: 1.0,
557
+ pitchVariation: 1.0,
558
+ ssml: false,
559
+ splitToSentences: true,
560
+ segmentEndPause: 1.0,
561
+ sentenceEndPause: 0.75,
562
+ alignment: {
563
+ engine: "dtw"
564
+ },
565
+ postProcessing: {
566
+ normalizeAudio: true,
567
+ targetPeakDb: -3,
568
+ maxIncreaseDb: 30,
569
+ speed: undefined,
570
+ pitch: undefined,
571
+ timePitchShiftingMethod: "sonic",
572
+ rubberband: {}
573
+ },
574
+ vits: {
575
+ speakerId: undefined,
576
+ },
577
+ pico: {},
578
+ flite: {},
579
+ espeak: {
580
+ rate: undefined,
581
+ pitch: undefined,
582
+ pitchRange: undefined
583
+ },
584
+ sam: {
585
+ speed: undefined,
586
+ pitch: undefined,
587
+ mouth: 128,
588
+ throat: 128
589
+ },
590
+ sapi: {
591
+ rate: 0,
592
+ },
593
+ msspeech: {
594
+ rate: 0,
595
+ },
596
+ coquiServer: {
597
+ serverUrl: "http://[::1]:5002",
598
+ speakerId: null
599
+ },
600
+ googleCloud: {
601
+ apiKey: undefined,
602
+ pitchDeltaSemitones: undefined,
603
+ customVoice: {}
604
+ },
605
+ microsoftAzure: {
606
+ subscriptionKey: undefined,
607
+ serviceRegion: undefined,
608
+ pitchDeltaHz: undefined
609
+ },
610
+ awsPolly: {
611
+ region: undefined,
612
+ accessKeyId: undefined,
613
+ secretAccessKey: undefined,
614
+ pollyEngine: undefined,
615
+ lexiconNames: undefined,
616
+ },
617
+ elevenlabs: {
618
+ stability: 0,
619
+ similarityBoost: 0,
620
+ apiKey: undefined
621
+ },
622
+ googleTranslate: {
623
+ tld: "us"
624
+ },
625
+ microsoftEdge: {
626
+ trustedClientToken: undefined,
627
+ pitchDeltaHz: undefined
628
+ },
629
+ streamlabsPolly: {},
630
+ };
631
+ /////////////////////////////////////////////////////////////////////////////////////////////
632
+ // Voice list request
633
+ /////////////////////////////////////////////////////////////////////////////////////////////
634
+ export async function requestVoiceList(options) {
635
+ options = extendDeep(defaultVoiceListRequestOptions, options);
636
+ const cacheOptions = options.cache;
637
+ let cacheDir = cacheOptions?.path;
638
+ if (!cacheDir) {
639
+ const appDataDir = getAppDataDir(appName);
640
+ cacheDir = path.join(appDataDir, 'voice-list-cache');
641
+ await ensureDir(cacheDir);
642
+ }
643
+ const cacheFilePath = path.join(cacheDir, `${options.engine}.voices.json`);
644
+ async function loadVoiceList() {
645
+ let voiceList = [];
646
+ switch (options.engine) {
647
+ case "espeak": {
648
+ const EspeakTTS = await import("../synthesis/EspeakTTS.js");
649
+ const voices = await EspeakTTS.listVoices();
650
+ voiceList = voices.map(voice => {
651
+ const languages = voice.languages.map(lang => normalizeLanguageCode(lang.name));
652
+ for (const language of languages) {
653
+ const shortLanguageCode = getShortLanguageCode(language);
654
+ if (!languages.includes(shortLanguageCode)) {
655
+ languages.push(shortLanguageCode);
656
+ }
657
+ }
658
+ return {
659
+ name: voice.identifier,
660
+ languages,
661
+ gender: "male"
662
+ };
663
+ });
664
+ break;
665
+ }
666
+ case "flite": {
667
+ const FliteTTS = await import("../synthesis/FliteTTS.js");
668
+ voiceList = deepClone(FliteTTS.voices);
669
+ break;
670
+ }
671
+ case "pico": {
672
+ const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js");
673
+ voiceList = SvoxPicoTTS.voiceList;
674
+ break;
675
+ }
676
+ case "sam": {
677
+ voiceList.push({
678
+ name: "sam",
679
+ languages: ["en-US", "en"],
680
+ gender: "male"
681
+ });
682
+ break;
683
+ }
684
+ case "vits": {
685
+ const VitsTTS = await import("../synthesis/VitsTTS.js");
686
+ voiceList = VitsTTS.voiceList.map(entry => {
687
+ return { ...entry, packageName: `vits-${entry.name}` };
688
+ });
689
+ break;
690
+ }
691
+ case "sapi": {
692
+ const SapiTTS = await import("../synthesis/SapiTTS.js");
693
+ await SapiTTS.AssertSAPIAvailable(false);
694
+ voiceList = await SapiTTS.getVoiceList(false);
695
+ break;
696
+ }
697
+ case "msspeech": {
698
+ const SapiTTS = await import("../synthesis/SapiTTS.js");
699
+ await SapiTTS.AssertSAPIAvailable(true);
700
+ voiceList = await SapiTTS.getVoiceList(true);
701
+ break;
702
+ }
703
+ case "coqui-server": {
704
+ voiceList = [{
705
+ name: "coqui",
706
+ languages: ["en-US"],
707
+ gender: "unknown"
708
+ }];
709
+ break;
710
+ }
711
+ case "google-cloud": {
712
+ const GoogleCloudTTS = await import("../synthesis/GoogleCloudTTS.js");
713
+ const apiKey = options.googleCloud.apiKey;
714
+ if (!apiKey) {
715
+ throw new Error(`No API key given`);
716
+ }
717
+ const voices = await GoogleCloudTTS.getVoiceList(apiKey);
718
+ voiceList = voices.map(voice => ({
719
+ name: voice.name,
720
+ languages: [normalizeLanguageCode(voice.languageCodes[0]), getShortLanguageCode(voice.languageCodes[0])],
721
+ gender: voice.ssmlGender.toLowerCase(),
722
+ }));
723
+ break;
724
+ }
725
+ case "microsoft-azure": {
726
+ const AzureCognitiveServicesTTS = await import("../synthesis/AzureCognitiveServicesTTS.js");
727
+ const subscriptionKey = options.microsoftAzure.subscriptionKey;
728
+ if (!subscriptionKey) {
729
+ throw new Error(`No subscription key given`);
730
+ }
731
+ const serviceRegion = options.microsoftAzure.serviceRegion;
732
+ if (!serviceRegion) {
733
+ throw new Error(`No service region given`);
734
+ }
735
+ const voices = await AzureCognitiveServicesTTS.getVoiceList(subscriptionKey, serviceRegion);
736
+ for (const voice of voices) {
737
+ voiceList.push({
738
+ name: voice.name,
739
+ languages: [normalizeLanguageCode(voice.locale), getShortLanguageCode(voice.locale)],
740
+ gender: voice.gender == 1 ? "female" : "male"
741
+ });
742
+ }
743
+ break;
744
+ }
745
+ case "amazon-polly": {
746
+ const AwsPollyTTS = await import("../synthesis/AwsPollyTTS.js");
747
+ const region = options.awsPolly.region;
748
+ if (!region) {
749
+ throw new Error(`No region given`);
750
+ }
751
+ const accessKeyId = options.awsPolly.accessKeyId;
752
+ if (!accessKeyId) {
753
+ throw new Error(`No access key id given`);
754
+ }
755
+ const secretAccessKey = options.awsPolly.secretAccessKey;
756
+ if (!secretAccessKey) {
757
+ throw new Error(`No secret access key given`);
758
+ }
759
+ const voices = await AwsPollyTTS.getVoiceList(region, accessKeyId, secretAccessKey);
760
+ for (const voice of voices) {
761
+ const languageCode = normalizeLanguageCode(voice.LanguageCode);
762
+ const languageCodes = [languageCode, getShortLanguageCode(languageCode)];
763
+ if (voice.AdditionalLanguageCodes) {
764
+ for (const additionalLanguageCode of voice.AdditionalLanguageCodes) {
765
+ languageCodes.push(normalizeLanguageCode(additionalLanguageCode), getShortLanguageCode(additionalLanguageCode));
766
+ }
767
+ }
768
+ voiceList.push({
769
+ name: voice.Id,
770
+ languages: languageCodes,
771
+ gender: voice.Gender.toLowerCase()
772
+ });
773
+ }
774
+ break;
775
+ }
776
+ case "elevenlabs": {
777
+ const ElevenLabsTTS = await import("../synthesis/ElevenLabsTTS.js");
778
+ voiceList = await ElevenLabsTTS.getVoiceList();
779
+ break;
780
+ }
781
+ case "google-translate": {
782
+ const GoogleTranslateTTS = await import("../synthesis/GoogleTranslateTTS.js");
783
+ const langLookup = GoogleTranslateTTS.supportedLanguageLookup;
784
+ for (const langCode in langLookup) {
785
+ voiceList.push({
786
+ name: langLookup[langCode],
787
+ languages: langCode.includes("-") ? [normalizeLanguageCode(langCode), getShortLanguageCode(langCode)] : [normalizeLanguageCode(langCode)],
788
+ gender: "unknown"
789
+ });
790
+ }
791
+ break;
792
+ }
793
+ case "microsoft-edge": {
794
+ const MicrosoftEdgeTTS = await import("../synthesis/MicrosoftEdgeTTS.js");
795
+ const trustedClientToken = options.microsoftEdge?.trustedClientToken;
796
+ if (!trustedClientToken) {
797
+ throw new Error("No trusted client token provided");
798
+ }
799
+ const voices = await MicrosoftEdgeTTS.getVoiceList(trustedClientToken);
800
+ voiceList = voices.map((voice) => ({
801
+ name: voice.Name,
802
+ languages: [normalizeLanguageCode(voice.Locale), getShortLanguageCode(voice.Locale)],
803
+ gender: voice.Gender == "Male" ? "male" : "female",
804
+ }));
805
+ break;
806
+ }
807
+ case "streamlabs-polly": {
808
+ const StreamlabsPollyTTS = await import("../synthesis/StreamlabsPollyTTS.js");
809
+ voiceList = StreamlabsPollyTTS.voiceList;
810
+ break;
811
+ }
812
+ }
813
+ if (cacheFilePath) {
814
+ await writeFileSafe(cacheFilePath, stringifyAndFormatJson(voiceList));
815
+ }
816
+ return voiceList;
817
+ }
818
+ let voiceList;
819
+ if (cacheFilePath && existsSync(cacheFilePath) && await isFileIsUpToDate(cacheFilePath, options.cache.duration)) {
820
+ voiceList = await readAndParseJsonFile(cacheFilePath);
821
+ }
822
+ else {
823
+ voiceList = await loadVoiceList();
824
+ }
825
+ const languageCode = normalizeLanguageCode(options.language || "");
826
+ if (languageCode) {
827
+ let filteredVoiceList = voiceList.filter(voice => voice.languages.includes(languageCode));
828
+ if (filteredVoiceList.length == 0 && languageCode.includes("-")) {
829
+ const shortLanguageCode = getShortLanguageCode(languageCode);
830
+ filteredVoiceList = voiceList.filter(voice => voice.languages.includes(shortLanguageCode));
831
+ }
832
+ voiceList = filteredVoiceList;
833
+ }
834
+ if (options.voiceGender) {
835
+ const genderLowercase = options.voiceGender.toLowerCase();
836
+ voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender == "unknown");
837
+ }
838
+ if (options.voice) {
839
+ const namePatternLowerCase = options.voice.toLocaleLowerCase();
840
+ const namePatternParts = namePatternLowerCase.split(/\b/g);
841
+ if (namePatternParts.length > 1) {
842
+ voiceList = voiceList.filter(voice => voice.name.toLocaleLowerCase().includes(namePatternLowerCase));
843
+ }
844
+ else {
845
+ voiceList = voiceList.filter(voice => {
846
+ const name = voice.name.toLocaleLowerCase();
847
+ const nameParts = name.split(/\b/g);
848
+ for (const namePart of nameParts) {
849
+ if (namePart.startsWith(namePatternLowerCase)) {
850
+ return true;
851
+ }
852
+ }
853
+ return false;
854
+ });
855
+ }
856
+ }
857
+ let bestMatchingVoice = voiceList[0];
858
+ if (bestMatchingVoice && voiceList.length > 1 && shortLanguageCodeToLong[languageCode]) {
859
+ const expandedLanguageCode = shortLanguageCodeToLong[languageCode];
860
+ for (const voice of voiceList) {
861
+ if (voice.languages.includes(expandedLanguageCode)) {
862
+ bestMatchingVoice = voice;
863
+ break;
864
+ }
865
+ }
866
+ }
867
+ return { voiceList, bestMatchingVoice };
868
+ }
869
+ export function selectBestOfflineEngineForLanguage(language) {
870
+ language = normalizeLanguageCode(language);
871
+ const vitsLanguages = ["ca", "da", "de", "en", "en-US", "en-GB", "es", "fi", "fr", "it", "kk", "ne", "nl", "no", "pl", "pt-BR", "uk", "vi", "zh", "zh-CN"];
872
+ if (vitsLanguages.includes(language)) {
873
+ return "vits";
874
+ }
875
+ const fliteLanguages = ["en-US", "be", "bn", "gu", "hi", /*"ka"*/ , "mr", "pa", "te"];
876
+ if (fliteLanguages.includes(language)) {
877
+ return "flite";
878
+ }
879
+ const picoLanguages = ["en-US", "en-GB", "de", "es", "fr", "it"];
880
+ if (picoLanguages.includes(language)) {
881
+ return "pico";
882
+ }
883
+ return "espeak";
884
+ }
885
+ export const defaultVoiceListRequestOptions = {
886
+ ...defaultSynthesisOptions,
887
+ cache: {
888
+ path: undefined,
889
+ duration: 60 * 60
890
+ },
891
+ };
892
+ //# sourceMappingURL=Synthesis.js.map