echogarden 2.8.6 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/api/Recognition.d.ts +3 -3
  2. package/dist/api/Recognition.js +5 -5
  3. package/dist/api/Recognition.js.map +1 -1
  4. package/dist/api/SpeechTranslation.d.ts +4 -4
  5. package/dist/api/SpeechTranslation.js +4 -4
  6. package/dist/api/SpeechTranslation.js.map +1 -1
  7. package/dist/denoising/NSNet2.js +8 -8
  8. package/dist/denoising/NSNet2.js.map +1 -1
  9. package/dist/dsp/FFT.d.ts +2 -2
  10. package/dist/dsp/FFT.js +19 -15
  11. package/dist/dsp/FFT.js.map +1 -1
  12. package/dist/dsp/MFCC.d.ts +1 -1
  13. package/dist/dsp/MFCC.js +8 -8
  14. package/dist/dsp/MFCC.js.map +1 -1
  15. package/dist/dsp/{MelSpectogram.d.ts → MelSpectrogram.d.ts} +5 -5
  16. package/dist/dsp/{MelSpectogram.js → MelSpectrogram.js} +9 -9
  17. package/dist/dsp/MelSpectrogram.js.map +1 -0
  18. package/dist/math/VectorMath.d.ts +5 -1
  19. package/dist/math/VectorMath.js +15 -2
  20. package/dist/math/VectorMath.js.map +1 -1
  21. package/dist/nlp/IPA.d.ts +3 -3
  22. package/dist/recognition/WhisperSTT.d.ts +9 -6
  23. package/dist/recognition/WhisperSTT.js +92 -60
  24. package/dist/recognition/WhisperSTT.js.map +1 -1
  25. package/dist/source-separation/MDXNetSourceSeparation.js +5 -5
  26. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
  27. package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js +14 -14
  28. package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js.map +1 -1
  29. package/dist/utilities/Utilities.d.ts +1 -1
  30. package/dist/utilities/Utilities.js +4 -2
  31. package/dist/utilities/Utilities.js.map +1 -1
  32. package/docs/Licenses.md +0 -2
  33. package/docs/Tasklist.md +18 -5
  34. package/package.json +14 -14
  35. package/src/api/Recognition.ts +10 -7
  36. package/src/api/SpeechTranslation.ts +16 -6
  37. package/src/denoising/NSNet2.ts +8 -8
  38. package/src/dsp/FFT.ts +23 -17
  39. package/src/dsp/MFCC.ts +8 -8
  40. package/src/dsp/{MelSpectogram.ts → MelSpectrogram.ts} +9 -8
  41. package/src/math/VectorMath.ts +22 -2
  42. package/src/recognition/WhisperSTT.ts +131 -65
  43. package/src/source-separation/MDXNetSourceSeparation.ts +5 -5
  44. package/src/speech-embeddings/WavToVec2BertFeatureEmbeddings.ts +14 -14
  45. package/src/utilities/Utilities.ts +4 -2
  46. package/dist/dsp/MelSpectogram.js.map +0 -1
@@ -1,9 +1,10 @@
1
+ import chalk from 'chalk'
1
2
  import type * as Onnx from 'onnxruntime-node'
2
3
 
3
4
  import { Logger } from '../utilities/Logger.js'
4
- import { computeMelSpectogramUsingFilterbanks, Filterbank } from '../dsp/MelSpectogram.js'
5
+ import { computeMelSpectrogramUsingFilterbanks, Filterbank } from '../dsp/MelSpectrogram.js'
5
6
  import { clip, getIntegerRange, getTopKIndexes, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js'
6
- import { indexOfMax, logOfVector, logSumExp, meanOfVector, softmax, sumOfSquaresForVector, sumVector } from '../math/VectorMath.js'
7
+ import { indexOfMax, logOfVector, logSumExp, meanOfVector, medianOfVector, softmax, sumAndSumOfSquaresOfVector, sumOfSquaresOfVector, sumVector } from '../math/VectorMath.js'
7
8
 
8
9
  import { alignDTWWindowed } from '../alignment/DTWSequenceAlignmentWindowed.js'
9
10
  import { extendDeep } from '../utilities/ObjectUtilities.js'
@@ -11,10 +12,9 @@ import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
11
12
  import { AlignmentPath } from '../alignment/SpeechAlignment.js'
12
13
  import { getRawAudioDuration, RawAudio, sliceRawAudio } from '../audio/AudioUtilities.js'
13
14
  import { readFileAsUtf8 } from '../utilities/FileSystem.js'
14
- import type { LanguageDetectionResults } from '../api/API.js'
15
+ import { logLevelGreaterOrEqualTo, type LanguageDetectionResults } from '../api/API.js'
15
16
  import { formatLanguageCodeWithName, getShortLanguageCode, languageCodeToName } from '../utilities/Locale.js'
16
17
  import { loadPackage } from '../utilities/PackageManager.js'
17
- import chalk from 'chalk'
18
18
  import { XorShift32PRNG } from '../utilities/RandomGenerator.js'
19
19
  import { detectSpeechLanguageByParts } from '../api/SpeechLanguageDetection.js'
20
20
  import { type Tiktoken } from 'tiktoken/lite'
@@ -25,6 +25,7 @@ import { dmlProviderAvailable, getOnnxSessionOptions, makeOnnxLikeFloat32Tensor,
25
25
  import { murmurHash3_int32Input } from '../utilities/Hashing.js'
26
26
  import { containsInvalidCodepoint, getTokenRepetitionScore } from '../utilities/StringUtilities.js'
27
27
  import { joinPath } from '../utilities/PathUtilities.js'
28
+ import { Timer } from '../utilities/Timer.js'
28
29
 
29
30
  export async function recognize(
30
31
  sourceRawAudio: RawAudio,
@@ -32,11 +33,12 @@ export async function recognize(
32
33
  modelDir: string,
33
34
  task: WhisperTask,
34
35
  sourceLanguage: string,
35
- options: WhisperOptions) {
36
+ options: WhisperOptions,
37
+ onPart?: WhisperPartCallback) {
36
38
 
37
39
  options = extendDeep(defaultWhisperOptions, options)
38
40
 
39
- if (sourceRawAudio.sampleRate != 16000) {
41
+ if (sourceRawAudio.sampleRate !== 16000) {
40
42
  throw new Error('Source audio must have a sample rate of 16000 Hz')
41
43
  }
42
44
 
@@ -46,7 +48,7 @@ export async function recognize(
46
48
  throw new Error(`The language ${formatLanguageCodeWithName(sourceLanguage)} is not supported by the Whisper engine.`)
47
49
  }
48
50
 
49
- if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
51
+ if (isEnglishOnlyModel(modelName) && sourceLanguage !== 'en') {
50
52
  throw new Error(`The model '${modelName}' can only be used with English inputs. However, the given source language was ${languageCodeToName(sourceLanguage)}.`)
51
53
  }
52
54
 
@@ -60,7 +62,7 @@ export async function recognize(
60
62
 
61
63
  // Workaround issue with large-v3-turbo that produces invalid results when a prompt is passed to it.
62
64
  // Always disable autoprompting for that model.
63
- if (options.autoPromptParts && modelName == 'large-v3-turbo') {
65
+ if (options.autoPromptParts && modelName === 'large-v3-turbo') {
64
66
  options.autoPromptParts = false
65
67
  }
66
68
 
@@ -81,7 +83,7 @@ export async function recognize(
81
83
  decoderProviders,
82
84
  seed)
83
85
 
84
- const result = await whisper.recognize(sourceRawAudio, task, sourceLanguage, options)
86
+ const result = await whisper.recognize(sourceRawAudio, task, sourceLanguage, options, undefined, onPart)
85
87
 
86
88
  return result
87
89
  }
@@ -96,7 +98,7 @@ export async function align(
96
98
 
97
99
  options = extendDeep(defaultWhisperAlignmentOptions, options)
98
100
 
99
- if (sourceRawAudio.sampleRate != 16000) {
101
+ if (sourceRawAudio.sampleRate !== 16000) {
100
102
  throw new Error('Source audio must have a sample rate of 16000 Hz')
101
103
  }
102
104
 
@@ -106,7 +108,7 @@ export async function align(
106
108
  throw new Error(`The language ${formatLanguageCodeWithName(sourceLanguage)} is not supported by the Whisper engine.`)
107
109
  }
108
110
 
109
- if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
111
+ if (isEnglishOnlyModel(modelName) && sourceLanguage !== 'en') {
110
112
  throw new Error(`The model '${modelName}' can only be used with English inputs. However, the given source language was ${languageCodeToName(sourceLanguage)}.`)
111
113
  }
112
114
 
@@ -139,7 +141,7 @@ export async function alignEnglishTranslation(
139
141
 
140
142
  options = extendDeep(defaultWhisperAlignmentOptions, options)
141
143
 
142
- if (sourceRawAudio.sampleRate != 16000) {
144
+ if (sourceRawAudio.sampleRate !== 16000) {
143
145
  throw new Error('Source audio must have a sample rate of 16000 Hz')
144
146
  }
145
147
 
@@ -184,7 +186,7 @@ export async function detectLanguage(
184
186
 
185
187
  options = extendDeep(defaultWhisperLanguageDetectionOptions, options)
186
188
 
187
- if (sourceRawAudio.sampleRate != 16000) {
189
+ if (sourceRawAudio.sampleRate !== 16000) {
188
190
  throw new Error('Source audio must have a sample rate of 16000 Hz')
189
191
  }
190
192
 
@@ -232,7 +234,7 @@ export async function detectVoiceActivity(
232
234
 
233
235
  options = extendDeep(defaultWhisperVADOptions, options)
234
236
 
235
- if (sourceRawAudio.sampleRate != 16000) {
237
+ if (sourceRawAudio.sampleRate !== 16000) {
236
238
  throw new Error('Source audio must have a sample rate of 16000 Hz')
237
239
  }
238
240
 
@@ -365,6 +367,7 @@ export class Whisper {
365
367
  language: string,
366
368
  options: WhisperOptions,
367
369
  logitFilter?: WhisperLogitFilter,
370
+ onPart?: WhisperPartCallback,
368
371
  ) {
369
372
  await this.initializeIfNeeded()
370
373
 
@@ -441,7 +444,11 @@ export class Whisper {
441
444
  let {
442
445
  decodedTokens: partTokens,
443
446
  decodedTokensConfidence: partTokensConfidence,
444
- decodedTokensCrossAttentionQKs: partCrossAttentionQKs,
447
+ decodedTokensCrossAttentionQKs: partTokensCrossAttentionQKs,
448
+
449
+ decodedTokensDecodingTime: partTokensDecodingTime,
450
+ decodedTokensInferenceTime: partTokensInferenceTime,
451
+ decodedTokensOverheadTime: partTokensOverheadTime,
445
452
  } = await this.decodeTokens(
446
453
  audioPartFeatures,
447
454
  initialTokens,
@@ -470,19 +477,19 @@ export class Whisper {
470
477
 
471
478
  await logger.startAsync(`Extract timeline for part (timestamp accuracy: ${options.timestampAccuracy!})`)
472
479
 
473
- if (partTokens.length != partCrossAttentionQKs.length) {
474
- throw new Error('Unexpected: partTokens.length != partCrossAttentionQKs.length')
480
+ if (partTokens.length !== partTokensCrossAttentionQKs.length) {
481
+ throw new Error('Unexpected: partTokens.length !== partCrossAttentionQKs.length')
475
482
  }
476
483
 
477
484
  // Prepare tokens
478
485
  partTokens = partTokens.slice(initialTokens.length)
479
486
  partTokensConfidence = partTokensConfidence.slice(initialTokens.length)
480
- partCrossAttentionQKs = partCrossAttentionQKs.slice(initialTokens.length)
487
+ partTokensCrossAttentionQKs = partTokensCrossAttentionQKs.slice(initialTokens.length)
481
488
 
482
489
  // Find alignment path
483
490
  let alignmentHeads: number[] | undefined
484
491
 
485
- if (options.timestampAccuracy === 'medium' || options.model == 'large-v3-turbo') {
492
+ if (options.timestampAccuracy === 'medium' || options.model === 'large-v3-turbo') {
486
493
  alignmentHeads = this.alignmentHeadIndexes
487
494
  } else if (options.timestampAccuracy === 'high') {
488
495
  alignmentHeads = undefined
@@ -490,11 +497,18 @@ export class Whisper {
490
497
  throw new Error(`Unsupported timestamp accuracy '${options.timestampAccuracy}', can only be 'medium' or 'high'.`)
491
498
  }
492
499
 
493
- const alignmentPath = await this.findAlignmentPathFromQKs(partCrossAttentionQKs, partTokens, 0, segmentFrameCount, alignmentHeads)
500
+ const alignmentPath = await this.findAlignmentPathFromQKs(partTokensCrossAttentionQKs, partTokens, 0, segmentFrameCount, alignmentHeads)
494
501
 
495
502
  // Generate timeline from alignment path
496
503
  const partTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, partTokensConfidence)
497
504
 
505
+ if (onPart) {
506
+ const partWordTimeline = this.tokenTimelineToWordTimeline(partTimeline, language)
507
+ const partTranscript = this.tokensToText(partTokens)
508
+
509
+ onPart(partTranscript, partTimeline, partWordTimeline)
510
+ }
511
+
498
512
  // Add tokens to output
499
513
  allDecodedTokens.push(...partTokens)
500
514
  timeline.push(...partTimeline)
@@ -514,6 +528,15 @@ export class Whisper {
514
528
  audioOffset = audioEndOffset
515
529
 
516
530
  logger.end()
531
+
532
+ if (logLevelGreaterOrEqualTo('trace')) {
533
+ const promptDecodingTime = partTokensDecodingTime[0]
534
+ const medianTokenDecodingTime = medianOfVector(partTokensDecodingTime.slice(1))
535
+ const medianTokenInferenceTime = medianOfVector(partTokensInferenceTime.slice(1))
536
+ const medianOverheadTime = medianOfVector(partTokensOverheadTime.slice(1))
537
+
538
+ logger.log(`${chalk.blueBright('Context')}: ${initialTokens.length + partTokens.length} tokens (${initialTokens.length} prompt, ${partTokens.length} decoded)\n${chalk.blueBright('Prompt decode time')}: ${promptDecodingTime.toFixed(1)}ms\n${chalk.blueBright('Median token decode time')}: ${medianTokenDecodingTime.toFixed(1)}ms (${medianTokenInferenceTime.toFixed(1)}ms inference, ${medianOverheadTime.toFixed(2)}ms overhead)`, 'trace')
539
+ }
517
540
  }
518
541
 
519
542
  // Convert token timeline to word timeline
@@ -812,8 +835,17 @@ export class Whisper {
812
835
 
813
836
  const maxTokensPerPart = Math.min(options.maxTokensPerPart!, largestMaximumTokensPerPart)
814
837
 
838
+ let decodedTokensInferenceTime: number[] = []
839
+ let decodedTokensDecodingTime: number[] = []
840
+
841
+ const tokenDecodingTimeTimer = new Timer()
842
+
815
843
  // Start decoding loop
816
844
  for (let decodedTokenCount = 0; decodedTokenCount < maxTokensPerPart; decodedTokenCount++) {
845
+ if (decodedTokenCount > 0) {
846
+ decodedTokensDecodingTime.push(tokenDecodingTimeTimer.getElapsedTimeAndRestart())
847
+ }
848
+
817
849
  const isInitialState = decodedTokens.length === initialTokens.length
818
850
  const atLeastOneTextTokenDecoded = decodedTokens.slice(initialTokens.length).some(token => this.isTextToken(token))
819
851
 
@@ -847,9 +879,13 @@ export class Whisper {
847
879
  offset: offsetTensor
848
880
  }
849
881
 
850
- // Run decoder model
882
+ //// Infer with ONNX decoder model
883
+ const tokenInferenceTimeTimer = new Timer()
884
+
851
885
  const decoderOutputs = await this.textDecoder!.run(decoderInputs)
852
886
 
887
+ decodedTokensInferenceTime.push(tokenInferenceTimeTimer.elapsedTime)
888
+
853
889
  // Extract decoder model results
854
890
  const logitsBuffer = decoderOutputs['logits'].data as Float32Array
855
891
  kvCacheTensor = decoderOutputs['output_kv_cache'] as any
@@ -1142,6 +1178,10 @@ export class Whisper {
1142
1178
  await yieldToEventLoop()
1143
1179
  }
1144
1180
 
1181
+ if (decodedTokensDecodingTime.length === decodedTokensInferenceTime.length - 1) {
1182
+ decodedTokensDecodingTime.push(tokenDecodingTimeTimer.getElapsedTimeAndRestart())
1183
+ }
1184
+
1145
1185
  // If at least two timestamp tokens were decoded and it's not the final part,
1146
1186
  // truncate up to the last timestamp token
1147
1187
  if (timestampTokenSeenCount >= 2 && !isFinalPart) {
@@ -1151,8 +1191,13 @@ export class Whisper {
1151
1191
  decodedTokensTimestampLogits = decodedTokensTimestampLogits.slice(0, sliceEndTokenIndex)
1152
1192
  decodedTokensCrossAttentionQKs = decodedTokensCrossAttentionQKs.slice(0, sliceEndTokenIndex)
1153
1193
  decodedTokensConfidence = decodedTokensConfidence.slice(0, sliceEndTokenIndex)
1194
+
1195
+ decodedTokensDecodingTime = decodedTokensDecodingTime.slice(0, sliceEndTokenIndex)
1196
+ decodedTokensInferenceTime = decodedTokensInferenceTime.slice(0, sliceEndTokenIndex)
1154
1197
  }
1155
1198
 
1199
+ const decodedTokensOverheadTime = decodedTokensDecodingTime.map((time, index) => time - decodedTokensInferenceTime[index])
1200
+
1156
1201
  logger.write('\n')
1157
1202
  logger.end()
1158
1203
 
@@ -1162,6 +1207,10 @@ export class Whisper {
1162
1207
  decodedTokensTimestampLogits,
1163
1208
  decodedTokensConfidence,
1164
1209
  decodedTokensCrossAttentionQKs,
1210
+
1211
+ decodedTokensDecodingTime,
1212
+ decodedTokensInferenceTime,
1213
+ decodedTokensOverheadTime,
1165
1214
  }
1166
1215
  }
1167
1216
 
@@ -1186,7 +1235,7 @@ export class Whisper {
1186
1235
  const maxAudioSamples = sampleRate * 30
1187
1236
  const maxAudioFrames = 3000
1188
1237
 
1189
- if (sampleRate != 16000) {
1238
+ if (sampleRate !== 16000) {
1190
1239
  throw new Error('Audio must have a sample rate of 16000 Hz')
1191
1240
  }
1192
1241
 
@@ -1194,8 +1243,7 @@ export class Whisper {
1194
1243
  throw new Error(`Audio part is longer than 30 seconds`)
1195
1244
  }
1196
1245
 
1197
- // Compute a mel spectogram
1198
- await logger.startAsync('Extract mel spectogram from audio part')
1246
+ await logger.startAsync('Extract Mel spectrogram from audio part')
1199
1247
 
1200
1248
  // Pad audio samples to ensure that have a duration of 30 seconds
1201
1249
  const paddedAudioSamples = new Float32Array(maxAudioSamples)
@@ -1203,40 +1251,40 @@ export class Whisper {
1203
1251
 
1204
1252
  const rawAudioPart: RawAudio = { audioChannels: [paddedAudioSamples], sampleRate }
1205
1253
 
1206
- const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder, fftWindowSize, fftHopLength, filterbanks)
1254
+ // Compute Mel spectrogram
1255
+ const { melSpectrogram } = await computeMelSpectrogramUsingFilterbanks(rawAudioPart, fftOrder, fftWindowSize, fftHopLength, filterbanks)
1207
1256
 
1208
- await logger.startAsync('Normalize mel spectogram')
1257
+ // Flatten, transpose, apply logarithm and normalize Mel spectrogram
1258
+ await logger.startAsync('Process Mel spectrogram')
1209
1259
 
1210
- const logMelSpectogram = melSpectogram.map(spectrum => spectrum.map(mel => Math.log10(Math.max(mel, 1e-10))))
1260
+ const flattenedLogMelSpectrogram = new Float32Array(maxAudioFrames * filterbankCount)
1211
1261
 
1212
- // Find maximum log mel value in the spectrum
1213
1262
  let maxLogMel = -Infinity
1214
1263
 
1215
- for (const spectrum of logMelSpectogram) {
1216
- for (const mel of spectrum) {
1217
- if (mel > maxLogMel) {
1218
- maxLogMel = mel
1264
+ for (let i = 0; i < filterbankCount; i++) {
1265
+ for (let j = 0; j < maxAudioFrames; j++) {
1266
+ const mel = melSpectrogram[j][i]
1267
+ const logMel = Math.log10(Math.max(mel, 1e-10))
1268
+
1269
+ if (logMel > maxLogMel) {
1270
+ maxLogMel = logMel
1219
1271
  }
1272
+
1273
+ flattenedLogMelSpectrogram[(i * maxAudioFrames) + j] = logMel
1220
1274
  }
1221
1275
  }
1222
1276
 
1223
- // Normalize log mel spectogram (based on Python reference code)
1224
- const normalizedLogMelSpectogram = logMelSpectogram.map(spectrum => spectrum.map(
1225
- logMel => (Math.max(logMel, maxLogMel - 8) + 4) / 4))
1226
-
1227
- // Flatten the normalized log mel spectogram
1228
- const flattenedNormalizedLogMelSpectogram = new Float32Array(maxAudioFrames * filterbankCount)
1277
+ for (let i = 0; i < flattenedLogMelSpectrogram.length; i++) {
1278
+ const logMel = flattenedLogMelSpectrogram[i]
1279
+ const normalizedLogMel = (Math.max(logMel, maxLogMel - 8) + 4) / 4
1229
1280
 
1230
- for (let i = 0; i < filterbankCount; i++) {
1231
- for (let j = 0; j < maxAudioFrames; j++) {
1232
- flattenedNormalizedLogMelSpectogram[(i * maxAudioFrames) + j] = normalizedLogMelSpectogram[j][i]
1233
- }
1281
+ flattenedLogMelSpectrogram[i] = normalizedLogMel
1234
1282
  }
1235
1283
 
1236
1284
  // Run the encoder model
1237
- await logger.startAsync('Encode mel spectogram with Whisper encoder model')
1285
+ await logger.startAsync('Encode Mel spectrogram with Whisper encoder model')
1238
1286
 
1239
- const inputTensor = new Onnx.Tensor('float32', flattenedNormalizedLogMelSpectogram, [1, filterbankCount, maxAudioFrames])
1287
+ const inputTensor = new Onnx.Tensor('float32', flattenedLogMelSpectrogram, [1, filterbankCount, maxAudioFrames])
1240
1288
 
1241
1289
  const encoderInputs = { mel: inputTensor }
1242
1290
 
@@ -1267,7 +1315,7 @@ export class Whisper {
1267
1315
  return isSeparatorCharacter(text[text.length - 1])
1268
1316
  }
1269
1317
 
1270
- if (language != 'zh' && language != 'ja') {
1318
+ if (language !== 'zh' && language !== 'ja') {
1271
1319
  tokenTimeline = tokenTimeline.filter(entry => this.isTextToken(entry.id!))
1272
1320
  }
1273
1321
 
@@ -1282,7 +1330,7 @@ export class Whisper {
1282
1330
  const text = entry.text
1283
1331
  const previousEntryText = previousEntry?.text
1284
1332
 
1285
- if (groups.length == 0 ||
1333
+ if (groups.length === 0 ||
1286
1334
  text === '' ||
1287
1335
  startsWithSeparatorCharacter(text) ||
1288
1336
  (previousEntryText != null && endsWithSeparatorCharacter(previousEntryText))) {
@@ -1346,14 +1394,14 @@ export class Whisper {
1346
1394
  }
1347
1395
 
1348
1396
  async getTokenTimelineFromAlignmentPath(alignmentPath: AlignmentPath, tokens: number[], startTimeOffset: number, endTimeOffset: number, tokensConfidence?: number[], correctionAmount = 0.0) {
1349
- if (alignmentPath.length == 0) {
1397
+ if (alignmentPath.length === 0) {
1350
1398
  return []
1351
1399
  }
1352
1400
 
1353
1401
  const tokenTimeline: Timeline = []
1354
1402
 
1355
1403
  for (let pathIndex = 0; pathIndex < alignmentPath.length; pathIndex++) {
1356
- if (pathIndex != 0 && alignmentPath[pathIndex].source == alignmentPath[pathIndex - 1].source) {
1404
+ if (pathIndex !== 0 && alignmentPath[pathIndex].source === alignmentPath[pathIndex - 1].source) {
1357
1405
  continue
1358
1406
  }
1359
1407
 
@@ -1448,8 +1496,10 @@ export class Whisper {
1448
1496
  let countOfAllWeightsForHead = 0
1449
1497
 
1450
1498
  for (const tokenFrames of head) {
1451
- sumOfAllWeightsForHead += sumVector(tokenFrames)
1452
- sumOfAllSquaredWeightsForHead += sumOfSquaresForVector(tokenFrames)
1499
+ const { sum, sumOfSquares } = sumAndSumOfSquaresOfVector(tokenFrames)
1500
+
1501
+ sumOfAllWeightsForHead += sum
1502
+ sumOfAllSquaredWeightsForHead += sumOfSquares
1453
1503
  countOfAllWeightsForHead += tokenFrames.length
1454
1504
  }
1455
1505
 
@@ -1604,7 +1654,7 @@ export class Whisper {
1604
1654
 
1605
1655
  const onnxSessionOptions = getOnnxSessionOptions({ executionProviders: this.encoderExecutionProviders })
1606
1656
 
1607
- const onnxProvidersString = onnxSessionOptions.executionProviders!.length > 0 ? `${ onnxSessionOptions.executionProviders!.join(', ') }` : `default`
1657
+ const onnxProvidersString = onnxSessionOptions.executionProviders!.length > 0 ? `${onnxSessionOptions.executionProviders!.join(', ')}` : `default`
1608
1658
 
1609
1659
  await logger.startAsync(`Create encoder inference session for model '${this.modelName}' (ONNX provider: ${onnxProvidersString})`)
1610
1660
 
@@ -1642,15 +1692,15 @@ export class Whisper {
1642
1692
  getKvDimensions(groupCount: number, length: number) {
1643
1693
  const modelName = this.modelName
1644
1694
 
1645
- if (modelName == 'tiny' || modelName == 'tiny.en') {
1695
+ if (modelName === 'tiny' || modelName === 'tiny.en') {
1646
1696
  return [8, groupCount, length, 384]
1647
- } else if (modelName == 'base' || modelName == 'base.en') {
1697
+ } else if (modelName === 'base' || modelName === 'base.en') {
1648
1698
  return [12, groupCount, length, 512]
1649
- } else if (modelName == 'small' || modelName == 'small.en') {
1699
+ } else if (modelName === 'small' || modelName === 'small.en') {
1650
1700
  return [24, groupCount, length, 768]
1651
- } else if (modelName == 'medium' || modelName == 'medium.en') {
1701
+ } else if (modelName === 'medium' || modelName === 'medium.en') {
1652
1702
  return [48, groupCount, length, 1024]
1653
- } else if (modelName == 'large-v1' || modelName == 'large-v2' || modelName == 'large-v3' || modelName == 'large-v3-turbo') {
1703
+ } else if (modelName === 'large-v1' || modelName === 'large-v2' || modelName === 'large-v3' || modelName === 'large-v3-turbo') {
1654
1704
  return [64, groupCount, length, 1280]
1655
1705
  } else {
1656
1706
  throw new Error(`Unsupported model: ${modelName}`)
@@ -1664,7 +1714,7 @@ export class Whisper {
1664
1714
 
1665
1715
  if (this.isMultiligualModel) {
1666
1716
  const languageToken = this.tokenConfig.languageTokensStart + languageIdLookup[language]
1667
- const taskToken = task == 'translate' ? this.tokenConfig.translateTaskToken : this.tokenConfig.transcribeTaskToken
1717
+ const taskToken = task === 'translate' ? this.tokenConfig.translateTaskToken : this.tokenConfig.transcribeTaskToken
1668
1718
 
1669
1719
  tokens = [startOfTextToken, languageToken, taskToken]
1670
1720
  } else {
@@ -1900,7 +1950,7 @@ export async function loadPackagesAndGetPaths(modelName: WhisperModelName | unde
1900
1950
  if (languageCode) {
1901
1951
  const shortLanguageCode = getShortLanguageCode(languageCode)
1902
1952
 
1903
- modelName = shortLanguageCode == 'en' ? 'tiny.en' : 'tiny'
1953
+ modelName = shortLanguageCode === 'en' ? 'tiny.en' : 'tiny'
1904
1954
  } else {
1905
1955
  modelName = 'tiny'
1906
1956
  }
@@ -1918,7 +1968,7 @@ export async function loadPackagesAndGetPaths(modelName: WhisperModelName | unde
1918
1968
  }
1919
1969
 
1920
1970
  export function normalizeWhisperModelName(modelName: WhisperModelName, languageCode: string | undefined): WhisperModelName {
1921
- if (languageCode != 'en' && modelName.endsWith('.en')) {
1971
+ if (languageCode !== 'en' && modelName.endsWith('.en')) {
1922
1972
  const originalModelName = modelName
1923
1973
  modelName = modelName.slice(0, modelName.length - 3) as WhisperModelName
1924
1974
 
@@ -1964,7 +2014,20 @@ export type WhisperTokenData = {
1964
2014
 
1965
2015
  export type WhisperLogitFilter = (logits: number[], decodedTokens: number[], isFirstPart: boolean, isFinalPart: boolean) => number[]
1966
2016
 
1967
- export type WhisperModelName = 'tiny' | 'tiny.en' | 'base' | 'base.en' | 'small' | 'small.en' | 'medium' | 'medium.en' | 'large-v1' | 'large-v2' | 'large-v3' | 'large-v3-turbo'
2017
+ export type WhisperModelName =
2018
+ 'tiny' |
2019
+ 'tiny.en' |
2020
+ 'base' |
2021
+ 'base.en' |
2022
+ 'small' |
2023
+ 'small.en' |
2024
+ 'medium' |
2025
+ 'medium.en' |
2026
+ 'large-v1' |
2027
+ 'large-v2' |
2028
+ 'large-v3' |
2029
+ 'large-v3-turbo'
2030
+
1968
2031
  export type WhisperTask = 'transcribe' | 'translate' | 'detect-language'
1969
2032
 
1970
2033
  export const modelNameToPackageName: { [modelName in WhisperModelName]: string } = {
@@ -2084,17 +2147,18 @@ const languageIdLookup: { [s: string]: number } = {
2084
2147
  'ba': 96,
2085
2148
  'jw': 97,
2086
2149
  'su': 98,
2150
+ //'yue': 99
2087
2151
  }
2088
2152
 
2089
2153
  const alignmentHeadsIndexes: { [name in WhisperModelName]: number[] } = {
2090
- 'tiny.en': [6, 12, 17, 18, 19, 20, 21, 22,],
2091
2154
  'tiny': [14, 18, 20, 21, 22, 23,],
2092
- 'base.en': [27, 39, 41, 45, 47,],
2155
+ 'tiny.en': [6, 12, 17, 18, 19, 20, 21, 22,],
2093
2156
  'base': [25, 34, 35, 39, 41, 42, 44, 46,],
2094
- 'small.en': [78, 84, 87, 92, 98, 101, 103, 108, 112, 116, 118, 120, 121, 122, 123, 126, 131, 134, 136,],
2157
+ 'base.en': [27, 39, 41, 45, 47,],
2095
2158
  'small': [63, 69, 96, 100, 103, 104, 108, 115, 117, 125,],
2096
- 'medium.en': [180, 225, 236, 238, 244, 256, 260, 265, 284, 286, 295, 298, 303, 320, 323, 329, 334, 348,],
2159
+ 'small.en': [78, 84, 87, 92, 98, 101, 103, 108, 112, 116, 118, 120, 121, 122, 123, 126, 131, 134, 136,],
2097
2160
  'medium': [223, 244, 255, 257, 320, 372,],
2161
+ 'medium.en': [180, 225, 236, 238, 244, 256, 260, 265, 284, 286, 295, 298, 303, 320, 323, 329, 334, 348,],
2098
2162
  'large-v1': [199, 222, 224, 237, 447, 451, 457, 462, 475,],
2099
2163
  'large-v2': [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555,],
2100
2164
  'large-v3': [140, 217, 258, 272, 321, 354, 391, 424, 481, 506,],
@@ -2613,4 +2677,6 @@ export const defaultWhisperVADOptions: WhisperVADOptions = {
2613
2677
  decoderProvider: undefined,
2614
2678
  }
2615
2679
 
2616
- type WhisperTimestampAccuracy = 'medium' | 'high'
2680
+ export type WhisperTimestampAccuracy = 'medium' | 'high'
2681
+
2682
+ export type WhisperPartCallback = (partTranscript: string, partTokenTimeline: Timeline, partWordTimeline: Timeline) => void
@@ -1,6 +1,6 @@
1
1
  import type * as Onnx from 'onnxruntime-node'
2
2
  import { getEmptyRawAudio, RawAudio } from '../audio/AudioUtilities.js'
3
- import { getWindowWeights, stftrGenerator, stiftr, WindowType } from '../dsp/FFT.js'
3
+ import { getWindowWeights, createStftrGenerator, stiftr, WindowType } from '../dsp/FFT.js'
4
4
  import { logToStderr } from '../utilities/Utilities.js'
5
5
  import { Logger } from '../utilities/Logger.js'
6
6
  import { OnnxExecutionProvider, dmlProviderAvailable, getOnnxSessionOptions } from '../utilities/OnnxUtilities.js'
@@ -74,8 +74,8 @@ export class MDXNet {
74
74
  const fftSizeReciprocal = 1 / fftSize
75
75
 
76
76
  // Initialize generators for STFT frames for each channel
77
- const fftFramesLeftGenerator = stftrGenerator(rawAudio.audioChannels[0], fftSize, fftWindowSize, fftHopSize, fftWindowType)
78
- const fftFramesRightGenerator = stftrGenerator(rawAudio.audioChannels[1], fftSize, fftWindowSize, fftHopSize, fftWindowType)
77
+ const fftFramesLeftGenerator = await createStftrGenerator(rawAudio.audioChannels[0], fftSize, fftWindowSize, fftHopSize, fftWindowType)
78
+ const fftFramesRightGenerator = await createStftrGenerator(rawAudio.audioChannels[1], fftSize, fftWindowSize, fftHopSize, fftWindowType)
79
79
 
80
80
  // Initial windowed lists to store recently computed STFT frames
81
81
  const fftFramesLeftWindowedList = new WindowedList<Float32Array>(segmentSize)
@@ -95,13 +95,13 @@ export class MDXNet {
95
95
  }
96
96
 
97
97
  while (fftFramesLeftWindowedList.endOffset < segmentEndFrameOffset) {
98
- const nextLeftFrameResult = await fftFramesLeftGenerator.next()
98
+ const nextLeftFrameResult = fftFramesLeftGenerator.next()
99
99
 
100
100
  if (nextLeftFrameResult.done) {
101
101
  break
102
102
  }
103
103
 
104
- const nextRightFrameResult = await fftFramesRightGenerator.next()
104
+ const nextRightFrameResult = fftFramesRightGenerator.next()
105
105
 
106
106
  if (nextRightFrameResult.done) {
107
107
  break
@@ -2,7 +2,7 @@ import type * as Onnx from 'onnxruntime-node'
2
2
  import { OnnxExecutionProvider, getOnnxSessionOptions } from '../utilities/OnnxUtilities.js';
3
3
 
4
4
  import { RawAudio } from "../audio/AudioUtilities.js";
5
- import { computeMelSpectogram } from "../dsp/MelSpectogram.js";
5
+ import { computeMelSpectrogram } from "../dsp/MelSpectrogram.js";
6
6
  import { Logger } from '../utilities/Logger.js';
7
7
  import { concatFloat32Arrays, splitFloat32Array } from '../utilities/Utilities.js';
8
8
  import { applyEmphasis } from '../dsp/MFCC.js';
@@ -31,7 +31,7 @@ export class Wav2Vec2BertFeatureEmbeddings {
31
31
 
32
32
  rawAudio.audioChannels[0] = applyEmphasis(rawAudio.audioChannels[0], 0.97)
33
33
 
34
- const { melSpectogram } = await computeMelSpectogram(
34
+ const { melSpectrogram } = await computeMelSpectrogram(
35
35
  rawAudio,
36
36
  512,
37
37
  400,
@@ -42,8 +42,8 @@ export class Wav2Vec2BertFeatureEmbeddings {
42
42
  'povey')
43
43
 
44
44
  // Ensure even length
45
- if (melSpectogram.length % 2 != 0) {
46
- melSpectogram.push(new Float32Array(80))
45
+ if (melSpectrogram.length % 2 != 0) {
46
+ melSpectrogram.push(new Float32Array(80))
47
47
  }
48
48
 
49
49
  // Normalize filterbanks
@@ -51,24 +51,24 @@ export class Wav2Vec2BertFeatureEmbeddings {
51
51
  let sum = 0
52
52
  let sumOfSquares = 0
53
53
 
54
- for (let i = 0; i < melSpectogram.length; i++) {
55
- const value = melSpectogram[i][filterbankIndex]
54
+ for (let i = 0; i < melSpectrogram.length; i++) {
55
+ const value = melSpectrogram[i][filterbankIndex]
56
56
 
57
57
  sum += value
58
58
  sumOfSquares += value ** 2
59
59
  }
60
60
 
61
- const mean = sum / melSpectogram.length
62
- const normalizationFactor = 1 / (Math.sqrt(sumOfSquares / melSpectogram.length) + 1e-40)
61
+ const mean = sum / melSpectrogram.length
62
+ const normalizationFactor = 1 / (Math.sqrt(sumOfSquares / melSpectrogram.length) + 1e-40)
63
63
 
64
- for (let i = 0; i < melSpectogram.length; i++) {
65
- melSpectogram[i][filterbankIndex] -= mean
66
- melSpectogram[i][filterbankIndex] *= normalizationFactor
64
+ for (let i = 0; i < melSpectrogram.length; i++) {
65
+ melSpectrogram[i][filterbankIndex] -= mean
66
+ melSpectrogram[i][filterbankIndex] *= normalizationFactor
67
67
  }
68
68
  }
69
69
 
70
70
  // Flatten
71
- const flattenedMelSpectogram = concatFloat32Arrays(melSpectogram)
71
+ const flattenedMelSpectrogram = concatFloat32Arrays(melSpectrogram)
72
72
 
73
73
  // Initialize session
74
74
  await this.initializeSessionIfNeeded()
@@ -77,9 +77,9 @@ export class Wav2Vec2BertFeatureEmbeddings {
77
77
 
78
78
  const Onnx = await import('onnxruntime-node')
79
79
 
80
- const inputTensor = new Onnx.Tensor('float32', flattenedMelSpectogram, [1, melSpectogram.length / 2, 80 * 2])
80
+ const inputTensor = new Onnx.Tensor('float32', flattenedMelSpectrogram, [1, melSpectrogram.length / 2, 80 * 2])
81
81
 
82
- const attentionMask = new Int32Array(melSpectogram.length / 2).fill(1)
82
+ const attentionMask = new Int32Array(melSpectrogram.length / 2).fill(1)
83
83
  const attentionMaskTensor = new Onnx.Tensor('int32', attentionMask, [1, attentionMask.length])
84
84
 
85
85
  // Run inference
@@ -130,13 +130,15 @@ export function sleep(timeMs: number) {
130
130
  return new Promise<void>((resolve) => {
131
131
  const tickCallback = () => {
132
132
  if (timer.elapsedTime < timeMs) {
133
- setImmediate(tickCallback)
133
+ //setImmediate(tickCallback)
134
+ setTimeout(tickCallback, 0)
134
135
  } else {
135
136
  resolve()
136
137
  }
137
138
  }
138
139
 
139
- setImmediate(tickCallback)
140
+ //setImmediate(tickCallback)
141
+ setTimeout(tickCallback, 0)
140
142
  })
141
143
  }
142
144
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"MelSpectogram.js","sourceRoot":"","sources":["../../src/dsp/MelSpectogram.ts"],"names":[],"mappings":"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"}