echogarden 1.0.1 → 1.0.3
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 +3 -3
- package/dist/alignment/SpeechAlignment.d.ts +1 -1
- package/dist/alignment/SpeechAlignment.js +2 -1
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/API.d.ts +1 -1
- package/dist/api/API.js +1 -1
- package/dist/api/API.js.map +1 -1
- package/dist/api/Alignment.d.ts +1 -1
- package/dist/api/Alignment.js +11 -7
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/LanguageDetection.d.ts +1 -1
- package/dist/api/LanguageDetection.js +1 -1
- package/dist/api/LanguageDetection.js.map +1 -1
- package/dist/api/Recognition.d.ts +1 -1
- package/dist/api/Synthesis.d.ts +2 -2
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.d.ts +3 -3
- package/dist/api/Translation.js.map +1 -1
- package/dist/api/{Vad.js → VoiceActivityDetection.js} +1 -1
- package/dist/api/VoiceActivityDetection.js.map +1 -0
- package/dist/dsp/FFT.js +1 -1
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/nlp/Segmentation.d.ts +2 -0
- package/dist/nlp/Segmentation.js +8 -3
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/recognition/SileroSTT.js +1 -1
- package/dist/recognition/SileroSTT.js.map +1 -1
- package/dist/recognition/WhisperCppSTT.js +3 -2
- package/dist/recognition/WhisperCppSTT.js.map +1 -1
- package/dist/recognition/WhisperSTT.d.ts +13 -4
- package/dist/recognition/WhisperSTT.js +113 -32
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.js +2 -1
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js +2 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js.map +1 -1
- package/dist/subtitles/Subtitles.js +2 -2
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/VitsTTS.d.ts +2 -2
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/tests/Test.js +1 -1
- package/dist/tests/Test.js.map +1 -1
- package/dist/voice-activity-detection/SileroVAD.d.ts +5 -5
- package/dist/voice-activity-detection/SileroVAD.js +13 -12
- package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
- package/docs/CLI.md +7 -0
- package/docs/Development.md +1 -1
- package/docs/Engines.md +1 -1
- package/docs/Options.md +4 -10
- package/docs/Releases.md +41 -30
- package/docs/Tasklist.md +5 -2
- package/package.json +8 -8
- package/src/alignment/SpeechAlignment.ts +2 -1
- package/src/api/API.ts +1 -1
- package/src/api/Alignment.ts +15 -9
- package/src/api/LanguageDetection.ts +2 -2
- package/src/api/Recognition.ts +1 -1
- package/src/api/Synthesis.ts +3 -3
- package/src/api/Translation.ts +3 -3
- package/src/dsp/FFT.ts +1 -1
- package/src/nlp/Segmentation.ts +11 -3
- package/src/recognition/SileroSTT.ts +3 -1
- package/src/recognition/WhisperCppSTT.ts +3 -2
- package/src/recognition/WhisperSTT.ts +154 -45
- package/src/source-separation/MDXNetSourceSeparation.ts +5 -1
- package/src/speech-language-detection/SileroLanguageDetection.ts +5 -1
- package/src/subtitles/Subtitles.ts +1 -1
- package/src/synthesis/VitsTTS.ts +3 -3
- package/src/tests/Test.ts +5 -4
- package/src/voice-activity-detection/SileroVAD.ts +23 -19
- package/dist/api/Vad.js.map +0 -1
- /package/dist/api/{Vad.d.ts → VoiceActivityDetection.d.ts} +0 -0
- /package/src/api/{Vad.ts → VoiceActivityDetection.ts} +0 -0
package/src/api/Synthesis.ts
CHANGED
|
@@ -22,8 +22,8 @@ import { shouldCancelCurrentTask } from '../server/Worker.js'
|
|
|
22
22
|
import chalk from 'chalk'
|
|
23
23
|
import { SubtitlesConfig, defaultSubtitlesBaseConfig } from '../subtitles/Subtitles.js'
|
|
24
24
|
import { type EspeakOptions } from '../synthesis/EspeakTTS.js'
|
|
25
|
-
import { OpenAICloudTTSOptions, defaultOpenAICloudTTSOptions } from '../synthesis/OpenAICloudTTS.js'
|
|
26
|
-
import { ElevenlabsTTSOptions, defaultElevenlabsTTSOptions } from '../synthesis/ElevenlabsTTS.js'
|
|
25
|
+
import { type OpenAICloudTTSOptions, defaultOpenAICloudTTSOptions } from '../synthesis/OpenAICloudTTS.js'
|
|
26
|
+
import { type ElevenlabsTTSOptions, defaultElevenlabsTTSOptions } from '../synthesis/ElevenlabsTTS.js'
|
|
27
27
|
|
|
28
28
|
const log = logToStderr
|
|
29
29
|
|
|
@@ -1036,7 +1036,7 @@ export interface SynthesisOptions {
|
|
|
1036
1036
|
rate?: number
|
|
1037
1037
|
pitch?: number
|
|
1038
1038
|
pitchRange?: number
|
|
1039
|
-
|
|
1039
|
+
|
|
1040
1040
|
useKlatt?: boolean
|
|
1041
1041
|
insertSeparators?: boolean
|
|
1042
1042
|
}
|
package/src/api/Translation.ts
CHANGED
|
@@ -8,13 +8,13 @@ import { Timeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTi
|
|
|
8
8
|
import { defaultWhisperOptions, type WhisperOptions } from '../recognition/WhisperSTT.js'
|
|
9
9
|
import { formatLanguageCodeWithName, getShortLanguageCode, normalizeLanguageCode } from '../utilities/Locale.js'
|
|
10
10
|
import { EngineMetadata } from './Common.js'
|
|
11
|
-
import { SpeechLanguageDetectionOptions, detectSpeechLanguage } from './API.js'
|
|
11
|
+
import { type SpeechLanguageDetectionOptions, detectSpeechLanguage } from './API.js'
|
|
12
12
|
import chalk from 'chalk'
|
|
13
13
|
import { SubtitlesConfig, defaultSubtitlesBaseConfig } from '../subtitles/Subtitles.js'
|
|
14
14
|
|
|
15
15
|
import * as API from './API.js'
|
|
16
|
-
import { OpenAICloudSTTOptions, defaultOpenAICloudSTTOptions } from '../recognition/OpenAICloudSTT.js'
|
|
17
|
-
import { WhisperCppOptions, defaultWhisperCppOptions } from '../recognition/WhisperCppSTT.js'
|
|
16
|
+
import { type OpenAICloudSTTOptions, defaultOpenAICloudSTTOptions } from '../recognition/OpenAICloudSTT.js'
|
|
17
|
+
import { type WhisperCppOptions, defaultWhisperCppOptions } from '../recognition/WhisperCppSTT.js'
|
|
18
18
|
|
|
19
19
|
const log = logToStderr
|
|
20
20
|
|
package/src/dsp/FFT.ts
CHANGED
|
@@ -122,7 +122,7 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
|
|
|
122
122
|
|
|
123
123
|
wasmMemory.freeAll()
|
|
124
124
|
|
|
125
|
-
// Divide by sum of
|
|
125
|
+
// Divide each output sample by the sum of squared weights
|
|
126
126
|
for (let i = 0; i < outSamples.length; i++) {
|
|
127
127
|
outSamples[i] /= sumOfSquaredWeightsForSample[i] + 1e-8
|
|
128
128
|
}
|
package/src/nlp/Segmentation.ts
CHANGED
|
@@ -10,6 +10,7 @@ const log = logToStderr
|
|
|
10
10
|
|
|
11
11
|
export const wordCharacterPattern = /[\p{Letter}\p{Number}]/u
|
|
12
12
|
export const punctuationPattern = /[\p{Punctuation}]/u
|
|
13
|
+
|
|
13
14
|
export const phraseSeparators = [',', ';', ':']
|
|
14
15
|
export const sentenceSeparators = ['.', '?', '!']
|
|
15
16
|
export const symbolWords = ['$', '€', '¢', '£', '¥', '©', '®', '™', '%', '&', '#', '~', '@', '+', '±', '÷', '/', '*', '=', '¼', '½', '¾']
|
|
@@ -18,13 +19,20 @@ export function isWordOrSymbolWord(str: string) {
|
|
|
18
19
|
return isWord(str) || symbolWords.includes(str)
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export function isSymbolWord(str: string) {
|
|
23
|
+
return symbolWords.includes(str.trim())
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export function isWord(str: string) {
|
|
22
|
-
|
|
23
|
-
return wordCharacterPattern.test(str) || symbolWords.includes(str)
|
|
27
|
+
return wordCharacterPattern.test(str.trim())
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function isPunctuation(str: string) {
|
|
27
|
-
return punctuationPattern.test(str)
|
|
31
|
+
return punctuationPattern.test(str.trim())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isWhitespace(str: string) {
|
|
35
|
+
return str.trim().length === 0
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
export class Sentence {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { indexOfMax } from '../math/VectorMath.js'
|
|
2
2
|
import { wordCharacterPattern } from '../nlp/Segmentation.js'
|
|
3
|
-
import Onnx from 'onnxruntime-node'
|
|
4
3
|
import { Logger } from '../utilities/Logger.js'
|
|
5
4
|
import { logToStderr } from '../utilities/Utilities.js'
|
|
6
5
|
import { Timeline } from '../utilities/Timeline.js'
|
|
7
6
|
import { RawAudio, getRawAudioDuration } from '../audio/AudioUtilities.js'
|
|
8
7
|
import { readAndParseJsonFile, readFile } from '../utilities/FileSystem.js'
|
|
8
|
+
import type * as Onnx from 'onnxruntime-node'
|
|
9
9
|
import path from 'path'
|
|
10
10
|
|
|
11
11
|
const log = logToStderr
|
|
@@ -14,6 +14,8 @@ export async function recognize(rawAudio: RawAudio, modelDirectory: string) {
|
|
|
14
14
|
const logger = new Logger()
|
|
15
15
|
logger.start('Create ONNX inference session')
|
|
16
16
|
|
|
17
|
+
const Onnx = await import('onnxruntime-node')
|
|
18
|
+
|
|
17
19
|
const modelPath = path.join(modelDirectory, 'model.onnx')
|
|
18
20
|
const labelsPath = path.join(modelDirectory, 'labels.json')
|
|
19
21
|
|
|
@@ -288,13 +288,14 @@ async function parseResultObject(resultObject: WhisperCppVerboseResult, modelNam
|
|
|
288
288
|
|
|
289
289
|
const allTokenIds = tokenTimeline.map(entry => entry.id!)
|
|
290
290
|
const transcript = whisper.tokensToText(allTokenIds).trim()
|
|
291
|
+
const language = resultObject.result.language
|
|
291
292
|
|
|
292
|
-
|
|
293
|
+
const timeline = whisper.tokenTimelineToWordTimeline(tokenTimeline, language)
|
|
293
294
|
|
|
294
295
|
return {
|
|
295
296
|
transcript,
|
|
296
297
|
timeline,
|
|
297
|
-
language
|
|
298
|
+
language
|
|
298
299
|
}
|
|
299
300
|
}
|
|
300
301
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import Onnx from 'onnxruntime-node'
|
|
1
|
+
import type * as Onnx from 'onnxruntime-node'
|
|
2
2
|
|
|
3
3
|
import { Logger } from '../utilities/Logger.js'
|
|
4
4
|
import { computeMelSpectogramUsingFilterbanks, Filterbank } from '../dsp/MelSpectogram.js'
|
|
5
|
-
import { clip, getIntegerRange, getRepetitionScoreRelativeToFirstSubstring, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js'
|
|
5
|
+
import { clip, getIntegerRange, getRepetitionScoreRelativeToFirstSubstring, getUTF32Chars, logToStderr, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js'
|
|
6
6
|
import { indexOfMax, logOfVector, logSumExp, meanOfVector, medianFilter, softmax, stdDeviationOfVector } from '../math/VectorMath.js'
|
|
7
7
|
|
|
8
8
|
import { alignDTWWindowed } from '../alignment/DTWSequenceAlignmentWindowed.js'
|
|
@@ -19,6 +19,7 @@ import chalk from 'chalk'
|
|
|
19
19
|
import { XorShift32RNG } from '../utilities/RandomGenerator.js'
|
|
20
20
|
import { detectSpeechLanguageByParts } from '../api/LanguageDetection.js'
|
|
21
21
|
import { type Tiktoken } from 'tiktoken/lite'
|
|
22
|
+
import { isPunctuation, isWhitespace } from '../nlp/Segmentation.js'
|
|
22
23
|
|
|
23
24
|
export async function recognize(sourceRawAudio: RawAudio, modelName: WhisperModelName, modelDir: string, task: WhisperTask, sourceLanguage: string, options: WhisperOptions) {
|
|
24
25
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
@@ -134,8 +135,6 @@ export class Whisper {
|
|
|
134
135
|
|
|
135
136
|
timestampTokensStart: number
|
|
136
137
|
timestampTokensEnd: number
|
|
137
|
-
|
|
138
|
-
suppressedTokens: Set<number>
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
randomGen: XorShift32RNG
|
|
@@ -162,16 +161,7 @@ export class Whisper {
|
|
|
162
161
|
|
|
163
162
|
timestampTokensStart: 50364,
|
|
164
163
|
timestampTokensEnd: 50364 + 1501,
|
|
165
|
-
|
|
166
|
-
suppressedTokens: new Set()
|
|
167
164
|
}
|
|
168
|
-
|
|
169
|
-
const suppressedTextTokens = [1, 2, 6, 7, 8, 9, 10, 12, 14, 25, 26, 27, 28, 29, 31, 58, 59, 60, 61, 62, 63, 90, 91, 92, 93, 359, 503, 522, 542, 873, 893, 902, 918, 922, 931, 1350, 1853, 1982, 2460, 2627, 3246, 3253, 3268, 3536, 3846, 3961, 4183, 4667, 6585, 6647, 7273, 9061, 9383, 10428, 10929, 11938, 12033, 12331, 12562, 13793, 14157, 14635, 15265, 15618, 16553, 16604, 18362, 18956, 20075, 21675, 22520, 26130, 26161, 26435, 28279, 29464, 31650, 32302, 32470, 36865, 42863, 47425, 49870, 50254]
|
|
170
|
-
const suppressedMetadataTokens = [50256, ...getIntegerRange(50258, 50364)]
|
|
171
|
-
|
|
172
|
-
const suppressedTokens = [...suppressedTextTokens, ...suppressedMetadataTokens]
|
|
173
|
-
|
|
174
|
-
this.tokenConfig.suppressedTokens = new Set(suppressedTokens)
|
|
175
165
|
} else {
|
|
176
166
|
this.tokenConfig = {
|
|
177
167
|
endOfTextToken: 50256,
|
|
@@ -188,16 +178,7 @@ export class Whisper {
|
|
|
188
178
|
|
|
189
179
|
timestampTokensStart: 50363,
|
|
190
180
|
timestampTokensEnd: 50363 + 1501,
|
|
191
|
-
|
|
192
|
-
suppressedTokens: new Set()
|
|
193
181
|
}
|
|
194
|
-
|
|
195
|
-
const suppressedTextTokens = [1, 2, 7, 8, 9, 10, 14, 25, 26, 27, 28, 29, 31, 58, 59, 60, 61, 62, 63, 90, 91, 92, 93, 357, 366, 438, 532, 685, 705, 796, 930, 1058, 1220, 1267, 1279, 1303, 1343, 1377, 1391, 1635, 1782, 1875, 2162, 2361, 2488, 3467, 4008, 4211, 4600, 4808, 5299, 5855, 6329, 7203, 9609, 9959, 10563, 10786, 11420, 11709, 11907, 13163, 13697, 13700, 14808, 15306, 16410, 16791, 17992, 19203, 19510, 20724, 22305, 22935, 27007, 30109, 30420, 33409, 34949, 40283, 40493, 40549, 47282, 49146]
|
|
196
|
-
const suppressedMetadataTokens = [...getIntegerRange(50257, 50363)]
|
|
197
|
-
|
|
198
|
-
const suppressedTokens = [...suppressedTextTokens, ...suppressedMetadataTokens]
|
|
199
|
-
|
|
200
|
-
this.tokenConfig.suppressedTokens = new Set(suppressedTokens)
|
|
201
182
|
}
|
|
202
183
|
|
|
203
184
|
this.randomGen = new XorShift32RNG(rngSeed)
|
|
@@ -282,6 +263,8 @@ export class Whisper {
|
|
|
282
263
|
|
|
283
264
|
const encoderFilePath = path.join(this.modelDir, 'encoder.onnx')
|
|
284
265
|
|
|
266
|
+
const Onnx = await import('onnxruntime-node')
|
|
267
|
+
|
|
285
268
|
this.audioEncoder = await Onnx.InferenceSession.create(encoderFilePath, this.onnxOptions)
|
|
286
269
|
|
|
287
270
|
logger.end()
|
|
@@ -298,6 +281,8 @@ export class Whisper {
|
|
|
298
281
|
|
|
299
282
|
const decoderFilePath = path.join(this.modelDir, 'decoder.onnx')
|
|
300
283
|
|
|
284
|
+
const Onnx = await import('onnxruntime-node')
|
|
285
|
+
|
|
301
286
|
this.textDecoder = await Onnx.InferenceSession.create(decoderFilePath, this.onnxOptions)
|
|
302
287
|
|
|
303
288
|
logger.end()
|
|
@@ -398,7 +383,7 @@ export class Whisper {
|
|
|
398
383
|
logger.end()
|
|
399
384
|
}
|
|
400
385
|
|
|
401
|
-
timeline = this.tokenTimelineToWordTimeline(timeline)
|
|
386
|
+
timeline = this.tokenTimelineToWordTimeline(timeline, language)
|
|
402
387
|
|
|
403
388
|
const transcript = this.tokensToText(allDecodedTokens).trim()
|
|
404
389
|
|
|
@@ -438,7 +423,7 @@ export class Whisper {
|
|
|
438
423
|
const alignmentPath = await this.findAlignmentPathFromQKs(crossAttentionQKs, tokens, 0, audioFrameCount)//, this.getAlignmentHeadIndexes())
|
|
439
424
|
let timeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, tokens, 0, audioDuration)
|
|
440
425
|
|
|
441
|
-
timeline = this.tokenTimelineToWordTimeline(timeline)
|
|
426
|
+
timeline = this.tokenTimelineToWordTimeline(timeline, language)
|
|
442
427
|
|
|
443
428
|
logger.end()
|
|
444
429
|
|
|
@@ -462,6 +447,8 @@ export class Whisper {
|
|
|
462
447
|
const initialTokens = [sotToken]
|
|
463
448
|
const offset = 0
|
|
464
449
|
|
|
450
|
+
const Onnx = await import('onnxruntime-node')
|
|
451
|
+
|
|
465
452
|
const initialKvDimensions = this.getKvDimensions(1, initialTokens.length)
|
|
466
453
|
const kvCacheTensor = new Onnx.Tensor('float32', new Float32Array(initialKvDimensions[0] * initialKvDimensions[1] * initialKvDimensions[2] * initialKvDimensions[3]), initialKvDimensions)
|
|
467
454
|
|
|
@@ -514,21 +501,23 @@ export class Whisper {
|
|
|
514
501
|
|
|
515
502
|
const logger = new Logger()
|
|
516
503
|
|
|
504
|
+
const allowedPunctuationMarks = this.getAllowedPunctuationMarks()
|
|
505
|
+
|
|
517
506
|
await logger.startAsync('Decode text tokens with Whisper decoder model')
|
|
518
507
|
|
|
519
508
|
options = extendDeep(defaultWhisperOptions, options)
|
|
520
509
|
|
|
510
|
+
const Onnx = await import('onnxruntime-node')
|
|
511
|
+
|
|
521
512
|
const endOfTextToken = this.tokenConfig.endOfTextToken
|
|
522
513
|
|
|
523
514
|
const timestampTokensStart = this.tokenConfig.timestampTokensStart
|
|
524
|
-
const suppressedTokens = this.
|
|
515
|
+
const suppressedTokens = new Set(this.getSuppressedTokens())
|
|
525
516
|
|
|
526
517
|
const spaceToken = this.textToTokens(' ')[0]
|
|
527
518
|
|
|
528
519
|
const maxDecodedTokenCount = options.maxTokensPerPart!
|
|
529
520
|
|
|
530
|
-
//const suppressedTokensText = Array.from(this.tokenConfig.suppressedTokens).map(token => this.tokenToText(token, true))
|
|
531
|
-
|
|
532
521
|
let decodedTokens = initialTokens.slice()
|
|
533
522
|
const initialKvDimensions = this.getKvDimensions(1, decodedTokens.length)
|
|
534
523
|
let kvCacheTensor = new Onnx.Tensor('float32', new Float32Array(initialKvDimensions[0] * initialKvDimensions[1] * initialKvDimensions[2] * initialKvDimensions[3]), initialKvDimensions)
|
|
@@ -688,7 +677,7 @@ export class Whisper {
|
|
|
688
677
|
return maxScore
|
|
689
678
|
})
|
|
690
679
|
|
|
691
|
-
const thresholdRepetitionScore =
|
|
680
|
+
const thresholdRepetitionScore = 4
|
|
692
681
|
|
|
693
682
|
if (topCandidatesRepetitionScores.every(score => score >= thresholdRepetitionScore)) {
|
|
694
683
|
const indexOfMaxScore = topCandidatesRepetitionScores.indexOf(Math.max(...topCandidatesRepetitionScores))
|
|
@@ -716,7 +705,7 @@ export class Whisper {
|
|
|
716
705
|
const rankOfPromisingPunctuationToken = topCandidates.findIndex((entry, index) => {
|
|
717
706
|
const tokenText = this.tokenToText(entry.token).trim()
|
|
718
707
|
|
|
719
|
-
const isPunctuationToken =
|
|
708
|
+
const isPunctuationToken = allowedPunctuationMarks.includes(tokenText)
|
|
720
709
|
|
|
721
710
|
if (!isPunctuationToken) {
|
|
722
711
|
return false
|
|
@@ -793,6 +782,8 @@ export class Whisper {
|
|
|
793
782
|
async inferCrossAttentionQKs(tokens: number[], audioFeatures: Onnx.Tensor) {
|
|
794
783
|
const offset = 0
|
|
795
784
|
|
|
785
|
+
const Onnx = await import('onnxruntime-node')
|
|
786
|
+
|
|
796
787
|
const tokensTensor = new Onnx.Tensor('int64', new BigInt64Array(tokens.map(token => BigInt(token))), [1, tokens.length])
|
|
797
788
|
const offsetTensor = new Onnx.Tensor('int64', new BigInt64Array([BigInt(offset)]), [])
|
|
798
789
|
|
|
@@ -841,6 +832,8 @@ export class Whisper {
|
|
|
841
832
|
async encodeAudio(rawAudio: RawAudio) {
|
|
842
833
|
await this.initializeEncoderSessionIfNeeded()
|
|
843
834
|
|
|
835
|
+
const Onnx = await import('onnxruntime-node')
|
|
836
|
+
|
|
844
837
|
const logger = new Logger()
|
|
845
838
|
|
|
846
839
|
const audioSamples = rawAudio.audioChannels[0]
|
|
@@ -1022,33 +1015,40 @@ export class Whisper {
|
|
|
1022
1015
|
}
|
|
1023
1016
|
}
|
|
1024
1017
|
|
|
1025
|
-
tokenTimelineToWordTimeline(tokenTimeline: Timeline) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1018
|
+
tokenTimelineToWordTimeline(tokenTimeline: Timeline, language: string): Timeline {
|
|
1019
|
+
function isSeparatorCharacter(char: string) {
|
|
1020
|
+
const nonSeparatingPunctuation = [`'`, `-`, `.`, `·`, `•`]
|
|
1021
|
+
|
|
1022
|
+
if (nonSeparatingPunctuation.includes(char)) {
|
|
1023
|
+
return false
|
|
1024
|
+
}
|
|
1028
1025
|
|
|
1029
|
-
|
|
1030
|
-
return separatorChars.some(char => text.startsWith(char))
|
|
1026
|
+
return isWhitespace(char) || isPunctuation(char)
|
|
1031
1027
|
}
|
|
1032
1028
|
|
|
1033
|
-
function
|
|
1034
|
-
return
|
|
1029
|
+
function startsWithSeparatorCharacter(text: string) {
|
|
1030
|
+
return isSeparatorCharacter(text[0])
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function endsWithSeparatorCharacter(text: string) {
|
|
1034
|
+
return isSeparatorCharacter(text[text.length - 1])
|
|
1035
1035
|
}
|
|
1036
1036
|
|
|
1037
1037
|
const resultTimeline: Timeline = []
|
|
1038
1038
|
|
|
1039
|
-
|
|
1039
|
+
let groups: TimelineEntry[][] = []
|
|
1040
1040
|
|
|
1041
|
-
for (let
|
|
1042
|
-
const entry = tokenTimeline[
|
|
1043
|
-
const previousEntry =
|
|
1041
|
+
for (let tokenIndex = 0; tokenIndex < tokenTimeline.length; tokenIndex++) {
|
|
1042
|
+
const entry = tokenTimeline[tokenIndex]
|
|
1043
|
+
const previousEntry = tokenIndex > 0 ? tokenTimeline[tokenIndex - 1] : undefined
|
|
1044
1044
|
|
|
1045
1045
|
const text = entry.text
|
|
1046
1046
|
const previousEntryText = previousEntry?.text
|
|
1047
1047
|
|
|
1048
1048
|
if (groups.length == 0 ||
|
|
1049
1049
|
text === '' ||
|
|
1050
|
-
|
|
1051
|
-
(previousEntryText != null &&
|
|
1050
|
+
startsWithSeparatorCharacter(text) ||
|
|
1051
|
+
(previousEntryText != null && endsWithSeparatorCharacter(previousEntryText))) {
|
|
1052
1052
|
|
|
1053
1053
|
groups.push([entry])
|
|
1054
1054
|
} else {
|
|
@@ -1056,8 +1056,28 @@ export class Whisper {
|
|
|
1056
1056
|
}
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
|
+
const newGroups: TimelineEntry[][] = []
|
|
1060
|
+
|
|
1061
|
+
for (let groupIndex = 0; groupIndex < groups.length - 1; groupIndex++) {
|
|
1062
|
+
const group = groups[groupIndex]
|
|
1063
|
+
const nextGroup = groups[groupIndex + 1]
|
|
1064
|
+
|
|
1065
|
+
if (
|
|
1066
|
+
group.length > 1 &&
|
|
1067
|
+
group[group.length - 1].text === '.' &&
|
|
1068
|
+
[' ', '['].includes(nextGroup[0].text[0])) {
|
|
1069
|
+
|
|
1070
|
+
newGroups.push(group.slice(0, group.length - 1))
|
|
1071
|
+
newGroups.push(group.slice(group.length - 1))
|
|
1072
|
+
} else {
|
|
1073
|
+
newGroups.push(group)
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
groups = newGroups
|
|
1078
|
+
|
|
1059
1079
|
for (const group of groups) {
|
|
1060
|
-
|
|
1080
|
+
let groupText = this.tokensToText(group.map(entry => entry.id!))
|
|
1061
1081
|
|
|
1062
1082
|
if (groupText === '') {
|
|
1063
1083
|
continue
|
|
@@ -1401,6 +1421,90 @@ export class Whisper {
|
|
|
1401
1421
|
getAlignmentHeadIndexes() {
|
|
1402
1422
|
return alignmentHeadsIndexes[this.modelName]
|
|
1403
1423
|
}
|
|
1424
|
+
|
|
1425
|
+
getSuppressedTokens() {
|
|
1426
|
+
return [
|
|
1427
|
+
...this.getSuppressedTextTokens(),
|
|
1428
|
+
...this.getSuppressedMetadataTokens(),
|
|
1429
|
+
]
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
getSuppressedTextTokens() {
|
|
1433
|
+
const allowedPunctuationMarks = this.getAllowedPunctuationMarks()
|
|
1434
|
+
|
|
1435
|
+
const nonWordTokensData = this.getNonWordTokenData()
|
|
1436
|
+
|
|
1437
|
+
const suppressedTextTokens = nonWordTokensData
|
|
1438
|
+
.filter(entry => !allowedPunctuationMarks.includes(entry.text))
|
|
1439
|
+
.map(entry => entry.id)
|
|
1440
|
+
|
|
1441
|
+
return suppressedTextTokens
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
getSuppressedMetadataTokens() {
|
|
1445
|
+
if (this.isMultiligualModel) {
|
|
1446
|
+
return [50256, ...getIntegerRange(50258, 50364)]
|
|
1447
|
+
} else {
|
|
1448
|
+
return [...getIntegerRange(50257, 50363)]
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
getAllowedPunctuationMarks() {
|
|
1453
|
+
const generalPunctuation = [`'`, ',', '.', '?', '!']
|
|
1454
|
+
|
|
1455
|
+
let allowedPunctuation: string[]
|
|
1456
|
+
|
|
1457
|
+
if (this.isMultiligualModel) {
|
|
1458
|
+
const spanish = ['¿', '¡']
|
|
1459
|
+
const chinese = ['、', ',', '。', '?', '!']
|
|
1460
|
+
const arabic = ['،', '؟']
|
|
1461
|
+
const various = ['·', '•', '・']
|
|
1462
|
+
|
|
1463
|
+
allowedPunctuation = [...generalPunctuation, ...arabic, ...chinese, ...spanish, ...various]
|
|
1464
|
+
} else {
|
|
1465
|
+
allowedPunctuation = generalPunctuation
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
return allowedPunctuation
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
getNonWordTokenData() {
|
|
1472
|
+
const nonWordTokenData: WhisperTokenData[] = []
|
|
1473
|
+
|
|
1474
|
+
const invalidUTF8Char = String.fromCharCode(65533)
|
|
1475
|
+
|
|
1476
|
+
for (let i = 0; i < this.tokenConfig.endOfTextToken; i++) {
|
|
1477
|
+
const tokenText = this.tokenToText(i, false)
|
|
1478
|
+
const tokenTextWithoutWhitespace = tokenText.replaceAll(/\s/g, '')
|
|
1479
|
+
|
|
1480
|
+
const isNonWordToken = /^[\p{Punctuation}\p{Symbol}]+$/u.test(tokenTextWithoutWhitespace)
|
|
1481
|
+
|
|
1482
|
+
const containsInvalidUTF8 = getUTF32Chars(tokenTextWithoutWhitespace).utf32chars.includes(invalidUTF8Char)
|
|
1483
|
+
|
|
1484
|
+
if (isNonWordToken && !containsInvalidUTF8) {
|
|
1485
|
+
nonWordTokenData.push({
|
|
1486
|
+
id: i,
|
|
1487
|
+
text: tokenText,
|
|
1488
|
+
})
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
return nonWordTokenData
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
|
|
1496
|
+
getTokensData(tokens: number[]) {
|
|
1497
|
+
const tokensData: WhisperTokenData[] = []
|
|
1498
|
+
|
|
1499
|
+
for (const token of tokens) {
|
|
1500
|
+
tokensData.push({
|
|
1501
|
+
id: token,
|
|
1502
|
+
text: this.tokenToText(token, true),
|
|
1503
|
+
})
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
return tokensData
|
|
1507
|
+
}
|
|
1404
1508
|
}
|
|
1405
1509
|
|
|
1406
1510
|
const filterbanks: Filterbank[] = [
|
|
@@ -1609,6 +1713,11 @@ export function isEnglishOnlyModel(modelName: WhisperModelName) {
|
|
|
1609
1713
|
return modelName.endsWith('.en')
|
|
1610
1714
|
}
|
|
1611
1715
|
|
|
1716
|
+
export type WhisperTokenData = {
|
|
1717
|
+
id: number
|
|
1718
|
+
text: string
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1612
1721
|
export type WhisperModelName = 'tiny' | 'tiny.en' | 'base' | 'base.en' | 'small' | 'small.en' | 'medium' | 'medium.en' | 'large' | 'large-v1' | 'large-v2' | 'large-v3'
|
|
1613
1722
|
export type WhisperTask = 'transcribe' | 'translate' | 'detect-language'
|
|
1614
1723
|
|
|
@@ -1755,8 +1864,8 @@ export interface WhisperOptions {
|
|
|
1755
1864
|
autoPromptParts?: boolean
|
|
1756
1865
|
maxTokensPerPart?: number
|
|
1757
1866
|
suppressRepetition?: boolean
|
|
1758
|
-
seed?: number
|
|
1759
1867
|
decodeTimestampTokens?: boolean
|
|
1868
|
+
seed?: number
|
|
1760
1869
|
}
|
|
1761
1870
|
|
|
1762
1871
|
export const defaultWhisperOptions: WhisperOptions = {
|
|
@@ -1768,6 +1877,6 @@ export const defaultWhisperOptions: WhisperOptions = {
|
|
|
1768
1877
|
autoPromptParts: true,
|
|
1769
1878
|
maxTokensPerPart: 250,
|
|
1770
1879
|
suppressRepetition: true,
|
|
1880
|
+
decodeTimestampTokens: true,
|
|
1771
1881
|
seed: undefined,
|
|
1772
|
-
decodeTimestampTokens: false,
|
|
1773
1882
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Onnx from 'onnxruntime-node'
|
|
1
|
+
import type * as Onnx from 'onnxruntime-node'
|
|
2
2
|
import { RawAudio } from '../audio/AudioUtilities.js';
|
|
3
3
|
import { binBufferToComplex, complexToBinBuffer, getWindowWeights, stftr, stiftr } from '../dsp/FFT.js';
|
|
4
4
|
import { ComplexNumber } from '../math/VectorMath.js';
|
|
@@ -32,6 +32,8 @@ export class MDXNet {
|
|
|
32
32
|
await this.initializeSession(this.modelFilePath)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
const Onnx = await import('onnxruntime-node')
|
|
36
|
+
|
|
35
37
|
const logger = new Logger()
|
|
36
38
|
|
|
37
39
|
const session = this.session!
|
|
@@ -223,6 +225,8 @@ export class MDXNet {
|
|
|
223
225
|
logSeverityLevel: 3
|
|
224
226
|
}
|
|
225
227
|
|
|
228
|
+
const Onnx = await import('onnxruntime-node')
|
|
229
|
+
|
|
226
230
|
this.session = await Onnx.InferenceSession.create(modelPath, onnxOptions)
|
|
227
231
|
}
|
|
228
232
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Onnx from 'onnxruntime-node'
|
|
1
|
+
import type * as Onnx from 'onnxruntime-node'
|
|
2
2
|
import { softmax } from '../math/VectorMath.js'
|
|
3
3
|
import { Logger } from '../utilities/Logger.js'
|
|
4
4
|
import { RawAudio } from '../audio/AudioUtilities.js'
|
|
@@ -50,6 +50,8 @@ export class SileroLanguageDetection {
|
|
|
50
50
|
logSeverityLevel: 3
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const Onnx = await import('onnxruntime-node')
|
|
54
|
+
|
|
53
55
|
this.session = await Onnx.InferenceSession.create(this.modelPath, onnxOptions)
|
|
54
56
|
|
|
55
57
|
logger.end()
|
|
@@ -62,6 +64,8 @@ export class SileroLanguageDetection {
|
|
|
62
64
|
|
|
63
65
|
const audioSamples = rawAudio.audioChannels[0]
|
|
64
66
|
|
|
67
|
+
const Onnx = await import('onnxruntime-node')
|
|
68
|
+
|
|
65
69
|
const inputTensor = new Onnx.Tensor('float32', audioSamples, [1, audioSamples.length])
|
|
66
70
|
|
|
67
71
|
const inputs = { input: inputTensor }
|
|
@@ -200,7 +200,7 @@ function getCuesFromTimeline_IsolateSegmentSentence(timeline: Timeline, config:
|
|
|
200
200
|
continue
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const wordTimeline = entry.timeline!.filter(entry =>
|
|
203
|
+
const wordTimeline = entry.timeline!.filter(entry => isWordOrSymbolWord(entry.text))
|
|
204
204
|
|
|
205
205
|
// First, add word start and end offsets for all word entries
|
|
206
206
|
let lastWordEndOffset = 0
|
package/src/synthesis/VitsTTS.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as Onnx from 'onnxruntime-node'
|
|
2
2
|
import { SynthesisVoice } from '../api/API.js'
|
|
3
3
|
import { Logger } from '../utilities/Logger.js'
|
|
4
4
|
import { RawAudio, getEmptyRawAudio, getRawAudioDuration } from '../audio/AudioUtilities.js'
|
|
@@ -32,7 +32,7 @@ export class VitsTTS {
|
|
|
32
32
|
voiceName: string
|
|
33
33
|
modelPath: string
|
|
34
34
|
|
|
35
|
-
modelSession?: InferenceSession
|
|
35
|
+
modelSession?: Onnx.InferenceSession
|
|
36
36
|
metadata?: any
|
|
37
37
|
phonemeMap?: Map<string, number[]>
|
|
38
38
|
|
|
@@ -45,7 +45,7 @@ export class VitsTTS {
|
|
|
45
45
|
const logger = new Logger()
|
|
46
46
|
await logger.startAsync('Initialize VITS ONNX synthesis model')
|
|
47
47
|
|
|
48
|
-
const onnxOptions: InferenceSession.SessionOptions = {
|
|
48
|
+
const onnxOptions: Onnx.InferenceSession.SessionOptions = {
|
|
49
49
|
logSeverityLevel: 3
|
|
50
50
|
}
|
|
51
51
|
|
package/src/tests/Test.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import { getRepetitionScoreRelativeToFirstSubstring, logToStderr, setupProgramTerminationListeners } from '../utilities/Utilities.js'
|
|
1
|
+
import { getRepetitionScoreRelativeToFirstSubstring, logToStderr, setupProgramTerminationListeners, writeToStderr } from '../utilities/Utilities.js'
|
|
2
2
|
import { makeTarballsForInstalledPackages } from '../utilities/TarballMaker.js'
|
|
3
3
|
import { testEspeakSynthesisWithPrePhonemizedInputs, testKirshenbaumPhonemization } from '../synthesis/EspeakTTS.js'
|
|
4
4
|
|
|
5
5
|
const log = logToStderr
|
|
6
6
|
|
|
7
7
|
setupProgramTerminationListeners()
|
|
8
|
+
|
|
8
9
|
//process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
9
10
|
//process.env.http_proxy = 'http://localhost:8080'
|
|
10
11
|
|
|
11
12
|
//const testText = `After a while, finding that nothing more happened, she decided on going into the garden at once; but, alas for poor Alice! when she got to the door, she found she had forgotten the little golden key, and when she went back to the table for it, she found she could not possibly reach it: she could see it quite plainly through the glass, and she tried her best to climb up one of the legs of the table, but it was too slippery; and when she had tired herself out with trying, the poor little thing sat down and cried.`
|
|
12
|
-
|
|
13
13
|
//await testKirshenbaumPhonemization(testText)
|
|
14
14
|
|
|
15
|
-
//await makeTarballsForInstalledPackages(true)
|
|
16
|
-
|
|
17
15
|
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'c', 'a', 'b', 'c'])
|
|
18
16
|
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'd', 'a', 'b', 'a', 'd'])
|
|
19
17
|
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'b', 'c', 'a', 'b', 'a', 'b'])
|
|
@@ -21,4 +19,7 @@ setupProgramTerminationListeners()
|
|
|
21
19
|
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'c', 'a', 'b', 'a', 'c', 'a'])
|
|
22
20
|
//getRepetitionScoreRelativeToFirstSubstring(['a', 'a', 'a', 'b', 'b', 'a', 'a', 'a', 'b'])
|
|
23
21
|
|
|
22
|
+
//await makeTarballsForInstalledPackages(true)
|
|
23
|
+
|
|
24
24
|
process.exit(0)
|
|
25
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Onnx from 'onnxruntime-node'
|
|
1
|
+
import type * as Onnx from 'onnxruntime-node'
|
|
2
2
|
|
|
3
3
|
import { concatFloat32Arrays } from '../utilities/Utilities.js'
|
|
4
4
|
import { RawAudio } from '../audio/AudioUtilities.js'
|
|
@@ -38,33 +38,24 @@ export async function detectVoiceActivity(rawAudio: RawAudio, modelPath: string,
|
|
|
38
38
|
export class SileroVAD {
|
|
39
39
|
session?: Onnx.InferenceSession
|
|
40
40
|
|
|
41
|
-
modelStateH
|
|
42
|
-
modelStateC
|
|
43
|
-
modelSampleRate
|
|
41
|
+
modelStateH?: Onnx.Tensor
|
|
42
|
+
modelStateC?: Onnx.Tensor
|
|
43
|
+
modelSampleRate?: Onnx.Tensor
|
|
44
44
|
|
|
45
45
|
modelPath: string
|
|
46
46
|
|
|
47
47
|
constructor(modelPath: string) {
|
|
48
|
-
this.modelSampleRate = new Onnx.Tensor('int64', new BigInt64Array([BigInt(16000)]), [])
|
|
49
|
-
|
|
50
|
-
const h = new Float32Array(2 * 1 * 64)
|
|
51
|
-
const c = new Float32Array(2 * 1 * 64)
|
|
52
|
-
|
|
53
|
-
this.modelStateH = new Onnx.Tensor('float32', h, [2, 1, 64])
|
|
54
|
-
this.modelStateC = new Onnx.Tensor('float32', c, [2, 1, 64])
|
|
55
|
-
|
|
56
48
|
this.modelPath = modelPath
|
|
57
49
|
}
|
|
58
50
|
|
|
59
51
|
async predictAudioFrame(frame: Float32Array) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
await this.initializeIfNeeded()
|
|
53
|
+
|
|
54
|
+
const Onnx = await import('onnxruntime-node')
|
|
63
55
|
|
|
64
56
|
const inputTensor = new Onnx.Tensor('float32', frame, [1, frame.length])
|
|
65
57
|
|
|
66
|
-
|
|
67
|
-
const inputs = { input: inputTensor, sr: this.modelSampleRate, h: this.modelStateH, c: this.modelStateC }
|
|
58
|
+
const inputs = { input: inputTensor, sr: this.modelSampleRate!, h: this.modelStateH!, c: this.modelStateC! }
|
|
68
59
|
|
|
69
60
|
const results = await this.session!.run(inputs)
|
|
70
61
|
|
|
@@ -75,12 +66,25 @@ export class SileroVAD {
|
|
|
75
66
|
|
|
76
67
|
return probability
|
|
77
68
|
}
|
|
69
|
+
private async initializeIfNeeded() {
|
|
70
|
+
if (this.session) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const Onnx = await import('onnxruntime-node')
|
|
75
|
+
|
|
76
|
+
const h = new Float32Array(2 * 1 * 64)
|
|
77
|
+
const c = new Float32Array(2 * 1 * 64)
|
|
78
|
+
|
|
79
|
+
this.modelStateH = new Onnx.Tensor('float32', h, [2, 1, 64])
|
|
80
|
+
this.modelStateC = new Onnx.Tensor('float32', c, [2, 1, 64])
|
|
81
|
+
|
|
82
|
+
this.modelSampleRate = new Onnx.Tensor('int64', new BigInt64Array([BigInt(16000)]), [])
|
|
78
83
|
|
|
79
|
-
private async initializeSession(modelPath: string) {
|
|
80
84
|
const onnxOptions: Onnx.InferenceSession.SessionOptions = {
|
|
81
85
|
logSeverityLevel: 3
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
this.session = await Onnx.InferenceSession.create(modelPath, onnxOptions)
|
|
88
|
+
this.session = await Onnx.InferenceSession.create(this.modelPath, onnxOptions)
|
|
85
89
|
}
|
|
86
90
|
}
|