echogarden 1.8.2 → 1.8.4
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/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWMfccSequenceAlignment.js +6 -5
- package/dist/alignment/DTWMfccSequenceAlignment.js.map +1 -1
- package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignment.js.map +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
- package/dist/alignment/SpeechAlignment.d.ts +2 -0
- package/dist/alignment/SpeechAlignment.js +1 -1
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/SpeechSearch.d.ts +6 -0
- package/dist/api/SpeechSearch.js +4 -0
- package/dist/api/SpeechSearch.js.map +1 -0
- package/dist/dsp/MFCC.d.ts +5 -6
- package/dist/dsp/MFCC.js +4 -19
- package/dist/dsp/MFCC.js.map +1 -1
- package/dist/math/MedianFilter.d.ts +2 -2
- package/dist/math/MedianFilter.js +3 -4
- package/dist/math/MedianFilter.js.map +1 -1
- package/dist/math/VectorMath.d.ts +34 -34
- package/dist/math/VectorMath.js +63 -44
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/nlp/IPA.d.ts +3 -3
- package/dist/recognition/WhisperSTT.d.ts +1 -0
- package/dist/recognition/WhisperSTT.js +36 -20
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/speech-search/DTWSpeechSearch.d.ts +2 -0
- package/dist/speech-search/DTWSpeechSearch.js +18 -0
- package/dist/speech-search/DTWSpeechSearch.js.map +1 -0
- package/dist/utilities/RandomGenerator.d.ts +2 -2
- package/dist/utilities/RandomGenerator.js.map +1 -1
- package/dist/utilities/Utilities.js +7 -1
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/WebReader.js +2 -2
- package/dist/utilities/WebReader.js.map +1 -1
- package/docs/Options.md +8 -6
- package/package.json +2 -2
- package/src/alignment/DTWMfccSequenceAlignment.ts +9 -6
- package/src/alignment/DTWSequenceAlignment.ts +2 -2
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
- package/src/alignment/SpeechAlignment.ts +1 -1
- package/src/api/SpeechSearch.ts +12 -0
- package/src/dsp/MFCC.ts +11 -31
- package/src/math/MedianFilter.ts +5 -5
- package/src/math/VectorMath.ts +109 -75
- package/src/recognition/WhisperSTT.ts +45 -27
- package/src/speech-search/DTWSpeechSearch.ts +26 -0
- package/src/utilities/RandomGenerator.ts +2 -2
- package/src/utilities/Utilities.ts +11 -3
- package/src/utilities/WebReader.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echogarden",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
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",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"jsdom": "^25.0.1",
|
|
86
86
|
"json5": "^2.2.3",
|
|
87
87
|
"kuromoji": "^0.1.2",
|
|
88
|
-
"microsoft-cognitiveservices-speech-sdk": "^1.
|
|
88
|
+
"microsoft-cognitiveservices-speech-sdk": "^1.41.0",
|
|
89
89
|
"moving-median": "^1.0.0",
|
|
90
90
|
"msgpack-lite": "^0.1.26",
|
|
91
91
|
"onnxruntime-node": "^1.19.2",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { cosineDistancePrecomputedMagnitudes,
|
|
2
|
-
import { logToStderr } from '../utilities/Utilities.js'
|
|
1
|
+
import { cosineDistancePrecomputedMagnitudes, euclideanDistance, euclideanDistance13Dim, magnitude } from '../math/VectorMath.js'
|
|
2
|
+
import { getIntegerRange, logToStderr } from '../utilities/Utilities.js'
|
|
3
3
|
import { alignDTWWindowed } from './DTWSequenceAlignmentWindowed.js'
|
|
4
4
|
|
|
5
5
|
const log = logToStderr
|
|
6
6
|
|
|
7
|
-
export async function alignMFCC_DTW(mfccFrames1: number[]
|
|
7
|
+
export async function alignMFCC_DTW(mfccFrames1: ArrayLike<number>[], mfccFrames2: ArrayLike<number>[], windowLength: number, distanceFunctionKind: 'euclidean' | 'cosine' = 'euclidean', centerIndexes?: number[]) {
|
|
8
8
|
if (distanceFunctionKind == 'euclidean') {
|
|
9
9
|
let distanceFunction = euclideanDistance
|
|
10
10
|
|
|
@@ -22,16 +22,19 @@ export async function alignMFCC_DTW(mfccFrames1: number[][], mfccFrames2: number
|
|
|
22
22
|
|
|
23
23
|
return path
|
|
24
24
|
} else if (distanceFunctionKind == 'cosine') {
|
|
25
|
-
const indexes1 =
|
|
26
|
-
const indexes2 =
|
|
25
|
+
const indexes1 = getIntegerRange(0, mfccFrames1.length)
|
|
26
|
+
const indexes2 = getIntegerRange(0, mfccFrames2.length)
|
|
27
27
|
|
|
28
28
|
const magnitudes1 = mfccFrames1.map(magnitude)
|
|
29
29
|
const magnitudes2 = mfccFrames2.map(magnitude)
|
|
30
30
|
|
|
31
|
+
const distanceFunction = (i: number, j: number) =>
|
|
32
|
+
cosineDistancePrecomputedMagnitudes(mfccFrames1[i], mfccFrames2[j], magnitudes1[i], magnitudes2[j])
|
|
33
|
+
|
|
31
34
|
const { path } = alignDTWWindowed(
|
|
32
35
|
indexes1,
|
|
33
36
|
indexes2,
|
|
34
|
-
|
|
37
|
+
distanceFunction,
|
|
35
38
|
windowLength,
|
|
36
39
|
centerIndexes
|
|
37
40
|
)
|
|
@@ -3,7 +3,7 @@ import { AlignmentPath } from './SpeechAlignment.js'
|
|
|
3
3
|
|
|
4
4
|
const log = logToStderr
|
|
5
5
|
|
|
6
|
-
export function alignDTW<T, U>(sequence1: T
|
|
6
|
+
export function alignDTW<T, U>(sequence1: ArrayLike<T>, sequence2: ArrayLike<U>, costFunction: (a: T, b: U) => number, deletionEnabled = true) {
|
|
7
7
|
if (sequence1.length == 0 || sequence2.length == 0) {
|
|
8
8
|
return { path: [] as AlignmentPath, pathCost: 0 }
|
|
9
9
|
}
|
|
@@ -23,7 +23,7 @@ export function alignDTW<T, U>(sequence1: T[], sequence2: U[], costFunction: (a:
|
|
|
23
23
|
return { path, pathCost }
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function computeAccumulatedCostMatrix<T, U>(sequence1: T
|
|
26
|
+
function computeAccumulatedCostMatrix<T, U>(sequence1: ArrayLike<T>, sequence2: ArrayLike<U>, costFunction: (a: T, b: U) => number, deletionEnabled = true) {
|
|
27
27
|
const rowCount = sequence2.length
|
|
28
28
|
const columnCount = sequence1.length
|
|
29
29
|
|
|
@@ -3,7 +3,7 @@ import { AlignmentPath } from './SpeechAlignment.js'
|
|
|
3
3
|
|
|
4
4
|
const log = logToStderr
|
|
5
5
|
|
|
6
|
-
export function alignDTWWindowed<T, U>(sequence1: T
|
|
6
|
+
export function alignDTWWindowed<T, U>(sequence1: ArrayLike<T>, sequence2: ArrayLike<U>, costFunction: (a: T, b: U) => number, windowMaxLength: number, centerIndexes?: ArrayLike<number>) {
|
|
7
7
|
windowMaxLength = Math.max(windowMaxLength, 2)
|
|
8
8
|
|
|
9
9
|
if (sequence1.length == 0 || sequence2.length == 0) {
|
|
@@ -29,7 +29,7 @@ export function alignDTWWindowed<T, U>(sequence1: T[], sequence2: U[], costFunct
|
|
|
29
29
|
return { path, pathCost }
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T
|
|
32
|
+
function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: ArrayLike<T>, sequence2: ArrayLike<U>, costFunction: (a: T, b: U) => number, windowMaxLength: number, centerIndexes?: ArrayLike<number>) {
|
|
33
33
|
const halfWindowMaxLength = Math.floor(windowMaxLength / 2)
|
|
34
34
|
|
|
35
35
|
const columnCount = sequence1.length
|
|
@@ -139,7 +139,7 @@ function computeAccumulatedCostMatrixTransposed<T, U>(sequence1: T[], sequence2:
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
function computeBestPathTransposed(accumulatedCostMatrixTransposed:
|
|
142
|
+
function computeBestPathTransposed(accumulatedCostMatrixTransposed: ArrayLike<number>[], windowStartOffsets: ArrayLike<number>) {
|
|
143
143
|
const columnCount = accumulatedCostMatrixTransposed.length
|
|
144
144
|
const rowCount = accumulatedCostMatrixTransposed[0].length
|
|
145
145
|
|
|
@@ -716,7 +716,7 @@ function getMappedFrameIndexForPath(referenceFrameIndex: number, compactedPath:
|
|
|
716
716
|
return mappedFrameIndex
|
|
717
717
|
}
|
|
718
718
|
|
|
719
|
-
function getMfccOptionsForGranularity(granularity: DtwGranularity) {
|
|
719
|
+
export function getMfccOptionsForGranularity(granularity: DtwGranularity) {
|
|
720
720
|
let mfccOptions: MfccOptions
|
|
721
721
|
|
|
722
722
|
if (granularity == 'xx-low') {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AudioSourceParam } from "../audio/AudioUtilities.js";
|
|
2
|
+
|
|
3
|
+
export async function searchSpeech(inputAudio: AudioSourceParam, text: string, options: SpeechSearchOptions): Promise<SpeechSearchResult> {
|
|
4
|
+
return {}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface SpeechSearchOptions {
|
|
8
|
+
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SpeechSearchResult {
|
|
12
|
+
}
|
package/src/dsp/MFCC.ts
CHANGED
|
@@ -36,8 +36,6 @@ export async function computeMFCCs(monoAudio: RawAudio, options: MfccOptions = {
|
|
|
36
36
|
logger.start(`Resample audio to analysis sample rate (${analysisSampleRate}Hz)`)
|
|
37
37
|
const resampledAudio = await resampleAudioSpeex(monoAudio, analysisSampleRate)
|
|
38
38
|
|
|
39
|
-
let mfccs: number[][]
|
|
40
|
-
|
|
41
39
|
if (emphasisFactor > 0) {
|
|
42
40
|
logger.start('Apply emphasis')
|
|
43
41
|
resampledAudio.audioChannels[0] = applyEmphasis(resampledAudio.audioChannels[0], emphasisFactor)
|
|
@@ -47,20 +45,19 @@ export async function computeMFCCs(monoAudio: RawAudio, options: MfccOptions = {
|
|
|
47
45
|
const { melSpectogram } = await computeMelSpectogram(resampledAudio, fftOrder, windowSize, hopLength, filterbankCount, lowerFrequencyHz, upperFrequencyHz)
|
|
48
46
|
|
|
49
47
|
logger.start('Extract MFCCs from Mel spectogram')
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
mfccs = mfccsFloat32.map(mfcc => Array.from(mfcc))
|
|
48
|
+
let mfccs = melSpectogramToMFCCs(melSpectogram, featureCount)
|
|
53
49
|
|
|
54
50
|
if (options.normalize!) {
|
|
55
51
|
logger.start('Normalize MFCCs')
|
|
56
52
|
|
|
57
53
|
const { normalizedVectors, mean, stdDeviation } = normalizeVectors(mfccs)
|
|
54
|
+
|
|
58
55
|
mfccs = normalizedVectors
|
|
59
|
-
//mfccs = mfccs.map(mfcc => subtractVectors(mfcc, mean))
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
if (lifteringFactor > 0) {
|
|
63
59
|
logger.start('Apply liftering to MFCCs')
|
|
60
|
+
|
|
64
61
|
mfccs = applyLiftering(mfccs, lifteringFactor)
|
|
65
62
|
}
|
|
66
63
|
|
|
@@ -75,7 +72,7 @@ export async function computeMFCCs(monoAudio: RawAudio, options: MfccOptions = {
|
|
|
75
72
|
return mfccs
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
export function melSpectogramToMFCCs(melSpectogram:
|
|
75
|
+
export function melSpectogramToMFCCs(melSpectogram: ArrayLike<number>[], mfccFeatureCount: number) {
|
|
79
76
|
const melBandCount = melSpectogram[0].length
|
|
80
77
|
const dctMatrix = createDCTType2CoefficientMatrix(mfccFeatureCount, melBandCount)
|
|
81
78
|
|
|
@@ -84,7 +81,7 @@ export function melSpectogramToMFCCs(melSpectogram: Float32Array[], mfccFeatureC
|
|
|
84
81
|
return mfccs
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
export function melSpectrumToMFCC(melSpectrum:
|
|
84
|
+
export function melSpectrumToMFCC(melSpectrum: ArrayLike<number>, mfccFeatureCount: number, dctMatrix: ArrayLike<number>[], normalization: 'none' | 'orthonormal' = 'orthonormal') {
|
|
88
85
|
const melBandCount = melSpectrum.length
|
|
89
86
|
|
|
90
87
|
let firstFeatureNormalizationFactor: number
|
|
@@ -108,7 +105,6 @@ export function melSpectrumToMFCC(melSpectrum: Float32Array, mfccFeatureCount: n
|
|
|
108
105
|
for (let j = 0; j < melBandCount; j++) {
|
|
109
106
|
const dctCoefficient = dctMatrixRow[j]
|
|
110
107
|
const logMel = powerToDecibels(melSpectrum[j])
|
|
111
|
-
//const logMel = Math.log(1e-40 + melSpectrum[j])
|
|
112
108
|
|
|
113
109
|
sum += dctCoefficient * logMel
|
|
114
110
|
}
|
|
@@ -123,7 +119,7 @@ export function melSpectrumToMFCC(melSpectrum: Float32Array, mfccFeatureCount: n
|
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
export function createDCTType2CoefficientMatrix(mfccFeatureCount: number, melBandCount: number) {
|
|
126
|
-
const dctMatrix =
|
|
122
|
+
const dctMatrix: Float32Array[] = []
|
|
127
123
|
|
|
128
124
|
for (let mfccFeatureIndex = 0; mfccFeatureIndex < mfccFeatureCount; mfccFeatureIndex++) {
|
|
129
125
|
const row = new Float32Array(melBandCount)
|
|
@@ -134,29 +130,13 @@ export function createDCTType2CoefficientMatrix(mfccFeatureCount: number, melBan
|
|
|
134
130
|
row[melBandIndex] = Math.cos(innerMultiplier * (melBandIndex + 0.5))
|
|
135
131
|
}
|
|
136
132
|
|
|
137
|
-
dctMatrix
|
|
133
|
+
dctMatrix.push(row)
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
return dctMatrix
|
|
141
137
|
}
|
|
142
138
|
|
|
143
|
-
export function
|
|
144
|
-
if (mfccBuffer.length % mfccFeatureCount != 0) {
|
|
145
|
-
throw new Error(`MFCC buffer length is not a multiple of the expected feature count (${mfccFeatureCount})`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const mfccVectors: number[][] = []
|
|
149
|
-
|
|
150
|
-
for (let offset = 0; offset < mfccBuffer.length; offset += mfccFeatureCount) {
|
|
151
|
-
const mfccVector = Array.from(mfccBuffer.subarray(offset, offset + mfccFeatureCount))
|
|
152
|
-
|
|
153
|
-
mfccVectors.push(mfccVector)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return mfccVectors
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function applyEmphasis(samples: Float32Array, emphasisFactor = 0.97, initialState = 0) {
|
|
139
|
+
export function applyEmphasis(samples: ArrayLike<number>, emphasisFactor = 0.97, initialState = 0) {
|
|
160
140
|
const processedSamples = new Float32Array(samples.length)
|
|
161
141
|
|
|
162
142
|
processedSamples[0] = samples[0] - (emphasisFactor * initialState)
|
|
@@ -168,7 +148,7 @@ export function applyEmphasis(samples: Float32Array, emphasisFactor = 0.97, init
|
|
|
168
148
|
return processedSamples
|
|
169
149
|
}
|
|
170
150
|
|
|
171
|
-
export function applyLiftering(mfccs: number[]
|
|
151
|
+
export function applyLiftering(mfccs: ArrayLike<number>[], lifteringFactor: number) {
|
|
172
152
|
const featureCount = mfccs[0].length
|
|
173
153
|
|
|
174
154
|
const lifterMultipliers = new Float32Array(featureCount)
|
|
@@ -177,10 +157,10 @@ export function applyLiftering(mfccs: number[][], lifteringFactor: number) {
|
|
|
177
157
|
lifterMultipliers[i] = 1 + (lifteringFactor / 2) * Math.sin(Math.PI * (i + 1) / lifteringFactor)
|
|
178
158
|
}
|
|
179
159
|
|
|
180
|
-
const lifteredMfccs:
|
|
160
|
+
const lifteredMfccs: Float32Array[] = []
|
|
181
161
|
|
|
182
162
|
for (const mfcc of mfccs) {
|
|
183
|
-
const lifteredMfcc = new
|
|
163
|
+
const lifteredMfcc = new Float32Array(featureCount)
|
|
184
164
|
|
|
185
165
|
for (let i = 0; i < featureCount; i++) {
|
|
186
166
|
lifteredMfcc[i] = mfcc[i] * lifterMultipliers[i]
|
package/src/math/MedianFilter.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createVector } from "./VectorMath.js"
|
|
2
2
|
|
|
3
|
-
export function medianOf5Filter(points: number
|
|
3
|
+
export function medianOf5Filter(points: ArrayLike<number>): Float32Array {
|
|
4
4
|
// This function computes the moving median with a window of 5 elements.
|
|
5
5
|
|
|
6
6
|
// I initialized the window such that at the edges of the range no median would be computed.
|
|
@@ -10,10 +10,10 @@ export function medianOf5Filter(points: number[]) {
|
|
|
10
10
|
const pointCount = points.length
|
|
11
11
|
|
|
12
12
|
if (pointCount < 5) {
|
|
13
|
-
return points
|
|
13
|
+
return Float32Array.from(points)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const medians =
|
|
16
|
+
const medians = new Float32Array(pointCount)
|
|
17
17
|
|
|
18
18
|
medians[0] = points[0]
|
|
19
19
|
medians[1] = points[1]
|
|
@@ -38,7 +38,7 @@ export function medianOf3Filter(points: ArrayLike<number>) {
|
|
|
38
38
|
return points
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const medians
|
|
41
|
+
const medians = new Float32Array(pointCount)
|
|
42
42
|
|
|
43
43
|
medians[0] = points[0]
|
|
44
44
|
medians[pointCount - 1] = points[pointCount - 1]
|
|
@@ -110,7 +110,7 @@ export function medianOf3(a: number, b: number, c: number) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// Slower, variable-width median filter using the `moving-median` package
|
|
113
|
-
export async function medianFilter(points: number
|
|
113
|
+
export async function medianFilter(points: ArrayLike<number>, width: number) {
|
|
114
114
|
const { default: createMedianFilter } = await import('moving-median')
|
|
115
115
|
|
|
116
116
|
const filter = createMedianFilter(width)
|