echogarden 0.11.11 → 0.11.13

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 (130) hide show
  1. package/data/schemas/options.json +16 -0
  2. package/dist/alignment/DTWMfccSequenceAlignment.js +2 -2
  3. package/dist/alignment/DTWMfccSequenceAlignment.js.map +1 -1
  4. package/dist/alignment/SpeechAlignment.d.ts +1 -1
  5. package/dist/alignment/SpeechAlignment.js +15 -3
  6. package/dist/alignment/SpeechAlignment.js.map +1 -1
  7. package/dist/api/Alignment.js +3 -3
  8. package/dist/api/Alignment.js.map +1 -1
  9. package/dist/api/LanguageDetection.js +1 -1
  10. package/dist/api/LanguageDetection.js.map +1 -1
  11. package/dist/api/Recognition.js +3 -3
  12. package/dist/api/Recognition.js.map +1 -1
  13. package/dist/api/Synthesis.js +7 -6
  14. package/dist/api/Synthesis.js.map +1 -1
  15. package/dist/api/Translation.js +3 -3
  16. package/dist/api/Translation.js.map +1 -1
  17. package/dist/audio/AudioUtilities.d.ts +5 -2
  18. package/dist/audio/AudioUtilities.js +50 -22
  19. package/dist/audio/AudioUtilities.js.map +1 -1
  20. package/dist/cli/CLI.js +2 -2
  21. package/dist/cli/CLI.js.map +1 -1
  22. package/dist/dsp/SpeexResampler.js +1 -1
  23. package/dist/recognition/WhisperSTT.js +2 -2
  24. package/dist/recognition/WhisperSTT.js.map +1 -1
  25. package/dist/subtitles/Subtitles.d.ts +10 -7
  26. package/dist/subtitles/Subtitles.js +268 -207
  27. package/dist/subtitles/Subtitles.js.map +1 -1
  28. package/docs/Options.md +4 -2
  29. package/package.json +12 -11
  30. package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
  31. package/src/alignment/DTWSequenceAlignment.ts +121 -0
  32. package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
  33. package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
  34. package/src/alignment/SpeechAlignment.ts +488 -0
  35. package/src/api/API.ts +12 -0
  36. package/src/api/APIOptions.ts +15 -0
  37. package/src/api/Alignment.ts +329 -0
  38. package/src/api/Common.ts +16 -0
  39. package/src/api/Denoising.ts +120 -0
  40. package/src/api/LanguageDetection.ts +286 -0
  41. package/src/api/Recognition.ts +344 -0
  42. package/src/api/Synthesis.ts +1735 -0
  43. package/src/api/Translation.ts +143 -0
  44. package/src/api/Vad.ts +172 -0
  45. package/src/audio/AudioBufferConversion.ts +248 -0
  46. package/src/audio/AudioPlayer.ts +358 -0
  47. package/src/audio/AudioRecorder.ts +91 -0
  48. package/src/audio/AudioUtilities.ts +392 -0
  49. package/src/audio/SoxPath.ts +24 -0
  50. package/src/cli/CLI.ts +1360 -0
  51. package/src/cli/CLIConfigFile.ts +91 -0
  52. package/src/cli/CLILauncher.ts +26 -0
  53. package/src/cli/CLIOptionsSchema.ts +54 -0
  54. package/src/cli/CLIParser.ts +41 -0
  55. package/src/cli/CLIStarter.ts +40 -0
  56. package/src/codecs/FFMpegTranscoder.ts +214 -0
  57. package/src/codecs/TIMITCodec.ts +17 -0
  58. package/src/codecs/WaveCodec.ts +260 -0
  59. package/src/denoising/RNNoise.ts +95 -0
  60. package/src/dsp/BiquadFilter.ts +488 -0
  61. package/src/dsp/FFT.ts +187 -0
  62. package/src/dsp/MFCC.ts +227 -0
  63. package/src/dsp/MelSpectogram.ts +145 -0
  64. package/src/dsp/Rubberband.ts +249 -0
  65. package/src/dsp/Sonic.ts +59 -0
  66. package/src/dsp/SpeexResampler.ts +79 -0
  67. package/src/math/VectorMath.ts +812 -0
  68. package/src/nlp/ChineseSegmentation.ts +68 -0
  69. package/src/nlp/CompromiseNLP.ts +113 -0
  70. package/src/nlp/EspeakPhonemizer.ts +168 -0
  71. package/src/nlp/IPA.ts +139 -0
  72. package/src/nlp/JapaneseSegmentation.ts +53 -0
  73. package/src/nlp/Lexicon.ts +119 -0
  74. package/src/nlp/PhoneConversion.ts +508 -0
  75. package/src/nlp/Segmentation.ts +237 -0
  76. package/src/nlp/TextNormalizer.ts +160 -0
  77. package/src/recognition/AmazonTranscribeSTT.ts +112 -0
  78. package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
  79. package/src/recognition/GoogleCloudSTT.ts +92 -0
  80. package/src/recognition/SileroSTT.ts +173 -0
  81. package/src/recognition/VoskSTT.ts +112 -0
  82. package/src/recognition/WhisperSTT.ts +1518 -0
  83. package/src/server/Client.ts +297 -0
  84. package/src/server/Server.ts +178 -0
  85. package/src/server/ServerStarter.ts +12 -0
  86. package/src/server/Worker.ts +400 -0
  87. package/src/server/WorkerStarter.ts +38 -0
  88. package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
  89. package/src/subtitles/Subtitles.ts +478 -0
  90. package/src/synthesis/AwsPollyTTS.ts +78 -0
  91. package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
  92. package/src/synthesis/CoquiServerTTS.ts +29 -0
  93. package/src/synthesis/ElevenLabsTTS.ts +104 -0
  94. package/src/synthesis/EspeakTTS.ts +552 -0
  95. package/src/synthesis/FliteTTS.ts +387 -0
  96. package/src/synthesis/GoogleCloudTTS.ts +112 -0
  97. package/src/synthesis/GoogleTranslateTTS.ts +210 -0
  98. package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
  99. package/src/synthesis/SamTTS.ts +30 -0
  100. package/src/synthesis/SapiTTS.ts +222 -0
  101. package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
  102. package/src/synthesis/SvoxPicoTTS.ts +318 -0
  103. package/src/synthesis/VitsTTS.ts +734 -0
  104. package/src/tests/Test.ts +24 -0
  105. package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
  106. package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
  107. package/src/typings/Fillers.d.ts +41 -0
  108. package/src/utilities/BinaryArrayConversion.ts +159 -0
  109. package/src/utilities/Compression.ts +91 -0
  110. package/src/utilities/FileDownloader.ts +201 -0
  111. package/src/utilities/FileSystem.ts +265 -0
  112. package/src/utilities/Hashing.ts +230 -0
  113. package/src/utilities/Locale.ts +119 -0
  114. package/src/utilities/Logger.ts +72 -0
  115. package/src/utilities/NdArrayUtilities.ts +31 -0
  116. package/src/utilities/ObjectUtilities.ts +169 -0
  117. package/src/utilities/OpenPromise.ts +13 -0
  118. package/src/utilities/PackageManager.ts +97 -0
  119. package/src/utilities/Queue.ts +17 -0
  120. package/src/utilities/RandomGenerator.ts +237 -0
  121. package/src/utilities/SignalChannel.ts +22 -0
  122. package/src/utilities/TarballMaker.ts +68 -0
  123. package/src/utilities/Timeline.ts +231 -0
  124. package/src/utilities/Timer.ts +93 -0
  125. package/src/utilities/Utilities.ts +574 -0
  126. package/src/utilities/WasmMemoryManager.ts +516 -0
  127. package/src/utilities/WebReader.ts +55 -0
  128. package/src/utilities/WikipediaReader.ts +41 -0
  129. package/src/voice-activity-detection/SileroVAD.ts +86 -0
  130. package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
@@ -0,0 +1,329 @@
1
+ import { extendDeep } from "../utilities/ObjectUtilities.js"
2
+
3
+ import { logToStderr } from "../utilities/Utilities.js"
4
+ import { AudioSourceParam, RawAudio, downmixToMonoAndNormalize, ensureRawAudio, getRawAudioDuration, normalizeAudioLevel, trimAudioEnd } from "../audio/AudioUtilities.js"
5
+ import { Logger } from "../utilities/Logger.js"
6
+ import { resampleAudioSpeex } from "../dsp/SpeexResampler.js"
7
+
8
+ import * as API from "./API.js"
9
+ import { Timeline, addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from "../utilities/Timeline.js"
10
+ import { formatLanguageCodeWithName, getDefaultDialectForLanguageCodeIfPossible, getShortLanguageCode, normalizeLanguageCode } from "../utilities/Locale.js"
11
+ import { WhisperOptions, whisperOptionsDefaults } from "../recognition/WhisperSTT.js"
12
+ import chalk from "chalk"
13
+ import { DtwGranularity } from "../alignment/SpeechAlignment.js"
14
+ import { SubtitlesConfig, defaultSubtitlesBaseConfig } from "../subtitles/Subtitles.js"
15
+ import { synthesize } from "./API.js"
16
+ import { EspeakOptions, defaultEspeakOptions } from "../synthesis/EspeakTTS.js"
17
+
18
+ const log = logToStderr
19
+
20
+ export async function align(input: AudioSourceParam, transcript: string, options: AlignmentOptions): Promise<AlignmentResult> {
21
+ const logger = new Logger()
22
+ const startTimestamp = logger.getTimestamp()
23
+
24
+ logger.start("Prepare for alignment")
25
+
26
+ const inputRawAudio = await ensureRawAudio(input)
27
+
28
+ let sourceRawAudio = await ensureRawAudio(inputRawAudio, 16000, 1)
29
+ sourceRawAudio = normalizeAudioLevel(sourceRawAudio)
30
+ sourceRawAudio.audioChannels[0] = trimAudioEnd(sourceRawAudio.audioChannels[0])
31
+
32
+ options = extendDeep(defaultAlignmentOptions, options)
33
+
34
+ if (options.dtw!.windowDuration == null) {
35
+ const sourceAudioDuration = getRawAudioDuration(sourceRawAudio)
36
+
37
+ if (sourceAudioDuration < 5 * 60) { // If up to 5 minutes, set window to one minute
38
+ options.dtw!.windowDuration = 60
39
+ } else if (sourceAudioDuration < 60 * 60) { // If up to 1 hour, set window to 20% of total duration
40
+ options.dtw!.windowDuration = Math.ceil(sourceAudioDuration * 0.2)
41
+ } else { // If 1 hour or more, set window to 12 minutes
42
+ options.dtw!.windowDuration = 12 * 60
43
+ }
44
+ }
45
+
46
+ let language: string
47
+
48
+ if (options.language) {
49
+ language = normalizeLanguageCode(options.language!)
50
+ } else {
51
+ logger.start("No language specified. Detecting language")
52
+ const { detectedLanguage } = await API.detectTextLanguage(transcript, options.languageDetection || {})
53
+ language = detectedLanguage
54
+
55
+ logger.end()
56
+ logger.logTitledMessage('Language detected', formatLanguageCodeWithName(detectedLanguage))
57
+ }
58
+
59
+ language = getDefaultDialectForLanguageCodeIfPossible(language)
60
+
61
+ logger.start("Load alignment module")
62
+
63
+ const { alignUsingDtwWithRecognition, alignUsingDtw } = await import("../alignment/SpeechAlignment.js")
64
+
65
+ async function getAlignmentReference() {
66
+ logger.start("Create alignment reference with eSpeak")
67
+
68
+ const synthesisOptions: API.SynthesisOptions = {
69
+ engine: "espeak",
70
+ language,
71
+ plainText: options.plainText,
72
+ customLexiconPaths: options.customLexiconPaths,
73
+
74
+ espeak: {
75
+ useKlatt: false
76
+ }
77
+ }
78
+
79
+ let { audio: referenceRawAudio, timeline: segmentTimeline, voice: espeakVoice } = await synthesize(transcript, synthesisOptions)
80
+
81
+ const sentenceTimeline = segmentTimeline.flatMap(entry => entry.timeline!)
82
+ const wordTimeline = sentenceTimeline.flatMap(entry => entry.timeline!)
83
+
84
+ referenceRawAudio = await resampleAudioSpeex(referenceRawAudio as RawAudio, 16000)
85
+ referenceRawAudio = downmixToMonoAndNormalize(referenceRawAudio)
86
+
87
+ return { referenceRawAudio, referenceTimeline: wordTimeline, espeakVoice }
88
+ }
89
+
90
+ function getDtwWindowDurationsAndGranularities() {
91
+ let granularities: DtwGranularity[]
92
+ let windowDurations: number[]
93
+
94
+ if (typeof options.dtw!.granularity == 'string') {
95
+ granularities = [options.dtw!.granularity]
96
+ } else if (Array.isArray(options.dtw!.granularity)) {
97
+ granularities = options.dtw!.granularity
98
+ } else {
99
+ granularities = ['auto']
100
+ }
101
+
102
+ if (typeof options.dtw!.windowDuration == 'number') {
103
+ if (granularities.length == 1) {
104
+ windowDurations = [options.dtw!.windowDuration]
105
+ } else if (granularities.length == 2) {
106
+ windowDurations = [options.dtw!.windowDuration, 15]
107
+ } else {
108
+ throw new Error(`More than two passes requested, this requires window durations to be explicitly specified for each pass. For example 'dtw.windowDuration=[600,60,10]'.`)
109
+ }
110
+ } else if (Array.isArray(options.dtw!.windowDuration)) {
111
+ windowDurations = options.dtw!.windowDuration
112
+ } else {
113
+ throw new Error('No window duration given')
114
+ }
115
+
116
+ if (granularities.length != windowDurations.length) {
117
+ throw new Error(`Unequal element counts in options. 'dtw.granularity' has ${granularities.length} items, but 'dtw.windowDuration' has ${windowDurations.length} items. Can't infer what number of DTW passes were intended.`)
118
+ }
119
+
120
+ return { windowDurations, granularities }
121
+ }
122
+
123
+ let mappedTimeline: Timeline
124
+
125
+ switch (options.engine) {
126
+ case "dtw": {
127
+ const { referenceRawAudio, referenceTimeline } = await getAlignmentReference()
128
+ logger.end()
129
+
130
+ const { windowDurations, granularities } = getDtwWindowDurationsAndGranularities()
131
+
132
+ mappedTimeline = await alignUsingDtw(sourceRawAudio, referenceRawAudio, referenceTimeline, granularities, windowDurations)
133
+
134
+ break
135
+ }
136
+
137
+ case "dtw-ra": {
138
+ /*
139
+ const promptWords = (await splitToWords(prompt, language)).filter(word => isWord(word))
140
+
141
+ shuffleArrayInPlace(promptWords, this.randomGen)
142
+ //promptWords.reverse()
143
+
144
+ prompt = promptWords.join(" ")
145
+ */
146
+
147
+ const recognitionOptionsDefaults: API.RecognitionOptions = {
148
+ engine: "whisper",
149
+ language,
150
+ }
151
+
152
+ const recognitionOptions: API.RecognitionOptions = extendDeep(recognitionOptionsDefaults, options.recognition || {})
153
+
154
+ logger.end()
155
+
156
+ const { wordTimeline: recognitionTimeline } = await API.recognize(sourceRawAudio, recognitionOptions)
157
+
158
+ const { referenceRawAudio, referenceTimeline, espeakVoice } = await getAlignmentReference()
159
+
160
+ logger.end()
161
+
162
+ const { windowDurations, granularities } = getDtwWindowDurationsAndGranularities()
163
+
164
+ const espeakOptions: EspeakOptions = { ...defaultEspeakOptions, voice: espeakVoice, useKlatt: false }
165
+
166
+ const phoneAlignmentMethod = options.dtw!.phoneAlignmentMethod!
167
+
168
+ mappedTimeline = await alignUsingDtwWithRecognition(sourceRawAudio, referenceRawAudio, referenceTimeline, recognitionTimeline, granularities, windowDurations, espeakOptions, phoneAlignmentMethod)
169
+
170
+ break
171
+ }
172
+
173
+ case "whisper": {
174
+ const WhisperSTT = await import("../recognition/WhisperSTT.js")
175
+
176
+ const whisperOptions = options.whisper!
177
+
178
+ const shortLanguageCode = getShortLanguageCode(language)
179
+
180
+ const { modelName, modelDir, tokenizerDir } = await WhisperSTT.loadPackagesAndGetPaths(whisperOptions.model, language)
181
+
182
+ if (modelName.endsWith(".en") && shortLanguageCode != "en") {
183
+ throw new Error(`The model '${modelName}' is English only and cannot transcribe language '${shortLanguageCode}'`)
184
+ }
185
+
186
+ if (getRawAudioDuration(sourceRawAudio) > 30) {
187
+ throw new Error("Whisper based alignment currently only supports audio inputs that are 30s or less")
188
+ }
189
+
190
+ logger.end()
191
+
192
+ mappedTimeline = await WhisperSTT.align(sourceRawAudio, transcript, modelName, modelDir, tokenizerDir, shortLanguageCode)
193
+
194
+ break
195
+ }
196
+
197
+ default: {
198
+ throw new Error(`Engine '${options.engine}' is not supported`)
199
+ }
200
+ }
201
+
202
+ addWordTextOffsetsToTimeline(mappedTimeline, transcript)
203
+
204
+ const { segmentTimeline } = await wordTimelineToSegmentSentenceTimeline(mappedTimeline, transcript, language, options.plainText?.paragraphBreaks, options.plainText?.whitespace)
205
+
206
+ logger.end()
207
+ logger.logDuration(`Total alignment time`, startTimestamp, chalk.magentaBright)
208
+
209
+ return {
210
+ timeline: segmentTimeline,
211
+ wordTimeline: mappedTimeline,
212
+ inputRawAudio,
213
+ transcript,
214
+ language
215
+ }
216
+ }
217
+
218
+ export async function alignSegments(sourceRawAudio: RawAudio, segmentTimeline: Timeline, alignmentOptions: AlignmentOptions) {
219
+ const timeline: Timeline = []
220
+
221
+ for (const segmentEntry of segmentTimeline) {
222
+ const segmentText = segmentEntry.text
223
+
224
+ const segmentStartTime = segmentEntry.startTime
225
+ const segmentEndTime = segmentEntry.endTime
226
+
227
+ const segmentStartSampleIndex = Math.floor(segmentStartTime * sourceRawAudio.sampleRate)
228
+ const segmentEndSampleIndex = Math.floor(segmentEndTime * sourceRawAudio.sampleRate)
229
+
230
+ const segmentAudioSamples = sourceRawAudio.audioChannels[0].slice(segmentStartSampleIndex, segmentEndSampleIndex)
231
+ const segmentRawAudio: RawAudio = {
232
+ audioChannels: [segmentAudioSamples],
233
+ sampleRate: sourceRawAudio.sampleRate
234
+ }
235
+
236
+ const { wordTimeline: mappedTimeline } = await align(segmentRawAudio, segmentText, alignmentOptions)
237
+
238
+ const segmentTimelineWithOffset = addTimeOffsetToTimeline(mappedTimeline, segmentStartTime)
239
+
240
+ timeline.push(...segmentTimelineWithOffset)
241
+ }
242
+
243
+ return timeline
244
+ }
245
+
246
+ export interface AlignmentResult {
247
+ timeline: Timeline
248
+ wordTimeline: Timeline
249
+ transcript: string
250
+ language: string
251
+ inputRawAudio: RawAudio
252
+ }
253
+
254
+ export type AlignmentEngine = "dtw" | "dtw-ra" | "whisper"
255
+ export type PhoneAlignmentMethod = "interpolation" | "dtw"
256
+
257
+ export interface AlignmentOptions {
258
+ engine?: AlignmentEngine
259
+
260
+ language?: string
261
+
262
+ languageDetection?: API.TextLanguageDetectionOptions
263
+
264
+ customLexiconPaths?: string[]
265
+
266
+ plainText?: API.PlainTextOptions
267
+
268
+ subtitles?: SubtitlesConfig
269
+
270
+ dtw?: {
271
+ granularity?: DtwGranularity | DtwGranularity[]
272
+ windowDuration?: number | number[]
273
+ phoneAlignmentMethod?: PhoneAlignmentMethod
274
+ }
275
+
276
+ recognition?: API.RecognitionOptions
277
+
278
+ whisper?: WhisperOptions
279
+ }
280
+
281
+ export const defaultAlignmentOptions: AlignmentOptions = {
282
+ engine: "dtw",
283
+
284
+ language: undefined,
285
+
286
+ languageDetection: {
287
+ },
288
+
289
+ customLexiconPaths: undefined,
290
+
291
+ plainText: {
292
+ paragraphBreaks: 'double',
293
+ whitespace: 'collapse'
294
+ },
295
+
296
+ subtitles: defaultSubtitlesBaseConfig,
297
+
298
+ dtw: {
299
+ granularity: 'auto',
300
+ windowDuration: undefined,
301
+ phoneAlignmentMethod: 'dtw'
302
+ },
303
+
304
+ recognition: {
305
+ },
306
+
307
+ whisper: whisperOptionsDefaults
308
+ }
309
+
310
+ export const alignmentEngines: API.EngineMetadata[] = [
311
+ {
312
+ id: 'dtw',
313
+ name: 'Dynamic Time Warping',
314
+ description: 'Makes use of synthesis to find the best mapping between the original audio and its transcript.',
315
+ type: 'local'
316
+ },
317
+ {
318
+ id: 'dtw-ra',
319
+ name: 'Dynamic Time Warping with Recognition Assist',
320
+ description: 'Makes use of both synthesis and recognition to find the best mapping between the original audio and its transcript.',
321
+ type: 'local'
322
+ },
323
+ {
324
+ id: 'whisper',
325
+ name: 'OpenAI Whisper',
326
+ description: 'Extracts timestamps from the internal state of the Whisper recognition model (note: currently limited to a maximum of 30s audio length).',
327
+ type: 'local'
328
+ }
329
+ ]
@@ -0,0 +1,16 @@
1
+ export const appName = "echogarden"
2
+
3
+ export interface EngineMetadata {
4
+ id: string
5
+ name: string
6
+ description: string
7
+ type: 'local' | 'server' | 'cloud'
8
+ }
9
+
10
+ export interface PlainTextOptions {
11
+ paragraphBreaks?: ParagraphBreakType
12
+ whitespace?: WhitespaceProcessing
13
+ }
14
+
15
+ export type ParagraphBreakType = 'single' | 'double'
16
+ export type WhitespaceProcessing = 'preserve' | 'removeLineBreaks' | 'collapse'
@@ -0,0 +1,120 @@
1
+ import { extendDeep } from "../utilities/ObjectUtilities.js"
2
+
3
+ import { AudioSourceParam, RawAudio, applyGainDecibels, ensureRawAudio, getAudioPeakDecibels, mixAudio, normalizeAudioLevel } from "../audio/AudioUtilities.js"
4
+ import { Logger } from "../utilities/Logger.js"
5
+
6
+ import { logToStderr } from "../utilities/Utilities.js"
7
+ import { resampleAudioSpeex } from "../dsp/SpeexResampler.js"
8
+ import { EngineMetadata } from "./Common.js"
9
+ import chalk from "chalk"
10
+
11
+ const log = logToStderr
12
+
13
+ export async function denoise(input: AudioSourceParam, options: DenoisingOptions) {
14
+ const logger = new Logger()
15
+ const startTime = logger.getTimestamp()
16
+
17
+ logger.start("Prepare for denoising")
18
+
19
+ options = extendDeep(defaultDenoisingOptions, options)
20
+
21
+ const inputRawAudio = await ensureRawAudio(input)
22
+
23
+ const processingSampleRate = 48000
24
+
25
+ logger.start(`Resample audio to ${processingSampleRate} Hz`)
26
+ const resampledRawAudio = await resampleAudioSpeex(inputRawAudio, processingSampleRate, 3)
27
+
28
+ logger.start(`Initialize ${options.method} module`)
29
+
30
+ let denoisedAudio: RawAudio
31
+
32
+ switch (options.method) {
33
+ case "rnnoise": {
34
+ const RNNoise = await import("../denoising/RNNoise.js")
35
+ logger.end()
36
+
37
+ const denoisedAudioChannels: Float32Array[] = []
38
+
39
+ for (const audioChannel of resampledRawAudio.audioChannels) {
40
+ const audioChannelRawAudio: RawAudio = { audioChannels: [audioChannel], sampleRate: processingSampleRate }
41
+
42
+ const { denoisedRawAudio, frameVadProbabilities } = await RNNoise.denoiseAudio(audioChannelRawAudio)
43
+ denoisedAudioChannels.push(denoisedRawAudio.audioChannels[0])
44
+ }
45
+
46
+ denoisedAudio = { audioChannels: denoisedAudioChannels, sampleRate: processingSampleRate }
47
+
48
+ break
49
+ }
50
+
51
+ default: {
52
+ throw new Error(`Method: '${options.method}' is not supported`)
53
+ }
54
+ }
55
+
56
+ logger.start("Postprocess audio")
57
+
58
+ const shouldNormalize = options.postProcessing!.normalizeAudio!
59
+ const targetPeakDb = options.postProcessing!.targetPeakDb!
60
+ const maxIncreaseDb = options.postProcessing!.maxIncreaseDb!
61
+ const dryMixGainDb = options.postProcessing!.dryMixGainDb!
62
+
63
+ const preMixPeakDb = getAudioPeakDecibels(denoisedAudio.audioChannels)
64
+ denoisedAudio = mixAudio(denoisedAudio, applyGainDecibels(resampledRawAudio, dryMixGainDb))
65
+ const postMixPeakDb = getAudioPeakDecibels(denoisedAudio.audioChannels)
66
+
67
+ if (shouldNormalize) {
68
+ denoisedAudio = normalizeAudioLevel(denoisedAudio, targetPeakDb, maxIncreaseDb)
69
+ } else {
70
+ denoisedAudio = applyGainDecibels(denoisedAudio, preMixPeakDb - postMixPeakDb)
71
+ }
72
+
73
+ logger.end()
74
+
75
+ logger.log('')
76
+ logger.logDuration("Total denoising time", startTime, chalk.magentaBright)
77
+
78
+ return {
79
+ denoisedAudio,
80
+ inputRawAudio
81
+ }
82
+ }
83
+
84
+ export interface DenoisingResult {
85
+ denoisedAudio: RawAudio
86
+ inputRawAudio: RawAudio
87
+ }
88
+
89
+ export type DenoisingMethod = "rnnoise"
90
+
91
+ export interface DenoisingOptions {
92
+ method?: DenoisingMethod,
93
+ postProcessing?: {
94
+ normalizeAudio: boolean
95
+ targetPeakDb: number
96
+ maxIncreaseDb: number
97
+
98
+ dryMixGainDb?: number
99
+ }
100
+ }
101
+
102
+ export const defaultDenoisingOptions: DenoisingOptions = {
103
+ method: "rnnoise",
104
+
105
+ postProcessing: {
106
+ normalizeAudio: false,
107
+ targetPeakDb: -3,
108
+ maxIncreaseDb: 30,
109
+ dryMixGainDb: -20,
110
+ }
111
+ }
112
+
113
+ export const denoisingEngines: EngineMetadata[] = [
114
+ {
115
+ id: 'rnnoise',
116
+ name: 'RNNoise',
117
+ description: 'A noise suppression library based on a recurrent neural network.',
118
+ type: 'local'
119
+ }
120
+ ]