echogarden 0.11.12 → 0.11.13

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 (122) hide show
  1. package/data/schemas/options.json +16 -0
  2. package/dist/api/Alignment.js +2 -2
  3. package/dist/api/Alignment.js.map +1 -1
  4. package/dist/api/Recognition.js +2 -2
  5. package/dist/api/Recognition.js.map +1 -1
  6. package/dist/api/Synthesis.js +5 -4
  7. package/dist/api/Synthesis.js.map +1 -1
  8. package/dist/api/Translation.js +2 -2
  9. package/dist/api/Translation.js.map +1 -1
  10. package/dist/audio/AudioUtilities.d.ts +1 -0
  11. package/dist/audio/AudioUtilities.js +25 -7
  12. package/dist/audio/AudioUtilities.js.map +1 -1
  13. package/dist/cli/CLI.js +2 -2
  14. package/dist/cli/CLI.js.map +1 -1
  15. package/dist/recognition/WhisperSTT.js +2 -2
  16. package/dist/recognition/WhisperSTT.js.map +1 -1
  17. package/dist/subtitles/Subtitles.d.ts +10 -7
  18. package/dist/subtitles/Subtitles.js +268 -207
  19. package/dist/subtitles/Subtitles.js.map +1 -1
  20. package/docs/Options.md +4 -2
  21. package/package.json +7 -6
  22. package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
  23. package/src/alignment/DTWSequenceAlignment.ts +121 -0
  24. package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
  25. package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
  26. package/src/alignment/SpeechAlignment.ts +488 -0
  27. package/src/api/API.ts +12 -0
  28. package/src/api/APIOptions.ts +15 -0
  29. package/src/api/Alignment.ts +329 -0
  30. package/src/api/Common.ts +16 -0
  31. package/src/api/Denoising.ts +120 -0
  32. package/src/api/LanguageDetection.ts +286 -0
  33. package/src/api/Recognition.ts +344 -0
  34. package/src/api/Synthesis.ts +1735 -0
  35. package/src/api/Translation.ts +143 -0
  36. package/src/api/Vad.ts +172 -0
  37. package/src/audio/AudioBufferConversion.ts +248 -0
  38. package/src/audio/AudioPlayer.ts +358 -0
  39. package/src/audio/AudioRecorder.ts +91 -0
  40. package/src/audio/AudioUtilities.ts +392 -0
  41. package/src/audio/SoxPath.ts +24 -0
  42. package/src/cli/CLI.ts +1360 -0
  43. package/src/cli/CLIConfigFile.ts +91 -0
  44. package/src/cli/CLILauncher.ts +26 -0
  45. package/src/cli/CLIOptionsSchema.ts +54 -0
  46. package/src/cli/CLIParser.ts +41 -0
  47. package/src/cli/CLIStarter.ts +40 -0
  48. package/src/codecs/FFMpegTranscoder.ts +214 -0
  49. package/src/codecs/TIMITCodec.ts +17 -0
  50. package/src/codecs/WaveCodec.ts +260 -0
  51. package/src/denoising/RNNoise.ts +95 -0
  52. package/src/dsp/BiquadFilter.ts +488 -0
  53. package/src/dsp/FFT.ts +187 -0
  54. package/src/dsp/MFCC.ts +227 -0
  55. package/src/dsp/MelSpectogram.ts +145 -0
  56. package/src/dsp/Rubberband.ts +249 -0
  57. package/src/dsp/Sonic.ts +59 -0
  58. package/src/dsp/SpeexResampler.ts +79 -0
  59. package/src/math/VectorMath.ts +812 -0
  60. package/src/nlp/ChineseSegmentation.ts +68 -0
  61. package/src/nlp/CompromiseNLP.ts +113 -0
  62. package/src/nlp/EspeakPhonemizer.ts +168 -0
  63. package/src/nlp/IPA.ts +139 -0
  64. package/src/nlp/JapaneseSegmentation.ts +53 -0
  65. package/src/nlp/Lexicon.ts +119 -0
  66. package/src/nlp/PhoneConversion.ts +508 -0
  67. package/src/nlp/Segmentation.ts +237 -0
  68. package/src/nlp/TextNormalizer.ts +160 -0
  69. package/src/recognition/AmazonTranscribeSTT.ts +112 -0
  70. package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
  71. package/src/recognition/GoogleCloudSTT.ts +92 -0
  72. package/src/recognition/SileroSTT.ts +173 -0
  73. package/src/recognition/VoskSTT.ts +112 -0
  74. package/src/recognition/WhisperSTT.ts +1518 -0
  75. package/src/server/Client.ts +297 -0
  76. package/src/server/Server.ts +178 -0
  77. package/src/server/ServerStarter.ts +12 -0
  78. package/src/server/Worker.ts +400 -0
  79. package/src/server/WorkerStarter.ts +38 -0
  80. package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
  81. package/src/subtitles/Subtitles.ts +478 -0
  82. package/src/synthesis/AwsPollyTTS.ts +78 -0
  83. package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
  84. package/src/synthesis/CoquiServerTTS.ts +29 -0
  85. package/src/synthesis/ElevenLabsTTS.ts +104 -0
  86. package/src/synthesis/EspeakTTS.ts +552 -0
  87. package/src/synthesis/FliteTTS.ts +387 -0
  88. package/src/synthesis/GoogleCloudTTS.ts +112 -0
  89. package/src/synthesis/GoogleTranslateTTS.ts +210 -0
  90. package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
  91. package/src/synthesis/SamTTS.ts +30 -0
  92. package/src/synthesis/SapiTTS.ts +222 -0
  93. package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
  94. package/src/synthesis/SvoxPicoTTS.ts +318 -0
  95. package/src/synthesis/VitsTTS.ts +734 -0
  96. package/src/tests/Test.ts +24 -0
  97. package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
  98. package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
  99. package/src/typings/Fillers.d.ts +41 -0
  100. package/src/utilities/BinaryArrayConversion.ts +159 -0
  101. package/src/utilities/Compression.ts +91 -0
  102. package/src/utilities/FileDownloader.ts +201 -0
  103. package/src/utilities/FileSystem.ts +265 -0
  104. package/src/utilities/Hashing.ts +230 -0
  105. package/src/utilities/Locale.ts +119 -0
  106. package/src/utilities/Logger.ts +72 -0
  107. package/src/utilities/NdArrayUtilities.ts +31 -0
  108. package/src/utilities/ObjectUtilities.ts +169 -0
  109. package/src/utilities/OpenPromise.ts +13 -0
  110. package/src/utilities/PackageManager.ts +97 -0
  111. package/src/utilities/Queue.ts +17 -0
  112. package/src/utilities/RandomGenerator.ts +237 -0
  113. package/src/utilities/SignalChannel.ts +22 -0
  114. package/src/utilities/TarballMaker.ts +68 -0
  115. package/src/utilities/Timeline.ts +231 -0
  116. package/src/utilities/Timer.ts +93 -0
  117. package/src/utilities/Utilities.ts +574 -0
  118. package/src/utilities/WasmMemoryManager.ts +516 -0
  119. package/src/utilities/WebReader.ts +55 -0
  120. package/src/utilities/WikipediaReader.ts +41 -0
  121. package/src/voice-activity-detection/SileroVAD.ts +86 -0
  122. package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echogarden",
3
- "version": "0.11.12",
3
+ "version": "0.11.13",
4
4
  "description": "An integrated speech system, providing a variety of synthesis, recognition, alignment, and other processing tools, designed to be directly accessible to end-users.",
5
5
  "author": "Rotem Dan",
6
6
  "license": "GPL-3.0",
@@ -37,6 +37,7 @@
37
37
  "main": "./dist/API/API.js",
38
38
  "type": "module",
39
39
  "files": [
40
+ "src",
40
41
  "dist",
41
42
  "data",
42
43
  "docs",
@@ -53,8 +54,8 @@
53
54
  "echogarden": "./dist/cli/CLILauncher.js"
54
55
  },
55
56
  "dependencies": {
56
- "@aws-sdk/client-polly": "^3.409.0",
57
- "@aws-sdk/client-transcribe-streaming": "^3.409.0",
57
+ "@aws-sdk/client-polly": "^3.410.0",
58
+ "@aws-sdk/client-transcribe-streaming": "^3.410.0",
58
59
  "@echogarden/espeak-ng-emscripten": "^0.1.2",
59
60
  "@echogarden/fasttext-wasm": "^0.1.0",
60
61
  "@echogarden/flite-wasi": "^0.1.1",
@@ -121,10 +122,10 @@
121
122
  "@types/ndarray-ops": "^1.2.4",
122
123
  "@types/node": "^20.6.0",
123
124
  "@types/recursive-readdir": "^2.2.1",
124
- "@types/tar": "^6.1.5",
125
+ "@types/tar": "^6.1.6",
125
126
  "@types/ws": "^8.5.5",
126
- "@typescript-eslint/eslint-plugin": "^6.6.0",
127
- "@typescript-eslint/parser": "^6.6.0",
127
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
128
+ "@typescript-eslint/parser": "^6.7.0",
128
129
  "@types/graceful-fs": "^4.1.6",
129
130
  "eslint": "^8.49.0",
130
131
  "ts-json-schema-generator": "^1.3.0",
@@ -0,0 +1,43 @@
1
+ import { cosineDistancePrecomputedMagnitudes, createVectorForIntegerRange, eucledianDistance, magnitude } from "../math/VectorMath.js"
2
+ import { logToStderr } from "../utilities/Utilities.js"
3
+ import { alignDTWWindowed } from "./DTWSequenceAlignmentWindowed.js"
4
+
5
+ const log = logToStderr
6
+
7
+ export async function alignMFCC_DTW(mfccFrames1: number[][], mfccFrames2: number[][], windowLength: number, distanceFunction: "eucledian" | "cosine" = "eucledian", centerIndexes?: number[]) {
8
+ if (distanceFunction == "eucledian") {
9
+ const { path } = alignDTWWindowed(
10
+ mfccFrames1,
11
+ mfccFrames2,
12
+ eucledianDistance,
13
+ windowLength,
14
+ centerIndexes
15
+ )
16
+
17
+ return path
18
+ } else if (distanceFunction == "cosine") {
19
+ const indexes1 = createVectorForIntegerRange(0, mfccFrames1.length)
20
+ const indexes2 = createVectorForIntegerRange(0, mfccFrames2.length)
21
+
22
+ const magnitudes1 = mfccFrames1.map(magnitude)
23
+ const magnitudes2 = mfccFrames2.map(magnitude)
24
+
25
+ const { path } = alignDTWWindowed(
26
+ indexes1,
27
+ indexes2,
28
+ (i, j) => cosineDistancePrecomputedMagnitudes(mfccFrames1[i], mfccFrames2[j], magnitudes1[i], magnitudes2[j]),
29
+ windowLength,
30
+ centerIndexes
31
+ )
32
+
33
+ return path
34
+ } else {
35
+ throw new Error("Invalid distance function")
36
+ }
37
+ }
38
+
39
+ export function getCostMatrixMemorySizeMB(sequence1Length: number, sequence2Length: number, windowLength: number) {
40
+ const costMatrixMemorySizeMB = sequence1Length * Math.min(sequence2Length, windowLength) * 4 / 1000000
41
+
42
+ return costMatrixMemorySizeMB
43
+ }
@@ -0,0 +1,121 @@
1
+ import { logToStderr } from "../utilities/Utilities.js"
2
+ import { AlignmentPath } from "./SpeechAlignment.js"
3
+
4
+ const log = logToStderr
5
+
6
+ export function alignDTW<T, U>(sequence1: T[], sequence2: U[], costFunction: (a: T, b: U) => number, deletionEnabled = true) {
7
+ if (sequence1.length == 0 || sequence2.length == 0) {
8
+ return { path: [] as AlignmentPath, pathCost: 0 }
9
+ }
10
+
11
+ const rowCount = sequence2.length
12
+ const columnCount = sequence1.length
13
+
14
+ // Compute accumulated cost matrix
15
+ const accumulatedCostMatrix = computeAccumulatedCostMatrix(sequence1, sequence2, costFunction, deletionEnabled)
16
+
17
+ // Find best path for the computed matrix
18
+ const path = computeBestPath(accumulatedCostMatrix, deletionEnabled)
19
+
20
+ // Best path cost is the bottom right element of the matrix
21
+ const pathCost = accumulatedCostMatrix[rowCount - 1][columnCount - 1]
22
+
23
+ return { path, pathCost }
24
+ }
25
+
26
+ function computeAccumulatedCostMatrix<T, U>(sequence1: T[], sequence2: U[], costFunction: (a: T, b: U) => number, deletionEnabled = true) {
27
+ const rowCount = sequence2.length
28
+ const columnCount = sequence1.length
29
+
30
+ const accumulatedCostMatrix: Float32Array[] = new Array<Float32Array>(rowCount)
31
+
32
+ // Initialize matrix
33
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
34
+ accumulatedCostMatrix[rowIndex] = new Float32Array(columnCount)
35
+ }
36
+
37
+ for (let i = 1; i < columnCount; i++) {
38
+ accumulatedCostMatrix[0][i] = Infinity
39
+ }
40
+
41
+ for (let i = 1; i < rowCount; i++) {
42
+ accumulatedCostMatrix[i][0] = Infinity
43
+ }
44
+
45
+ // Fill out the matrix, go row by row
46
+ for (let rowIndex = 1; rowIndex < rowCount; rowIndex++) {
47
+ const previousRow = accumulatedCostMatrix[rowIndex - 1]
48
+ const currentRow = accumulatedCostMatrix[rowIndex]
49
+
50
+ for (let columnIndex = 1; columnIndex < columnCount; columnIndex++) {
51
+ const cost = costFunction(sequence1[columnIndex], sequence2[rowIndex])
52
+
53
+ const up = previousRow[columnIndex] // insertion
54
+ const left = currentRow[columnIndex - 1] // deletion
55
+ const upAndLeft = previousRow[columnIndex - 1] // match
56
+
57
+ currentRow[columnIndex] = cost + minimumOf3(up, deletionEnabled ? left : Infinity, upAndLeft)
58
+ }
59
+ }
60
+
61
+ return accumulatedCostMatrix
62
+ }
63
+
64
+ function computeBestPath(costMatrix: Float32Array[], deletionEnabled = true) {
65
+ const rowCount = costMatrix.length
66
+ const columnCount = costMatrix[0].length
67
+
68
+ const bestPath: AlignmentPath = []
69
+
70
+ let rowIndex = rowCount - 1
71
+ let columnIndex = columnCount - 1
72
+
73
+ bestPath.push({
74
+ source: columnIndex,
75
+ dest: rowIndex
76
+ })
77
+
78
+ while (rowIndex > 0 || columnIndex > 0) {
79
+ const up = costMatrix[rowIndex - 1][columnIndex]
80
+ const left = costMatrix[rowIndex][columnIndex - 1]
81
+ const upAndLeft = costMatrix[rowIndex - 1][columnIndex - 1]
82
+
83
+ const smallestCostDirection = argIndexOfMinimumOf3(up, deletionEnabled ? left : Infinity, upAndLeft)
84
+
85
+ if (smallestCostDirection == 1) {
86
+ rowIndex -= 1
87
+ } else if (smallestCostDirection == 2) {
88
+ columnIndex -= 1
89
+ } else {
90
+ rowIndex -= 1
91
+ columnIndex -= 1
92
+ }
93
+
94
+ bestPath.push({
95
+ source: columnIndex,
96
+ dest: rowIndex
97
+ })
98
+ }
99
+
100
+ return bestPath.reverse() as AlignmentPath
101
+ }
102
+
103
+ function minimumOf3(x1: number, x2: number, x3: number) {
104
+ if (x1 <= x2 && x1 <= x3) {
105
+ return x1
106
+ } else if (x2 <= x3) {
107
+ return x2
108
+ } else {
109
+ return x3
110
+ }
111
+ }
112
+
113
+ function argIndexOfMinimumOf3(x1: number, x2: number, x3: number) {
114
+ if (x1 <= x2 && x1 <= x3) {
115
+ return 1
116
+ } else if (x2 <= x3) {
117
+ return 2
118
+ } else {
119
+ return 3
120
+ }
121
+ }
@@ -0,0 +1,210 @@
1
+ import { logToStderr } from "../utilities/Utilities.js"
2
+ import { AlignmentPath } from "./SpeechAlignment.js"
3
+
4
+ const log = logToStderr
5
+
6
+ export function alignDTWWindowed<T, U>(sequence1: T[], sequence2: U[], costFunction: (a: T, b: U) => number, windowMaxLength: number, centerIndexes?: number[]) {
7
+ if (windowMaxLength < 2) {
8
+ throw new Error("Window length must be greater or equal to 2")
9
+ }
10
+
11
+ if (sequence1.length == 0 || sequence2.length == 0) {
12
+ return { path: [] as AlignmentPath, pathCost: 0 }
13
+ }
14
+
15
+ // Compute accumulated cost matrix (transposed)
16
+ const { accumulatedCostMatrixTransposed, windowStartOffsets } = computeAccumulatedCostMatrixTransposed(sequence1, sequence2, costFunction, windowMaxLength, centerIndexes)
17
+
18
+ // Find best path for the computed matrix
19
+ const path = computeBestPathTransposed(accumulatedCostMatrixTransposed, windowStartOffsets)
20
+
21
+ // Best path cost is the bottom right element of the matrix
22
+ const columnCount = accumulatedCostMatrixTransposed.length
23
+ const rowCount = accumulatedCostMatrixTransposed[0].length
24
+
25
+ const pathCost = accumulatedCostMatrixTransposed[columnCount - 1][rowCount - 1]
26
+
27
+ // Return
28
+ return { path, pathCost }
29
+ }
30
+
31
+ function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T[], sequence2: U[], costFunction: (a: T, b: U) => number, windowMaxLength: number, centerIndexes?: number[]) {
32
+ const halfWindowMaxLength = Math.floor(windowMaxLength / 2)
33
+
34
+ const columnCount = sequence1.length
35
+ const rowCount = Math.min(windowMaxLength, sequence2.length)
36
+
37
+ const accumulatedCostMatrixTransposed: Float32Array[] = new Array<Float32Array>(columnCount)
38
+
39
+ // Initialize window start offsets array
40
+ const windowStartOffsets = new Int32Array(columnCount)
41
+
42
+ // Compute matrix column by column
43
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
44
+ // Create new column and add it to the matrix
45
+ const currentColumn = new Float32Array(rowCount)
46
+ accumulatedCostMatrixTransposed[columnIndex] = currentColumn
47
+
48
+ // Compute window center, or use given one
49
+ let windowCenter: number
50
+
51
+ if (centerIndexes) {
52
+ windowCenter = centerIndexes[columnIndex]
53
+ } else {
54
+ windowCenter = Math.floor((columnIndex / columnCount) * sequence2.length)
55
+ }
56
+
57
+ // Compute window start and end offsets
58
+ let windowStartOffset = Math.max(windowCenter - halfWindowMaxLength, 0)
59
+ let windowEndOffset = windowStartOffset + rowCount
60
+
61
+ if (windowEndOffset > sequence2.length) {
62
+ windowEndOffset = sequence2.length
63
+ windowStartOffset = windowEndOffset - rowCount
64
+ }
65
+
66
+ // Store the start offset in the array
67
+ windowStartOffsets[columnIndex] = windowStartOffset
68
+
69
+ // Get target sequence1 value
70
+ const targetSequence1Value = sequence1[columnIndex]
71
+
72
+ // If first column, fill it only using the 'up' neighbor
73
+ if (columnIndex == 0) {
74
+ for (let rowIndex = 1; rowIndex < rowCount; rowIndex++) {
75
+ const cost = costFunction(targetSequence1Value, sequence2[windowStartOffset + rowIndex])
76
+ const upCost = currentColumn[rowIndex - 1]
77
+
78
+ currentColumn[rowIndex] = cost + upCost
79
+ }
80
+
81
+ continue
82
+ }
83
+
84
+ // If not first column
85
+ const leftColumn = accumulatedCostMatrixTransposed[columnIndex - 1]
86
+
87
+ // Compute the delta between the current window offset and previous column's window offset
88
+ const windowOffsetDelta = windowStartOffset - windowStartOffsets[columnIndex - 1]
89
+
90
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
91
+ // Compute the cost for current cell
92
+ const cost = costFunction(targetSequence1Value, sequence2[windowStartOffset + rowIndex])
93
+
94
+ // Retrieve the cost for the 'up' (insertion) neighbor
95
+ let upCost = Infinity
96
+ if (rowIndex > 0) {
97
+ upCost = currentColumn[rowIndex - 1]
98
+ }
99
+
100
+ // Retrieve the cost for the 'left' (deletion) neighbor
101
+ let leftCost = Infinity
102
+ const leftRowIndex = rowIndex + windowOffsetDelta
103
+
104
+ if (leftRowIndex < rowCount) {
105
+ leftCost = leftColumn[leftRowIndex]
106
+ }
107
+
108
+ // Retrieve the cost for the 'up and left' (match) neighbor
109
+ let upAndLeftCost = Infinity
110
+ const upAndLeftRowIndex = leftRowIndex - 1
111
+
112
+ if (upAndLeftRowIndex >= 0 && upAndLeftRowIndex < rowCount) {
113
+ upAndLeftCost = leftColumn[upAndLeftRowIndex]
114
+ }
115
+
116
+ // Write cost + minimum neighbor cost to the current column
117
+ currentColumn[rowIndex] = cost + minimumOf3(upCost, leftCost, upAndLeftCost)
118
+ }
119
+ }
120
+
121
+ return { accumulatedCostMatrixTransposed, windowStartOffsets }
122
+ }
123
+
124
+ function computeBestPathTransposed(accumulatedCostMatrixTransposed: Float32Array[], windowStartOffsets: Int32Array) {
125
+ const columnCount = accumulatedCostMatrixTransposed.length
126
+ const rowCount = accumulatedCostMatrixTransposed[0].length
127
+
128
+ const bestPath: AlignmentPath = []
129
+
130
+ let columnIndex = columnCount - 1
131
+ let rowIndex = rowCount - 1
132
+
133
+ while (columnIndex > 0 || rowIndex > 0) {
134
+ const windowStartIndex = windowStartOffsets[columnIndex]
135
+ const windowStartDelta = columnIndex > 0 ? windowStartIndex - windowStartOffsets[columnIndex - 1] : 0
136
+
137
+ bestPath.push({
138
+ source: columnIndex,
139
+ dest: windowStartIndex + rowIndex
140
+ })
141
+
142
+ const upRowIndex = rowIndex - 1
143
+ const upColumnIndex = columnIndex
144
+ let upCost = Infinity
145
+
146
+ if (upRowIndex >= 0) {
147
+ upCost = accumulatedCostMatrixTransposed[upColumnIndex][upRowIndex] // insertion
148
+ }
149
+
150
+ const leftRowIndex = rowIndex + windowStartDelta
151
+ const leftColumnIndex = columnIndex - 1
152
+ let leftCost = Infinity
153
+
154
+ if (leftColumnIndex >= 0 && leftRowIndex < rowCount) {
155
+ leftCost = accumulatedCostMatrixTransposed[leftColumnIndex][leftRowIndex] // deletion
156
+ }
157
+
158
+ const upAndLeftRowIndex = rowIndex - 1 + windowStartDelta
159
+ const upAndLeftColumnIndex = columnIndex - 1
160
+ let upAndLeftCost = Infinity
161
+
162
+ if (upAndLeftColumnIndex >= 0 && upAndLeftRowIndex >= 0 && upAndLeftRowIndex < rowCount) {
163
+ upAndLeftCost = accumulatedCostMatrixTransposed[upAndLeftColumnIndex][upAndLeftRowIndex] // match
164
+ }
165
+
166
+ if (upCost == Infinity && leftCost == Infinity && upAndLeftCost == Infinity) {
167
+ log(`computeBestPath: Unexpected - all cost directions are equal to infinity (${columnIndex}, ${rowIndex}).`)
168
+ }
169
+
170
+ const smallestCostDirection = argIndexOfMinimumOf3(upCost, leftCost, upAndLeftCost)
171
+
172
+ if (smallestCostDirection == 1) {
173
+ rowIndex = upRowIndex
174
+ columnIndex = upColumnIndex
175
+ } else if (smallestCostDirection == 2) {
176
+ rowIndex = leftRowIndex
177
+ columnIndex = leftColumnIndex
178
+ } else {
179
+ rowIndex = upAndLeftRowIndex
180
+ columnIndex = upAndLeftColumnIndex
181
+ }
182
+ }
183
+
184
+ bestPath.push({
185
+ source: 0,
186
+ dest: 0
187
+ })
188
+
189
+ return bestPath.reverse() as AlignmentPath
190
+ }
191
+
192
+ function minimumOf3(x1: number, x2: number, x3: number) {
193
+ if (x1 <= x2 && x1 <= x3) {
194
+ return x1
195
+ } else if (x2 <= x3) {
196
+ return x2
197
+ } else {
198
+ return x3
199
+ }
200
+ }
201
+
202
+ function argIndexOfMinimumOf3(x1: number, x2: number, x3: number) {
203
+ if (x1 <= x2 && x1 <= x3) {
204
+ return 1
205
+ } else if (x2 <= x3) {
206
+ return 2
207
+ } else {
208
+ return 3
209
+ }
210
+ }
@@ -0,0 +1,126 @@
1
+ import { logToStderr } from "../utilities/Utilities.js"
2
+ import { AlignmentPath } from "./SpeechAlignment.js"
3
+
4
+ const log = logToStderr
5
+
6
+ export function alignLevenshtein<T, U>(sequence1: T[], sequence2: U[], getSubstitutionCost: (a: T, b: U) => number) {
7
+ const rowCount = sequence2.length + 1
8
+ const columnCount = sequence1.length + 1
9
+
10
+ // Compute accumulated cost matrix
11
+ const accumulatedCostMatrix = computeAccumulatedCostMatrix(sequence1, sequence2, getSubstitutionCost)
12
+
13
+ // Find best path for the computed matrix
14
+ const path = computeBestPath(accumulatedCostMatrix)
15
+
16
+ // Best path cost is the bottom right element of the matrix
17
+ const pathCost = accumulatedCostMatrix[rowCount - 1][columnCount - 1]
18
+
19
+ return { path, pathCost }
20
+ }
21
+
22
+ function computeAccumulatedCostMatrix<T, U>(sequence1: T[], sequence2: U[], getSubstitutionCost: (a: T, b: U) => number) {
23
+ const rowCount = sequence2.length + 1
24
+ const columnCount = sequence1.length + 1
25
+
26
+ const accumulatedCostMatrix: Float32Array[] = new Array<Float32Array>(rowCount)
27
+
28
+ // Allocate rows and initialize first column
29
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
30
+ const newRow = new Float32Array(columnCount)
31
+
32
+ newRow[0] = rowIndex
33
+ accumulatedCostMatrix[rowIndex] = newRow
34
+ }
35
+
36
+ // Initialize first row
37
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
38
+ accumulatedCostMatrix[0][columnIndex] = columnIndex
39
+ }
40
+
41
+ // Fill out the rest of the matrix, go row by row
42
+ for (let rowIndex = 1; rowIndex < rowCount; rowIndex++) {
43
+ const previousRow = accumulatedCostMatrix[rowIndex - 1]
44
+ const currentRow = accumulatedCostMatrix[rowIndex]
45
+
46
+ for (let columnIndex = 1; columnIndex < columnCount; columnIndex++) {
47
+ const upAndLeft = previousRow[columnIndex - 1]
48
+ const up = previousRow[columnIndex]
49
+ const left = currentRow[columnIndex - 1]
50
+
51
+ const smallestCostDirection = argIndexOfSmallestOf3(upAndLeft, up, left)
52
+
53
+ let computedCostForCurrentElement: number
54
+
55
+ if (smallestCostDirection == 1) {
56
+ const subtitutionCost = getSubstitutionCost(sequence1[columnIndex - 1], sequence2[rowIndex - 1])
57
+
58
+ computedCostForCurrentElement = upAndLeft + subtitutionCost
59
+ } else if (smallestCostDirection == 2) {
60
+ computedCostForCurrentElement = up + 1
61
+ } else {
62
+ computedCostForCurrentElement = left + 1
63
+ }
64
+
65
+ currentRow[columnIndex] = computedCostForCurrentElement
66
+ }
67
+ }
68
+
69
+ return accumulatedCostMatrix
70
+ }
71
+
72
+ function computeBestPath(costMatrix: Float32Array[]) {
73
+ const bestPath: AlignmentPath = []
74
+
75
+ let rowIndex = costMatrix.length - 1
76
+ let columnIndex = costMatrix[0].length - 1
77
+
78
+ while (rowIndex > 1 || columnIndex > 1) {
79
+ bestPath.push({
80
+ source: columnIndex - 1,
81
+ dest: rowIndex - 1
82
+ })
83
+
84
+ if (rowIndex == 1) {
85
+ columnIndex -= 1
86
+ continue
87
+ }
88
+
89
+ if (columnIndex == 1) {
90
+ rowIndex -= 1
91
+ continue
92
+ }
93
+
94
+ const upAndLeft = costMatrix[rowIndex - 1][columnIndex - 1]
95
+ const up = costMatrix[rowIndex - 1][columnIndex]
96
+ const left = costMatrix[rowIndex][columnIndex - 1]
97
+
98
+ const smallestCostDirection = argIndexOfSmallestOf3(upAndLeft, up, left)
99
+
100
+ if (smallestCostDirection == 1) {
101
+ rowIndex -= 1
102
+ columnIndex -= 1
103
+ } else if (smallestCostDirection == 2) {
104
+ rowIndex -= 1
105
+ } else {
106
+ columnIndex -= 1
107
+ }
108
+ }
109
+
110
+ bestPath.push({
111
+ source: 0,
112
+ dest: 0
113
+ })
114
+
115
+ return bestPath.reverse() as AlignmentPath
116
+ }
117
+
118
+ function argIndexOfSmallestOf3(x1: number, x2: number, x3: number) {
119
+ if (x1 <= x2 && x1 <= x3) {
120
+ return 1
121
+ } else if (x2 <= x3) {
122
+ return 2
123
+ } else {
124
+ return 3
125
+ }
126
+ }