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.
- package/data/schemas/options.json +16 -0
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Recognition.js +2 -2
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/Synthesis.js +5 -4
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.js +2 -2
- package/dist/api/Translation.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +1 -0
- package/dist/audio/AudioUtilities.js +25 -7
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.js +2 -2
- package/dist/cli/CLI.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +2 -2
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/subtitles/Subtitles.d.ts +10 -7
- package/dist/subtitles/Subtitles.js +268 -207
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/docs/Options.md +4 -2
- package/package.json +7 -6
- package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
- package/src/alignment/DTWSequenceAlignment.ts +121 -0
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
- package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
- package/src/alignment/SpeechAlignment.ts +488 -0
- package/src/api/API.ts +12 -0
- package/src/api/APIOptions.ts +15 -0
- package/src/api/Alignment.ts +329 -0
- package/src/api/Common.ts +16 -0
- package/src/api/Denoising.ts +120 -0
- package/src/api/LanguageDetection.ts +286 -0
- package/src/api/Recognition.ts +344 -0
- package/src/api/Synthesis.ts +1735 -0
- package/src/api/Translation.ts +143 -0
- package/src/api/Vad.ts +172 -0
- package/src/audio/AudioBufferConversion.ts +248 -0
- package/src/audio/AudioPlayer.ts +358 -0
- package/src/audio/AudioRecorder.ts +91 -0
- package/src/audio/AudioUtilities.ts +392 -0
- package/src/audio/SoxPath.ts +24 -0
- package/src/cli/CLI.ts +1360 -0
- package/src/cli/CLIConfigFile.ts +91 -0
- package/src/cli/CLILauncher.ts +26 -0
- package/src/cli/CLIOptionsSchema.ts +54 -0
- package/src/cli/CLIParser.ts +41 -0
- package/src/cli/CLIStarter.ts +40 -0
- package/src/codecs/FFMpegTranscoder.ts +214 -0
- package/src/codecs/TIMITCodec.ts +17 -0
- package/src/codecs/WaveCodec.ts +260 -0
- package/src/denoising/RNNoise.ts +95 -0
- package/src/dsp/BiquadFilter.ts +488 -0
- package/src/dsp/FFT.ts +187 -0
- package/src/dsp/MFCC.ts +227 -0
- package/src/dsp/MelSpectogram.ts +145 -0
- package/src/dsp/Rubberband.ts +249 -0
- package/src/dsp/Sonic.ts +59 -0
- package/src/dsp/SpeexResampler.ts +79 -0
- package/src/math/VectorMath.ts +812 -0
- package/src/nlp/ChineseSegmentation.ts +68 -0
- package/src/nlp/CompromiseNLP.ts +113 -0
- package/src/nlp/EspeakPhonemizer.ts +168 -0
- package/src/nlp/IPA.ts +139 -0
- package/src/nlp/JapaneseSegmentation.ts +53 -0
- package/src/nlp/Lexicon.ts +119 -0
- package/src/nlp/PhoneConversion.ts +508 -0
- package/src/nlp/Segmentation.ts +237 -0
- package/src/nlp/TextNormalizer.ts +160 -0
- package/src/recognition/AmazonTranscribeSTT.ts +112 -0
- package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
- package/src/recognition/GoogleCloudSTT.ts +92 -0
- package/src/recognition/SileroSTT.ts +173 -0
- package/src/recognition/VoskSTT.ts +112 -0
- package/src/recognition/WhisperSTT.ts +1518 -0
- package/src/server/Client.ts +297 -0
- package/src/server/Server.ts +178 -0
- package/src/server/ServerStarter.ts +12 -0
- package/src/server/Worker.ts +400 -0
- package/src/server/WorkerStarter.ts +38 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
- package/src/subtitles/Subtitles.ts +478 -0
- package/src/synthesis/AwsPollyTTS.ts +78 -0
- package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
- package/src/synthesis/CoquiServerTTS.ts +29 -0
- package/src/synthesis/ElevenLabsTTS.ts +104 -0
- package/src/synthesis/EspeakTTS.ts +552 -0
- package/src/synthesis/FliteTTS.ts +387 -0
- package/src/synthesis/GoogleCloudTTS.ts +112 -0
- package/src/synthesis/GoogleTranslateTTS.ts +210 -0
- package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
- package/src/synthesis/SamTTS.ts +30 -0
- package/src/synthesis/SapiTTS.ts +222 -0
- package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
- package/src/synthesis/SvoxPicoTTS.ts +318 -0
- package/src/synthesis/VitsTTS.ts +734 -0
- package/src/tests/Test.ts +24 -0
- package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
- package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
- package/src/typings/Fillers.d.ts +41 -0
- package/src/utilities/BinaryArrayConversion.ts +159 -0
- package/src/utilities/Compression.ts +91 -0
- package/src/utilities/FileDownloader.ts +201 -0
- package/src/utilities/FileSystem.ts +265 -0
- package/src/utilities/Hashing.ts +230 -0
- package/src/utilities/Locale.ts +119 -0
- package/src/utilities/Logger.ts +72 -0
- package/src/utilities/NdArrayUtilities.ts +31 -0
- package/src/utilities/ObjectUtilities.ts +169 -0
- package/src/utilities/OpenPromise.ts +13 -0
- package/src/utilities/PackageManager.ts +97 -0
- package/src/utilities/Queue.ts +17 -0
- package/src/utilities/RandomGenerator.ts +237 -0
- package/src/utilities/SignalChannel.ts +22 -0
- package/src/utilities/TarballMaker.ts +68 -0
- package/src/utilities/Timeline.ts +231 -0
- package/src/utilities/Timer.ts +93 -0
- package/src/utilities/Utilities.ts +574 -0
- package/src/utilities/WasmMemoryManager.ts +516 -0
- package/src/utilities/WebReader.ts +55 -0
- package/src/utilities/WikipediaReader.ts +41 -0
- package/src/voice-activity-detection/SileroVAD.ts +86 -0
- 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.
|
|
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.
|
|
57
|
-
"@aws-sdk/client-transcribe-streaming": "^3.
|
|
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.
|
|
125
|
+
"@types/tar": "^6.1.6",
|
|
125
126
|
"@types/ws": "^8.5.5",
|
|
126
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
|
127
|
-
"@typescript-eslint/parser": "^6.
|
|
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
|
+
}
|