echogarden 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +5 -0
- package/dist/recognition/WhisperSTT.js +47 -7
- 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 +66 -8
- 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
|
|
|
@@ -497,7 +497,7 @@ export class Whisper {
|
|
|
497
497
|
|
|
498
498
|
logger.end()
|
|
499
499
|
|
|
500
|
-
return { transcript, timeline }
|
|
500
|
+
return { transcript, timeline, allDecodedTokens }
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
async align(rawAudio: RawAudio, transcript: string, sourceLanguage: string, task: 'transcribe' | 'translate', whisperAlignmentOptions: WhisperAlignmentOptions) {
|
|
@@ -523,8 +523,10 @@ export class Whisper {
|
|
|
523
523
|
}
|
|
524
524
|
} else {
|
|
525
525
|
let words = await splitToWords(transcript, targetLanguage)
|
|
526
|
+
|
|
526
527
|
words = words.map(word => word.trim())
|
|
527
528
|
words = words.filter(word => isWord(word))
|
|
529
|
+
|
|
528
530
|
simplifiedTranscript = words.join(' ')
|
|
529
531
|
}
|
|
530
532
|
|
|
@@ -566,17 +568,64 @@ export class Whisper {
|
|
|
566
568
|
topCandidateCount: 1,
|
|
567
569
|
punctuationThreshold: Infinity,
|
|
568
570
|
autoPromptParts: false,
|
|
569
|
-
maxTokensPerPart:
|
|
571
|
+
maxTokensPerPart: whisperAlignmentOptions.maxTokensPerPart!,
|
|
570
572
|
suppressRepetition: false,
|
|
571
573
|
repetitionThreshold: Infinity,
|
|
572
574
|
decodeTimestampTokens: true,
|
|
573
|
-
endTokenThreshold: whisperAlignmentOptions
|
|
575
|
+
endTokenThreshold: whisperAlignmentOptions.endTokenThreshold!,
|
|
574
576
|
includeEndTokenInCandidates: false,
|
|
577
|
+
useOptimizedAlignmentHeads: whisperAlignmentOptions.useOptimizedAlignmentHeads!,
|
|
578
|
+
encoderProvider: whisperAlignmentOptions.encoderProvider!,
|
|
579
|
+
decoderProvider: whisperAlignmentOptions.decoderProvider!,
|
|
575
580
|
seed: undefined,
|
|
576
581
|
}
|
|
577
582
|
|
|
578
583
|
// Recognize
|
|
579
|
-
const { timeline } = await this.recognize(rawAudio, task, sourceLanguage, options, logitFilter)
|
|
584
|
+
const { timeline, allDecodedTokens } = await this.recognize(rawAudio, task, sourceLanguage, options, logitFilter)
|
|
585
|
+
|
|
586
|
+
{
|
|
587
|
+
// If not all tokens were decoded, add the remaining ones to the timeline
|
|
588
|
+
const lastKnownWordStartTime = timeline.length > 0 ? timeline[timeline.length - 1].startTime : 0
|
|
589
|
+
|
|
590
|
+
const allDecodedTextTokens = allDecodedTokens.filter(token => this.isTextToken(token))
|
|
591
|
+
|
|
592
|
+
while (allDecodedTextTokens.length < simplifiedTranscriptTokens.length) {
|
|
593
|
+
const token = simplifiedTranscriptTokens[allDecodedTextTokens.length]
|
|
594
|
+
const tokenText = this.tokenToText(token)
|
|
595
|
+
|
|
596
|
+
allDecodedTextTokens.push(token)
|
|
597
|
+
|
|
598
|
+
const newTokenEntry: TimelineEntry = {
|
|
599
|
+
type: 'token',
|
|
600
|
+
text: tokenText,
|
|
601
|
+
|
|
602
|
+
startTime: lastKnownWordStartTime,
|
|
603
|
+
endTime: lastKnownWordStartTime,
|
|
604
|
+
|
|
605
|
+
id: token,
|
|
606
|
+
confidence: 0,
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (tokenText.startsWith(' ') || timeline.length === 0) {
|
|
610
|
+
timeline.push({
|
|
611
|
+
type: 'word',
|
|
612
|
+
text: tokenText.trim(),
|
|
613
|
+
|
|
614
|
+
startTime: lastKnownWordStartTime,
|
|
615
|
+
endTime: lastKnownWordStartTime,
|
|
616
|
+
|
|
617
|
+
timeline: [newTokenEntry],
|
|
618
|
+
|
|
619
|
+
confidence: 0,
|
|
620
|
+
})
|
|
621
|
+
} else {
|
|
622
|
+
const lastWordEntry = timeline[timeline.length - 1]
|
|
623
|
+
|
|
624
|
+
lastWordEntry.timeline!.push(newTokenEntry)
|
|
625
|
+
lastWordEntry.text += tokenText
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
580
629
|
|
|
581
630
|
return timeline
|
|
582
631
|
}
|
|
@@ -1103,7 +1152,8 @@ export class Whisper {
|
|
|
1103
1152
|
const sampleRate = rawAudio.sampleRate
|
|
1104
1153
|
|
|
1105
1154
|
const fftOrder = 400
|
|
1106
|
-
const
|
|
1155
|
+
const fftWindowSize = 400
|
|
1156
|
+
const fftHopLength = 160
|
|
1107
1157
|
|
|
1108
1158
|
const filterbankCount = this.filterbankCount
|
|
1109
1159
|
const filterbanks = this.filterbanks
|
|
@@ -1128,7 +1178,7 @@ export class Whisper {
|
|
|
1128
1178
|
|
|
1129
1179
|
const rawAudioPart: RawAudio = { audioChannels: [paddedAudioSamples], sampleRate }
|
|
1130
1180
|
|
|
1131
|
-
const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder,
|
|
1181
|
+
const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder, fftWindowSize, fftHopLength, filterbanks)
|
|
1132
1182
|
|
|
1133
1183
|
await logger.startAsync('Normalize mel spectogram')
|
|
1134
1184
|
|
|
@@ -2440,6 +2490,7 @@ export interface WhisperOptions {
|
|
|
2440
2490
|
decodeTimestampTokens?: boolean
|
|
2441
2491
|
endTokenThreshold?: number
|
|
2442
2492
|
includeEndTokenInCandidates?: boolean
|
|
2493
|
+
useOptimizedAlignmentHeads?: boolean,
|
|
2443
2494
|
encoderProvider?: OnnxExecutionProvider
|
|
2444
2495
|
decoderProvider?: OnnxExecutionProvider
|
|
2445
2496
|
seed?: number
|
|
@@ -2458,6 +2509,7 @@ export const defaultWhisperOptions: WhisperOptions = {
|
|
|
2458
2509
|
decodeTimestampTokens: true,
|
|
2459
2510
|
endTokenThreshold: 0.9,
|
|
2460
2511
|
includeEndTokenInCandidates: true,
|
|
2512
|
+
useOptimizedAlignmentHeads: true,
|
|
2461
2513
|
encoderProvider: undefined,
|
|
2462
2514
|
decoderProvider: undefined,
|
|
2463
2515
|
seed: undefined,
|
|
@@ -2467,6 +2519,9 @@ export const defaultWhisperOptions: WhisperOptions = {
|
|
|
2467
2519
|
export interface WhisperAlignmentOptions {
|
|
2468
2520
|
model?: WhisperModelName
|
|
2469
2521
|
endTokenThreshold?: number
|
|
2522
|
+
maxTokensPerPart?: number
|
|
2523
|
+
useOptimizedAlignmentHeads?: boolean
|
|
2524
|
+
|
|
2470
2525
|
encoderProvider?: OnnxExecutionProvider
|
|
2471
2526
|
decoderProvider?: OnnxExecutionProvider
|
|
2472
2527
|
}
|
|
@@ -2474,8 +2529,11 @@ export interface WhisperAlignmentOptions {
|
|
|
2474
2529
|
export const defaultWhisperAlignmentOptions: WhisperAlignmentOptions = {
|
|
2475
2530
|
model: undefined,
|
|
2476
2531
|
endTokenThreshold: 0.9,
|
|
2532
|
+
maxTokensPerPart: 250,
|
|
2533
|
+
useOptimizedAlignmentHeads: true,
|
|
2534
|
+
|
|
2477
2535
|
encoderProvider: undefined,
|
|
2478
|
-
decoderProvider: undefined
|
|
2536
|
+
decoderProvider: undefined,
|
|
2479
2537
|
}
|
|
2480
2538
|
|
|
2481
2539
|
// Language detection options
|