echogarden 1.3.3 → 1.4.1
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 +0 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js +68 -27
- package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
- package/dist/alignment/SpeechAlignment.d.ts +1 -1
- package/dist/alignment/SpeechAlignment.js +6 -21
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/Alignment.js +47 -28
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/LanguageDetection.js +2 -2
- package/dist/api/LanguageDetection.js.map +1 -1
- package/dist/api/Synthesis.js +1 -1
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/synthesis/EspeakTTS.js +36 -17
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/utilities/Compression.d.ts +1 -0
- package/dist/utilities/Compression.js +10 -0
- package/dist/utilities/Compression.js.map +1 -1
- package/dist/utilities/LEB128.d.ts +5 -0
- package/dist/utilities/LEB128.js +168 -0
- package/dist/utilities/LEB128.js.map +1 -0
- package/docs/Options.md +1 -1
- package/docs/Tasklist.md +0 -1
- package/package.json +6 -6
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +69 -29
- package/src/alignment/SpeechAlignment.ts +7 -23
- package/src/api/Alignment.ts +48 -29
- package/src/api/LanguageDetection.ts +2 -2
- package/src/api/Synthesis.ts +1 -1
- package/src/synthesis/EspeakTTS.ts +39 -18
- package/src/utilities/Compression.ts +15 -0
- package/src/utilities/LEB128.ts +237 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { logToStderr } from "./Utilities.js";
|
|
2
|
+
////////////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Encode
|
|
4
|
+
////////////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export function encodeSignedInt32(value, outEncodedData) {
|
|
6
|
+
if (value < -2147483648 || value > 2147483647) {
|
|
7
|
+
throw new Error('Value must be between -2147483648 and 2147483647');
|
|
8
|
+
}
|
|
9
|
+
while (true) {
|
|
10
|
+
const lowest7Bits = value & 127;
|
|
11
|
+
value >>= 7;
|
|
12
|
+
if ((value === 0 && (lowest7Bits & 64) === 0) ||
|
|
13
|
+
(value === -1 && (lowest7Bits & 64) !== 0)) {
|
|
14
|
+
outEncodedData.push(lowest7Bits);
|
|
15
|
+
return outEncodedData;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
outEncodedData.push(lowest7Bits | 128);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function encodeSignedInt32sFast(value, outEncodedData) {
|
|
23
|
+
const absValue = Math.abs(value);
|
|
24
|
+
//const absMask = value >> 31
|
|
25
|
+
//const absValue = (value ^ absMask) - absMask
|
|
26
|
+
if (absValue < (2 ** 6)) {
|
|
27
|
+
outEncodedData.push((value & 127));
|
|
28
|
+
}
|
|
29
|
+
else if (absValue < (2 ** 13)) {
|
|
30
|
+
outEncodedData.push((value & 127) | 128, (value >> 7) & 127);
|
|
31
|
+
}
|
|
32
|
+
else if (absValue < (2 ** 20)) {
|
|
33
|
+
outEncodedData.push((value & 127) | 128, ((value >> 7) & 127) | 128, (value >> 14) & 127);
|
|
34
|
+
}
|
|
35
|
+
else if (absValue < (2 ** 27)) {
|
|
36
|
+
outEncodedData.push((value & 127) | 128, ((value >> 7) & 127) | 128, ((value >> 14) & 127) | 128, (value >> 21) & 127);
|
|
37
|
+
}
|
|
38
|
+
else if (value < (2 ** 31) && value >= -(2 ** 31)) {
|
|
39
|
+
outEncodedData.push((value & 127) | 128, ((value >> 7) & 127) | 128, ((value >> 14) & 127) | 128, ((value >> 21) & 127) | 128, (value >> 28) & 127);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw new Error(`Value must be between -2147483648 and 2147483647`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
////////////////////////////////////////////////////////////////////////////////////
|
|
46
|
+
// Decode
|
|
47
|
+
////////////////////////////////////////////////////////////////////////////////////
|
|
48
|
+
export function decodeSignedInt32s(encodedData, outDecodedValues) {
|
|
49
|
+
for (let readIndex = 0; readIndex < encodedData.length;) {
|
|
50
|
+
let currentDecodedValue = 0;
|
|
51
|
+
let shiftAmount = 0;
|
|
52
|
+
while (true) {
|
|
53
|
+
const encodedByte = encodedData[readIndex++];
|
|
54
|
+
const lowest7Bits = encodedByte & 127;
|
|
55
|
+
currentDecodedValue |= lowest7Bits << shiftAmount;
|
|
56
|
+
// If 8th bit is 0, then this is the last byte in the sequence
|
|
57
|
+
if ((encodedByte & 128) === 0) {
|
|
58
|
+
// If 7th bit is 1, then the value is negative
|
|
59
|
+
if ((encodedByte & 64) !== 0) {
|
|
60
|
+
// If the value should be negative
|
|
61
|
+
// Ensure that the value is encoded as a negative number by
|
|
62
|
+
// setting all higher bits to 1
|
|
63
|
+
currentDecodedValue |= -1 << Math.min(shiftAmount + 7, 31);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (readIndex === encodedData.length) {
|
|
68
|
+
throw new Error(`Invalid LEB128 data. Last encoded byte sequence is truncated.`);
|
|
69
|
+
}
|
|
70
|
+
shiftAmount += 7;
|
|
71
|
+
if (shiftAmount > 31) {
|
|
72
|
+
throw new Error(`LEB128 sequence can't be decoded. Byte sequence extends beyond the range of a signed 32 bit integer.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
outDecodedValues.push(currentDecodedValue);
|
|
76
|
+
}
|
|
77
|
+
return outDecodedValues;
|
|
78
|
+
}
|
|
79
|
+
export function decodeSignedInt32sFast(encodedData, outDecodedValues) {
|
|
80
|
+
for (let readIndex = 0; readIndex < encodedData.length;) {
|
|
81
|
+
const byte0 = encodedData[readIndex++];
|
|
82
|
+
if ((byte0 & 128) === 0) {
|
|
83
|
+
let decodedValue = (byte0 & 127);
|
|
84
|
+
if ((byte0 & 64) !== 0) {
|
|
85
|
+
decodedValue |= -1 << 7;
|
|
86
|
+
}
|
|
87
|
+
outDecodedValues.push(decodedValue);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const byte1 = encodedData[readIndex++];
|
|
91
|
+
if ((byte1 & 128) === 0) {
|
|
92
|
+
let decodedValue = (byte0 & 127) |
|
|
93
|
+
(byte1 & 127) << 7;
|
|
94
|
+
if ((byte1 & 64) !== 0) {
|
|
95
|
+
decodedValue |= -1 << 14;
|
|
96
|
+
}
|
|
97
|
+
outDecodedValues.push(decodedValue);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const byte2 = encodedData[readIndex++];
|
|
101
|
+
if ((byte2 & 128) === 0) {
|
|
102
|
+
let decodedValue = (byte0 & 127) |
|
|
103
|
+
(byte1 & 127) << 7 |
|
|
104
|
+
(byte2 & 127) << 14;
|
|
105
|
+
if ((byte2 & 64) !== 0) {
|
|
106
|
+
decodedValue |= -1 << 21;
|
|
107
|
+
}
|
|
108
|
+
outDecodedValues.push(decodedValue);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const byte3 = encodedData[readIndex++];
|
|
112
|
+
if ((byte3 & 128) === 0) {
|
|
113
|
+
let decodedValue = (byte0 & 127) |
|
|
114
|
+
(byte1 & 127) << 7 |
|
|
115
|
+
(byte2 & 127) << 14 |
|
|
116
|
+
(byte3 & 127) << 21;
|
|
117
|
+
if ((byte3 & 64) !== 0) {
|
|
118
|
+
decodedValue |= -1 << 28;
|
|
119
|
+
}
|
|
120
|
+
outDecodedValues.push(decodedValue);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const byte4 = encodedData[readIndex++];
|
|
124
|
+
if ((byte4 & 128) === 0) {
|
|
125
|
+
let decodedValue = (byte0 & 127) |
|
|
126
|
+
(byte1 & 127) << 7 |
|
|
127
|
+
(byte2 & 127) << 14 |
|
|
128
|
+
(byte3 & 127) << 21 |
|
|
129
|
+
(byte4 & 127) << 28;
|
|
130
|
+
if ((byte4 & 64) !== 0) {
|
|
131
|
+
decodedValue |= -1 << 31;
|
|
132
|
+
}
|
|
133
|
+
outDecodedValues.push(decodedValue);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (readIndex >= encodedData.length) {
|
|
137
|
+
throw new Error(`Invalid LEB128 data. Last encoded byte sequence is truncated.`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw new Error(`LEB128 sequence can't be decoded. Encoded byte sequence represents a value that extends beyond the range of a signed 32 bit integer.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return outDecodedValues;
|
|
144
|
+
}
|
|
145
|
+
////////////////////////////////////////////////////////////////////////////////////
|
|
146
|
+
// Tests
|
|
147
|
+
////////////////////////////////////////////////////////////////////////////////////
|
|
148
|
+
export function testLeb128() {
|
|
149
|
+
const encodedBytes = [];
|
|
150
|
+
const decodedValues = [];
|
|
151
|
+
function runTest(testValue) {
|
|
152
|
+
encodedBytes.length = 0;
|
|
153
|
+
decodedValues.length = 0;
|
|
154
|
+
encodeSignedInt32sFast(testValue, encodedBytes);
|
|
155
|
+
decodeSignedInt32sFast(encodedBytes, decodedValues);
|
|
156
|
+
if (decodedValues[0] !== testValue) {
|
|
157
|
+
throw new Error(`Expected ${testValue} but got ${decodedValues[0]}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (let i = -(2 ** 22); i < 2 ** 22; i++) {
|
|
161
|
+
if (i % 1000000 === 0) {
|
|
162
|
+
logToStderr(i);
|
|
163
|
+
}
|
|
164
|
+
runTest(i);
|
|
165
|
+
}
|
|
166
|
+
const x = 1;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=LEB128.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LEB128.js","sourceRoot":"","sources":["../../src/utilities/LEB128.ts"],"names":[],"mappings":"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"}
|
package/docs/Options.md
CHANGED
|
@@ -222,7 +222,7 @@ Applies to CLI operation: `align`, API method: `align`
|
|
|
222
222
|
* `plainText.whitespace`: determines how to process whitespace within transcript paragraphs. Can be `preserve` (leave as is), `removeLineBreaks` (convert line breaks to spaces) or `collapse` (convert runs of whitespace characters, including line breaks, to a single space character). Defaults to `collapse`
|
|
223
223
|
|
|
224
224
|
**DTW**:
|
|
225
|
-
* `dtw.granularity`: adjusts the MFCC frame width and hop size based on the profile selected. Can be set to either `
|
|
225
|
+
* `dtw.granularity`: adjusts the MFCC frame width and hop size based on the profile selected. Can be set to either `xx-low` (400ms width, 160ms hop), `x-low` (200ms width, 80ms hop), `low` (100ms width, 40ms hop), `medium` (50ms width, 20ms hop), `high` (25ms width, 10ms hop), `x-high` (20ms width, 5ms hop). For multi-pass processing, multiple granularities can be provided, like `dtw.granularity=['xx-low','medium']`. Auto-selected by default.
|
|
226
226
|
* `dtw.windowDuration`: maximum duration (in seconds) of the Sakoe-Chiba window when performing DTW alignment. Higher values consume quadratically larger amounts of memory. The estimated memory requirement is shown in the log before alignment starts. Recommended to be set to at least 10% - 20% of total audio duration. For multi-pass processing, multiple durations can be provided, like `dtw.windowDuration=[240,20]`. Auto-selected by default
|
|
227
227
|
|
|
228
228
|
**DTW-RA**:
|
package/docs/Tasklist.md
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
### eSpeak
|
|
13
13
|
|
|
14
|
-
* Missing marker errors occurs in rare cases, when processing very long inputs. It is possibly related to `'` characters, but it's not 100% clear what the true cause is
|
|
15
14
|
* IPA -> Kirshenbaum translation is still not completely similar to what is output by eSpeak. Also, in rare situations, it outputs characters that are not accepted by eSpeak and eSpeak errors. Investigate when that happens and how to improve on this
|
|
16
15
|
|
|
17
16
|
### Browser extension
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echogarden",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.",
|
|
5
5
|
"author": "Rotem Dan",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"url": "https://github.com/echogarden-project/echogarden/issues"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=18"
|
|
28
|
+
"node": ">=18.16.0 <19.0.0 || >=19.8.0"
|
|
29
29
|
},
|
|
30
30
|
"os": [
|
|
31
31
|
"win32",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"echogarden": "./dist/cli/CLILauncher.js"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@aws-sdk/client-polly": "^3.
|
|
59
|
-
"@aws-sdk/client-transcribe-streaming": "^3.
|
|
58
|
+
"@aws-sdk/client-polly": "^3.574.0",
|
|
59
|
+
"@aws-sdk/client-transcribe-streaming": "^3.574.0",
|
|
60
60
|
"@echogarden/espeak-ng-emscripten": "^0.1.2",
|
|
61
61
|
"@echogarden/fasttext-wasm": "^0.1.0",
|
|
62
62
|
"@echogarden/flite-wasi": "^0.1.1",
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
"moving-median": "^1.0.0",
|
|
90
90
|
"msgpack-lite": "^0.1.26",
|
|
91
91
|
"onnxruntime-node": "^1.17.3",
|
|
92
|
-
"openai": "^4.
|
|
92
|
+
"openai": "^4.45.0",
|
|
93
93
|
"sam-js": "^0.2.1",
|
|
94
94
|
"strip-ansi": "^7.1.0",
|
|
95
95
|
"tar": "^7.1.0",
|
|
96
96
|
"tiktoken": "^1.0.14",
|
|
97
97
|
"tinyld": "^1.3.4",
|
|
98
98
|
"ws": "^8.17.0",
|
|
99
|
-
"wtf_wikipedia": "^10.3.
|
|
99
|
+
"wtf_wikipedia": "^10.3.1"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@echogarden/vosk": "^0.3.39-patched.1",
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import { Logger } from '../utilities/Logger.js'
|
|
3
1
|
import { logToStderr } from '../utilities/Utilities.js'
|
|
4
2
|
import { AlignmentPath } from './SpeechAlignment.js'
|
|
5
3
|
|
|
@@ -11,7 +9,10 @@ export function alignDTWWindowed<T, U>(sequence1: T[], sequence2: U[], costFunct
|
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
if (sequence1.length == 0 || sequence2.length == 0) {
|
|
14
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
path: [] as AlignmentPath,
|
|
14
|
+
pathCost: 0
|
|
15
|
+
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// Compute accumulated cost matrix (transposed)
|
|
@@ -38,10 +39,10 @@ function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T[], sequence2:
|
|
|
38
39
|
|
|
39
40
|
const accumulatedCostMatrixTransposed: Float32Array[] = new Array<Float32Array>(columnCount)
|
|
40
41
|
|
|
41
|
-
// Initialize window start offsets
|
|
42
|
+
// Initialize an array to store window start offsets
|
|
42
43
|
const windowStartOffsets = new Int32Array(columnCount)
|
|
43
44
|
|
|
44
|
-
// Compute matrix column by column
|
|
45
|
+
// Compute accumulated cost matrix column by column
|
|
45
46
|
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
|
46
47
|
// Create new column and add it to the matrix
|
|
47
48
|
const currentColumn = new Float32Array(rowCount)
|
|
@@ -65,13 +66,13 @@ function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T[], sequence2:
|
|
|
65
66
|
windowStartOffset = windowEndOffset - rowCount
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
// Store the start offset
|
|
69
|
+
// Store the start offset for this column
|
|
69
70
|
windowStartOffsets[columnIndex] = windowStartOffset
|
|
70
71
|
|
|
71
72
|
// Get target sequence1 value
|
|
72
73
|
const targetSequence1Value = sequence1[columnIndex]
|
|
73
74
|
|
|
74
|
-
// If first column, fill it only using the 'up'
|
|
75
|
+
// If this is the first column, fill it only using the 'up' neighbors
|
|
75
76
|
if (columnIndex == 0) {
|
|
76
77
|
for (let rowIndex = 1; rowIndex < rowCount; rowIndex++) {
|
|
77
78
|
const cost = costFunction(targetSequence1Value, sequence2[windowStartOffset + rowIndex])
|
|
@@ -84,11 +85,15 @@ function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T[], sequence2:
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
// If not first column
|
|
88
|
+
|
|
89
|
+
// Store the column to the left
|
|
87
90
|
const leftColumn = accumulatedCostMatrixTransposed[columnIndex - 1]
|
|
88
91
|
|
|
89
|
-
// Compute the delta between the current window
|
|
92
|
+
// Compute the delta between the current window start offset
|
|
93
|
+
// and left column's window offset
|
|
90
94
|
const windowOffsetDelta = windowStartOffset - windowStartOffsets[columnIndex - 1]
|
|
91
95
|
|
|
96
|
+
// Iterate over all rows in the window
|
|
92
97
|
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
93
98
|
// Compute the cost for current cell
|
|
94
99
|
const cost = costFunction(targetSequence1Value, sequence2[windowStartOffset + rowIndex])
|
|
@@ -115,12 +120,25 @@ function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T[], sequence2:
|
|
|
115
120
|
upAndLeftCost = leftColumn[upAndLeftRowIndex]
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
// Find the minimum of all neighbors
|
|
124
|
+
let minimumNeighborCost = minimumOf3(upCost, leftCost, upAndLeftCost)
|
|
125
|
+
|
|
126
|
+
// If all neighbors are infinity, then it means there is a "jump" between the window
|
|
127
|
+
// of the current column and the left column, and they don't have overlapping rows.
|
|
128
|
+
// In this case, only the cost of the current cell will be used
|
|
129
|
+
if (minimumNeighborCost === Infinity) {
|
|
130
|
+
minimumNeighborCost = 0
|
|
131
|
+
}
|
|
132
|
+
|
|
118
133
|
// Write cost + minimum neighbor cost to the current column
|
|
119
|
-
currentColumn[rowIndex] = cost +
|
|
134
|
+
currentColumn[rowIndex] = cost + minimumNeighborCost
|
|
120
135
|
}
|
|
121
136
|
}
|
|
122
137
|
|
|
123
|
-
return {
|
|
138
|
+
return {
|
|
139
|
+
accumulatedCostMatrixTransposed,
|
|
140
|
+
windowStartOffsets
|
|
141
|
+
}
|
|
124
142
|
}
|
|
125
143
|
|
|
126
144
|
function computeBestPathTransposed(accumulatedCostMatrixTransposed: Float32Array[], windowStartOffsets: Int32Array) {
|
|
@@ -129,6 +147,8 @@ function computeBestPathTransposed(accumulatedCostMatrixTransposed: Float32Array
|
|
|
129
147
|
|
|
130
148
|
const bestPath: AlignmentPath = []
|
|
131
149
|
|
|
150
|
+
// Start at the bottom right corner and find the best path
|
|
151
|
+
// towards the top left
|
|
132
152
|
let columnIndex = columnCount - 1
|
|
133
153
|
let rowIndex = rowCount - 1
|
|
134
154
|
|
|
@@ -136,19 +156,21 @@ function computeBestPathTransposed(accumulatedCostMatrixTransposed: Float32Array
|
|
|
136
156
|
const windowStartIndex = windowStartOffsets[columnIndex]
|
|
137
157
|
const windowStartDelta = columnIndex > 0 ? windowStartIndex - windowStartOffsets[columnIndex - 1] : 0
|
|
138
158
|
|
|
159
|
+
// Add the current cell to the best path
|
|
139
160
|
bestPath.push({
|
|
140
161
|
source: columnIndex,
|
|
141
162
|
dest: windowStartIndex + rowIndex
|
|
142
163
|
})
|
|
143
164
|
|
|
165
|
+
// Retrieve the cost for the 'up' (insertion) neighbor
|
|
144
166
|
const upRowIndex = rowIndex - 1
|
|
145
|
-
const upColumnIndex = columnIndex
|
|
146
167
|
let upCost = Infinity
|
|
147
168
|
|
|
148
169
|
if (upRowIndex >= 0) {
|
|
149
|
-
upCost = accumulatedCostMatrixTransposed[
|
|
170
|
+
upCost = accumulatedCostMatrixTransposed[columnIndex][upRowIndex] // insertion
|
|
150
171
|
}
|
|
151
172
|
|
|
173
|
+
// Retrieve the cost for the 'left' (deletion) neighbor
|
|
152
174
|
const leftRowIndex = rowIndex + windowStartDelta
|
|
153
175
|
const leftColumnIndex = columnIndex - 1
|
|
154
176
|
let leftCost = Infinity
|
|
@@ -157,6 +179,7 @@ function computeBestPathTransposed(accumulatedCostMatrixTransposed: Float32Array
|
|
|
157
179
|
leftCost = accumulatedCostMatrixTransposed[leftColumnIndex][leftRowIndex] // deletion
|
|
158
180
|
}
|
|
159
181
|
|
|
182
|
+
// Retrieve the cost for the 'up and left' (match) neighbor
|
|
160
183
|
const upAndLeftRowIndex = rowIndex - 1 + windowStartDelta
|
|
161
184
|
const upAndLeftColumnIndex = columnIndex - 1
|
|
162
185
|
let upAndLeftCost = Infinity
|
|
@@ -165,25 +188,42 @@ function computeBestPathTransposed(accumulatedCostMatrixTransposed: Float32Array
|
|
|
165
188
|
upAndLeftCost = accumulatedCostMatrixTransposed[upAndLeftColumnIndex][upAndLeftRowIndex] // match
|
|
166
189
|
}
|
|
167
190
|
|
|
191
|
+
// If all neighbors have a cost of infinity, it means
|
|
192
|
+
// there is a "jump" between the window for the current and previous column
|
|
168
193
|
if (upCost == Infinity && leftCost == Infinity && upAndLeftCost == Infinity) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
// In that case:
|
|
195
|
+
//
|
|
196
|
+
// If there are rows above
|
|
197
|
+
if (upRowIndex >= 0) {
|
|
198
|
+
// Move upward
|
|
199
|
+
rowIndex = upRowIndex
|
|
200
|
+
} else if (leftColumnIndex >= 0) {
|
|
201
|
+
// Otherwise, move to the left
|
|
202
|
+
columnIndex = leftColumnIndex
|
|
203
|
+
} else {
|
|
204
|
+
// Since we know that either columnIndex > 0 or rowIndex > 0,
|
|
205
|
+
// one of these directions must be available.
|
|
206
|
+
// This error should never happen
|
|
207
|
+
|
|
208
|
+
throw new Error(`Unexpected state: columnIndex: ${columnIndex}, rowIndex: ${rowIndex}`)
|
|
209
|
+
}
|
|
184
210
|
} else {
|
|
185
|
-
|
|
186
|
-
|
|
211
|
+
// Choose the direction with the smallest cost
|
|
212
|
+
const smallestCostDirection = argIndexOfMinimumOf3(upCost, leftCost, upAndLeftCost)
|
|
213
|
+
|
|
214
|
+
if (smallestCostDirection == 1) {
|
|
215
|
+
// Move upward
|
|
216
|
+
rowIndex = upRowIndex
|
|
217
|
+
// The upper column index stays the same
|
|
218
|
+
} else if (smallestCostDirection == 2) {
|
|
219
|
+
// Move to the left
|
|
220
|
+
rowIndex = leftRowIndex
|
|
221
|
+
columnIndex = leftColumnIndex
|
|
222
|
+
} else {
|
|
223
|
+
// Move upward and to the left
|
|
224
|
+
rowIndex = upAndLeftRowIndex
|
|
225
|
+
columnIndex = upAndLeftColumnIndex
|
|
226
|
+
}
|
|
187
227
|
}
|
|
188
228
|
}
|
|
189
229
|
|
|
@@ -38,12 +38,12 @@ export async function alignUsingDtw(
|
|
|
38
38
|
let relativeCenters: number[] | undefined
|
|
39
39
|
|
|
40
40
|
for (let passIndex = 0; passIndex < windowDurations.length; passIndex++) {
|
|
41
|
+
const granularity = granularities[passIndex]
|
|
41
42
|
const windowDuration = windowDurations[passIndex]
|
|
42
|
-
const granularity = resolveAutoGranularityIfNeeded(granularities[passIndex], rawAudioDuration)
|
|
43
43
|
|
|
44
44
|
logger.logTitledMessage(`\nStarting alignment pass ${passIndex + 1}/${windowDurations.length}`, `max window duration: ${windowDuration}s, granularity: ${granularity}`, chalk.magentaBright)
|
|
45
45
|
|
|
46
|
-
const mfccOptions = extendDefaultMfccOptions({ ...getMfccOptionsForGranularity(granularity
|
|
46
|
+
const mfccOptions = extendDefaultMfccOptions({ ...getMfccOptionsForGranularity(granularity), zeroFirstCoefficient: true }) as MfccOptions
|
|
47
47
|
|
|
48
48
|
framesPerSecond = 1 / mfccOptions.hopDuration!
|
|
49
49
|
|
|
@@ -67,7 +67,7 @@ export async function alignUsingDtw(
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
logger.start('Align MFCC features using DTW')
|
|
70
|
+
logger.start('Align reference and source MFCC features using DTW')
|
|
71
71
|
const dtwWindowLength = Math.floor(windowDuration * framesPerSecond)
|
|
72
72
|
|
|
73
73
|
let centerIndexes: number[] | undefined
|
|
@@ -458,7 +458,7 @@ export async function createAlignmentReferenceUsingEspeakForFragments(fragments:
|
|
|
458
458
|
progressLogger.start("Load espeak module")
|
|
459
459
|
const Espeak = await import("../synthesis/EspeakTTS.js")
|
|
460
460
|
|
|
461
|
-
progressLogger.start("
|
|
461
|
+
progressLogger.start("Synthesize alignment reference with eSpeak")
|
|
462
462
|
|
|
463
463
|
const result = await Espeak.synthesizeFragments(fragments, espeakOptions)
|
|
464
464
|
|
|
@@ -476,7 +476,7 @@ export async function createAlignmentReferenceUsingEspeakForFragments(fragments:
|
|
|
476
476
|
export async function createAlignmentReferenceUsingEspeak(transcript: string, language: string, plaintextOptions?: API.PlainTextOptions, customLexiconPaths?: string[], insertSeparators?: boolean) {
|
|
477
477
|
const logger = new Logger()
|
|
478
478
|
|
|
479
|
-
logger.start('
|
|
479
|
+
logger.start('Synthesize alignment reference with eSpeak')
|
|
480
480
|
|
|
481
481
|
const synthesisOptions: API.SynthesisOptions = {
|
|
482
482
|
engine: 'espeak',
|
|
@@ -544,25 +544,9 @@ function getMappedFrameIndexForPath(referenceFrameIndex: number, compactedPath:
|
|
|
544
544
|
return mappedFrameIndex
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
-
function
|
|
548
|
-
if (granularity != 'auto') {
|
|
549
|
-
return granularity
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (audioDuration < 60) {
|
|
553
|
-
return 'high'
|
|
554
|
-
} else if (audioDuration < 60 * 10) {
|
|
555
|
-
return 'medium'
|
|
556
|
-
} else {
|
|
557
|
-
return 'low'
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function getMfccOptionsForGranularity(granularity: DtwGranularity, audioDuration: number) {
|
|
547
|
+
function getMfccOptionsForGranularity(granularity: DtwGranularity) {
|
|
562
548
|
let mfccOptions: MfccOptions
|
|
563
549
|
|
|
564
|
-
granularity = resolveAutoGranularityIfNeeded(granularity, audioDuration)
|
|
565
|
-
|
|
566
550
|
if (granularity == 'xx-low') {
|
|
567
551
|
mfccOptions = { windowDuration: 0.400, hopDuration: 0.160, fftOrder: 8192 }
|
|
568
552
|
} else if (granularity == 'x-low') {
|
|
@@ -595,4 +579,4 @@ export type CompactedPathEntry = {
|
|
|
595
579
|
first: number, last: number
|
|
596
580
|
}
|
|
597
581
|
|
|
598
|
-
export type DtwGranularity = '
|
|
582
|
+
export type DtwGranularity = 'xx-low' | 'x-low' | 'low' | 'medium' | 'high' | 'x-high'
|
package/src/api/Alignment.ts
CHANGED
|
@@ -64,18 +64,6 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
64
64
|
sourceRawAudio = normalizeAudioLevel(sourceRawAudio)
|
|
65
65
|
sourceRawAudio.audioChannels[0] = trimAudioEnd(sourceRawAudio.audioChannels[0])
|
|
66
66
|
|
|
67
|
-
if (options.dtw!.windowDuration == null) {
|
|
68
|
-
const sourceAudioDuration = getRawAudioDuration(sourceRawAudio)
|
|
69
|
-
|
|
70
|
-
if (sourceAudioDuration < 5 * 60) { // If up to 5 minutes, set window to one minute
|
|
71
|
-
options.dtw!.windowDuration = 60
|
|
72
|
-
} else if (sourceAudioDuration < 60 * 60) { // If up to 1 hour, set window to 20% of total duration
|
|
73
|
-
options.dtw!.windowDuration = Math.ceil(sourceAudioDuration * 0.2)
|
|
74
|
-
} else { // If 1 hour or more, set window to 12 minutes
|
|
75
|
-
options.dtw!.windowDuration = 12 * 60
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
67
|
logger.end()
|
|
80
68
|
|
|
81
69
|
let language: string
|
|
@@ -87,7 +75,7 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
87
75
|
|
|
88
76
|
logger.logTitledMessage('Language specified', formatLanguageCodeWithName(language))
|
|
89
77
|
} else {
|
|
90
|
-
logger.start('No language specified.
|
|
78
|
+
logger.start('No language specified. Detect language')
|
|
91
79
|
const { detectedLanguage } = await API.detectTextLanguage(transcript, options.languageDetection || {})
|
|
92
80
|
|
|
93
81
|
language = detectedLanguage
|
|
@@ -103,7 +91,9 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
103
91
|
|
|
104
92
|
const { alignUsingDtwWithRecognition, alignUsingDtw } = await import('../alignment/SpeechAlignment.js')
|
|
105
93
|
|
|
106
|
-
function
|
|
94
|
+
function getDtwWindowGranularitiesAndDurations() {
|
|
95
|
+
const sourceAudioDuration = getRawAudioDuration(sourceRawAudio)
|
|
96
|
+
|
|
107
97
|
let granularities: DtwGranularity[]
|
|
108
98
|
let windowDurations: number[]
|
|
109
99
|
|
|
@@ -112,25 +102,52 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
112
102
|
} else if (Array.isArray(options.dtw!.granularity)) {
|
|
113
103
|
granularities = options.dtw!.granularity
|
|
114
104
|
} else {
|
|
115
|
-
|
|
105
|
+
if (sourceAudioDuration < 1 * 60) {
|
|
106
|
+
// If up to 1 minute, set granularity to high, single pass
|
|
107
|
+
granularities = ['high']
|
|
108
|
+
} else if (sourceAudioDuration < 5 * 60) {
|
|
109
|
+
// If up to 5 minutes, set granularity to medium, single pass
|
|
110
|
+
granularities = ['medium']
|
|
111
|
+
} else if (sourceAudioDuration < 30 * 60) {
|
|
112
|
+
// If up to 30 minutes, set granularity to low, single pass
|
|
113
|
+
granularities = ['low']
|
|
114
|
+
} else {
|
|
115
|
+
// Otherwise, use multipass processing, first with xx-low granularity, then low
|
|
116
|
+
granularities = ['xx-low', 'low']
|
|
117
|
+
}
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
if (
|
|
119
|
-
if (
|
|
120
|
+
if (options.dtw!.windowDuration) {
|
|
121
|
+
if (typeof options.dtw!.windowDuration === 'number') {
|
|
120
122
|
windowDurations = [options.dtw!.windowDuration]
|
|
121
|
-
} else if (
|
|
122
|
-
windowDurations =
|
|
123
|
+
} else if (Array.isArray(options.dtw!.windowDuration)) {
|
|
124
|
+
windowDurations = options.dtw!.windowDuration
|
|
123
125
|
} else {
|
|
124
|
-
throw new Error(`
|
|
126
|
+
throw new Error(`'dtw.windowDuration' must be a number or an array of numbers.`)
|
|
125
127
|
}
|
|
126
|
-
} else if (Array.isArray(options.dtw!.windowDuration)) {
|
|
127
|
-
windowDurations = options.dtw!.windowDuration
|
|
128
128
|
} else {
|
|
129
|
-
|
|
129
|
+
if (granularities.length > 2) {
|
|
130
|
+
throw new Error(`More than two passes requested, this requires window durations to be explicitly specified for each pass. For example 'dtw.windowDuration=[600,60,10]'.`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (sourceAudioDuration < 5 * 60) {
|
|
134
|
+
// If up to 5 minutes, set window duration to one minute
|
|
135
|
+
windowDurations = [60]
|
|
136
|
+
} else if (sourceAudioDuration < 2.5 * 60 * 60) {
|
|
137
|
+
// If less than 2.5 hours, set window duration to 20% of total duration
|
|
138
|
+
windowDurations = [Math.ceil(sourceAudioDuration * 0.2)]
|
|
139
|
+
} else {
|
|
140
|
+
// Otherwise, set window duration to 30 minutes
|
|
141
|
+
windowDurations = [30 * 60]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (granularities.length === 2 && windowDurations.length === 1) {
|
|
146
|
+
windowDurations = [windowDurations[0], 15]
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
if (granularities.length != windowDurations.length) {
|
|
133
|
-
throw new Error(`
|
|
150
|
+
throw new Error(`The option 'dtw.granularity' has ${granularities.length} values, but 'dtw.windowDuration' has ${windowDurations.length} values. The lengths should be equal.`)
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
return { windowDurations, granularities }
|
|
@@ -140,6 +157,8 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
140
157
|
|
|
141
158
|
switch (options.engine) {
|
|
142
159
|
case 'dtw': {
|
|
160
|
+
const { windowDurations, granularities } = getDtwWindowGranularitiesAndDurations()
|
|
161
|
+
|
|
143
162
|
logger.end()
|
|
144
163
|
|
|
145
164
|
const {
|
|
@@ -149,14 +168,14 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
149
168
|
|
|
150
169
|
logger.end()
|
|
151
170
|
|
|
152
|
-
const { windowDurations, granularities } = getDtwWindowDurationsAndGranularities()
|
|
153
|
-
|
|
154
171
|
mappedTimeline = await alignUsingDtw(sourceRawAudio, referenceRawAudio, referenceTimeline, granularities, windowDurations)
|
|
155
172
|
|
|
156
173
|
break
|
|
157
174
|
}
|
|
158
175
|
|
|
159
176
|
case 'dtw-ra': {
|
|
177
|
+
const { windowDurations, granularities } = getDtwWindowGranularitiesAndDurations()
|
|
178
|
+
|
|
160
179
|
logger.end()
|
|
161
180
|
|
|
162
181
|
const recognitionOptions: API.RecognitionOptions =
|
|
@@ -181,8 +200,6 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
181
200
|
|
|
182
201
|
logger.end()
|
|
183
202
|
|
|
184
|
-
const { windowDurations, granularities } = getDtwWindowDurationsAndGranularities()
|
|
185
|
-
|
|
186
203
|
const phoneAlignmentMethod = options.dtw!.phoneAlignmentMethod!
|
|
187
204
|
|
|
188
205
|
const espeakOptions: EspeakOptions = {
|
|
@@ -229,6 +246,8 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
229
246
|
}
|
|
230
247
|
}
|
|
231
248
|
|
|
249
|
+
logger.start(`Postprocess timeline`)
|
|
250
|
+
|
|
232
251
|
// If the audio was cropped before recognition, map the timestamps back to the original audio
|
|
233
252
|
if (sourceUncropTimeline && sourceUncropTimeline.length > 0) {
|
|
234
253
|
API.convertCroppedToUncroppedTimeline(mappedTimeline, sourceUncropTimeline)
|
|
@@ -354,7 +373,7 @@ export const defaultAlignmentOptions: AlignmentOptions = {
|
|
|
354
373
|
},
|
|
355
374
|
|
|
356
375
|
dtw: {
|
|
357
|
-
granularity:
|
|
376
|
+
granularity: undefined,
|
|
358
377
|
windowDuration: undefined,
|
|
359
378
|
phoneAlignmentMethod: 'dtw'
|
|
360
379
|
},
|