echogarden 2.5.2 → 2.7.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 (84) hide show
  1. package/data/lexicons/heteronyms.en.json +39 -5
  2. package/data/lexicons/words.en.json +1336 -1470
  3. package/dist/api/SourceSeparation.js +1 -1
  4. package/dist/audio/AudioBufferConversion.d.ts +5 -6
  5. package/dist/audio/AudioBufferConversion.js +16 -248
  6. package/dist/audio/AudioBufferConversion.js.map +1 -1
  7. package/dist/audio/AudioPlayer.js.map +1 -1
  8. package/dist/audio/AudioRecorder.js +1 -1
  9. package/dist/audio/AudioRecorder.js.map +1 -1
  10. package/dist/audio/AudioUtilities.d.ts +3 -9
  11. package/dist/audio/AudioUtilities.js +4 -3
  12. package/dist/audio/AudioUtilities.js.map +1 -1
  13. package/dist/codecs/FFMpegTranscoder.d.ts +1 -4
  14. package/dist/codecs/TIMITCodec.js +1 -2
  15. package/dist/codecs/TIMITCodec.js.map +1 -1
  16. package/dist/encodings/Ascii.js +1 -1
  17. package/dist/encodings/Ascii.js.map +1 -1
  18. package/dist/encodings/Utf16.js +1 -1
  19. package/dist/encodings/Utf16.js.map +1 -1
  20. package/dist/encodings/Utf8.js +1 -1
  21. package/dist/encodings/Utf8.js.map +1 -1
  22. package/dist/math/VectorMath.js +8 -7
  23. package/dist/math/VectorMath.js.map +1 -1
  24. package/dist/nlp/CompromiseNLP.js +4 -1
  25. package/dist/nlp/CompromiseNLP.js.map +1 -1
  26. package/dist/nlp/Lexicon.d.ts +1 -1
  27. package/dist/nlp/Lexicon.js +4 -1
  28. package/dist/nlp/Lexicon.js.map +1 -1
  29. package/dist/nlp/PhoneConversion.js +17 -5
  30. package/dist/nlp/PhoneConversion.js.map +1 -1
  31. package/dist/recognition/WhisperCppSTT.js +6 -3
  32. package/dist/recognition/WhisperCppSTT.js.map +1 -1
  33. package/dist/recognition/WhisperSTT.js +18 -17
  34. package/dist/recognition/WhisperSTT.js.map +1 -1
  35. package/dist/synthesis/AwsPollyTTS.d.ts +1 -4
  36. package/dist/synthesis/CoquiServerTTS.d.ts +1 -4
  37. package/dist/synthesis/DeepgramTTS.d.ts +1 -4
  38. package/dist/synthesis/ElevenLabsTTS.d.ts +1 -4
  39. package/dist/synthesis/FliteTTS.d.ts +1 -4
  40. package/dist/synthesis/GnuSpeechTTS.d.ts +1 -7
  41. package/dist/synthesis/GoogleCloudTTS.d.ts +1 -4
  42. package/dist/synthesis/SapiTTS.js +1 -2
  43. package/dist/synthesis/SapiTTS.js.map +1 -1
  44. package/dist/synthesis/SvoxPicoTTS.js +1 -2
  45. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  46. package/dist/utilities/PackageManager.js +4 -3
  47. package/dist/utilities/PackageManager.js.map +1 -1
  48. package/dist/utilities/StringUtilities.d.ts +2 -2
  49. package/dist/utilities/StringUtilities.js +7 -7
  50. package/dist/utilities/StringUtilities.js.map +1 -1
  51. package/dist/utilities/Utilities.d.ts +1 -0
  52. package/dist/utilities/Utilities.js +48 -0
  53. package/dist/utilities/Utilities.js.map +1 -1
  54. package/dist/utilities/WasmMemoryManager.d.ts +1 -1
  55. package/docs/Options.md +2 -2
  56. package/package.json +7 -7
  57. package/src/api/SourceSeparation.ts +1 -1
  58. package/src/audio/AudioBufferConversion.ts +17 -263
  59. package/src/audio/AudioPlayer.ts +1 -1
  60. package/src/audio/AudioRecorder.ts +1 -1
  61. package/src/audio/AudioUtilities.ts +5 -3
  62. package/src/codecs/TIMITCodec.ts +1 -2
  63. package/src/encodings/Ascii.ts +1 -1
  64. package/src/encodings/Utf16.ts +1 -1
  65. package/src/encodings/Utf8.ts +1 -1
  66. package/src/math/VectorMath.ts +9 -7
  67. package/src/nlp/CompromiseNLP.ts +5 -1
  68. package/src/nlp/Lexicon.ts +6 -2
  69. package/src/nlp/PhoneConversion.ts +20 -6
  70. package/src/recognition/WhisperCppSTT.ts +5 -3
  71. package/src/recognition/WhisperSTT.ts +19 -18
  72. package/src/synthesis/SapiTTS.ts +1 -2
  73. package/src/synthesis/SvoxPicoTTS.ts +1 -2
  74. package/src/utilities/PackageManager.ts +4 -3
  75. package/src/utilities/StringUtilities.ts +7 -7
  76. package/src/utilities/Utilities.ts +64 -0
  77. package/dist/codecs/WaveCodec.d.ts +0 -19
  78. package/dist/codecs/WaveCodec.js +0 -224
  79. package/dist/codecs/WaveCodec.js.map +0 -1
  80. package/dist/utilities/BinaryArrayConversion.d.ts +0 -14
  81. package/dist/utilities/BinaryArrayConversion.js +0 -92
  82. package/dist/utilities/BinaryArrayConversion.js.map +0 -1
  83. package/src/codecs/WaveCodec.ts +0 -281
  84. package/src/utilities/BinaryArrayConversion.ts +0 -117
@@ -6,8 +6,8 @@ import * as AudioBufferConversion from './AudioBufferConversion.js'
6
6
  import { Timer } from '../utilities/Timer.js'
7
7
  import { logToStderr } from '../utilities/Utilities.js'
8
8
  import { OpenPromise } from '../utilities/OpenPromise.js'
9
- import { SampleFormat } from '../codecs/WaveCodec.js'
10
9
  import { tryResolvingSoxPath } from './SoxPath.js'
10
+ import { SampleFormat } from './AudioBufferConversion.js'
11
11
 
12
12
  const log = logToStderr
13
13
 
@@ -1,5 +1,5 @@
1
1
  import * as FFMpegTranscoder from '../codecs/FFMpegTranscoder.js'
2
- import { SampleFormat, encodeWave, decodeWave, BitDepth } from '../codecs/WaveCodec.js'
2
+ import { SampleFormat, BitDepth, encodeWaveFromFloat32Channels, decodeWaveToFloat32Channels } from '@echogarden/wave-codec'
3
3
  import { resampleAudioSpeex } from '../dsp/SpeexResampler.js'
4
4
  import { Timeline } from '../utilities/Timeline.js'
5
5
  import { concatFloat32Arrays } from '../utilities/Utilities.js'
@@ -8,11 +8,13 @@ import { concatFloat32Arrays } from '../utilities/Utilities.js'
8
8
  // Wave encoding and decoding
9
9
  ////////////////////////////////////////////////////////////////////////////////////////////////
10
10
  export function encodeRawAudioToWave(rawAudio: RawAudio, bitDepth: BitDepth = 16, sampleFormat: SampleFormat = SampleFormat.PCM, speakerPositionMask = 0) {
11
- return encodeWave(rawAudio, bitDepth, sampleFormat, speakerPositionMask)
11
+ return encodeWaveFromFloat32Channels(rawAudio.audioChannels, rawAudio.sampleRate, bitDepth, sampleFormat, speakerPositionMask)
12
12
  }
13
13
 
14
14
  export function decodeWaveToRawAudio(waveFileBuffer: Uint8Array, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
15
- return decodeWave(waveFileBuffer, ignoreTruncatedChunks, ignoreOverflowingDataChunks)
15
+ const rawAudio = decodeWaveToFloat32Channels(waveFileBuffer, ignoreTruncatedChunks, ignoreOverflowingDataChunks)
16
+
17
+ return { rawAudio }
16
18
  }
17
19
 
18
20
  ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1,7 +1,6 @@
1
- import { decodeToChannels } from '../audio/AudioBufferConversion.js'
1
+ import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
2
2
  import { decodeAscii } from '../encodings/Ascii.js'
3
3
  import { readFileAsBinary } from '../utilities/FileSystem.js'
4
- import { SampleFormat } from './WaveCodec.js'
5
4
 
6
5
  export async function decodeTimitAudioFile(filename: string) {
7
6
  return decodeTimitAudio(await readFileAsBinary(filename))
@@ -49,7 +49,7 @@ export class ChunkedAsciiDecoder {
49
49
  private readonly textDecoder = new TextDecoder('windows-1252')
50
50
 
51
51
  writeChunk(chunk: Uint8Array) {
52
- const decodedChunk = this.textDecoder.decode(chunk)
52
+ const decodedChunk = this.textDecoder.decode(chunk, { stream: true })
53
53
 
54
54
  this.str += decodedChunk
55
55
  }
@@ -41,7 +41,7 @@ export class ChunkedUtf16Decoder {
41
41
  private readonly textDecoder = new TextDecoder('utf-16le')
42
42
 
43
43
  writeChunk(chunk: Uint16Array) {
44
- const decodedChunk = this.textDecoder.decode(chunk)
44
+ const decodedChunk = this.textDecoder.decode(chunk, { stream: true })
45
45
 
46
46
  this.str += decodedChunk
47
47
  }
@@ -33,7 +33,7 @@ export class ChunkedUtf8Decoder {
33
33
  private readonly textDecoder = new TextDecoder('utf-8')
34
34
 
35
35
  writeChunk(chunk: Uint8Array) {
36
- const decodedChunk = this.textDecoder.decode(chunk)
36
+ const decodedChunk = this.textDecoder.decode(chunk, { stream: true })
37
37
 
38
38
  this.str += decodedChunk
39
39
  }
@@ -727,6 +727,8 @@ export function sigmoid(x: number) {
727
727
  }
728
728
 
729
729
  export function softmax(logits: ArrayLike<number>, temperature = 1.0) {
730
+ temperature = Math.max(temperature, 0.00001)
731
+
730
732
  const logitCount = logits.length
731
733
 
732
734
  if (logitCount === 0) {
@@ -743,23 +745,23 @@ export function softmax(logits: ArrayLike<number>, temperature = 1.0) {
743
745
  }
744
746
  }
745
747
 
746
- const temperatureReciprocal = 1 / (temperature + 1e-40)
748
+ const temperatureReciprocal = 1 / temperature
747
749
 
748
750
  const results = new Float32Array(logitCount)
749
751
 
750
- let sumOfExponentiatedValues = 0.0
752
+ let sumOfExponentiatedLogits = 0.0
751
753
 
752
754
  for (let i = 0; i < logitCount; i++) {
753
- const value = logits[i]
755
+ const logit = logits[i]
754
756
 
755
- const eToValue = Math.exp((value - maxValue) * temperatureReciprocal)
757
+ const eToLogit = Math.exp((logit - maxValue) * temperatureReciprocal)
756
758
 
757
- sumOfExponentiatedValues += eToValue
759
+ sumOfExponentiatedLogits += eToLogit
758
760
 
759
- results[i] = eToValue
761
+ results[i] = eToLogit
760
762
  }
761
763
 
762
- const sumOfExponentiatedValuesReciprocal = 1 / (sumOfExponentiatedValues + 1e-40)
764
+ const sumOfExponentiatedValuesReciprocal = 1 / (sumOfExponentiatedLogits + 1e-20)
763
765
 
764
766
  for (let i = 0; i < logitCount; i++) {
765
767
  results[i] *= sumOfExponentiatedValuesReciprocal
@@ -79,12 +79,16 @@ export function tryMatchInLexicon(term: CompromiseParsedTerm, lexicon: Lexicon,
79
79
  const termText = term.text
80
80
  const lowerCaseTermText = termText.toLocaleLowerCase()
81
81
 
82
- const entry = lexiconForLanguage[lowerCaseTermText]
82
+ let entry = lexiconForLanguage[lowerCaseTermText]
83
83
 
84
84
  if (!entry) {
85
85
  return undefined
86
86
  }
87
87
 
88
+ if (!Array.isArray(entry)) {
89
+ entry = [entry]
90
+ }
91
+
88
92
  for (const substitutionEntry of entry) {
89
93
  if (!substitutionEntry.pos || substitutionEntry.pos.includes(term.pos)) {
90
94
  const substitutionPhonemesText = substitutionEntry?.pronunciation?.espeak?.[espeakVoice]
@@ -30,12 +30,16 @@ export function tryGetLexiconSubstitution(sentenceWords: string[], wordIndex: nu
30
30
  return undefined
31
31
  }
32
32
 
33
- const lexiconEntry = lexiconForLanguage[word]
33
+ let lexiconEntry = lexiconForLanguage[word]
34
34
 
35
35
  if (!lexiconEntry) {
36
36
  return undefined
37
37
  }
38
38
 
39
+ if (!Array.isArray(lexiconEntry)) {
40
+ lexiconEntry = [lexiconEntry]
41
+ }
42
+
39
43
  for (let i = 0; i < lexiconEntry.length; i++) {
40
44
  const candidateEntry = lexiconEntry[i]
41
45
 
@@ -100,7 +104,7 @@ export type Lexicon = {
100
104
  }
101
105
 
102
106
  export type LexiconForLanguage = {
103
- [word: string]: LexiconEntry[]
107
+ [word: string]: LexiconEntry | LexiconEntry[]
104
108
  }
105
109
 
106
110
  export type LexiconEntry = {
@@ -1,14 +1,26 @@
1
1
  export function ipaPhoneToKirshenbaum(ipaPhone: string) {
2
2
  let result = ''
3
3
 
4
- for (const char of ipaPhone) {
5
- const convertedChar = ipaToKirshenbaum[char]
4
+ if (ipaPhone[0] === 'ˈ' || ipaPhone[0] === 'ˌ') {
5
+ result += ipaToKirshenbaum[ipaPhone[0]]
6
6
 
7
- if (convertedChar == undefined) {
8
- throw new Error(`Couldn't convert IPA character '${char}' (part of phone '${ipaPhone}') to the Kirshenbaum notation`)
9
- }
7
+ ipaPhone = ipaPhone.substring(1)
8
+ }
9
+
10
+ const convertedFullPhone = ipaToKirshenbaum[ipaPhone]
10
11
 
11
- result += convertedChar || '_'
12
+ if (convertedFullPhone) {
13
+ result += convertedFullPhone
14
+ } else {
15
+ for (const char of ipaPhone) {
16
+ const convertedChar = ipaToKirshenbaum[char]
17
+
18
+ if (convertedChar === undefined) {
19
+ throw new Error(`Couldn't convert IPA character '${char}' (part of phone '${ipaPhone}') to the Kirshenbaum notation`)
20
+ }
21
+
22
+ result += convertedChar ?? '_'
23
+ }
12
24
  }
13
25
 
14
26
  return result
@@ -252,6 +264,8 @@ const ipaToTimit: { [p: string]: string[] | undefined } = {
252
264
  'ɾ': ['dx'],
253
265
  }
254
266
 
267
+ // Kirshenbaum is an ASCII encoding of IPA
268
+ // The variant used by eSpeak is used here, and has many differences to standard Kirshenbaum
255
269
  export const ipaToKirshenbaum: Record<string, string> = {
256
270
  //// Vowels
257
271
 
@@ -405,7 +405,7 @@ export type WhisperCppBuild = 'cpu' | 'cublas-12.4.0' | 'custom'
405
405
 
406
406
  export async function loadExecutablePackage(buildKind: WhisperCppBuild) {
407
407
  if (buildKind === 'custom') {
408
- throw new Error(`A 'custom' build kind requires providing a custom path to the whisper.cpp binary in the 'executablePath' option.`)
408
+ throw new Error(`A 'custom' build kind requires providing a custom path to the 'whisper-cli' executable in the 'executablePath' option.`)
409
409
  }
410
410
 
411
411
  const platform = process.platform
@@ -416,8 +416,10 @@ export async function loadExecutablePackage(buildKind: WhisperCppBuild) {
416
416
  if (buildKind.startsWith('cublas-')) {
417
417
  if (platform === 'win32' && arch === 'x64') {
418
418
  packageName = `whisper.cpp-binaries-windows-x64-${buildKind}-latest`
419
+ } else if (platform === 'linux' && arch === 'x64') {
420
+ packageName = `whisper.cpp-binaries-linux-x64-${buildKind}-latest`
419
421
  } else {
420
- throw new Error(`whisper.cpp GPU builds (NVIDIA CUDA only) are currently only available as packages for Windows x64. Please specify a custom path to a whisper.cpp 'main' binary in the 'executablePath' option.`)
422
+ throw new Error(`whisper.cpp GPU builds (NVIDIA CUDA only) are currently only available as packages for Windows x64 and Linux x64. Please specify a custom path to a whisper.cpp 'main' binary in the 'executablePath' option.`)
421
423
  }
422
424
  } else if (buildKind === 'cpu') {
423
425
  if (platform === 'win32' && arch === 'x64') {
@@ -433,7 +435,7 @@ export async function loadExecutablePackage(buildKind: WhisperCppBuild) {
433
435
 
434
436
  const packagePath = await loadPackage(packageName)
435
437
 
436
- let filename = 'main'
438
+ let filename = 'whisper-cli' // used to be called 'main' but 'main' is now deprecated
437
439
 
438
440
  if (platform === 'win32') {
439
441
  filename += '.exe'
@@ -2,8 +2,8 @@ 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, concatFloat32Arrays, getIntegerRange, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js'
6
- import { indexOfMax, logOfVector, logSumExp, meanOfVector, softmax, stdDeviationOfVector, sumOfSquaresForVector, sumVector } from '../math/VectorMath.js'
5
+ import { clip, getIntegerRange, getTopKIndexes, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js'
6
+ import { indexOfMax, logOfVector, logSumExp, meanOfVector, softmax, sumOfSquaresForVector, sumVector } from '../math/VectorMath.js'
7
7
 
8
8
  import { alignDTWWindowed } from '../alignment/DTWSequenceAlignmentWindowed.js'
9
9
  import { extendDeep } from '../utilities/ObjectUtilities.js'
@@ -1019,36 +1019,37 @@ export class Whisper {
1019
1019
  }
1020
1020
 
1021
1021
  // Find top candidates
1022
- const sortedNonTimestampLogitsWithIndexes =
1023
- Array.from(nonTimestampTokenLogits).map((logit, index) => ({ token: index, logit }))
1022
+ const sortedTopCandidateTokens = getTopKIndexes(nonTimestampTokenLogits, options.topCandidateCount!, true)
1024
1023
 
1025
- sortedNonTimestampLogitsWithIndexes.sort((a, b) => b.logit - a.logit)
1024
+ let topCandidates = Array.from(sortedTopCandidateTokens).map(token => {
1025
+ const logit = nonTimestampTokenLogits[token]
1026
1026
 
1027
- let topCandidates = sortedNonTimestampLogitsWithIndexes.slice(0, options.topCandidateCount!)
1028
- .map(entry => ({
1029
- token: entry.token,
1030
- logit: entry.logit,
1031
- text: this.tokenToText(entry.token, true)
1032
- }))
1027
+ return {
1028
+ token,
1029
+ logit,
1030
+ text: this.tokenToText(token, true)
1031
+ }
1032
+ })
1033
1033
 
1034
1034
  // Apply repetition suppression if enabled
1035
1035
  if (options.suppressRepetition) {
1036
- // Using some hardcoded constants, for now
1036
+ // Using some hardcoded constants, for now:
1037
1037
  const tokenWindowSize = 30
1038
1038
  const thresholdMatchLength = 4
1039
- const thresholdCycleRepetition = 3
1039
+ const thresholdCycleRepetitionCount = 3
1040
1040
 
1041
1041
  const filteredCandidates: typeof topCandidates = []
1042
1042
 
1043
1043
  for (const candidate of topCandidates) {
1044
- const lastDecodedTextTokens = decodedTokens
1045
- .filter(token => this.isTextToken(token))
1044
+ const decodedTextTokens = decodedTokens.filter(token => this.isTextToken(token))
1045
+
1046
+ const lastDecodedTextTokens = decodedTextTokens
1047
+ .slice(Math.max(decodedTextTokens.length - tokenWindowSize, 0))
1046
1048
  .reverse()
1047
- .slice(0, tokenWindowSize)
1048
1049
 
1049
- const { longestMatch, longestCycleRepetition } = getTokenRepetitionScore([candidate.token, ...lastDecodedTextTokens])
1050
+ const { longestMatchLength, longestCycleRepetitionCount } = getTokenRepetitionScore([candidate.token, ...lastDecodedTextTokens])
1050
1051
 
1051
- if (longestMatch >= thresholdMatchLength || longestCycleRepetition >= thresholdCycleRepetition) {
1052
+ if (longestMatchLength >= thresholdMatchLength || longestCycleRepetitionCount >= thresholdCycleRepetitionCount) {
1052
1053
  continue
1053
1054
  }
1054
1055
 
@@ -1,7 +1,6 @@
1
1
  import { SynthesisVoice } from '../api/API.js'
2
- import { decodeToChannels } from '../audio/AudioBufferConversion.js'
2
+ import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
3
3
  import { RawAudio } from '../audio/AudioUtilities.js'
4
- import { SampleFormat } from '../codecs/WaveCodec.js'
5
4
  import { getShortLanguageCode, lcidToIsoLanguageCode } from '../utilities/Locale.js'
6
5
  import { Logger } from '../utilities/Logger.js'
7
6
  import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
@@ -1,6 +1,5 @@
1
1
  import { SynthesisVoice } from '../api/API.js'
2
- import { decodeToChannels } from '../audio/AudioBufferConversion.js'
3
- import { SampleFormat } from '../codecs/WaveCodec.js'
2
+ import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
4
3
  import { bandwidthToQFactor } from '../dsp/BiquadFilter.js'
5
4
  import { Logger } from '../utilities/Logger.js'
6
5
  import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
@@ -197,9 +197,10 @@ const packageVersionTagResolutionLookup: { [packageName: string]: string } = {
197
197
  'whisper.cpp-large-v3-turbo-q5_0': '20241003',
198
198
 
199
199
  // whisper.cpp binaries
200
- 'whisper.cpp-binaries-linux-x64-cpu-latest': '20241010',
201
- 'whisper.cpp-binaries-windows-x64-cpu-latest': '20241010',
202
- 'whisper.cpp-binaries-windows-x64-cublas-12.4.0-latest': '20241010',
200
+ 'whisper.cpp-binaries-windows-x64-cpu-latest': '20250502',
201
+ 'whisper.cpp-binaries-windows-x64-cublas-12.4.0-latest': '20250502',
202
+ 'whisper.cpp-binaries-linux-x64-cpu-latest': '20250502',
203
+ 'whisper.cpp-binaries-linux-x64-cublas-12.4.0-latest': '20250502',
203
204
 
204
205
  // E5 models
205
206
  'xenova-multilingual-e5-small-q8': '20240504',
@@ -148,24 +148,24 @@ export function getTokenRepetitionScore(tokens: string[] | number[]) {
148
148
  matchLengthForCycleLength.push(score)
149
149
  }
150
150
 
151
- let longestMatch = -Infinity
152
- let longestCycleRepetition = -Infinity
151
+ let longestMatchLength = -Infinity
152
+ let longestCycleRepetitionCount = -Infinity
153
153
 
154
154
  for (let i = 1; i <= matchLengthForCycleLength.length; i++) {
155
155
  const matchLength = matchLengthForCycleLength[i]
156
156
 
157
- if (matchLength > longestMatch) {
158
- longestMatch = matchLength
157
+ if (matchLength > longestMatchLength) {
158
+ longestMatchLength = matchLength
159
159
  }
160
160
 
161
161
  const cycleCount = (matchLength / i) + 1
162
162
 
163
- if (cycleCount > longestCycleRepetition) {
164
- longestCycleRepetition = cycleCount
163
+ if (cycleCount > longestCycleRepetitionCount) {
164
+ longestCycleRepetitionCount = cycleCount
165
165
  }
166
166
  }
167
167
 
168
- return { longestMatch, longestCycleRepetition }
168
+ return { longestMatchLength, longestCycleRepetitionCount }
169
169
  }
170
170
 
171
171
  export async function convertHtmlToText(html: string) {
@@ -511,3 +511,67 @@ export function encodeHTMLAngleBrackets(text: string) {
511
511
  .replaceAll('>', '&gt;')
512
512
  .replaceAll('&', '&amp;')
513
513
  }
514
+
515
+ export function getTopKIndexes(values: ArrayLike<number>, topCount: number, sort = true) {
516
+ if (topCount < 1) {
517
+ throw new Error(`Top count must be at least 1`)
518
+ }
519
+
520
+ const topKIndexes = new Uint32Array(Math.min(topCount, values.length))
521
+
522
+ // Initialize top k indexes with the first k elements
523
+ // (or less, if value list is shorter)
524
+ for (let i = 0; i < topKIndexes.length; i++) {
525
+ topKIndexes[i] = i
526
+ }
527
+
528
+ // Return if value list is shorter or equal in length to topCount
529
+ if (values.length <= topCount) {
530
+ return topKIndexes
531
+ }
532
+
533
+ ////
534
+
535
+ let positionOfMinimum = -1
536
+ let valueOfMinimum = -Infinity
537
+
538
+ // Method to scan the top-k array and update the latest minimum value position and value
539
+ function updateMinimum() {
540
+ positionOfMinimum = 0
541
+ valueOfMinimum = values[topKIndexes[0]]
542
+
543
+ for (let i = 1; i < topKIndexes.length; i++) {
544
+ const value = values[topKIndexes[i]]
545
+
546
+ if (value < valueOfMinimum) {
547
+ positionOfMinimum = i
548
+ valueOfMinimum = value
549
+ }
550
+ }
551
+ }
552
+
553
+ updateMinimum()
554
+
555
+ // Add remaining elements to the list, if needed
556
+ for (let insertedElementIndex = topCount; insertedElementIndex < values.length; insertedElementIndex++) {
557
+ const insertedElementValue = values[insertedElementIndex]
558
+
559
+ // If the inserted element's value is lesser or equal to the value
560
+ // of the smallest value on the list, skip it
561
+ if (insertedElementValue <= valueOfMinimum) {
562
+ continue
563
+ }
564
+
565
+ // Replace the minimum element with the inserted element
566
+ topKIndexes[positionOfMinimum] = insertedElementIndex
567
+
568
+ // Update the position and value of the minimum element
569
+ updateMinimum()
570
+ }
571
+
572
+ if (sort) {
573
+ topKIndexes.sort((a, b) => values[b] - values[a])
574
+ }
575
+
576
+ return topKIndexes
577
+ }
@@ -1,19 +0,0 @@
1
- import { RawAudio } from '../audio/AudioUtilities.js';
2
- export declare function encodeWave(rawAudio: RawAudio, bitDepth?: BitDepth, sampleFormat?: SampleFormat, speakerPositionMask?: number): Uint8Array<ArrayBufferLike>;
3
- export declare function decodeWave(waveData: Uint8Array, ignoreTruncatedChunks?: boolean, ignoreOverflowingDataChunks?: boolean): {
4
- rawAudio: {
5
- audioChannels: Float32Array<ArrayBufferLike>[];
6
- sampleRate: number;
7
- };
8
- sourceSampleFormat: SampleFormat;
9
- sourceBitDepth: BitDepth;
10
- sourceSpeakerPositionMask: number;
11
- };
12
- export declare function repairWave(waveData: Uint8Array): Uint8Array<ArrayBufferLike>;
13
- export declare enum SampleFormat {
14
- PCM = 1,
15
- Float = 3,
16
- Alaw = 6,
17
- Mulaw = 7
18
- }
19
- export type BitDepth = 8 | 16 | 24 | 32 | 64;