echogarden 1.8.7 → 2.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 (299) 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/{utilities → encodings}/LEB128.d.ts +1 -1
  75. package/dist/{utilities → encodings}/LEB128.js +4 -4
  76. package/dist/encodings/LEB128.js.map +1 -0
  77. package/dist/{utilities → encodings}/LPVarInt.d.ts +1 -1
  78. package/dist/{utilities → encodings}/LPVarInt.js +6 -6
  79. package/dist/encodings/LPVarInt.js.map +1 -0
  80. package/dist/encodings/TextEncodingsCommon.d.ts +4 -0
  81. package/dist/encodings/TextEncodingsCommon.js +2 -0
  82. package/dist/encodings/TextEncodingsCommon.js.map +1 -0
  83. package/dist/encodings/Utf16.d.ts +10 -0
  84. package/dist/encodings/Utf16.js +36 -0
  85. package/dist/encodings/Utf16.js.map +1 -0
  86. package/dist/encodings/Utf32.d.ts +9 -0
  87. package/dist/encodings/Utf32.js +44 -0
  88. package/dist/encodings/Utf32.js.map +1 -0
  89. package/dist/encodings/Utf8.d.ts +10 -0
  90. package/dist/encodings/Utf8.js +97 -0
  91. package/dist/encodings/Utf8.js.map +1 -0
  92. package/dist/math/MedianFilter.js.map +1 -1
  93. package/dist/nlp/JapaneseSegmentation.js +2 -2
  94. package/dist/nlp/JapaneseSegmentation.js.map +1 -1
  95. package/dist/recognition/GoogleCloudSTT.js +2 -1
  96. package/dist/recognition/GoogleCloudSTT.js.map +1 -1
  97. package/dist/recognition/SileroSTT.js +3 -3
  98. package/dist/recognition/SileroSTT.js.map +1 -1
  99. package/dist/recognition/VoskSTT.js +3 -3
  100. package/dist/recognition/VoskSTT.js.map +1 -1
  101. package/dist/recognition/WhisperCppSTT.js +4 -5
  102. package/dist/recognition/WhisperCppSTT.js.map +1 -1
  103. package/dist/recognition/WhisperSTT.js +8 -7
  104. package/dist/recognition/WhisperSTT.js.map +1 -1
  105. package/dist/server/Client.d.ts +1 -1
  106. package/dist/server/Client.js +2 -1
  107. package/dist/server/Client.js.map +1 -1
  108. package/dist/server/Server.js +5 -4
  109. package/dist/server/Server.js.map +1 -1
  110. package/dist/server/Worker.js +1 -1
  111. package/dist/server/Worker.js.map +1 -1
  112. package/dist/source-separation/MDXNetSourceSeparation.js +1 -1
  113. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
  114. package/dist/speech-search/DTWSpeechSearch.d.ts +1 -1
  115. package/dist/speech-search/DTWSpeechSearch.js +25 -6
  116. package/dist/speech-search/DTWSpeechSearch.js.map +1 -1
  117. package/dist/subtitles/Subtitles.js +2 -2
  118. package/dist/subtitles/Subtitles.js.map +1 -1
  119. package/dist/synthesis/AzureCognitiveServicesTTS.js +17 -18
  120. package/dist/synthesis/AzureCognitiveServicesTTS.js.map +1 -1
  121. package/dist/synthesis/CoquiServerTTS.js +1 -1
  122. package/dist/synthesis/CoquiServerTTS.js.map +1 -1
  123. package/dist/synthesis/ElevenlabsTTS.js +1 -1
  124. package/dist/synthesis/ElevenlabsTTS.js.map +1 -1
  125. package/dist/synthesis/EspeakTTS.d.ts +0 -1
  126. package/dist/synthesis/EspeakTTS.js +0 -9
  127. package/dist/synthesis/EspeakTTS.js.map +1 -1
  128. package/dist/synthesis/FliteTTS.js +7 -8
  129. package/dist/synthesis/FliteTTS.js.map +1 -1
  130. package/dist/synthesis/GoogleCloudTTS.d.ts +1 -1
  131. package/dist/synthesis/GoogleCloudTTS.js +2 -1
  132. package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
  133. package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
  134. package/dist/synthesis/GoogleTranslateTTS.js +4 -3
  135. package/dist/synthesis/GoogleTranslateTTS.js.map +1 -1
  136. package/dist/synthesis/MicrosoftEdgeTTS.js +32 -7
  137. package/dist/synthesis/MicrosoftEdgeTTS.js.map +1 -1
  138. package/dist/synthesis/SapiTTS.js +1 -1
  139. package/dist/synthesis/SapiTTS.js.map +1 -1
  140. package/dist/synthesis/StreamlabsPollyTTS.d.ts +1 -1
  141. package/dist/synthesis/StreamlabsPollyTTS.js +1 -1
  142. package/dist/synthesis/StreamlabsPollyTTS.js.map +1 -1
  143. package/dist/synthesis/SvoxPicoTTS.js +6 -6
  144. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  145. package/dist/synthesis/VitsTTS.js +2 -2
  146. package/dist/synthesis/VitsTTS.js.map +1 -1
  147. package/dist/tests/Test.js +1 -47
  148. package/dist/tests/Test.js.map +1 -1
  149. package/dist/text-language-detection/FastTextLanguageDetection.js +2 -2
  150. package/dist/text-language-detection/FastTextLanguageDetection.js.map +1 -1
  151. package/dist/typings/TypedArray.d.ts +4 -0
  152. package/dist/typings/TypedArray.js +2 -0
  153. package/dist/typings/TypedArray.js.map +1 -0
  154. package/dist/utilities/BinaryArrayConversion.d.ts +12 -22
  155. package/dist/utilities/BinaryArrayConversion.js +40 -90
  156. package/dist/utilities/BinaryArrayConversion.js.map +1 -1
  157. package/dist/utilities/BinaryUtilities.d.ts +13 -0
  158. package/dist/utilities/BinaryUtilities.js +113 -0
  159. package/dist/utilities/BinaryUtilities.js.map +1 -0
  160. package/dist/utilities/Compression.js +7 -6
  161. package/dist/utilities/Compression.js.map +1 -1
  162. package/dist/utilities/FileDownloader.js +26 -47
  163. package/dist/utilities/FileDownloader.js.map +1 -1
  164. package/dist/utilities/FileReader.d.ts +14 -0
  165. package/dist/utilities/FileReader.js +71 -0
  166. package/dist/utilities/FileReader.js.map +1 -0
  167. package/dist/utilities/FileSystem.d.ts +19 -16
  168. package/dist/utilities/FileSystem.js +170 -109
  169. package/dist/utilities/FileSystem.js.map +1 -1
  170. package/dist/utilities/FileWriter.d.ts +12 -0
  171. package/dist/utilities/FileWriter.js +60 -0
  172. package/dist/utilities/FileWriter.js.map +1 -0
  173. package/dist/utilities/NpmUtilities.d.ts +1 -0
  174. package/dist/utilities/NpmUtilities.js +14 -0
  175. package/dist/utilities/NpmUtilities.js.map +1 -0
  176. package/dist/utilities/PackageManager.js +7 -5
  177. package/dist/utilities/PackageManager.js.map +1 -1
  178. package/dist/utilities/PathUtilities.d.ts +8 -0
  179. package/dist/utilities/PathUtilities.js +28 -7
  180. package/dist/utilities/PathUtilities.js.map +1 -1
  181. package/dist/utilities/SignalChannel.d.ts +3 -1
  182. package/dist/utilities/SignalChannel.js +9 -5
  183. package/dist/utilities/SignalChannel.js.map +1 -1
  184. package/dist/utilities/StringBuilder.d.ts +10 -0
  185. package/dist/utilities/StringBuilder.js +39 -0
  186. package/dist/utilities/StringBuilder.js.map +1 -0
  187. package/dist/utilities/StringUtilities.js.map +1 -1
  188. package/dist/utilities/TarballMaker.js +6 -7
  189. package/dist/utilities/TarballMaker.js.map +1 -1
  190. package/dist/utilities/Timer.js +2 -2
  191. package/dist/utilities/Timer.js.map +1 -1
  192. package/dist/utilities/Utilities.d.ts +11 -5
  193. package/dist/utilities/Utilities.js +69 -33
  194. package/dist/utilities/Utilities.js.map +1 -1
  195. package/dist/utilities/VirtualFileReadStream.d.ts +3 -3
  196. package/dist/utilities/VirtualFileReadStream.js.map +1 -1
  197. package/dist/utilities/WasmMemoryManager.d.ts +5 -2
  198. package/dist/utilities/WasmMemoryManager.js +15 -4
  199. package/dist/utilities/WasmMemoryManager.js.map +1 -1
  200. package/docs/API.md +1 -1
  201. package/docs/Licenses.md +3 -0
  202. package/docs/Options.md +9 -3
  203. package/docs/Tasklist.md +0 -1
  204. package/docs/Technical.md +2 -2
  205. package/package.json +20 -22
  206. package/src/alignment/DTWSequenceAlignment.ts +5 -5
  207. package/src/alignment/SpeechAlignment.ts +6 -6
  208. package/src/api/Alignment.ts +65 -12
  209. package/src/api/Denoising.ts +97 -17
  210. package/src/api/SourceSeparation.ts +11 -8
  211. package/src/api/SpeechLanguageDetection.ts +4 -4
  212. package/src/api/Synthesis.ts +15 -11
  213. package/src/api/VoiceActivityDetection.ts +2 -2
  214. package/src/audio/AudioBufferConversion.ts +84 -45
  215. package/src/audio/AudioPlayer.ts +155 -53
  216. package/src/audio/AudioUtilities.ts +38 -22
  217. package/src/audio/SoxPath.ts +3 -3
  218. package/src/build-tools/MakeTarballsForInstalledPackages.ts +3 -3
  219. package/src/cli/CLI.ts +30 -36
  220. package/src/cli/CLIConfigFile.ts +2 -2
  221. package/src/cli/CLIOptions.ts +4 -1
  222. package/src/codecs/FFMpegTranscoder.ts +11 -11
  223. package/src/codecs/TIMITCodec.ts +5 -4
  224. package/src/codecs/WaveCodec.ts +42 -39
  225. package/src/data-structures/DynamicTypedArray.ts +64 -0
  226. package/src/denoising/NSNet2.ts +182 -0
  227. package/src/denoising/RNNoise.ts +3 -3
  228. package/src/dsp/FFT.ts +21 -1
  229. package/src/encodings/Ascii.ts +60 -0
  230. package/src/encodings/Base64.ts +157 -0
  231. package/src/encodings/Hex.ts +67 -0
  232. package/src/{utilities → encodings}/LEB128.ts +4 -4
  233. package/src/{utilities → encodings}/LPVarInt.ts +6 -6
  234. package/src/encodings/TextEncodingsCommon.ts +4 -0
  235. package/src/encodings/Utf16.ts +52 -0
  236. package/src/encodings/Utf32.ts +62 -0
  237. package/src/encodings/Utf8.ts +117 -0
  238. package/src/math/MedianFilter.ts +0 -2
  239. package/src/nlp/JapaneseSegmentation.ts +2 -2
  240. package/src/recognition/GoogleCloudSTT.ts +2 -1
  241. package/src/recognition/SileroSTT.ts +3 -3
  242. package/src/recognition/VoskSTT.ts +3 -3
  243. package/src/recognition/WhisperCppSTT.ts +4 -5
  244. package/src/recognition/WhisperSTT.ts +8 -7
  245. package/src/server/Client.ts +4 -3
  246. package/src/server/Server.ts +6 -5
  247. package/src/server/Worker.ts +1 -1
  248. package/src/source-separation/MDXNetSourceSeparation.ts +1 -1
  249. package/src/speech-search/DTWSpeechSearch.ts +36 -7
  250. package/src/subtitles/Subtitles.ts +3 -3
  251. package/src/synthesis/AzureCognitiveServicesTTS.ts +18 -15
  252. package/src/synthesis/CoquiServerTTS.ts +2 -2
  253. package/src/synthesis/ElevenlabsTTS.ts +1 -1
  254. package/src/synthesis/EspeakTTS.ts +0 -15
  255. package/src/synthesis/FliteTTS.ts +7 -8
  256. package/src/synthesis/GoogleCloudTTS.ts +3 -2
  257. package/src/synthesis/GoogleTranslateTTS.ts +5 -4
  258. package/src/synthesis/MicrosoftEdgeTTS.ts +47 -15
  259. package/src/synthesis/SapiTTS.ts +1 -1
  260. package/src/synthesis/StreamlabsPollyTTS.ts +1 -1
  261. package/src/synthesis/SvoxPicoTTS.ts +8 -8
  262. package/src/synthesis/VitsTTS.ts +2 -2
  263. package/src/tests/Test.ts +1 -60
  264. package/src/text-language-detection/FastTextLanguageDetection.ts +2 -2
  265. package/src/typings/Fillers.d.ts +2 -4
  266. package/src/typings/TypedArray.ts +5 -0
  267. package/src/utilities/BinaryArrayConversion.ts +51 -125
  268. package/src/utilities/BinaryUtilities.ts +138 -0
  269. package/src/utilities/Compression.ts +7 -6
  270. package/src/utilities/FileDownloader.ts +27 -52
  271. package/src/utilities/FileReader.ts +89 -0
  272. package/src/utilities/FileSystem.ts +213 -120
  273. package/src/utilities/FileWriter.ts +72 -0
  274. package/src/utilities/NpmUtilities.ts +23 -0
  275. package/src/utilities/PackageManager.ts +8 -5
  276. package/src/utilities/PathUtilities.ts +36 -7
  277. package/src/utilities/SignalChannel.ts +14 -7
  278. package/src/utilities/StringBuilder.ts +45 -0
  279. package/src/utilities/StringUtilities.ts +2 -0
  280. package/src/utilities/TarballMaker.ts +6 -7
  281. package/src/utilities/Timer.ts +4 -2
  282. package/src/utilities/Utilities.ts +79 -45
  283. package/src/utilities/VirtualFileReadStream.ts +5 -5
  284. package/src/utilities/WasmMemoryManager.ts +20 -6
  285. package/tsconfig.json +96 -0
  286. package/dist/utilities/DynamicUint8Array.d.ts +0 -9
  287. package/dist/utilities/DynamicUint8Array.js +0 -31
  288. package/dist/utilities/DynamicUint8Array.js.map +0 -1
  289. package/dist/utilities/LEB128.js.map +0 -1
  290. package/dist/utilities/LPVarInt.js.map +0 -1
  291. package/dist/utilities/Queue.js.map +0 -1
  292. package/dist/utilities/WindowedList.js.map +0 -1
  293. package/src/utilities/DynamicUint8Array.ts +0 -39
  294. /package/dist/{utilities → data-structures}/Queue.d.ts +0 -0
  295. /package/dist/{utilities → data-structures}/Queue.js +0 -0
  296. /package/dist/{utilities → data-structures}/WindowedList.d.ts +0 -0
  297. /package/dist/{utilities → data-structures}/WindowedList.js +0 -0
  298. /package/src/{utilities → data-structures}/Queue.ts +0 -0
  299. /package/src/{utilities → data-structures}/WindowedList.ts +0 -0
@@ -5,7 +5,7 @@ import { BitDepth, SampleFormat } from '../codecs/WaveCodec.js'
5
5
  /////////////////////////////////////////////////////////////////////////////////////////////
6
6
  // Low level audio sample conversions
7
7
  /////////////////////////////////////////////////////////////////////////////////////////////
8
- export function encodeToAudioBuffer(audioChannels: Float32Array[], targetBitDepth: BitDepth = 16, targetSampleFormat: SampleFormat = SampleFormat.PCM) {
8
+ export function encodeToAudioBuffer(audioChannels: Float32Array[], targetBitDepth: BitDepth = 16, targetSampleFormat: SampleFormat = SampleFormat.PCM): Uint8Array {
9
9
  const interleavedChannels = interleaveChannels(audioChannels)
10
10
 
11
11
  audioChannels = [] // Zero the array references to allow the GC to free up memory, if possible
@@ -32,13 +32,13 @@ export function encodeToAudioBuffer(audioChannels: Float32Array[], targetBitDept
32
32
  }
33
33
  } else if (targetSampleFormat === SampleFormat.Alaw) {
34
34
  if (targetBitDepth === 8) {
35
- return Buffer.from(AlawMulaw.alaw.encode(float32ToInt16Pcm(interleavedChannels)))
35
+ return AlawMulaw.alaw.encode(float32ToInt16Pcm(interleavedChannels))
36
36
  } else {
37
37
  throw new Error(`Unsupported alaw bit depth: ${targetBitDepth}`)
38
38
  }
39
39
  } else if (targetSampleFormat === SampleFormat.Mulaw) {
40
40
  if (targetBitDepth === 8) {
41
- return Buffer.from(AlawMulaw.mulaw.encode(float32ToInt16Pcm(interleavedChannels)))
41
+ return AlawMulaw.mulaw.encode(float32ToInt16Pcm(interleavedChannels))
42
42
  } else {
43
43
  throw new Error(`Unsupported mulaw bit depth: ${targetBitDepth}`)
44
44
  }
@@ -47,7 +47,7 @@ export function encodeToAudioBuffer(audioChannels: Float32Array[], targetBitDept
47
47
  }
48
48
  }
49
49
 
50
- export function decodeToChannels(audioBuffer: Buffer, channelCount: number, sourceBitDepth: number, sourceSampleFormat: SampleFormat) {
50
+ export function decodeToChannels(audioBuffer: Uint8Array, channelCount: number, sourceBitDepth: number, sourceSampleFormat: SampleFormat) {
51
51
  let interleavedChannels: Float32Array
52
52
 
53
53
  if (sourceSampleFormat === SampleFormat.PCM) {
@@ -86,29 +86,39 @@ export function decodeToChannels(audioBuffer: Buffer, channelCount: number, sour
86
86
  throw new Error(`Unsupported audio format: ${sourceSampleFormat}`)
87
87
  }
88
88
 
89
- audioBuffer = Buffer.from([]) // Zero the buffer reference to allow the GC to free up memory, if possible
89
+ audioBuffer = new Uint8Array(0) // Zero the buffer reference to allow the GC to free up memory, if possible
90
90
 
91
91
  return deInterleaveChannels(interleavedChannels, channelCount)
92
92
  }
93
93
 
94
94
  // Int8 PCM <-> Float32 conversion
95
95
  export function int8PcmToFloat32(input: Int8Array) {
96
- const output = new Float32Array(input.length)
96
+ const sampleCount = input.length
97
97
 
98
- for (let i = 0; i < input.length; i++) {
99
- const sample = input[i]
100
- output[i] = sample < 0 ? sample / 128 : sample / 127
98
+ const output = new Float32Array(sampleCount)
99
+
100
+ for (let i = 0; i < sampleCount; i++) {
101
+ output[i] = input[i] / 128
101
102
  }
102
103
 
103
104
  return output
104
105
  }
105
106
 
106
107
  export function float32ToInt8Pcm(input: Float32Array) {
107
- const output = new Int8Array(input.length)
108
+ const sampleCount = input.length
109
+
110
+ const output = new Int8Array(sampleCount)
111
+
112
+ for (let i = 0; i < sampleCount; i++) {
113
+ const int8Sample = input[i] * 128
108
114
 
109
- for (let i = 0; i < input.length; i++) {
110
- const sample = clampFloatSample(input[i])
111
- output[i] = (sample < 0 ? sample * 128 : sample * 127) | 0
115
+ if (int8Sample < -128) {
116
+ output[i] = -128
117
+ } else if (int8Sample > 127) {
118
+ output[i] = 127
119
+ } else {
120
+ output[i] = int8Sample
121
+ }
112
122
  }
113
123
 
114
124
  return output
@@ -116,22 +126,32 @@ export function float32ToInt8Pcm(input: Float32Array) {
116
126
 
117
127
  // Int16 PCM <-> Float32 conversion
118
128
  export function int16PcmToFloat32(input: Int16Array) {
119
- const output = new Float32Array(input.length)
129
+ const sampleCount = input.length
130
+
131
+ const output = new Float32Array(sampleCount)
120
132
 
121
- for (let i = 0; i < input.length; i++) {
122
- const sample = input[i]
123
- output[i] = sample < 0 ? sample / 32768 : sample / 32767
133
+ for (let i = 0; i < sampleCount; i++) {
134
+ output[i] = input[i] / 32768
124
135
  }
125
136
 
126
137
  return output
127
138
  }
128
139
 
129
140
  export function float32ToInt16Pcm(input: Float32Array) {
130
- const output = new Int16Array(input.length)
141
+ const sampleCount = input.length
142
+
143
+ const output = new Int16Array(sampleCount)
144
+
145
+ for (let i = 0; i < sampleCount; i++) {
146
+ const int16Sample = input[i] * 32768
131
147
 
132
- for (let i = 0; i < input.length; i++) {
133
- const sample = clampFloatSample(input[i])
134
- output[i] = (sample < 0 ? sample * 32768 : sample * 32767) | 0
148
+ if (int16Sample < -32768) {
149
+ output[i] = -32768
150
+ } else if (int16Sample > 32767) {
151
+ output[i] = 32767
152
+ } else {
153
+ output[i] = int16Sample
154
+ }
135
155
  }
136
156
 
137
157
  return output
@@ -139,22 +159,32 @@ export function float32ToInt16Pcm(input: Float32Array) {
139
159
 
140
160
  // Int24 PCM <-> Float32 conversion (uses int32 for storage)
141
161
  export function int24PcmToFloat32(input: Int32Array) {
142
- const output = new Float32Array(input.length)
162
+ const sampleCount = input.length
143
163
 
144
- for (let i = 0; i < input.length; i++) {
145
- const sample = input[i]
146
- output[i] = sample < 0 ? sample / 8388608 : sample / 8388607
164
+ const output = new Float32Array(sampleCount)
165
+
166
+ for (let i = 0; i < sampleCount; i++) {
167
+ output[i] = input[i] / 8388608
147
168
  }
148
169
 
149
170
  return output
150
171
  }
151
172
 
152
173
  export function float32ToInt24Pcm(input: Float32Array) {
153
- const output = new Int32Array(input.length)
174
+ const sampleCount = input.length
154
175
 
155
- for (let i = 0; i < input.length; i++) {
156
- const sample = clampFloatSample(input[i])
157
- output[i] = (sample < 0 ? sample * 8388608 : sample * 8388607) | 0
176
+ const output = new Int32Array(sampleCount)
177
+
178
+ for (let i = 0; i < sampleCount; i++) {
179
+ const int24Sample = input[i] * 8388608
180
+
181
+ if (int24Sample < -8388608) {
182
+ output[i] = -8388608
183
+ } else if (int24Sample > 8388607) {
184
+ output[i] = 8388607
185
+ } else {
186
+ output[i] = int24Sample
187
+ }
158
188
  }
159
189
 
160
190
  return output
@@ -162,22 +192,32 @@ export function float32ToInt24Pcm(input: Float32Array) {
162
192
 
163
193
  // Int32 PCM <-> Float32 conversion
164
194
  export function int32PcmToFloat32(input: Int32Array) {
165
- const output = new Float32Array(input.length)
195
+ const sampleCount = input.length
196
+
197
+ const output = new Float32Array(sampleCount)
166
198
 
167
- for (let i = 0; i < input.length; i++) {
168
- const sample = input[i]
169
- output[i] = sample < 0 ? sample / 2147483648 : sample / 2147483647
199
+ for (let i = 0; i < sampleCount; i++) {
200
+ output[i] = input[i] / 2147483648
170
201
  }
171
202
 
172
203
  return output
173
204
  }
174
205
 
175
206
  export function float32ToInt32Pcm(input: Float32Array) {
176
- const output = new Int32Array(input.length)
207
+ const sampleCount = input.length
208
+
209
+ const output = new Int32Array(sampleCount)
177
210
 
178
- for (let i = 0; i < input.length; i++) {
179
- const sample = clampFloatSample(input[i])
180
- output[i] = (sample < 0 ? sample * 2147483648 : sample * 2147483647) | 0
211
+ for (let i = 0; i < sampleCount; i++) {
212
+ const int32Sample = input[i] * 2147483648
213
+
214
+ if (int32Sample < -2147483648) {
215
+ output[i] = -2147483648
216
+ } else if (int32Sample > 2147483647) {
217
+ output[i] = 2147483647
218
+ } else {
219
+ output[i] = int32Sample
220
+ }
181
221
  }
182
222
 
183
223
  return output
@@ -202,10 +242,9 @@ export function interleaveChannels(channels: Float32Array[]) {
202
242
 
203
243
  let writeIndex = 0
204
244
 
205
- for (let i = 0; i < sampleCount; i++) {
206
- for (let c = 0; c < channelCount; c++) {
207
- result[writeIndex] = channels[c][i]
208
- writeIndex += 1
245
+ for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
246
+ for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
247
+ result[writeIndex++] = channels[channelIndex][sampleIndex]
209
248
  }
210
249
  }
211
250
 
@@ -226,6 +265,7 @@ export function deInterleaveChannels(interleavedChannels: Float32Array, channelC
226
265
  }
227
266
 
228
267
  const sampleCount = interleavedChannels.length / channelCount
268
+
229
269
  const channels: Float32Array[] = []
230
270
 
231
271
  for (let i = 0; i < channelCount; i++) {
@@ -234,10 +274,9 @@ export function deInterleaveChannels(interleavedChannels: Float32Array, channelC
234
274
 
235
275
  let readIndex = 0
236
276
 
237
- for (let i = 0; i < sampleCount; i++) {
238
- for (let c = 0; c < channelCount; c++) {
239
- channels[c][i] = interleavedChannels[readIndex]
240
- readIndex += 1
277
+ for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
278
+ for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
279
+ channels[channelIndex][sampleIndex] = interleavedChannels[readIndex++]
241
280
  }
242
281
  }
243
282
 
@@ -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