echogarden 1.3.3 → 1.4.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.
- 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 +5 -0
- 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 +5 -5
- 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 +7 -1
- package/src/utilities/Compression.ts +15 -0
- package/src/utilities/LEB128.ts +237 -0
|
@@ -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
|
+
// Otherwisek, 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
|
},
|
|
@@ -257,7 +257,7 @@ export async function detectTextLanguage(input: string, options: TextLanguageDet
|
|
|
257
257
|
case 'tinyld': {
|
|
258
258
|
const { detectLanguage } = await import('../text-language-detection/TinyLDLanguageDetection.js')
|
|
259
259
|
|
|
260
|
-
logger.start('
|
|
260
|
+
logger.start('Detect text language using tinyld')
|
|
261
261
|
|
|
262
262
|
detectedLanguageProbabilities = await detectLanguage(input)
|
|
263
263
|
|
|
@@ -267,7 +267,7 @@ export async function detectTextLanguage(input: string, options: TextLanguageDet
|
|
|
267
267
|
case 'fasttext': {
|
|
268
268
|
const { detectLanguage } = await import('../text-language-detection/FastTextLanguageDetection.js')
|
|
269
269
|
|
|
270
|
-
logger.start('
|
|
270
|
+
logger.start('Detect text language using FastText')
|
|
271
271
|
|
|
272
272
|
detectedLanguageProbabilities = await detectLanguage(input)
|
|
273
273
|
|
package/src/api/Synthesis.ts
CHANGED
|
@@ -54,7 +54,7 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
|
|
|
54
54
|
options = extendDeep(defaultSynthesisOptions, options)
|
|
55
55
|
|
|
56
56
|
if (!options.language && !options.voice) {
|
|
57
|
-
logger.start('No language or voice specified.
|
|
57
|
+
logger.start('No language or voice specified. Detect language')
|
|
58
58
|
|
|
59
59
|
let segmentsPlainText = segments
|
|
60
60
|
|
|
@@ -44,7 +44,7 @@ export async function preprocessAndSynthesize(text: string, language: string, es
|
|
|
44
44
|
|
|
45
45
|
for (let i = 0; i < words.length; i++) {
|
|
46
46
|
const currentWord = words[i]
|
|
47
|
-
const previousWord = words[i-1]
|
|
47
|
+
const previousWord = words[i - 1]
|
|
48
48
|
|
|
49
49
|
if (i > 0 && currentWord == previousWord && !wordCharacterPattern.test(currentWord)) {
|
|
50
50
|
wordsWithMerges[wordsWithMerges.length - 1] += currentWord
|
|
@@ -62,6 +62,12 @@ export async function preprocessAndSynthesize(text: string, language: string, es
|
|
|
62
62
|
|
|
63
63
|
const simplifiedFragments = normalizedFragments.map(word => simplifyPunctuationCharacters(word).toLocaleLowerCase())
|
|
64
64
|
|
|
65
|
+
for (let i = 0; i < normalizedFragments.length; i++) {
|
|
66
|
+
if ([`'`, `"`].includes(simplifiedFragments[i])) {
|
|
67
|
+
normalizedFragments[i] = '()'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
for (let fragmentIndex = 0; fragmentIndex < normalizedFragments.length; fragmentIndex++) {
|
|
66
72
|
const fragment = normalizedFragments[fragmentIndex]
|
|
67
73
|
|
|
@@ -110,3 +110,18 @@ export async function getDeflateCompressionMetricsForString(str: string) {
|
|
|
110
110
|
ratio: originalStringBytes.length / compressedStringBytes.length
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
export function computeDeltas(data: Float32Array) {
|
|
115
|
+
const deltas = new Float32Array(data.length)
|
|
116
|
+
|
|
117
|
+
let val = 0
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < data.length; i++) {
|
|
120
|
+
const delta = Math.floor(data[i] - val)
|
|
121
|
+
deltas[i] = delta
|
|
122
|
+
|
|
123
|
+
val += delta
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return deltas
|
|
127
|
+
}
|