echogarden 1.7.0 → 1.8.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.
- package/data/schemas/options.json +22 -10
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Denoising.js +5 -4
- package/dist/api/Denoising.js.map +1 -1
- package/dist/api/Recognition.js +2 -2
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/SourceSeparation.d.ts +2 -6
- package/dist/api/SourceSeparation.js +8 -9
- package/dist/api/SourceSeparation.js.map +1 -1
- package/dist/api/SpeechLanguageDetection.js +2 -2
- package/dist/api/SpeechLanguageDetection.js.map +1 -1
- package/dist/api/SpeechTranslation.js +2 -2
- package/dist/api/SpeechTranslation.js.map +1 -1
- package/dist/api/Synthesis.js +5 -5
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/TranslationAlignment.js +2 -2
- package/dist/api/TranslationAlignment.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +5 -3
- package/dist/audio/AudioUtilities.js +39 -25
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/dsp/FFT.d.ts +2 -1
- package/dist/dsp/FFT.js +37 -18
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/nlp/IPA.d.ts +3 -3
- package/dist/recognition/WhisperSTT.d.ts +3 -0
- package/dist/recognition/WhisperSTT.js +12 -5
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +8 -3
- package/dist/source-separation/MDXNetSourceSeparation.js +35 -13
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
- package/dist/utilities/PackageManager.js +3 -6
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/WasmMemoryManager.d.ts +9 -1
- package/dist/utilities/WasmMemoryManager.js +24 -3
- package/dist/utilities/WasmMemoryManager.js.map +1 -1
- package/docs/Options.md +9 -9
- package/package.json +6 -5
- package/src/alignment/SpeechAlignment.ts +1 -1
- package/src/api/Alignment.ts +2 -2
- package/src/api/Denoising.ts +5 -4
- package/src/api/Recognition.ts +3 -3
- package/src/api/SourceSeparation.ts +12 -16
- package/src/api/SpeechLanguageDetection.ts +2 -2
- package/src/api/SpeechTranslation.ts +2 -2
- package/src/api/Synthesis.ts +5 -5
- package/src/api/TranslationAlignment.ts +2 -2
- package/src/audio/AudioUtilities.ts +53 -33
- package/src/dsp/FFT.ts +45 -19
- package/src/recognition/WhisperSTT.ts +18 -6
- package/src/source-separation/MDXNetSourceSeparation.ts +44 -12
- package/src/typings/Fillers.d.ts +2 -0
- package/src/utilities/PackageManager.ts +3 -7
- package/src/utilities/WasmMemoryManager.ts +38 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { deepClone, extendDeep } from '../utilities/ObjectUtilities.js'
|
|
2
2
|
|
|
3
|
-
import { AudioSourceParam, RawAudio, ensureRawAudio, getRawAudioDuration,
|
|
3
|
+
import { AudioSourceParam, RawAudio, ensureRawAudio, getRawAudioDuration, normalizeAudioLevelInPlace, sliceRawAudioByTime, trimAudioEnd } from '../audio/AudioUtilities.js'
|
|
4
4
|
import { Logger } from '../utilities/Logger.js'
|
|
5
5
|
|
|
6
6
|
import * as API from './API.js'
|
|
@@ -28,7 +28,7 @@ export async function detectSpeechLanguage(input: AudioSourceParam, options: Spe
|
|
|
28
28
|
|
|
29
29
|
logger.start(`Resample audio to 16kHz mono`)
|
|
30
30
|
let sourceRawAudio = await ensureRawAudio(inputRawAudio, 16000, 1)
|
|
31
|
-
|
|
31
|
+
normalizeAudioLevelInPlace(sourceRawAudio)
|
|
32
32
|
sourceRawAudio.audioChannels[0] = trimAudioEnd(sourceRawAudio.audioChannels[0])
|
|
33
33
|
|
|
34
34
|
if (options.crop) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extendDeep } from '../utilities/ObjectUtilities.js'
|
|
2
2
|
|
|
3
3
|
import { logToStderr } from '../utilities/Utilities.js'
|
|
4
|
-
import { AudioSourceParam, RawAudio, ensureRawAudio,
|
|
4
|
+
import { AudioSourceParam, RawAudio, ensureRawAudio, normalizeAudioLevelInPlace, trimAudioEnd } from '../audio/AudioUtilities.js'
|
|
5
5
|
import { Logger } from '../utilities/Logger.js'
|
|
6
6
|
|
|
7
7
|
import { Timeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
|
|
@@ -61,7 +61,7 @@ export async function translateSpeech(input: AudioSourceParam, options: SpeechTr
|
|
|
61
61
|
|
|
62
62
|
logger.start('Normalize and trim audio')
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
normalizeAudioLevelInPlace(sourceRawAudio)
|
|
65
65
|
sourceRawAudio.audioChannels[0] = trimAudioEnd(sourceRawAudio.audioChannels[0])
|
|
66
66
|
|
|
67
67
|
if (options.sourceLanguage) {
|
package/src/api/Synthesis.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { deepClone, extendDeep } from '../utilities/ObjectUtilities.js'
|
|
|
5
5
|
import * as FFMpegTranscoder from '../codecs/FFMpegTranscoder.js'
|
|
6
6
|
|
|
7
7
|
import { clip, sha256AsHex, stringifyAndFormatJson, logToStderr, yieldToEventLoop, runOperationWithRetries } from '../utilities/Utilities.js'
|
|
8
|
-
import { RawAudio,
|
|
8
|
+
import { RawAudio, concatAudioSegments, downmixToMono, encodeRawAudioToWave, getSamplePeakDecibels, getEmptyRawAudio, getRawAudioDuration, trimAudioEnd, trimAudioStart, attenuateIfClippingInPlace, normalizeAudioLevelInPlace } from '../audio/AudioUtilities.js'
|
|
9
9
|
import { Logger } from '../utilities/Logger.js'
|
|
10
10
|
|
|
11
11
|
import { isWordOrSymbolWord, splitToParagraphs, splitToSentences } from '../nlp/Segmentation.js'
|
|
@@ -250,9 +250,9 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
|
|
|
250
250
|
resultRawAudio = { audioChannels: joinedAudioBuffers, sampleRate: segmentsRawAudio[0].sampleRate }
|
|
251
251
|
|
|
252
252
|
if (options.postProcessing!.normalizeAudio) {
|
|
253
|
-
|
|
253
|
+
normalizeAudioLevelInPlace(resultRawAudio, options.postProcessing!.targetPeak, options.postProcessing!.maxGainIncrease)
|
|
254
254
|
} else {
|
|
255
|
-
|
|
255
|
+
attenuateIfClippingInPlace(resultRawAudio)
|
|
256
256
|
}
|
|
257
257
|
} else {
|
|
258
258
|
resultRawAudio = getEmptyRawAudio(1, 24000)
|
|
@@ -866,9 +866,9 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
|
|
|
866
866
|
synthesizedAudio = downmixToMono(synthesizedAudio)
|
|
867
867
|
|
|
868
868
|
if (options.postProcessing!.normalizeAudio) {
|
|
869
|
-
|
|
869
|
+
normalizeAudioLevelInPlace(synthesizedAudio, options.postProcessing!.targetPeak!, options.postProcessing!.maxGainIncrease!)
|
|
870
870
|
} else {
|
|
871
|
-
|
|
871
|
+
attenuateIfClippingInPlace(synthesizedAudio)
|
|
872
872
|
}
|
|
873
873
|
|
|
874
874
|
const preTrimSampleCount = synthesizedAudio.audioChannels[0].length
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extendDeep } from '../utilities/ObjectUtilities.js'
|
|
2
2
|
|
|
3
3
|
import { logToStderr } from '../utilities/Utilities.js'
|
|
4
|
-
import { AudioSourceParam, RawAudio, ensureRawAudio,
|
|
4
|
+
import { AudioSourceParam, RawAudio, ensureRawAudio, normalizeAudioLevelInPlace, trimAudioEnd } from '../audio/AudioUtilities.js'
|
|
5
5
|
import { Logger } from '../utilities/Logger.js'
|
|
6
6
|
|
|
7
7
|
import * as API from './API.js'
|
|
@@ -53,7 +53,7 @@ export async function alignTranslation(input: AudioSourceParam, translatedTransc
|
|
|
53
53
|
|
|
54
54
|
logger.start('Normalize and trim audio')
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
normalizeAudioLevelInPlace(sourceRawAudio)
|
|
57
57
|
sourceRawAudio.audioChannels[0] = trimAudioEnd(sourceRawAudio.audioChannels[0])
|
|
58
58
|
|
|
59
59
|
logger.end()
|
|
@@ -47,11 +47,13 @@ export function trimAudioEnd(audioSamples: Float32Array, targetEndSilentSampleCo
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function getStartingSilentSampleCount(audioSamples: Float32Array, amplitudeThresholdDecibels = defaultSilenceThresholdDecibels) {
|
|
50
|
+
const sampleCount = audioSamples.length
|
|
51
|
+
|
|
50
52
|
const minSampleAmplitude = decibelsToGainFactor(amplitudeThresholdDecibels)
|
|
51
53
|
|
|
52
54
|
let silentSampleCount = 0
|
|
53
55
|
|
|
54
|
-
for (let i = 0; i <
|
|
56
|
+
for (let i = 0; i < sampleCount - 1; i++) {
|
|
55
57
|
if (Math.abs(audioSamples[i]) > minSampleAmplitude) {
|
|
56
58
|
break
|
|
57
59
|
}
|
|
@@ -63,11 +65,13 @@ export function getStartingSilentSampleCount(audioSamples: Float32Array, amplitu
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export function getEndingSilentSampleCount(audioSamples: Float32Array, amplitudeThresholdDecibels = defaultSilenceThresholdDecibels) {
|
|
68
|
+
const sampleCount = audioSamples.length
|
|
69
|
+
|
|
66
70
|
const minSampleAmplitude = decibelsToGainFactor(amplitudeThresholdDecibels)
|
|
67
71
|
|
|
68
72
|
let silentSampleCount = 0
|
|
69
73
|
|
|
70
|
-
for (let i =
|
|
74
|
+
for (let i = sampleCount - 1; i >= 0; i--) {
|
|
71
75
|
if (Math.abs(audioSamples[i]) > minSampleAmplitude) {
|
|
72
76
|
break
|
|
73
77
|
}
|
|
@@ -82,15 +86,27 @@ export function getEndingSilentSampleCount(audioSamples: Float32Array, amplitude
|
|
|
82
86
|
// Gain, normalization, mixing, and channel downmixing
|
|
83
87
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
84
88
|
export function downmixToMonoAndNormalize(rawAudio: RawAudio, targetPeakDecibels = -3) {
|
|
85
|
-
|
|
89
|
+
const downmixedAudio = downmixToMono(rawAudio)
|
|
90
|
+
|
|
91
|
+
normalizeAudioLevelInPlace(downmixedAudio, targetPeakDecibels)
|
|
92
|
+
|
|
93
|
+
return downmixedAudio
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function attenuateIfClippingInPlace(rawAudio: RawAudio, clippingThreshold = -0.1) {
|
|
97
|
+
normalizeAudioLevelInPlace(rawAudio, clippingThreshold, 0)
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
export function
|
|
89
|
-
|
|
100
|
+
export function normalizeAudioLevel(rawAudio: RawAudio, targetPeakDecibels = -3, maxGainIncreaseDecibels = 30) {
|
|
101
|
+
const clonedRawAudio = cloneRawAudio(rawAudio)
|
|
102
|
+
|
|
103
|
+
normalizeAudioLevelInPlace(clonedRawAudio, targetPeakDecibels, maxGainIncreaseDecibels)
|
|
104
|
+
|
|
105
|
+
return clonedRawAudio
|
|
90
106
|
}
|
|
91
107
|
|
|
92
|
-
export function
|
|
93
|
-
//
|
|
108
|
+
export function normalizeAudioLevelInPlace(rawAudio: RawAudio, targetPeakDecibels = -3, maxGainIncreaseDecibels = 30){
|
|
109
|
+
//correctDCBiasInPlace(rawAudio)
|
|
94
110
|
|
|
95
111
|
const targetPeakAmplitude = decibelsToGainFactor(targetPeakDecibels)
|
|
96
112
|
const maxGainFactor = decibelsToGainFactor(maxGainIncreaseDecibels)
|
|
@@ -99,12 +115,10 @@ export function normalizeAudioLevel(rawAudio: RawAudio, targetPeakDecibels = -3,
|
|
|
99
115
|
|
|
100
116
|
const gainFactor = Math.min(targetPeakAmplitude / peakAmplitude, maxGainFactor)
|
|
101
117
|
|
|
102
|
-
|
|
118
|
+
applyGainFactorInPlace(rawAudio, gainFactor)
|
|
103
119
|
}
|
|
104
120
|
|
|
105
|
-
export function
|
|
106
|
-
const outputAudioChannels: Float32Array[] = []
|
|
107
|
-
|
|
121
|
+
export function correctDCBiasInPlace(rawAudio: RawAudio) {
|
|
108
122
|
for (const channelSamples of rawAudio.audioChannels) {
|
|
109
123
|
const sampleCount = channelSamples.length
|
|
110
124
|
|
|
@@ -116,38 +130,36 @@ export function correctDCBias(rawAudio: RawAudio): RawAudio {
|
|
|
116
130
|
|
|
117
131
|
const sampleAverage = sampleSum / sampleCount
|
|
118
132
|
|
|
119
|
-
const outputChannelSamples = new Float32Array(sampleCount)
|
|
120
|
-
|
|
121
133
|
for (let i = 0; i < sampleCount; i++) {
|
|
122
|
-
|
|
134
|
+
channelSamples[i] -= sampleAverage
|
|
123
135
|
}
|
|
124
|
-
|
|
125
|
-
outputAudioChannels.push(outputChannelSamples)
|
|
126
136
|
}
|
|
137
|
+
}
|
|
127
138
|
|
|
128
|
-
|
|
139
|
+
export function applyGainDecibels(rawAudio: RawAudio, gainDecibels: number) {
|
|
140
|
+
const clonedRawAudio = cloneRawAudio(rawAudio)
|
|
141
|
+
|
|
142
|
+
applyGainDecibelsInPlace(clonedRawAudio, gainDecibels)
|
|
143
|
+
|
|
144
|
+
return clonedRawAudio
|
|
129
145
|
}
|
|
130
146
|
|
|
131
|
-
export function
|
|
132
|
-
|
|
147
|
+
export function applyGainDecibelsInPlace(rawAudio: RawAudio, gainDecibels: number) {
|
|
148
|
+
applyGainFactorInPlace(rawAudio, decibelsToGainFactor(gainDecibels))
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
export function
|
|
136
|
-
|
|
151
|
+
export function applyGainFactorInPlace(rawAudio: RawAudio, gainFactor: number) {
|
|
152
|
+
if (gainFactor === 1.0) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
137
155
|
|
|
138
156
|
for (const channelSamples of rawAudio.audioChannels) {
|
|
139
157
|
const sampleCount = channelSamples.length
|
|
140
158
|
|
|
141
|
-
const outputChannelSamples = new Float32Array(sampleCount)
|
|
142
|
-
|
|
143
159
|
for (let i = 0; i < sampleCount; i++) {
|
|
144
|
-
|
|
160
|
+
channelSamples[i] *= gainFactor
|
|
145
161
|
}
|
|
146
|
-
|
|
147
|
-
outputAudioChannels.push(outputChannelSamples)
|
|
148
162
|
}
|
|
149
|
-
|
|
150
|
-
return { audioChannels: outputAudioChannels, sampleRate: rawAudio.sampleRate } as RawAudio
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
export function downmixToMono(rawAudio: RawAudio): RawAudio {
|
|
@@ -183,8 +195,14 @@ export function getSamplePeakAmplitude(audioChannels: Float32Array[]) {
|
|
|
183
195
|
let maxAmplitude = 0.00001
|
|
184
196
|
|
|
185
197
|
for (const channelSamples of audioChannels) {
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
const sampleCount = channelSamples.length
|
|
199
|
+
|
|
200
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
201
|
+
const sampleAbsValue = Math.abs(channelSamples[i])
|
|
202
|
+
|
|
203
|
+
if (sampleAbsValue > maxAmplitude) {
|
|
204
|
+
maxAmplitude = sampleAbsValue
|
|
205
|
+
}
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
|
|
@@ -257,7 +275,7 @@ export function sliceAudioChannels(audioChannels: Float32Array[], startSampleInd
|
|
|
257
275
|
}
|
|
258
276
|
|
|
259
277
|
export function concatAudioSegments(audioSegments: Float32Array[][]) {
|
|
260
|
-
if (audioSegments.length
|
|
278
|
+
if (audioSegments.length === 0) {
|
|
261
279
|
return []
|
|
262
280
|
}
|
|
263
281
|
|
|
@@ -317,7 +335,9 @@ export function fadeAudioInOut(rawAudio: RawAudio, fadeTime: number): RawAudio {
|
|
|
317
335
|
const outAudioChannels = rawAudio.audioChannels.map(channel => channel.slice())
|
|
318
336
|
|
|
319
337
|
for (const channel of outAudioChannels) {
|
|
320
|
-
|
|
338
|
+
const sampleCount = channel.length
|
|
339
|
+
|
|
340
|
+
if (sampleCount < fadeSampleCount * 2) {
|
|
321
341
|
continue
|
|
322
342
|
}
|
|
323
343
|
|
|
@@ -331,7 +351,7 @@ export function fadeAudioInOut(rawAudio: RawAudio, fadeTime: number): RawAudio {
|
|
|
331
351
|
|
|
332
352
|
factor = 1.0
|
|
333
353
|
|
|
334
|
-
for (let i =
|
|
354
|
+
for (let i = sampleCount - fadeSampleCount; i < sampleCount; i++) {
|
|
335
355
|
channel[i] *= factor
|
|
336
356
|
|
|
337
357
|
factor *= gainReductionPerFrameFactor
|
package/src/dsp/FFT.ts
CHANGED
|
@@ -2,8 +2,6 @@ import { ComplexNumber } from '../math/VectorMath.js'
|
|
|
2
2
|
import { concatFloat32Arrays } from '../utilities/Utilities.js'
|
|
3
3
|
import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
|
|
4
4
|
|
|
5
|
-
let kissFFTInstance: any
|
|
6
|
-
|
|
7
5
|
// Compute short-term Fourier transform (real-valued)
|
|
8
6
|
export async function stftr(samples: Float32Array, fftOrder: number, windowSize: number, hopSize: number, windowType: WindowType) {
|
|
9
7
|
const frames: Float32Array[] = []
|
|
@@ -36,15 +34,18 @@ export async function* stftrGenerator(samples: Float32Array, fftOrder: number, w
|
|
|
36
34
|
|
|
37
35
|
const windowWeights = getWindowWeights(windowType, windowSize)
|
|
38
36
|
|
|
39
|
-
const m = await
|
|
40
|
-
const wasmMemory = new WasmMemoryManager(m
|
|
37
|
+
const m = await getPFFFTInstance(isPffftSimdSupportedForFFTOrder(fftOrder))
|
|
38
|
+
const wasmMemory = new WasmMemoryManager(m, {
|
|
39
|
+
wasmAlloc: m._pffft_aligned_malloc,
|
|
40
|
+
wasmFree: m._pffft_aligned_free
|
|
41
|
+
})
|
|
41
42
|
|
|
42
|
-
const statePtr = m.
|
|
43
|
-
wasmMemory.wrapPointer(statePtr)
|
|
43
|
+
const statePtr = m._pffft_new_setup(fftOrder, 0)
|
|
44
44
|
|
|
45
45
|
const sampleCount = samples.length
|
|
46
46
|
const frameBufferRef = wasmMemory.allocFloat32Array(fftOrder)
|
|
47
47
|
const binsBufferRef = wasmMemory.allocFloat32Array(fftOrder * 2)
|
|
48
|
+
const workBufferRef = wasmMemory.allocFloat32Array(fftOrder * 2)
|
|
48
49
|
|
|
49
50
|
for (let offset = 0; offset < sampleCount; offset += hopSize) {
|
|
50
51
|
const windowSamples = samples.subarray(offset, offset + windowSize)
|
|
@@ -58,13 +59,15 @@ export async function* stftrGenerator(samples: Float32Array, fftOrder: number, w
|
|
|
58
59
|
|
|
59
60
|
binsBufferRef.clear()
|
|
60
61
|
|
|
61
|
-
m.
|
|
62
|
+
m._pffft_transform_ordered(statePtr, frameBufferRef.address, binsBufferRef.address, workBufferRef.address, 0)
|
|
62
63
|
|
|
63
64
|
const bins = binsBufferRef.view.slice(0, fftOrder + 2)
|
|
64
65
|
|
|
65
66
|
yield bins
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
m._pffft_destroy_setup(statePtr)
|
|
70
|
+
|
|
68
71
|
wasmMemory.freeAll()
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -96,14 +99,18 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
|
|
|
96
99
|
|
|
97
100
|
const outSamples = new Float32Array(outSampleCount)
|
|
98
101
|
|
|
99
|
-
const m = await
|
|
100
|
-
const wasmMemory = new WasmMemoryManager(m)
|
|
102
|
+
const m = await getPFFFTInstance(isPffftSimdSupportedForFFTOrder(fftOrder))
|
|
101
103
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
+
const wasmMemory = new WasmMemoryManager(m, {
|
|
105
|
+
wasmAlloc: m._pffft_aligned_malloc,
|
|
106
|
+
wasmFree: m._pffft_aligned_free
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const statePtr = m._pffft_new_setup(fftOrder, 0)
|
|
104
110
|
|
|
105
111
|
const frameBufferRef = wasmMemory.allocFloat32Array(fftOrder)
|
|
106
112
|
const binsRef = wasmMemory.allocFloat32Array(fftOrder * 2)
|
|
113
|
+
const workBufferRef = wasmMemory.allocFloat32Array(fftOrder * 2)
|
|
107
114
|
|
|
108
115
|
const sumOfSquaredWeightsForSample = new Float32Array(outSampleCount)
|
|
109
116
|
|
|
@@ -114,7 +121,7 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
|
|
|
114
121
|
|
|
115
122
|
frameBufferRef.clear()
|
|
116
123
|
|
|
117
|
-
m.
|
|
124
|
+
m._pffft_transform_ordered(statePtr, binsRef.address, frameBufferRef.address, workBufferRef.address, 1)
|
|
118
125
|
|
|
119
126
|
const frameSamples = frameBufferRef.view
|
|
120
127
|
|
|
@@ -132,6 +139,7 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
|
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
m._pffft_destroy_setup(statePtr)
|
|
135
143
|
wasmMemory.freeAll()
|
|
136
144
|
|
|
137
145
|
// Divide each output sample by the sum of squared weights
|
|
@@ -247,15 +255,33 @@ export function getWindowWeights(windowType: WindowType, windowSize: number) {
|
|
|
247
255
|
return weights
|
|
248
256
|
}
|
|
249
257
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const { default: initializer } = await import('@echogarden/kissfft-wasm')
|
|
258
|
+
export function isPffftSimdSupportedForFFTOrder(fftOrder: number) {
|
|
259
|
+
return fftOrder % 32 === 0
|
|
260
|
+
}
|
|
254
261
|
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
let pffftNonSimdInstance: any
|
|
263
|
+
let pffftSimdInstance: any
|
|
264
|
+
|
|
265
|
+
export async function getPFFFTInstance(enableSimd: boolean) {
|
|
266
|
+
return enableSimd ? getSimdPFFFTInstance() : getNonSimdPFFFTInstance()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Get non-SIMD PFFFT instance (initialize new if not exists)
|
|
270
|
+
async function getNonSimdPFFFTInstance() {
|
|
271
|
+
const { default: initializer } = await import('@echogarden/pffft-wasm')
|
|
272
|
+
|
|
273
|
+
pffftNonSimdInstance = await initializer()
|
|
274
|
+
|
|
275
|
+
return pffftNonSimdInstance
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Get SIMD PFFFT instance (initialize new if not exists)
|
|
279
|
+
async function getSimdPFFFTInstance() {
|
|
280
|
+
const { default: initializer } = await import('@echogarden/pffft-wasm/simd')
|
|
281
|
+
|
|
282
|
+
pffftSimdInstance = await initializer()
|
|
257
283
|
|
|
258
|
-
return
|
|
284
|
+
return pffftSimdInstance
|
|
259
285
|
}
|
|
260
286
|
|
|
261
287
|
export type WindowType = 'hann' | 'hamming' | 'povey'
|
|
@@ -459,7 +459,7 @@ export class Whisper {
|
|
|
459
459
|
// Find alignment path
|
|
460
460
|
let alignmentHeads: number[] | undefined = undefined
|
|
461
461
|
|
|
462
|
-
if (
|
|
462
|
+
if (options.useOptimizedAlignmentHeads) {
|
|
463
463
|
alignmentHeads = this.alignmentHeadIndexes
|
|
464
464
|
}
|
|
465
465
|
|
|
@@ -566,12 +566,15 @@ export class Whisper {
|
|
|
566
566
|
topCandidateCount: 1,
|
|
567
567
|
punctuationThreshold: Infinity,
|
|
568
568
|
autoPromptParts: false,
|
|
569
|
-
maxTokensPerPart:
|
|
569
|
+
maxTokensPerPart: whisperAlignmentOptions.maxTokensPerPart!,
|
|
570
570
|
suppressRepetition: false,
|
|
571
571
|
repetitionThreshold: Infinity,
|
|
572
572
|
decodeTimestampTokens: true,
|
|
573
|
-
endTokenThreshold: whisperAlignmentOptions
|
|
573
|
+
endTokenThreshold: whisperAlignmentOptions.endTokenThreshold!,
|
|
574
574
|
includeEndTokenInCandidates: false,
|
|
575
|
+
useOptimizedAlignmentHeads: whisperAlignmentOptions.useOptimizedAlignmentHeads!,
|
|
576
|
+
encoderProvider: whisperAlignmentOptions.encoderProvider!,
|
|
577
|
+
decoderProvider: whisperAlignmentOptions.decoderProvider!,
|
|
575
578
|
seed: undefined,
|
|
576
579
|
}
|
|
577
580
|
|
|
@@ -1103,7 +1106,8 @@ export class Whisper {
|
|
|
1103
1106
|
const sampleRate = rawAudio.sampleRate
|
|
1104
1107
|
|
|
1105
1108
|
const fftOrder = 400
|
|
1106
|
-
const
|
|
1109
|
+
const fftWindowSize = 400
|
|
1110
|
+
const fftHopLength = 160
|
|
1107
1111
|
|
|
1108
1112
|
const filterbankCount = this.filterbankCount
|
|
1109
1113
|
const filterbanks = this.filterbanks
|
|
@@ -1128,7 +1132,7 @@ export class Whisper {
|
|
|
1128
1132
|
|
|
1129
1133
|
const rawAudioPart: RawAudio = { audioChannels: [paddedAudioSamples], sampleRate }
|
|
1130
1134
|
|
|
1131
|
-
const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder,
|
|
1135
|
+
const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder, fftWindowSize, fftHopLength, filterbanks)
|
|
1132
1136
|
|
|
1133
1137
|
await logger.startAsync('Normalize mel spectogram')
|
|
1134
1138
|
|
|
@@ -2440,6 +2444,7 @@ export interface WhisperOptions {
|
|
|
2440
2444
|
decodeTimestampTokens?: boolean
|
|
2441
2445
|
endTokenThreshold?: number
|
|
2442
2446
|
includeEndTokenInCandidates?: boolean
|
|
2447
|
+
useOptimizedAlignmentHeads?: boolean,
|
|
2443
2448
|
encoderProvider?: OnnxExecutionProvider
|
|
2444
2449
|
decoderProvider?: OnnxExecutionProvider
|
|
2445
2450
|
seed?: number
|
|
@@ -2458,6 +2463,7 @@ export const defaultWhisperOptions: WhisperOptions = {
|
|
|
2458
2463
|
decodeTimestampTokens: true,
|
|
2459
2464
|
endTokenThreshold: 0.9,
|
|
2460
2465
|
includeEndTokenInCandidates: true,
|
|
2466
|
+
useOptimizedAlignmentHeads: true,
|
|
2461
2467
|
encoderProvider: undefined,
|
|
2462
2468
|
decoderProvider: undefined,
|
|
2463
2469
|
seed: undefined,
|
|
@@ -2467,6 +2473,9 @@ export const defaultWhisperOptions: WhisperOptions = {
|
|
|
2467
2473
|
export interface WhisperAlignmentOptions {
|
|
2468
2474
|
model?: WhisperModelName
|
|
2469
2475
|
endTokenThreshold?: number
|
|
2476
|
+
maxTokensPerPart?: number
|
|
2477
|
+
useOptimizedAlignmentHeads?: boolean
|
|
2478
|
+
|
|
2470
2479
|
encoderProvider?: OnnxExecutionProvider
|
|
2471
2480
|
decoderProvider?: OnnxExecutionProvider
|
|
2472
2481
|
}
|
|
@@ -2474,8 +2483,11 @@ export interface WhisperAlignmentOptions {
|
|
|
2474
2483
|
export const defaultWhisperAlignmentOptions: WhisperAlignmentOptions = {
|
|
2475
2484
|
model: undefined,
|
|
2476
2485
|
endTokenThreshold: 0.9,
|
|
2486
|
+
maxTokensPerPart: 250,
|
|
2487
|
+
useOptimizedAlignmentHeads: true,
|
|
2488
|
+
|
|
2477
2489
|
encoderProvider: undefined,
|
|
2478
|
-
decoderProvider: undefined
|
|
2490
|
+
decoderProvider: undefined,
|
|
2479
2491
|
}
|
|
2480
2492
|
|
|
2481
2493
|
// Language detection options
|
|
@@ -6,6 +6,7 @@ import { Logger } from '../utilities/Logger.js'
|
|
|
6
6
|
import { OnnxExecutionProvider, dmlProviderAvailable, getOnnxSessionOptions } from '../utilities/OnnxUtilities.js'
|
|
7
7
|
import chalk from 'chalk'
|
|
8
8
|
import { WindowedList } from '../utilities/WindowedList.js'
|
|
9
|
+
import { logLevelGreaterOrEqualTo } from '../api/API.js'
|
|
9
10
|
|
|
10
11
|
const log = logToStderr
|
|
11
12
|
|
|
@@ -13,9 +14,9 @@ export async function isolate(
|
|
|
13
14
|
rawAudio: RawAudio,
|
|
14
15
|
modelFilePath: string,
|
|
15
16
|
modelProfile: MDXNetModelProfile,
|
|
16
|
-
|
|
17
|
+
options: MDXNetOptions) {
|
|
17
18
|
|
|
18
|
-
const model = new MDXNet(modelFilePath, modelProfile,
|
|
19
|
+
const model = new MDXNet(modelFilePath, modelProfile, options)
|
|
19
20
|
|
|
20
21
|
return model.processAudio(rawAudio)
|
|
21
22
|
}
|
|
@@ -27,11 +28,11 @@ export class MDXNet {
|
|
|
27
28
|
constructor(
|
|
28
29
|
public readonly modelFilePath: string,
|
|
29
30
|
public readonly modelProfile: MDXNetModelProfile,
|
|
30
|
-
public readonly
|
|
31
|
+
public readonly options: MDXNetOptions) {
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
async processAudio(rawAudio: RawAudio) {
|
|
34
|
-
if (rawAudio.audioChannels.length
|
|
35
|
+
if (rawAudio.audioChannels.length !== 2) {
|
|
35
36
|
throw new Error(`Input audio must be stereo`)
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -43,12 +44,18 @@ export class MDXNet {
|
|
|
43
44
|
return getEmptyRawAudio(rawAudio.audioChannels.length, rawAudio.sampleRate)
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
const enableTraceLogging = logLevelGreaterOrEqualTo('trace')
|
|
48
|
+
|
|
46
49
|
const logger = new Logger()
|
|
47
50
|
|
|
48
|
-
await logger.startAsync(`Initialize
|
|
51
|
+
await logger.startAsync(`Initialize session for MDX-NET model '${this.options.model!}'`)
|
|
49
52
|
|
|
50
53
|
await this.initializeSessionIfNeeded()
|
|
51
54
|
|
|
55
|
+
logger.end()
|
|
56
|
+
|
|
57
|
+
logger.logTitledMessage(`Using ONNX execution provider`, `${this.onnxSessionOptions!.executionProviders!.join(', ')}`)
|
|
58
|
+
|
|
52
59
|
const Onnx = await import('onnxruntime-node')
|
|
53
60
|
|
|
54
61
|
const sampleRate = this.modelProfile.sampleRate
|
|
@@ -81,7 +88,11 @@ export class MDXNet {
|
|
|
81
88
|
|
|
82
89
|
const timePosition = segmentStartFrameOffset * (fftHopSize / sampleRate)
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
if (enableTraceLogging) {
|
|
92
|
+
await logger.startAsync(`Compute STFT of segment at time position ${timePosition.toFixed(2)}`, undefined, chalk.magentaBright)
|
|
93
|
+
} else {
|
|
94
|
+
await logger.startAsync(`Process segment at time position ${timePosition.toFixed(2)}`)
|
|
95
|
+
}
|
|
85
96
|
|
|
86
97
|
while (fftFramesLeftWindowedList.endOffset < segmentEndFrameOffset) {
|
|
87
98
|
const nextLeftFrameResult = await fftFramesLeftGenerator.next()
|
|
@@ -109,7 +120,9 @@ export class MDXNet {
|
|
|
109
120
|
|
|
110
121
|
const isLastSegment = segmentLength < segmentSize
|
|
111
122
|
|
|
112
|
-
|
|
123
|
+
if (enableTraceLogging) {
|
|
124
|
+
await logger.startAsync(`Reshape STFT frames`)
|
|
125
|
+
}
|
|
113
126
|
|
|
114
127
|
const flattenedInputTensor = new Float32Array(1 * 4 * binCount * segmentSize)
|
|
115
128
|
|
|
@@ -142,13 +155,17 @@ export class MDXNet {
|
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
157
|
|
|
145
|
-
|
|
158
|
+
if (enableTraceLogging) {
|
|
159
|
+
await logger.startAsync(`Process segment with MDXNet model`)
|
|
160
|
+
}
|
|
146
161
|
|
|
147
162
|
const inputTensor = new Onnx.Tensor('float32', flattenedInputTensor, [1, 4, binCount, segmentSize])
|
|
148
163
|
|
|
149
164
|
const { output: outputTensor } = await this.session!.run({ input: inputTensor })
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
if (enableTraceLogging) {
|
|
167
|
+
await logger.startAsync('Reshape processed frames')
|
|
168
|
+
}
|
|
152
169
|
|
|
153
170
|
const flattenedOutputTensor = outputTensor.data as Float32Array
|
|
154
171
|
|
|
@@ -191,7 +208,9 @@ export class MDXNet {
|
|
|
191
208
|
|
|
192
209
|
const outputAudioChannels: Float32Array[] = []
|
|
193
210
|
|
|
194
|
-
|
|
211
|
+
if (enableTraceLogging) {
|
|
212
|
+
await logger.startAsync(`Compute inverse STFT of model output for segment`)
|
|
213
|
+
}
|
|
195
214
|
|
|
196
215
|
for (let channelIndex = 0; channelIndex < 2; channelIndex++) {
|
|
197
216
|
const samples = await stiftr(
|
|
@@ -268,13 +287,16 @@ export class MDXNet {
|
|
|
268
287
|
|
|
269
288
|
const Onnx = await import('onnxruntime-node')
|
|
270
289
|
|
|
271
|
-
|
|
290
|
+
const executionProviders: OnnxExecutionProvider[] =
|
|
291
|
+
this.options.provider ? [this.options.provider] : getDefaultMDXNetProviders()
|
|
292
|
+
|
|
293
|
+
this.onnxSessionOptions = getOnnxSessionOptions({ executionProviders })
|
|
272
294
|
|
|
273
295
|
this.session = await Onnx.InferenceSession.create(this.modelFilePath, this.onnxSessionOptions)
|
|
274
296
|
}
|
|
275
297
|
}
|
|
276
298
|
|
|
277
|
-
export function getDefaultMDXNetProviders()
|
|
299
|
+
export function getDefaultMDXNetProviders(): OnnxExecutionProvider[] {
|
|
278
300
|
if (dmlProviderAvailable()) {
|
|
279
301
|
return ['dml', 'cpu']
|
|
280
302
|
} else {
|
|
@@ -344,3 +366,13 @@ export type MDXNetModelName =
|
|
|
344
366
|
'UVR_MDXNET_Main' |
|
|
345
367
|
'Kim_Vocal_1' |
|
|
346
368
|
'Kim_Vocal_2'
|
|
369
|
+
|
|
370
|
+
export interface MDXNetOptions {
|
|
371
|
+
model?: MDXNetModelName
|
|
372
|
+
provider?: OnnxExecutionProvider
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export const defaultMDXNetOptions: MDXNetOptions = {
|
|
376
|
+
model: 'UVR_MDXNET_1_9703',
|
|
377
|
+
provider: undefined,
|
|
378
|
+
}
|
package/src/typings/Fillers.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ declare module '@echogarden/rnnoise-wasm'
|
|
|
56
56
|
declare module '@echogarden/fvad-wasm'
|
|
57
57
|
declare module '@echogarden/sonic-wasm'
|
|
58
58
|
declare module '@echogarden/kissfft-wasm'
|
|
59
|
+
declare module '@echogarden/pffft-wasm'
|
|
60
|
+
declare module '@echogarden/pffft-wasm/simd'
|
|
59
61
|
declare module '@echogarden/speex-resampler-wasm'
|
|
60
62
|
declare module '@echogarden/vosk'
|
|
61
63
|
|
|
@@ -192,13 +192,9 @@ const packageVersionTagResolutionLookup: { [packageName: string]: string } = {
|
|
|
192
192
|
'whisper.cpp-large-v3-turbo-q5_0': '20241003',
|
|
193
193
|
|
|
194
194
|
// whisper.cpp binaries
|
|
195
|
-
'whisper.cpp-binaries-linux-x64-cpu-
|
|
196
|
-
'whisper.cpp-binaries-windows-x64-cpu-
|
|
197
|
-
'whisper.cpp-binaries-windows-x64-cublas-12.4.0-
|
|
198
|
-
|
|
199
|
-
'whisper.cpp-binaries-linux-x64-cpu-latest': '20241003',
|
|
200
|
-
'whisper.cpp-binaries-windows-x64-cpu-latest': '20241003',
|
|
201
|
-
'whisper.cpp-binaries-windows-x64-cublas-12.4.0-latest': '20241003',
|
|
195
|
+
'whisper.cpp-binaries-linux-x64-cpu-latest': '20241010',
|
|
196
|
+
'whisper.cpp-binaries-windows-x64-cpu-latest': '20241010',
|
|
197
|
+
'whisper.cpp-binaries-windows-x64-cublas-12.4.0-latest': '20241010',
|
|
202
198
|
|
|
203
199
|
// E5 models
|
|
204
200
|
'xenova-multilingual-e5-small-q8': '20240504',
|