echogarden 1.8.7 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/README.md +23 -18
  2. package/data/schemas/options.json +52 -47
  3. package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
  4. package/dist/alignment/DTWSequenceAlignment.js +5 -5
  5. package/dist/alignment/DTWSequenceAlignment.js.map +1 -1
  6. package/dist/alignment/SpeechAlignment.js +6 -6
  7. package/dist/alignment/SpeechAlignment.js.map +1 -1
  8. package/dist/api/Alignment.d.ts +1 -1
  9. package/dist/api/Alignment.js +49 -11
  10. package/dist/api/Alignment.js.map +1 -1
  11. package/dist/api/Denoising.d.ts +4 -2
  12. package/dist/api/Denoising.js +65 -13
  13. package/dist/api/Denoising.js.map +1 -1
  14. package/dist/api/SourceSeparation.js +9 -6
  15. package/dist/api/SourceSeparation.js.map +1 -1
  16. package/dist/api/SpeechLanguageDetection.js +4 -4
  17. package/dist/api/SpeechLanguageDetection.js.map +1 -1
  18. package/dist/api/Synthesis.d.ts +2 -2
  19. package/dist/api/Synthesis.js +10 -7
  20. package/dist/api/Synthesis.js.map +1 -1
  21. package/dist/api/VoiceActivityDetection.js +2 -2
  22. package/dist/api/VoiceActivityDetection.js.map +1 -1
  23. package/dist/audio/AudioBufferConversion.d.ts +2 -2
  24. package/dist/audio/AudioBufferConversion.js +77 -43
  25. package/dist/audio/AudioBufferConversion.js.map +1 -1
  26. package/dist/audio/AudioPlayer.d.ts +7 -5
  27. package/dist/audio/AudioPlayer.js +135 -49
  28. package/dist/audio/AudioPlayer.js.map +1 -1
  29. package/dist/audio/AudioUtilities.d.ts +4 -3
  30. package/dist/audio/AudioUtilities.js +30 -15
  31. package/dist/audio/AudioUtilities.js.map +1 -1
  32. package/dist/audio/SoxPath.js +3 -3
  33. package/dist/audio/SoxPath.js.map +1 -1
  34. package/dist/build-tools/MakeTarballsForInstalledPackages.js +3 -3
  35. package/dist/build-tools/MakeTarballsForInstalledPackages.js.map +1 -1
  36. package/dist/cli/CLI.js +30 -36
  37. package/dist/cli/CLI.js.map +1 -1
  38. package/dist/cli/CLIConfigFile.js +2 -2
  39. package/dist/cli/CLIConfigFile.js.map +1 -1
  40. package/dist/cli/CLIOptions.d.ts +2 -0
  41. package/dist/cli/CLIOptions.js +1 -1
  42. package/dist/cli/CLIOptions.js.map +1 -1
  43. package/dist/codecs/FFMpegTranscoder.d.ts +3 -3
  44. package/dist/codecs/FFMpegTranscoder.js +6 -6
  45. package/dist/codecs/FFMpegTranscoder.js.map +1 -1
  46. package/dist/codecs/TIMITCodec.d.ts +1 -1
  47. package/dist/codecs/TIMITCodec.js +4 -3
  48. package/dist/codecs/TIMITCodec.js.map +1 -1
  49. package/dist/codecs/WaveCodec.d.ts +3 -3
  50. package/dist/codecs/WaveCodec.js +37 -34
  51. package/dist/codecs/WaveCodec.js.map +1 -1
  52. package/dist/data-structures/DynamicTypedArray.d.ts +17 -0
  53. package/dist/data-structures/DynamicTypedArray.js +47 -0
  54. package/dist/data-structures/DynamicTypedArray.js.map +1 -0
  55. package/dist/data-structures/Queue.js.map +1 -0
  56. package/dist/data-structures/WindowedList.js.map +1 -0
  57. package/dist/denoising/NSNet2.d.ts +26 -0
  58. package/dist/denoising/NSNet2.js +128 -0
  59. package/dist/denoising/NSNet2.js.map +1 -0
  60. package/dist/denoising/RNNoise.js +3 -3
  61. package/dist/denoising/RNNoise.js.map +1 -1
  62. package/dist/dsp/FFT.d.ts +6 -1
  63. package/dist/dsp/FFT.js +17 -0
  64. package/dist/dsp/FFT.js.map +1 -1
  65. package/dist/encodings/Ascii.d.ts +10 -0
  66. package/dist/encodings/Ascii.js +41 -0
  67. package/dist/encodings/Ascii.js.map +1 -0
  68. package/dist/encodings/Base64.d.ts +5 -0
  69. package/dist/encodings/Base64.js +114 -0
  70. package/dist/encodings/Base64.js.map +1 -0
  71. package/dist/encodings/Hex.d.ts +3 -0
  72. package/dist/encodings/Hex.js +52 -0
  73. package/dist/encodings/Hex.js.map +1 -0
  74. package/dist/encodings/HtmlEscape.d.ts +1 -0
  75. package/dist/encodings/HtmlEscape.js +30 -0
  76. package/dist/encodings/HtmlEscape.js.map +1 -0
  77. package/dist/{utilities → encodings}/LEB128.d.ts +1 -1
  78. package/dist/{utilities → encodings}/LEB128.js +4 -4
  79. package/dist/encodings/LEB128.js.map +1 -0
  80. package/dist/{utilities → encodings}/LPVarInt.d.ts +1 -1
  81. package/dist/{utilities → encodings}/LPVarInt.js +6 -6
  82. package/dist/encodings/LPVarInt.js.map +1 -0
  83. package/dist/encodings/TextEncodingsCommon.d.ts +4 -0
  84. package/dist/encodings/TextEncodingsCommon.js +2 -0
  85. package/dist/encodings/TextEncodingsCommon.js.map +1 -0
  86. package/dist/encodings/Utf16.d.ts +10 -0
  87. package/dist/encodings/Utf16.js +36 -0
  88. package/dist/encodings/Utf16.js.map +1 -0
  89. package/dist/encodings/Utf32.d.ts +4 -0
  90. package/dist/encodings/Utf32.js +32 -0
  91. package/dist/encodings/Utf32.js.map +1 -0
  92. package/dist/encodings/Utf8.d.ts +10 -0
  93. package/dist/encodings/Utf8.js +97 -0
  94. package/dist/encodings/Utf8.js.map +1 -0
  95. package/dist/math/MedianFilter.d.ts +0 -1
  96. package/dist/math/MedianFilter.js +0 -10
  97. package/dist/math/MedianFilter.js.map +1 -1
  98. package/dist/nlp/JapaneseSegmentation.js +2 -2
  99. package/dist/nlp/JapaneseSegmentation.js.map +1 -1
  100. package/dist/recognition/GoogleCloudSTT.js +2 -1
  101. package/dist/recognition/GoogleCloudSTT.js.map +1 -1
  102. package/dist/recognition/SileroSTT.js +3 -3
  103. package/dist/recognition/SileroSTT.js.map +1 -1
  104. package/dist/recognition/VoskSTT.js +3 -3
  105. package/dist/recognition/VoskSTT.js.map +1 -1
  106. package/dist/recognition/WhisperCppSTT.js +4 -5
  107. package/dist/recognition/WhisperCppSTT.js.map +1 -1
  108. package/dist/recognition/WhisperSTT.js +8 -7
  109. package/dist/recognition/WhisperSTT.js.map +1 -1
  110. package/dist/server/Client.d.ts +1 -1
  111. package/dist/server/Client.js +2 -1
  112. package/dist/server/Client.js.map +1 -1
  113. package/dist/server/Server.js +5 -4
  114. package/dist/server/Server.js.map +1 -1
  115. package/dist/server/Worker.js +1 -1
  116. package/dist/server/Worker.js.map +1 -1
  117. package/dist/source-separation/MDXNetSourceSeparation.js +1 -1
  118. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
  119. package/dist/speech-search/DTWSpeechSearch.d.ts +1 -1
  120. package/dist/speech-search/DTWSpeechSearch.js +25 -6
  121. package/dist/speech-search/DTWSpeechSearch.js.map +1 -1
  122. package/dist/subtitles/Subtitles.js +2 -2
  123. package/dist/subtitles/Subtitles.js.map +1 -1
  124. package/dist/synthesis/AzureCognitiveServicesTTS.js +19 -20
  125. package/dist/synthesis/AzureCognitiveServicesTTS.js.map +1 -1
  126. package/dist/synthesis/CoquiServerTTS.js +1 -1
  127. package/dist/synthesis/CoquiServerTTS.js.map +1 -1
  128. package/dist/synthesis/ElevenlabsTTS.js +1 -1
  129. package/dist/synthesis/ElevenlabsTTS.js.map +1 -1
  130. package/dist/synthesis/EspeakTTS.d.ts +0 -1
  131. package/dist/synthesis/EspeakTTS.js +2 -11
  132. package/dist/synthesis/EspeakTTS.js.map +1 -1
  133. package/dist/synthesis/FliteTTS.js +8 -9
  134. package/dist/synthesis/FliteTTS.js.map +1 -1
  135. package/dist/synthesis/GoogleCloudTTS.d.ts +1 -1
  136. package/dist/synthesis/GoogleCloudTTS.js +2 -1
  137. package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
  138. package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
  139. package/dist/synthesis/GoogleTranslateTTS.js +4 -3
  140. package/dist/synthesis/GoogleTranslateTTS.js.map +1 -1
  141. package/dist/synthesis/MicrosoftEdgeTTS.js +34 -9
  142. package/dist/synthesis/MicrosoftEdgeTTS.js.map +1 -1
  143. package/dist/synthesis/SapiTTS.js +1 -1
  144. package/dist/synthesis/SapiTTS.js.map +1 -1
  145. package/dist/synthesis/StreamlabsPollyTTS.d.ts +1 -1
  146. package/dist/synthesis/StreamlabsPollyTTS.js +1 -1
  147. package/dist/synthesis/StreamlabsPollyTTS.js.map +1 -1
  148. package/dist/synthesis/SvoxPicoTTS.js +6 -6
  149. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  150. package/dist/synthesis/VitsTTS.js +2 -2
  151. package/dist/synthesis/VitsTTS.js.map +1 -1
  152. package/dist/tests/Test.js +1 -47
  153. package/dist/tests/Test.js.map +1 -1
  154. package/dist/text-language-detection/FastTextLanguageDetection.js +2 -2
  155. package/dist/text-language-detection/FastTextLanguageDetection.js.map +1 -1
  156. package/dist/text-translation/GoogleTranslateTextTranslation.js +1 -1
  157. package/dist/text-translation/GoogleTranslateTextTranslation.js.map +1 -1
  158. package/dist/typings/TypedArray.d.ts +4 -0
  159. package/dist/typings/TypedArray.js +2 -0
  160. package/dist/typings/TypedArray.js.map +1 -0
  161. package/dist/utilities/BinaryArrayConversion.d.ts +12 -22
  162. package/dist/utilities/BinaryArrayConversion.js +40 -90
  163. package/dist/utilities/BinaryArrayConversion.js.map +1 -1
  164. package/dist/utilities/BinaryUtilities.d.ts +13 -0
  165. package/dist/utilities/BinaryUtilities.js +113 -0
  166. package/dist/utilities/BinaryUtilities.js.map +1 -0
  167. package/dist/utilities/Compression.js +7 -6
  168. package/dist/utilities/Compression.js.map +1 -1
  169. package/dist/utilities/FileDownloader.js +26 -47
  170. package/dist/utilities/FileDownloader.js.map +1 -1
  171. package/dist/utilities/FileReader.d.ts +14 -0
  172. package/dist/utilities/FileReader.js +71 -0
  173. package/dist/utilities/FileReader.js.map +1 -0
  174. package/dist/utilities/FileSystem.d.ts +19 -16
  175. package/dist/utilities/FileSystem.js +170 -109
  176. package/dist/utilities/FileSystem.js.map +1 -1
  177. package/dist/utilities/FileWriter.d.ts +12 -0
  178. package/dist/utilities/FileWriter.js +60 -0
  179. package/dist/utilities/FileWriter.js.map +1 -0
  180. package/dist/utilities/NpmUtilities.d.ts +1 -0
  181. package/dist/utilities/NpmUtilities.js +14 -0
  182. package/dist/utilities/NpmUtilities.js.map +1 -0
  183. package/dist/utilities/PackageManager.js +7 -5
  184. package/dist/utilities/PackageManager.js.map +1 -1
  185. package/dist/utilities/PathUtilities.d.ts +8 -0
  186. package/dist/utilities/PathUtilities.js +28 -7
  187. package/dist/utilities/PathUtilities.js.map +1 -1
  188. package/dist/utilities/SignalChannel.d.ts +3 -1
  189. package/dist/utilities/SignalChannel.js +9 -5
  190. package/dist/utilities/SignalChannel.js.map +1 -1
  191. package/dist/utilities/StringBuilder.d.ts +10 -0
  192. package/dist/utilities/StringBuilder.js +39 -0
  193. package/dist/utilities/StringBuilder.js.map +1 -0
  194. package/dist/utilities/StringUtilities.js.map +1 -1
  195. package/dist/utilities/TarballMaker.js +6 -7
  196. package/dist/utilities/TarballMaker.js.map +1 -1
  197. package/dist/utilities/Timer.js +2 -2
  198. package/dist/utilities/Timer.js.map +1 -1
  199. package/dist/utilities/Utilities.d.ts +11 -5
  200. package/dist/utilities/Utilities.js +69 -33
  201. package/dist/utilities/Utilities.js.map +1 -1
  202. package/dist/utilities/VirtualFileReadStream.d.ts +3 -3
  203. package/dist/utilities/VirtualFileReadStream.js.map +1 -1
  204. package/dist/utilities/WasmMemoryManager.d.ts +5 -2
  205. package/dist/utilities/WasmMemoryManager.js +15 -4
  206. package/dist/utilities/WasmMemoryManager.js.map +1 -1
  207. package/docs/API.md +1 -1
  208. package/docs/CLI.md +1 -1
  209. package/docs/Licenses.md +3 -0
  210. package/docs/Options.md +9 -3
  211. package/docs/Tasklist.md +0 -1
  212. package/docs/Technical.md +2 -2
  213. package/package.json +22 -25
  214. package/src/alignment/DTWSequenceAlignment.ts +5 -5
  215. package/src/alignment/SpeechAlignment.ts +6 -6
  216. package/src/api/Alignment.ts +65 -12
  217. package/src/api/Denoising.ts +97 -17
  218. package/src/api/SourceSeparation.ts +11 -8
  219. package/src/api/SpeechLanguageDetection.ts +4 -4
  220. package/src/api/Synthesis.ts +15 -11
  221. package/src/api/VoiceActivityDetection.ts +2 -2
  222. package/src/audio/AudioBufferConversion.ts +84 -45
  223. package/src/audio/AudioPlayer.ts +155 -53
  224. package/src/audio/AudioUtilities.ts +38 -22
  225. package/src/audio/SoxPath.ts +3 -3
  226. package/src/build-tools/MakeTarballsForInstalledPackages.ts +3 -3
  227. package/src/cli/CLI.ts +30 -36
  228. package/src/cli/CLIConfigFile.ts +2 -2
  229. package/src/cli/CLIOptions.ts +4 -1
  230. package/src/codecs/FFMpegTranscoder.ts +11 -11
  231. package/src/codecs/TIMITCodec.ts +5 -4
  232. package/src/codecs/WaveCodec.ts +42 -39
  233. package/src/data-structures/DynamicTypedArray.ts +64 -0
  234. package/src/denoising/NSNet2.ts +182 -0
  235. package/src/denoising/RNNoise.ts +3 -3
  236. package/src/dsp/FFT.ts +21 -1
  237. package/src/encodings/Ascii.ts +60 -0
  238. package/src/encodings/Base64.ts +157 -0
  239. package/src/encodings/Hex.ts +67 -0
  240. package/src/encodings/HtmlEscape.ts +39 -0
  241. package/src/{utilities → encodings}/LEB128.ts +4 -4
  242. package/src/{utilities → encodings}/LPVarInt.ts +6 -6
  243. package/src/encodings/TextEncodingsCommon.ts +4 -0
  244. package/src/encodings/Utf16.ts +52 -0
  245. package/src/encodings/Utf32.ts +44 -0
  246. package/src/encodings/Utf8.ts +117 -0
  247. package/src/math/MedianFilter.ts +0 -16
  248. package/src/nlp/JapaneseSegmentation.ts +2 -2
  249. package/src/recognition/GoogleCloudSTT.ts +2 -1
  250. package/src/recognition/SileroSTT.ts +3 -3
  251. package/src/recognition/VoskSTT.ts +3 -3
  252. package/src/recognition/WhisperCppSTT.ts +4 -5
  253. package/src/recognition/WhisperSTT.ts +8 -7
  254. package/src/server/Client.ts +4 -3
  255. package/src/server/Server.ts +6 -5
  256. package/src/server/Worker.ts +1 -1
  257. package/src/source-separation/MDXNetSourceSeparation.ts +1 -1
  258. package/src/speech-search/DTWSpeechSearch.ts +36 -7
  259. package/src/subtitles/Subtitles.ts +3 -3
  260. package/src/synthesis/AzureCognitiveServicesTTS.ts +20 -18
  261. package/src/synthesis/CoquiServerTTS.ts +2 -2
  262. package/src/synthesis/ElevenlabsTTS.ts +1 -1
  263. package/src/synthesis/EspeakTTS.ts +2 -18
  264. package/src/synthesis/FliteTTS.ts +8 -10
  265. package/src/synthesis/GoogleCloudTTS.ts +3 -2
  266. package/src/synthesis/GoogleTranslateTTS.ts +5 -4
  267. package/src/synthesis/MicrosoftEdgeTTS.ts +49 -18
  268. package/src/synthesis/SapiTTS.ts +1 -1
  269. package/src/synthesis/StreamlabsPollyTTS.ts +1 -1
  270. package/src/synthesis/SvoxPicoTTS.ts +8 -8
  271. package/src/synthesis/VitsTTS.ts +2 -2
  272. package/src/tests/Test.ts +1 -60
  273. package/src/text-language-detection/FastTextLanguageDetection.ts +2 -2
  274. package/src/text-translation/GoogleTranslateTextTranslation.ts +1 -1
  275. package/src/typings/Fillers.d.ts +2 -10
  276. package/src/typings/TypedArray.ts +5 -0
  277. package/src/utilities/BinaryArrayConversion.ts +51 -125
  278. package/src/utilities/BinaryUtilities.ts +138 -0
  279. package/src/utilities/Compression.ts +7 -6
  280. package/src/utilities/FileDownloader.ts +27 -52
  281. package/src/utilities/FileReader.ts +89 -0
  282. package/src/utilities/FileSystem.ts +213 -120
  283. package/src/utilities/FileWriter.ts +72 -0
  284. package/src/utilities/NpmUtilities.ts +23 -0
  285. package/src/utilities/PackageManager.ts +8 -5
  286. package/src/utilities/PathUtilities.ts +36 -7
  287. package/src/utilities/SignalChannel.ts +14 -7
  288. package/src/utilities/StringBuilder.ts +45 -0
  289. package/src/utilities/StringUtilities.ts +2 -0
  290. package/src/utilities/TarballMaker.ts +6 -7
  291. package/src/utilities/Timer.ts +4 -2
  292. package/src/utilities/Utilities.ts +80 -45
  293. package/src/utilities/VirtualFileReadStream.ts +5 -5
  294. package/src/utilities/WasmMemoryManager.ts +20 -6
  295. package/tsconfig.json +96 -0
  296. package/dist/utilities/DynamicUint8Array.d.ts +0 -9
  297. package/dist/utilities/DynamicUint8Array.js +0 -31
  298. package/dist/utilities/DynamicUint8Array.js.map +0 -1
  299. package/dist/utilities/LEB128.js.map +0 -1
  300. package/dist/utilities/LPVarInt.js.map +0 -1
  301. package/dist/utilities/Queue.js.map +0 -1
  302. package/dist/utilities/WindowedList.js.map +0 -1
  303. package/src/utilities/DynamicUint8Array.ts +0 -39
  304. /package/dist/{utilities → data-structures}/Queue.d.ts +0 -0
  305. /package/dist/{utilities → data-structures}/Queue.js +0 -0
  306. /package/dist/{utilities → data-structures}/WindowedList.d.ts +0 -0
  307. /package/dist/{utilities → data-structures}/WindowedList.js +0 -0
  308. /package/src/{utilities → data-structures}/Queue.ts +0 -0
  309. /package/src/{utilities → data-structures}/WindowedList.ts +0 -0
@@ -2,37 +2,36 @@ import { parentPort } from 'node:worker_threads'
2
2
 
3
3
  import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
4
4
 
5
- import { RawAudio, encodeRawAudioToWave, fadeAudioInOut, getRawAudioDuration, sliceRawAudioByTime } from './AudioUtilities.js'
6
- import * as AudioBufferConversion from './AudioBufferConversion.js'
5
+ import { RawAudio, encodeRawAudioToWave, fadeAudioInOut, getRawAudioDuration, sliceAudioChannels, sliceRawAudioByTime } from './AudioUtilities.js'
7
6
  import * as FFMpegTranscoder from '../codecs/FFMpegTranscoder.js'
8
7
 
9
8
  import { Timer } from '../utilities/Timer.js'
10
- import { getRandomHexString, waitTimeout, writeToStderr } from '../utilities/Utilities.js'
11
- import { encodeToAudioBuffer } from './AudioBufferConversion.js'
9
+ import { clip, getRandomHexString, waitTimeout, writeToStderr } from '../utilities/Utilities.js'
10
+ import { encodeToAudioBuffer, float32ToInt16Pcm, interleaveChannels } from './AudioBufferConversion.js'
12
11
  import { OpenPromise } from '../utilities/OpenPromise.js'
13
12
  import { Timeline, addWordTextOffsetsToTimeline } from '../utilities/Timeline.js'
14
- import { readAndParseJsonFile, readFile, remove, writeFile } from '../utilities/FileSystem.js'
13
+ import { readAndParseJsonFile, readFileAsUtf8, remove, writeFile } from '../utilities/FileSystem.js'
15
14
  import { tryResolvingSoxPath } from './SoxPath.js'
16
15
  import { SignalChannel } from '../utilities/SignalChannel.js'
17
16
  import { deepClone } from '../utilities/ObjectUtilities.js'
18
17
  import { appName } from '../api/Common.js'
19
- import path from 'node:path'
20
- import { getAppTempDir } from '../utilities/PathUtilities.js'
18
+ import { getAppTempDir, joinPath } from '../utilities/PathUtilities.js'
19
+ import { type AudioOutput } from '@echogarden/audio-io'
21
20
 
22
- export async function playAudioFileWithTimelineFile(audioFilename: string, timelineFileName: string, transcriptFileName?: string) {
21
+ export async function playAudioFileWithTimelineFile(audioFilename: string, timelineFileName: string, transcriptFileName?: string, player?: AudioPlayerID) {
23
22
  const rawAudio = await FFMpegTranscoder.decodeToChannels(audioFilename, 48000, 1)
24
23
 
25
24
  const timeline = await readAndParseJsonFile(timelineFileName)
26
25
 
27
26
  let transcript: string | undefined
28
27
  if (transcriptFileName) {
29
- transcript = await readFile(transcriptFileName, 'utf8')
28
+ transcript = await readFileAsUtf8(transcriptFileName)
30
29
  }
31
30
 
32
- await playAudioWithWordTimeline(rawAudio, timeline, transcript)
31
+ await playAudioWithWordTimeline(rawAudio, timeline, transcript, player)
33
32
  }
34
33
 
35
- export async function playAudioWithWordTimeline(rawAudio: RawAudio, wordTimeline: Timeline, transcript?: string) {
34
+ export async function playAudioWithWordTimeline(rawAudio: RawAudio, wordTimeline: Timeline, transcript?: string, player?: AudioPlayerID) {
36
35
  if (!transcript) {
37
36
  transcript = wordTimeline.map(entry => entry.text).join(' ')
38
37
  }
@@ -82,77 +81,178 @@ export async function playAudioWithWordTimeline(rawAudio: RawAudio, wordTimeline
82
81
  const keypressListenerStartTimestamp = Date.now()
83
82
 
84
83
  function keypressHandler(message: any) {
85
- if (message.name == 'keypress' &&
86
- message.key.name == 'return' &&
87
- message.timestamp >= keypressListenerStartTimestamp) {
84
+ if (message.name === 'keypress') {
85
+ //console.log(`Keypress: ${message.key.name}`)
88
86
 
89
- signalChannel.send('abort')
87
+ if (message.timestamp < keypressListenerStartTimestamp) {
88
+ return
89
+ }
90
+
91
+ const keyName = message.key.name
92
+
93
+ if (keyName === 'return') {
94
+ signalChannel.send('abort')
95
+ } else if (keyName === 'left') {
96
+ signalChannel.send('skip', -1)
97
+ } else if (keyName === 'right') {
98
+ signalChannel.send('skip', 1)
99
+
100
+ if (false) {
101
+ const nextTimelineEntryIndex = timelineEntryIndex
102
+
103
+ if (nextTimelineEntryIndex < wordTimeline.length) {
104
+ //signalChannel.send('skip', 5)
105
+ const targetTime = wordTimeline[nextTimelineEntryIndex].startTime
106
+
107
+ signalChannel.send('seek', targetTime)
108
+ }
109
+ }
110
+ } else if (keyName === 'space') {
111
+ signalChannel.send('togglePause')
112
+ }
90
113
  }
91
114
  }
92
115
 
93
116
  parentPort?.on('message', keypressHandler)
94
117
 
95
- await playAudioSamples(rawAudio, onTimePosition, signalChannel)
118
+ await playAudioSamples(rawAudio, onTimePosition, signalChannel, player)
96
119
 
97
120
  parentPort?.off('message', keypressHandler)
98
121
 
99
122
  writeToStderr('\n')
100
123
  }
101
124
 
102
- export async function playAudioWithTimelinePhones(rawAudio: RawAudio, timeline: Timeline) {
103
- let wordIndex = 0
104
- let phoneIndex = 0
125
+ export async function playAudioSamplesWithKeyboardControls(rawAudio: RawAudio, player?: AudioPlayerID) {
126
+ const signalChannel = new SignalChannel()
105
127
 
106
- function onTimePosition(timePosition: number) {
107
- for (; wordIndex < timeline.length; wordIndex++) {
108
- const wordEntry = timeline[wordIndex]
109
- const phoneTimeline = wordEntry.timeline!
128
+ const keypressListenerStartTimestamp = Date.now()
110
129
 
111
- if (phoneTimeline.every(phoneEntry => phoneEntry.text == '')) {
112
- continue
113
- }
130
+ function keypressHandler(message: any) {
131
+ if (message.name === 'keypress') {
132
+ //console.log(`Keypress: ${message.key.name}`)
114
133
 
115
- for (; phoneIndex < phoneTimeline.length; phoneIndex++) {
116
- const entry = phoneTimeline[phoneIndex]
134
+ if (message.timestamp < keypressListenerStartTimestamp) {
135
+ return
136
+ }
117
137
 
118
- if (entry.startTime > timePosition) {
119
- return
120
- }
138
+ const keyName = message.key.name
121
139
 
122
- writeToStderr(`${entry.text} `)
140
+ if (keyName === 'return') {
141
+ signalChannel.send('abort')
142
+ } else if (keyName === 'left') {
143
+ signalChannel.send('skip', -1)
144
+ } else if (keyName === 'right') {
145
+ signalChannel.send('skip', 1)
146
+ } else if (keyName === 'space') {
147
+ signalChannel.send('togglePause')
123
148
  }
124
-
125
- writeToStderr(`| `)
126
- phoneIndex = 0
127
149
  }
128
150
  }
129
151
 
130
- writeToStderr('\n')
152
+ parentPort?.on('message', keypressHandler)
131
153
 
132
- await playAudioSamples(rawAudio, onTimePosition)
154
+ await playAudioSamples(rawAudio, undefined, signalChannel, player)
133
155
 
134
- writeToStderr('\n')
156
+ parentPort?.off('message', keypressHandler)
157
+ }
158
+
159
+ export function playAudioSamples(rawAudio: RawAudio, onTimePosition?: (timePosition: number) => void, signalChannel?: SignalChannel, player?: AudioPlayerID) {
160
+ if (!player) {
161
+ player = 'audio-io'
162
+ }
163
+
164
+ if (player === 'audio-io') {
165
+ return playAudioSamples_AudioIO(rawAudio, onTimePosition, signalChannel)
166
+ } else if (player === 'sox') {
167
+ return playAudioSamples_Sox(rawAudio, onTimePosition, signalChannel)
168
+ } else {
169
+ throw new Error(`Unsupported audio player ID: ${player}`)
170
+ }
135
171
  }
136
172
 
173
+ export async function playAudioSamples_AudioIO(rawAudio: RawAudio, onTimePosition?: (timePosition: number) => void, signalChannel?: SignalChannel) {
174
+ const openPromise = new OpenPromise()
175
+
176
+ const sampleRate = rawAudio.sampleRate
177
+ const channelCount = rawAudio.audioChannels.length
178
+ const bufferDuration = 100.0
179
+ const audioFrameCount = rawAudio.audioChannels[0].length
180
+ const audioDuration = getRawAudioDuration(rawAudio)
181
+
182
+ const { createAudioOutput } = await import('@echogarden/audio-io')
183
+
184
+ let frameOffset = 0
185
+ let audioOutput: AudioOutput
186
+ let abortRequested = false
187
+ let ended = false
188
+
189
+ let isPaused = false
190
+
191
+ // Define an audio output handler function
192
+ async function audioOutputHandler(outputBuffer: Int16Array) {
193
+ if (ended) {
194
+ return
195
+ }
196
+
197
+ if (onTimePosition) {
198
+ const audioTime = Math.min(frameOffset / sampleRate, audioDuration)
199
+
200
+ onTimePosition(audioTime)
201
+ }
202
+
203
+ if (isPaused) {
204
+ return
205
+ }
206
+
207
+ const chunkFrameCount = outputBuffer.length / channelCount
208
+
209
+ const floatAudioChunk = sliceAudioChannels(rawAudio.audioChannels, frameOffset, frameOffset + chunkFrameCount)
210
+ const interleavedFloatAudioChunk = interleaveChannels(floatAudioChunk)
211
+ const int16AudioChunk = float32ToInt16Pcm(interleavedFloatAudioChunk)
137
212
 
138
- export async function playAudioPairWithTimelineInterleaved(rawAudio1: RawAudio, rawAudio2: RawAudio, timeline1: Timeline, timeline2: Timeline) {
139
- if (timeline1.length != timeline2.length) {
140
- throw new Error('Timelines have different lengths')
213
+ outputBuffer.set(int16AudioChunk)
214
+
215
+ frameOffset += chunkFrameCount
216
+
217
+ if (abortRequested || int16AudioChunk.length < outputBuffer.length) {
218
+ ended = true
219
+
220
+ await audioOutput.dispose()
221
+ openPromise.resolve()
222
+ }
141
223
  }
142
224
 
143
- for (let i = 0; i < timeline1.length; i++) {
144
- writeToStderr(`${timeline1[i].text} `)
225
+ if (signalChannel) {
226
+ signalChannel.on('abort', () => {
227
+ abortRequested = true
228
+ })
229
+
230
+ signalChannel.on('skip', (durationToSkip: number) => {
231
+ frameOffset += durationToSkip * sampleRate
232
+ frameOffset = Math.floor(frameOffset)
233
+ frameOffset = clip(frameOffset, 0, audioFrameCount)
234
+ })
145
235
 
146
- await playAudioSamples(sliceRawAudioByTime(rawAudio1, timeline1[i].startTime, timeline1[i].endTime))
147
- await waitTimeout(200)
148
- await playAudioSamples(sliceRawAudioByTime(rawAudio2, timeline2[i].startTime, timeline2[i].endTime))
149
- await waitTimeout(500)
236
+ signalChannel.on('seek', (timeToSeekTo: number) => {
237
+ frameOffset = Math.floor(timeToSeekTo * sampleRate)
238
+ frameOffset = clip(frameOffset, 0, audioFrameCount)
239
+ })
240
+
241
+ signalChannel.on('togglePause', () => {
242
+ isPaused = !isPaused
243
+ })
150
244
  }
151
245
 
152
- writeToStderr('\n')
246
+ audioOutput = await createAudioOutput({
247
+ sampleRate, // Sample rate in Hz, should be an integer like 44100, 22050, 8000
248
+ channelCount, // Channel count, likely 1 (mono), or 2 (stereo)
249
+ bufferDuration, // Target buffer duration, in milliseconds. Defaults to 100.0
250
+ }, audioOutputHandler)
251
+
252
+ return openPromise.promise
153
253
  }
154
254
 
155
- export function playAudioSamples(rawAudio: RawAudio, onTimePosition?: (timePosition: number) => void, signalChannel?: SignalChannel, microFadeInOut = true) {
255
+ export function playAudioSamples_Sox(rawAudio: RawAudio, onTimePosition?: (timePosition: number) => void, signalChannel?: SignalChannel, microFadeInOut = true) {
156
256
  return new Promise<void>(async (resolve, reject) => {
157
257
  if (microFadeInOut) {
158
258
  rawAudio = fadeAudioInOut(rawAudio, 0.0025)
@@ -180,7 +280,7 @@ export function playAudioSamples(rawAudio: RawAudio, onTimePosition?: (timePosit
180
280
  }
181
281
 
182
282
  let tempFilePath: string | undefined
183
- let audioBuffer: Buffer | undefined
283
+ let audioBuffer: Uint8Array | undefined
184
284
 
185
285
  async function cleanup() {
186
286
  if (tempFilePath) {
@@ -199,7 +299,7 @@ export function playAudioSamples(rawAudio: RawAudio, onTimePosition?: (timePosit
199
299
  {}
200
300
  )
201
301
  } else {
202
- tempFilePath = path.join(getAppTempDir(appName), `${getRandomHexString(16)}.wav`)
302
+ tempFilePath = joinPath(getAppTempDir(appName), `${getRandomHexString(16)}.wav`)
203
303
  const waveFileBuffer = encodeRawAudioToWave(rawAudio)
204
304
  await writeFile(tempFilePath, waveFileBuffer)
205
305
 
@@ -282,7 +382,7 @@ export function playAudioSamples_Speaker(rawAudio: RawAudio, onTimePosition?: (t
282
382
  }
283
383
 
284
384
  const channelCount = rawAudio.audioChannels.length
285
- let audioData = AudioBufferConversion.encodeToAudioBuffer(rawAudio.audioChannels)
385
+ let audioData = encodeToAudioBuffer(rawAudio.audioChannels)
286
386
 
287
387
  const { default: Speaker } = await import('speaker')
288
388
 
@@ -315,7 +415,7 @@ export function playAudioSamples_Speaker(rawAudio: RawAudio, onTimePosition?: (t
315
415
  mpg123AudioBufferSize = bytesPerSecond * mpg123AudioBufferDuration
316
416
  }
317
417
 
318
- audioData = concatBuffers([audioData, Buffer.alloc(mpg123AudioBufferSize)])
418
+ audioData = concatUint8Arrays([audioData, new Uint8Array(mpg123AudioBufferSize)])
319
419
 
320
420
  const maxChunkSize = mpg123AudioBufferSize
321
421
 
@@ -363,3 +463,5 @@ export const charactersToWriteAhead = [
363
463
  ')', ']', '}', `"`, `'`, '”', '’',
364
464
  '-', '—', '»', '،', '؟'
365
465
  ]
466
+
467
+ export type AudioPlayerID = 'audio-io' | 'sox'
@@ -11,7 +11,7 @@ export function encodeRawAudioToWave(rawAudio: RawAudio, bitDepth: BitDepth = 16
11
11
  return encodeWave(rawAudio, bitDepth, sampleFormat, speakerPositionMask)
12
12
  }
13
13
 
14
- export function decodeWaveToRawAudio(waveFileBuffer: Buffer, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
14
+ export function decodeWaveToRawAudio(waveFileBuffer: Uint8Array, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
15
15
  return decodeWave(waveFileBuffer, ignoreTruncatedChunks, ignoreOverflowingDataChunks)
16
16
  }
17
17
 
@@ -401,33 +401,25 @@ export function getRawAudioDuration(rawAudio: RawAudio) {
401
401
  export async function ensureRawAudio(input: AudioSourceParam, outSampleRate?: number, outChannelCount?: number) {
402
402
  let inputAsRawAudio: RawAudio = input as RawAudio
403
403
 
404
- if (inputAsRawAudio.audioChannels?.length > 0 && inputAsRawAudio.sampleRate) {
404
+ if (isRawAudio(input)) {
405
405
  const inputAudioChannelCount = inputAsRawAudio.audioChannels.length
406
406
 
407
407
  if (outChannelCount == 1 && inputAudioChannelCount > 1) {
408
408
  inputAsRawAudio = downmixToMono(inputAsRawAudio)
409
- }
410
-
411
- if (outChannelCount == 2 && inputAudioChannelCount == 1) {
409
+ } else if (outChannelCount == 2 && inputAudioChannelCount == 1) {
412
410
  inputAsRawAudio = cloneRawAudio(inputAsRawAudio)
413
411
  inputAsRawAudio.audioChannels.push(inputAsRawAudio.audioChannels[0].slice())
414
- }
415
-
416
- if (outChannelCount != null && outChannelCount > 2 && outChannelCount != inputAudioChannelCount) {
412
+ } else if (outChannelCount != null && outChannelCount > 2 && outChannelCount != inputAudioChannelCount) {
417
413
  throw new Error(`Can't convert ${inputAudioChannelCount} channels to ${outChannelCount} channels. Channel conversion of raw audio currently only supports mono and stereo inputs.`)
418
414
  }
419
415
 
420
- if (outSampleRate && inputAsRawAudio.sampleRate != outSampleRate) {
416
+ if (outSampleRate && inputAsRawAudio.sampleRate !== outSampleRate) {
421
417
  inputAsRawAudio = await resampleAudioSpeex(inputAsRawAudio, outSampleRate)
422
418
  }
423
419
  } else if (typeof input == 'string' || input instanceof Uint8Array) {
424
- if (input instanceof Uint8Array && !Buffer.isBuffer(input)) {
425
- input = Buffer.from(input)
426
- }
427
-
428
- const inputAsStringOrBuffer = input as string | Buffer
420
+ const inputAsStringOrUint8Array = input as string | Uint8Array
429
421
 
430
- inputAsRawAudio = await FFMpegTranscoder.decodeToChannels(inputAsStringOrBuffer, outSampleRate, outChannelCount)
422
+ inputAsRawAudio = await FFMpegTranscoder.decodeToChannels(inputAsStringOrUint8Array, outSampleRate, outChannelCount)
431
423
  } else {
432
424
  throw new Error('Received an invalid input audio data type.')
433
425
  }
@@ -436,21 +428,45 @@ export async function ensureRawAudio(input: AudioSourceParam, outSampleRate?: nu
436
428
  }
437
429
 
438
430
  export function subtractAudio(audio1: RawAudio, audio2: RawAudio) {
439
- const sampleCount = audio1.audioChannels[0].length
431
+ if (audio1.sampleRate !== audio2.sampleRate) {
432
+ throw new Error(`Audio sequences have different sample rates`)
433
+ }
434
+
435
+ if (audio1.audioChannels.length !== audio2.audioChannels.length) {
436
+ throw new Error(`Audio sequences have different channel counts`)
437
+ }
438
+
439
+ const sampleRate = audio1.sampleRate
440
+ const channelCount = audio1.audioChannels.length
441
+ const sampleCount = Math.min(audio1.audioChannels[0].length, audio2.audioChannels[0].length)
442
+
443
+ const subtractedAudioChannels: Float32Array[] = []
444
+
445
+ for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
446
+ const subtractedSamples = new Float32Array(sampleCount)
440
447
 
441
- const subtractedAudioChannels = [new Float32Array(sampleCount), new Float32Array(sampleCount)]
448
+ const samples1 = audio1.audioChannels[channelIndex]
449
+ const samples2 = audio2.audioChannels[channelIndex]
442
450
 
443
- for (let i = 0; i < sampleCount; i++) {
444
- for (let channelIndex = 0; channelIndex < 2; channelIndex++) {
445
- subtractedAudioChannels[channelIndex][i] = audio1.audioChannels[channelIndex][i] - audio2.audioChannels[channelIndex][i]
451
+ for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
452
+ subtractedSamples[sampleIndex] = samples1[sampleIndex] - samples2[sampleIndex]
446
453
  }
454
+
455
+ subtractedAudioChannels.push(subtractedSamples)
447
456
  }
448
457
 
449
- const subtractedRawAudio: RawAudio = { audioChannels: subtractedAudioChannels, sampleRate: audio1.sampleRate }
458
+ const subtractedRawAudio: RawAudio = {
459
+ audioChannels: subtractedAudioChannels,
460
+ sampleRate: sampleRate
461
+ }
450
462
 
451
463
  return subtractedRawAudio
452
464
  }
453
465
 
466
+ export function isRawAudio(obj: any): obj is RawAudio {
467
+ return typeof obj === 'object' && typeof obj.sampleRate === 'number' && Array.isArray(obj.audioChannels)
468
+ }
469
+
454
470
  ////////////////////////////////////////////////////////////////////////////////////////////////
455
471
  // Unit conversion
456
472
  ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -492,4 +508,4 @@ export type AudioEncoding = {
492
508
  bitrate?: number
493
509
  }
494
510
 
495
- export type AudioSourceParam = string | Buffer | Uint8Array | RawAudio
511
+ export type AudioSourceParam = string | Uint8Array | RawAudio
@@ -1,7 +1,7 @@
1
- import path from 'path'
2
1
  import { loadPackage } from '../utilities/PackageManager.js'
3
2
  import { commandExists } from '../utilities/Utilities.js'
4
3
  import { getGlobalOption } from '../api/GlobalOptions.js'
4
+ import { joinPath } from '../utilities/PathUtilities.js'
5
5
 
6
6
  export async function tryResolvingSoxPath() {
7
7
  if (getGlobalOption('soxPath')) {
@@ -14,7 +14,7 @@ export async function tryResolvingSoxPath() {
14
14
  if (platform === 'win32') {
15
15
  const soxPackagePath = await loadPackage('sox-14.4.1a-win32')
16
16
 
17
- return path.join(soxPackagePath, 'sox.exe')
17
+ return joinPath(soxPackagePath, 'sox.exe')
18
18
  }
19
19
 
20
20
  if (await commandExists('sox')) {
@@ -24,7 +24,7 @@ export async function tryResolvingSoxPath() {
24
24
  if (platform === 'linux' && arch === 'x64') {
25
25
  const soxPackagePath = await loadPackage('sox-14.4.2-linux-minimal')
26
26
 
27
- return path.join(soxPackagePath, 'sox')
27
+ return joinPath(soxPackagePath, 'sox')
28
28
  }
29
29
 
30
30
  return undefined
@@ -1,6 +1,6 @@
1
- import path from 'path'
2
1
  import { existsSync, readdir } from '../utilities/FileSystem.js'
3
2
  import { ensureAndGetPackagesDir } from '../utilities/PackageManager.js'
3
+ import { joinPath } from '../utilities/PathUtilities.js'
4
4
  import { createNamedTarball } from '../utilities/TarballMaker.js'
5
5
  import { setupProgramTerminationListeners } from '../utilities/Utilities.js'
6
6
 
@@ -9,11 +9,11 @@ async function makeTarballsForInstalledPackages(tarballDir: string, skipIfExists
9
9
  const packageList = await readdir(packagesDir)
10
10
 
11
11
  for (const packageName of packageList) {
12
- if (skipIfExists && existsSync(path.join(tarballDir, `${packageName}.tar.gz`))) {
12
+ if (skipIfExists && existsSync(joinPath(tarballDir, `${packageName}.tar.gz`))) {
13
13
  continue
14
14
  }
15
15
 
16
- const packagePath = path.join(packagesDir, packageName)
16
+ const packagePath = joinPath(packagesDir, packageName)
17
17
  await createNamedTarball(packagePath, packageName, tarballDir)
18
18
  }
19
19
  }