echogarden 0.11.12 → 0.11.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/data/schemas/options.json +16 -0
  2. package/dist/api/Alignment.js +2 -2
  3. package/dist/api/Alignment.js.map +1 -1
  4. package/dist/api/Recognition.js +2 -2
  5. package/dist/api/Recognition.js.map +1 -1
  6. package/dist/api/Synthesis.js +5 -4
  7. package/dist/api/Synthesis.js.map +1 -1
  8. package/dist/api/Translation.js +2 -2
  9. package/dist/api/Translation.js.map +1 -1
  10. package/dist/audio/AudioUtilities.d.ts +1 -0
  11. package/dist/audio/AudioUtilities.js +25 -7
  12. package/dist/audio/AudioUtilities.js.map +1 -1
  13. package/dist/cli/CLI.js +2 -2
  14. package/dist/cli/CLI.js.map +1 -1
  15. package/dist/recognition/WhisperSTT.js +2 -2
  16. package/dist/recognition/WhisperSTT.js.map +1 -1
  17. package/dist/subtitles/Subtitles.d.ts +10 -7
  18. package/dist/subtitles/Subtitles.js +268 -207
  19. package/dist/subtitles/Subtitles.js.map +1 -1
  20. package/docs/Options.md +4 -2
  21. package/package.json +7 -6
  22. package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
  23. package/src/alignment/DTWSequenceAlignment.ts +121 -0
  24. package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
  25. package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
  26. package/src/alignment/SpeechAlignment.ts +488 -0
  27. package/src/api/API.ts +12 -0
  28. package/src/api/APIOptions.ts +15 -0
  29. package/src/api/Alignment.ts +329 -0
  30. package/src/api/Common.ts +16 -0
  31. package/src/api/Denoising.ts +120 -0
  32. package/src/api/LanguageDetection.ts +286 -0
  33. package/src/api/Recognition.ts +344 -0
  34. package/src/api/Synthesis.ts +1735 -0
  35. package/src/api/Translation.ts +143 -0
  36. package/src/api/Vad.ts +172 -0
  37. package/src/audio/AudioBufferConversion.ts +248 -0
  38. package/src/audio/AudioPlayer.ts +358 -0
  39. package/src/audio/AudioRecorder.ts +91 -0
  40. package/src/audio/AudioUtilities.ts +392 -0
  41. package/src/audio/SoxPath.ts +24 -0
  42. package/src/cli/CLI.ts +1360 -0
  43. package/src/cli/CLIConfigFile.ts +91 -0
  44. package/src/cli/CLILauncher.ts +26 -0
  45. package/src/cli/CLIOptionsSchema.ts +54 -0
  46. package/src/cli/CLIParser.ts +41 -0
  47. package/src/cli/CLIStarter.ts +40 -0
  48. package/src/codecs/FFMpegTranscoder.ts +214 -0
  49. package/src/codecs/TIMITCodec.ts +17 -0
  50. package/src/codecs/WaveCodec.ts +260 -0
  51. package/src/denoising/RNNoise.ts +95 -0
  52. package/src/dsp/BiquadFilter.ts +488 -0
  53. package/src/dsp/FFT.ts +187 -0
  54. package/src/dsp/MFCC.ts +227 -0
  55. package/src/dsp/MelSpectogram.ts +145 -0
  56. package/src/dsp/Rubberband.ts +249 -0
  57. package/src/dsp/Sonic.ts +59 -0
  58. package/src/dsp/SpeexResampler.ts +79 -0
  59. package/src/math/VectorMath.ts +812 -0
  60. package/src/nlp/ChineseSegmentation.ts +68 -0
  61. package/src/nlp/CompromiseNLP.ts +113 -0
  62. package/src/nlp/EspeakPhonemizer.ts +168 -0
  63. package/src/nlp/IPA.ts +139 -0
  64. package/src/nlp/JapaneseSegmentation.ts +53 -0
  65. package/src/nlp/Lexicon.ts +119 -0
  66. package/src/nlp/PhoneConversion.ts +508 -0
  67. package/src/nlp/Segmentation.ts +237 -0
  68. package/src/nlp/TextNormalizer.ts +160 -0
  69. package/src/recognition/AmazonTranscribeSTT.ts +112 -0
  70. package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
  71. package/src/recognition/GoogleCloudSTT.ts +92 -0
  72. package/src/recognition/SileroSTT.ts +173 -0
  73. package/src/recognition/VoskSTT.ts +112 -0
  74. package/src/recognition/WhisperSTT.ts +1518 -0
  75. package/src/server/Client.ts +297 -0
  76. package/src/server/Server.ts +178 -0
  77. package/src/server/ServerStarter.ts +12 -0
  78. package/src/server/Worker.ts +400 -0
  79. package/src/server/WorkerStarter.ts +38 -0
  80. package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
  81. package/src/subtitles/Subtitles.ts +478 -0
  82. package/src/synthesis/AwsPollyTTS.ts +78 -0
  83. package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
  84. package/src/synthesis/CoquiServerTTS.ts +29 -0
  85. package/src/synthesis/ElevenLabsTTS.ts +104 -0
  86. package/src/synthesis/EspeakTTS.ts +552 -0
  87. package/src/synthesis/FliteTTS.ts +387 -0
  88. package/src/synthesis/GoogleCloudTTS.ts +112 -0
  89. package/src/synthesis/GoogleTranslateTTS.ts +210 -0
  90. package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
  91. package/src/synthesis/SamTTS.ts +30 -0
  92. package/src/synthesis/SapiTTS.ts +222 -0
  93. package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
  94. package/src/synthesis/SvoxPicoTTS.ts +318 -0
  95. package/src/synthesis/VitsTTS.ts +734 -0
  96. package/src/tests/Test.ts +24 -0
  97. package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
  98. package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
  99. package/src/typings/Fillers.d.ts +41 -0
  100. package/src/utilities/BinaryArrayConversion.ts +159 -0
  101. package/src/utilities/Compression.ts +91 -0
  102. package/src/utilities/FileDownloader.ts +201 -0
  103. package/src/utilities/FileSystem.ts +265 -0
  104. package/src/utilities/Hashing.ts +230 -0
  105. package/src/utilities/Locale.ts +119 -0
  106. package/src/utilities/Logger.ts +72 -0
  107. package/src/utilities/NdArrayUtilities.ts +31 -0
  108. package/src/utilities/ObjectUtilities.ts +169 -0
  109. package/src/utilities/OpenPromise.ts +13 -0
  110. package/src/utilities/PackageManager.ts +97 -0
  111. package/src/utilities/Queue.ts +17 -0
  112. package/src/utilities/RandomGenerator.ts +237 -0
  113. package/src/utilities/SignalChannel.ts +22 -0
  114. package/src/utilities/TarballMaker.ts +68 -0
  115. package/src/utilities/Timeline.ts +231 -0
  116. package/src/utilities/Timer.ts +93 -0
  117. package/src/utilities/Utilities.ts +574 -0
  118. package/src/utilities/WasmMemoryManager.ts +516 -0
  119. package/src/utilities/WebReader.ts +55 -0
  120. package/src/utilities/WikipediaReader.ts +41 -0
  121. package/src/voice-activity-detection/SileroVAD.ts +86 -0
  122. package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
@@ -0,0 +1,227 @@
1
+ import { extendDeep } from "../utilities/ObjectUtilities.js"
2
+ import { Logger } from "../utilities/Logger.js"
3
+ import { resampleAudioSpeex } from "./SpeexResampler.js"
4
+ import { computeMelSpectogram } from "./MelSpectogram.js"
5
+ import { RawAudio, powerToDecibels } from "../audio/AudioUtilities.js"
6
+ import { normalizeVectors } from "../math/VectorMath.js"
7
+
8
+ export async function computeMFCCs(monoAudio: RawAudio, options: MfccOptions = {}) {
9
+ const logger = new Logger()
10
+ logger.start("Initialize options")
11
+
12
+ if (monoAudio.audioChannels.length != 1) {
13
+ throw new Error("Audio must be mono")
14
+ }
15
+
16
+ options = extendDefaultMfccOptions(options)
17
+
18
+ const analysisSampleRate = options.analysisSampleRate!
19
+ const featureCount = options.featureCount!
20
+
21
+ const fftOrder = options.fftOrder!
22
+
23
+ const windowDuration = options.windowDuration!
24
+ const windowSize = windowDuration * analysisSampleRate
25
+ const hopDuration = options.hopDuration!
26
+ const hopLength = hopDuration * analysisSampleRate
27
+
28
+ const filterbankCount = options.filterbankCount!
29
+ const lowerFrequencyHz = options.lowerFreq!
30
+ const upperFrequencyHz = options.upperFreq!
31
+
32
+ const emphasisFactor = options.emphasisFactor!
33
+ const lifteringFactor = options.lifteringFactor!
34
+ const zeroFirstCoefficient = options.zeroFirstCoefficient!
35
+
36
+ logger.start(`Resample audio to analysis sample rate (${analysisSampleRate}Hz)`)
37
+ const resampledAudio = await resampleAudioSpeex(monoAudio, analysisSampleRate)
38
+
39
+ let mfccs: number[][]
40
+
41
+ if (emphasisFactor > 0) {
42
+ logger.start("Apply emphasis")
43
+ resampledAudio.audioChannels[0] = applyEmphasis(resampledAudio.audioChannels[0], emphasisFactor)
44
+ }
45
+
46
+ logger.start("Compute Mel spectogram")
47
+ const { melSpectogram } = await computeMelSpectogram(resampledAudio, fftOrder, windowSize, hopLength, filterbankCount, lowerFrequencyHz, upperFrequencyHz)
48
+
49
+ logger.start("Extract MFCCs from Mel spectogram")
50
+ const mfccsFloat32 = melSpectogramToMFCCs(melSpectogram, featureCount)
51
+
52
+ mfccs = mfccsFloat32.map(mfcc => Array.from(mfcc))
53
+
54
+ if (options.normalize!) {
55
+ logger.start("Normalize MFCCs")
56
+
57
+ const { normalizedVectors, mean, stdDeviation } = normalizeVectors(mfccs)
58
+ mfccs = normalizedVectors
59
+ //mfccs = mfccs.map(mfcc => subtractVectors(mfcc, mean))
60
+ }
61
+
62
+ if (lifteringFactor > 0) {
63
+ logger.start("Apply liftering to MFCCs")
64
+ mfccs = applyLiftering(mfccs, lifteringFactor)
65
+ }
66
+
67
+ if (zeroFirstCoefficient) {
68
+ for (const mfcc of mfccs) {
69
+ mfcc[0] = 0
70
+ }
71
+ }
72
+
73
+ logger.end()
74
+
75
+ return mfccs
76
+ }
77
+
78
+ export function melSpectogramToMFCCs(melSpectogram: Float32Array[], mfccFeatureCount: number) {
79
+ const melBandCount = melSpectogram[0].length
80
+ const dctMatrix = createDCTType2CoefficientMatrix(mfccFeatureCount, melBandCount)
81
+
82
+ const mfccs = melSpectogram.map(frame => melSpectrumToMFCC(frame, mfccFeatureCount, dctMatrix))
83
+
84
+ return mfccs
85
+ }
86
+
87
+ export function melSpectrumToMFCC(melSpectrum: Float32Array, mfccFeatureCount: number, dctMatrix: Float32Array[], normalization: "none" | "orthonormal" = "orthonormal") {
88
+ const melBandCount = melSpectrum.length
89
+
90
+ let firstFeatureNormalizationFactor: number
91
+ let nonfirstFeatureNormalizationFactor: number
92
+
93
+ if (normalization == "orthonormal") {
94
+ firstFeatureNormalizationFactor = Math.sqrt(1 / (4 * mfccFeatureCount))
95
+ nonfirstFeatureNormalizationFactor = Math.sqrt(1 / (2 * mfccFeatureCount))
96
+ } else {
97
+ firstFeatureNormalizationFactor = 1
98
+ nonfirstFeatureNormalizationFactor = 1
99
+ }
100
+
101
+ const mfcc = new Float32Array(mfccFeatureCount)
102
+
103
+ for (let mfccFeatureIndex = 0; mfccFeatureIndex < mfccFeatureCount; mfccFeatureIndex++) {
104
+ const dctMatrixRow = dctMatrix[mfccFeatureIndex]
105
+
106
+ let sum = 0
107
+
108
+ for (let j = 0; j < melBandCount; j++) {
109
+ const dctCoefficient = dctMatrixRow[j]
110
+ const logMel = powerToDecibels(melSpectrum[j])
111
+ //const logMel = Math.log(1e-40 + melSpectrum[j])
112
+
113
+ sum += dctCoefficient * logMel
114
+ }
115
+
116
+ const normalizationFactor = mfccFeatureIndex == 0 ? firstFeatureNormalizationFactor : nonfirstFeatureNormalizationFactor
117
+
118
+ //mfcc[mfccFeatureIndex] = normalizationFactor * sum
119
+ mfcc[mfccFeatureIndex] = normalizationFactor * 2 * sum // Sum multiplied by 2 to match with librosa
120
+ }
121
+
122
+ return mfcc
123
+ }
124
+
125
+ export function createDCTType2CoefficientMatrix(mfccFeatureCount: number, melBandCount: number) {
126
+ const dctMatrix = new Array<Float32Array>(mfccFeatureCount)
127
+
128
+ for (let mfccFeatureIndex = 0; mfccFeatureIndex < mfccFeatureCount; mfccFeatureIndex++) {
129
+ const row = new Float32Array(melBandCount)
130
+
131
+ const innerMultiplier = Math.PI * mfccFeatureIndex / melBandCount
132
+
133
+ for (let melBandIndex = 0; melBandIndex < melBandCount; melBandIndex++) {
134
+ row[melBandIndex] = Math.cos(innerMultiplier * (melBandIndex + 0.5))
135
+ }
136
+
137
+ dctMatrix[mfccFeatureIndex] = row
138
+ }
139
+
140
+ return dctMatrix
141
+ }
142
+
143
+ export function mfccBufferToVectors(mfccBuffer: Float64Array, mfccFeatureCount: number) {
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) {
160
+ const processedSamples = new Float32Array(samples.length)
161
+
162
+ processedSamples[0] = samples[0] - (emphasisFactor * initialState)
163
+
164
+ for (let i = 1; i < processedSamples.length; i++) {
165
+ processedSamples[i] = samples[i] - (emphasisFactor * samples[i - 1])
166
+ }
167
+
168
+ return processedSamples
169
+ }
170
+
171
+ export function applyLiftering(mfccs: number[][], lifteringFactor: number) {
172
+ const featureCount = mfccs[0].length
173
+
174
+ const lifterMultipliers = new Float32Array(featureCount)
175
+
176
+ for (let i = 0; i < featureCount; i++) {
177
+ lifterMultipliers[i] = 1 + (lifteringFactor / 2) * Math.sin(Math.PI * (i + 1) / lifteringFactor)
178
+ }
179
+
180
+ const lifteredMfccs: number[][] = []
181
+
182
+ for (const mfcc of mfccs) {
183
+ const lifteredMfcc = new Array(featureCount)
184
+
185
+ for (let i = 0; i < featureCount; i++) {
186
+ lifteredMfcc[i] = mfcc[i] * lifterMultipliers[i]
187
+ }
188
+
189
+ lifteredMfccs.push(lifteredMfcc)
190
+ }
191
+
192
+ return lifteredMfccs
193
+ }
194
+
195
+ export type MfccOptions = {
196
+ filterbankCount?: number
197
+ featureCount?: number
198
+ fftOrder?: number
199
+ lowerFreq?: number
200
+ upperFreq?: number
201
+ windowDuration?: number
202
+ hopDuration?: number
203
+ emphasisFactor?: number
204
+ analysisSampleRate?: number
205
+ lifteringFactor?: number
206
+ normalize?: boolean
207
+ zeroFirstCoefficient?: boolean
208
+ }
209
+
210
+ export const defaultMfccOptions: MfccOptions = {
211
+ filterbankCount: 40,
212
+ featureCount: 13,
213
+ fftOrder: 512,
214
+ lowerFreq: 133.3333,
215
+ upperFreq: 6855.4976,
216
+ windowDuration: 0.025,
217
+ hopDuration: 0.010,
218
+ emphasisFactor: 0.97,
219
+ analysisSampleRate: 16000,
220
+ lifteringFactor: 0,
221
+ normalize: false,
222
+ zeroFirstCoefficient: false,
223
+ }
224
+
225
+ export function extendDefaultMfccOptions(options: MfccOptions) {
226
+ return extendDeep(defaultMfccOptions, options)
227
+ }
@@ -0,0 +1,145 @@
1
+ import { RawAudio } from "../audio/AudioUtilities.js"
2
+ import { Logger } from "../utilities/Logger.js"
3
+ import * as FFT from "./FFT.js"
4
+
5
+ export async function computeMelSpectogram(rawAudio: RawAudio, fftOrder: number, windowSize: number, hopLength: number, filterbankCount: number, lowerFrequencyHz: number, upperFrequencyHz: number) {
6
+ const logger = new Logger()
7
+
8
+ logger.start("Compute mel filterbank")
9
+ const binCount = (fftOrder / 2) + 2
10
+ const nyquistFrequency = rawAudio.sampleRate / 2
11
+ const binFrequencies = FFT.getBinFrequencies(binCount, nyquistFrequency)
12
+
13
+ const lowerFrequencyMel = hertzToMel(lowerFrequencyHz)
14
+ const upperFrequencyMel = hertzToMel(upperFrequencyHz)
15
+
16
+ const filterbanksCenterFrequencies = getMelFilterbanksCenterFrequencies(filterbankCount, lowerFrequencyMel, upperFrequencyMel)
17
+ const melFilterbanks = getMelFilterbanks(binFrequencies, filterbanksCenterFrequencies, lowerFrequencyMel, upperFrequencyMel)
18
+
19
+ logger.end()
20
+
21
+ return computeMelSpectogramUsingFilterbanks(rawAudio, fftOrder, windowSize, hopLength, melFilterbanks)
22
+ }
23
+
24
+ export async function computeMelSpectogramUsingFilterbanks(rawAudio: RawAudio, fftOrder: number, windowSize: number, hopLength: number, filterbanks: Filterbank[]) {
25
+ const logger = new Logger()
26
+
27
+ logger.start("Compute short-time FFTs")
28
+ const audioSamples = rawAudio.audioChannels[0]
29
+ const fftFrames = await FFT.stftr(audioSamples, fftOrder, windowSize, hopLength, "hann")
30
+
31
+ logger.start("Convert FFT frames to a mel spectogram")
32
+ const melSpectogram = fftFramesToMelSpectogram(fftFrames, filterbanks)
33
+
34
+ logger.end()
35
+
36
+ return { melSpectogram, fftFrames }
37
+ }
38
+
39
+ export function fftFramesToMelSpectogram(fftFrames: Float32Array[], melFilterbanks: Filterbank[]) {
40
+ return fftFrames.map(fftFrame => {
41
+ const powerSpectrum = FFT.fftFrameToPowerSpectrum(fftFrame)
42
+ return powerSpectrumToMelSpectrum(powerSpectrum, melFilterbanks)
43
+ })
44
+ }
45
+
46
+ export function powerSpectrumToMelSpectrum(powerSpectrum: Float32Array, filterbanks: Filterbank[]) {
47
+ const filterbankCount = filterbanks.length
48
+ const melSpectrum = new Float32Array(filterbankCount)
49
+
50
+ for (let melBandIndex = 0; melBandIndex < filterbankCount; melBandIndex++) {
51
+ const filterbank = filterbanks[melBandIndex]
52
+ const filterbankStartIndex = filterbank.startIndex
53
+ const filterbankWeights = filterbank.weights
54
+
55
+ if (filterbankStartIndex == -1) {
56
+ continue
57
+ }
58
+
59
+ let bandValue = 0
60
+
61
+ for (let i = 0; i < filterbankWeights.length; i++) {
62
+ bandValue += filterbankWeights[i] * powerSpectrum[filterbankStartIndex + i]
63
+ }
64
+
65
+ melSpectrum[melBandIndex] = bandValue
66
+ }
67
+
68
+ return melSpectrum
69
+ }
70
+
71
+ export function getMelFilterbanks(powerSpectrumFrequenciesHz: Float32Array, centerFrequenciesMel: Float32Array, lowerFrequencyMel: number, upperFrequencyMel: number) {
72
+ const filterbankCount = centerFrequenciesMel.length
73
+ const powerSpectrumFrequenciesMel = powerSpectrumFrequenciesHz.map(frequencyHz => hertzToMel(frequencyHz))
74
+
75
+ const filterbanks: Filterbank[] = []
76
+
77
+ for (let filterbankIndex = 0; filterbankIndex < filterbankCount; filterbankIndex++) {
78
+ const centerFrequency = centerFrequenciesMel[filterbankIndex]
79
+
80
+ const leftFrequency = filterbankIndex > 0 ? centerFrequenciesMel[filterbankIndex - 1] : lowerFrequencyMel
81
+ const rightFrequency = filterbankIndex < filterbankCount - 1 ? centerFrequenciesMel[filterbankIndex + 1] : upperFrequencyMel
82
+
83
+ const width = rightFrequency - leftFrequency
84
+ const halfWidth = width / 2
85
+
86
+ let startIndex = -1
87
+ let weights: number[] = []
88
+
89
+ let weightSum = 0
90
+
91
+ for (let powerSpectrumBandIndex = 0; powerSpectrumBandIndex < powerSpectrumFrequenciesMel.length; powerSpectrumBandIndex++) {
92
+ const powerSpectrumBandFrequencyMel = powerSpectrumFrequenciesMel[powerSpectrumBandIndex]
93
+
94
+ let weight = 0
95
+
96
+ if (powerSpectrumBandFrequencyMel >= leftFrequency && powerSpectrumBandFrequencyMel <= centerFrequency) {
97
+ weight = (powerSpectrumBandFrequencyMel - leftFrequency) / halfWidth
98
+ } else if (powerSpectrumBandFrequencyMel > centerFrequency && powerSpectrumBandFrequencyMel <= rightFrequency) {
99
+ weight = (rightFrequency - powerSpectrumBandFrequencyMel) / halfWidth
100
+ }
101
+
102
+ if (weight > 0) {
103
+ if (startIndex == -1) {
104
+ startIndex = powerSpectrumBandIndex
105
+ }
106
+
107
+ weights.push(weight)
108
+ weightSum += weight
109
+ } else if (startIndex != -1) {
110
+ break
111
+ }
112
+ }
113
+
114
+ weights = weights.map(weight => weight / weightSum)
115
+
116
+ filterbanks.push({ startIndex, weights })
117
+ }
118
+
119
+ return filterbanks
120
+ }
121
+
122
+ export function getMelFilterbanksCenterFrequencies(melBandCount: number, lowerFrequencyMel: number, upperFrequencyMel: number) {
123
+ const stepSizeMel = (upperFrequencyMel - lowerFrequencyMel) / (melBandCount + 1)
124
+
125
+ const centerFrequencies = new Float32Array(melBandCount)
126
+
127
+ for (let i = 0; i < melBandCount; i++) {
128
+ centerFrequencies[i] = lowerFrequencyMel + ((i + 1) * stepSizeMel)
129
+ }
130
+
131
+ return centerFrequencies
132
+ }
133
+
134
+ export function hertzToMel(frequency: number) {
135
+ return 2595.0 * Math.log10(1.0 + (frequency / 700.0))
136
+ }
137
+
138
+ export function melToHertz(mel: number) {
139
+ return 700.0 * (Math.pow(10.0, mel / 2595.0) - 1.0)
140
+ }
141
+
142
+ export type Filterbank = {
143
+ startIndex: number
144
+ weights: number[]
145
+ }
@@ -0,0 +1,249 @@
1
+ import { RawAudio } from "../audio/AudioUtilities.js"
2
+ import { extendDeep } from "../utilities/ObjectUtilities.js"
3
+ import { concatFloat32Arrays } from "../utilities/Utilities.js"
4
+ import { Float32ArrayRef, WasmMemoryManager } from "../utilities/WasmMemoryManager.js"
5
+
6
+ let rubberbandInstance: any
7
+
8
+ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchScale: number, options: RubberbandOptions) {
9
+ options = extendDeep(defaultRubberbandOptions, options)
10
+
11
+ const channels = rawAudio.audioChannels
12
+ const channelCount = channels.length
13
+ const sampleCount = channels[0].length
14
+ const sampleRate = rawAudio.sampleRate
15
+
16
+ const m = await getRubberbandInstance()
17
+ const wasmMemory = new WasmMemoryManager(m)
18
+
19
+ const optionFlags = rubberBandOptionsToFlags(options)
20
+
21
+ const statePtr = m._rubberband_new(sampleRate, channelCount, optionFlags, 1, 1)
22
+ m._rubberband_set_time_ratio(statePtr, 1 / speed)
23
+ m._rubberband_set_pitch_scale(statePtr, pitchScale)
24
+
25
+ const samplesRequired = m._rubberband_get_samples_required(statePtr)
26
+ const bufferSize = Math.min(samplesRequired, sampleCount)
27
+
28
+ const bufferChannelPtrsRef = wasmMemory.allocUint32Array(bufferSize)
29
+ const bufferChannelRefs: Float32ArrayRef[] = []
30
+
31
+ for (let i = 0; i < channelCount; i++) {
32
+ const bufferChannelRef = wasmMemory.allocFloat32Array(bufferSize)
33
+ bufferChannelPtrsRef.view[i] = bufferChannelRef.address
34
+
35
+ bufferChannelRefs.push(bufferChannelRef)
36
+ }
37
+
38
+ m._rubberband_set_expected_input_duration(statePtr, sampleCount)
39
+
40
+ //m._rubberband_set_max_process_size(statePtr, bufferSize)
41
+
42
+ for (let offset = 0; offset < sampleCount; offset += bufferSize) {
43
+ let writtenSize: number
44
+ let isFinal: 0 | 1
45
+
46
+ if (sampleCount - offset > bufferSize) {
47
+ writtenSize = bufferSize
48
+ isFinal = 0
49
+ } else {
50
+ writtenSize = sampleCount - offset
51
+ isFinal = 1
52
+ }
53
+
54
+ for (let i = 0; i < channelCount; i++) {
55
+ const samplesToWrite = channels[i].subarray(offset, offset + writtenSize)
56
+ bufferChannelRefs[i].view.set(samplesToWrite)
57
+ }
58
+
59
+ m._rubberband_study(statePtr, bufferChannelPtrsRef.address, writtenSize, isFinal)
60
+ }
61
+
62
+ const outputAudioChannelChunks: Float32Array[][] = []
63
+
64
+ for (let i = 0; i < channelCount; i++) {
65
+ outputAudioChannelChunks.push([])
66
+ }
67
+
68
+ for (let readOffset = 0; readOffset < sampleCount; readOffset += bufferSize) {
69
+ let writtenSize: number
70
+ let isFinal: 0 | 1
71
+
72
+ if (sampleCount - readOffset > bufferSize) {
73
+ writtenSize = bufferSize
74
+ isFinal = 0
75
+ } else {
76
+ writtenSize = sampleCount - readOffset
77
+ isFinal = 1
78
+ }
79
+
80
+ for (let i = 0; i < channelCount; i++) {
81
+ const samplesToWrite = channels[i].subarray(readOffset, readOffset + writtenSize)
82
+ bufferChannelRefs[i].view.set(samplesToWrite)
83
+ }
84
+
85
+ m._rubberband_process(statePtr, bufferChannelPtrsRef.address, writtenSize, isFinal)
86
+
87
+ while (true) {
88
+ const samplesAvailable = m._rubberband_available(statePtr)
89
+ if (samplesAvailable <= 0) {
90
+ break
91
+ }
92
+
93
+ const sizeToRead = Math.min(samplesAvailable, bufferSize)
94
+
95
+ const readCount = m._rubberband_retrieve(statePtr, bufferChannelPtrsRef.address, sizeToRead)
96
+
97
+ for (let i = 0; i < channelCount; i++) {
98
+ const readSamplesForChannel = bufferChannelRefs[i].view.slice(0, readCount)
99
+ outputAudioChannelChunks[i].push(readSamplesForChannel)
100
+ }
101
+ }
102
+ }
103
+
104
+ m._rubberband_delete(statePtr)
105
+ wasmMemory.freeAll()
106
+
107
+ const outputAudioChannels = outputAudioChannelChunks.map(chunks => concatFloat32Arrays(chunks))
108
+ const outputRawAudio: RawAudio = { audioChannels: outputAudioChannels, sampleRate }
109
+
110
+ return outputRawAudio
111
+ }
112
+
113
+ export async function getRubberbandInstance() {
114
+ if (!rubberbandInstance) {
115
+ const { default: RubberbandInitializer } = await import('@echogarden/rubberband-wasm')
116
+
117
+ rubberbandInstance = await RubberbandInitializer()
118
+ }
119
+
120
+ return rubberbandInstance
121
+ }
122
+
123
+ export function rubberBandOptionsToFlags(options: RubberbandOptions) {
124
+ let flags = 0
125
+
126
+ if (options.stretch == "precise") {
127
+ flags += RubberBandOptionFlag.StretchPrecise
128
+ }
129
+
130
+ if (options.transients == "mixed") {
131
+ flags += RubberBandOptionFlag.TransientsMixed
132
+ } else if (options.transients == "smooth") {
133
+ flags += RubberBandOptionFlag.TransientsSmooth
134
+ }
135
+
136
+ if (options.detector == "percussive") {
137
+ flags += RubberBandOptionFlag.DetectorPercussive
138
+ } else if (options.detector == "soft") {
139
+ flags += RubberBandOptionFlag.DetectorSoft
140
+ }
141
+
142
+ if (options.phase == "independent") {
143
+ flags += RubberBandOptionFlag.PhaseIndependent
144
+ }
145
+
146
+ if (options.window == "short") {
147
+ flags += RubberBandOptionFlag.WindowShort
148
+ } else if (options.window == "long") {
149
+ flags += RubberBandOptionFlag.WindowLong
150
+ }
151
+
152
+ if (options.smoothing == "on") {
153
+ flags += RubberBandOptionFlag.SmoothingOn
154
+ }
155
+
156
+ if (options.formant == "preserved") {
157
+ flags += RubberBandOptionFlag.FormantPreserved
158
+ }
159
+
160
+ if (options.pitch == "high-quality") {
161
+ flags += RubberBandOptionFlag.PitchHighQuality
162
+ } else if (options.pitch == "high-consistency") {
163
+ flags += RubberBandOptionFlag.PitchHighConsistency
164
+ }
165
+
166
+ if (options.channels == "together") {
167
+ flags += RubberBandOptionFlag.ChannelsTogether
168
+ }
169
+
170
+ if (options.engine == "finer") {
171
+ flags += RubberBandOptionFlag.EngineFiner
172
+ }
173
+
174
+ return flags
175
+ }
176
+
177
+ export enum RubberBandOptionFlag {
178
+ ProcessOffline = 0x00000000,
179
+ ProcessRealTime = 0x00000001,
180
+
181
+ StretchElastic = 0x00000000,
182
+ StretchPrecise = 0x00000010,
183
+
184
+ TransientsCrisp = 0x00000000,
185
+ TransientsMixed = 0x00000100,
186
+ TransientsSmooth = 0x00000200,
187
+
188
+ DetectorCompound = 0x00000000,
189
+ DetectorPercussive = 0x00000400,
190
+ DetectorSoft = 0x00000800,
191
+
192
+ PhaseLaminar = 0x00000000,
193
+ PhaseIndependent = 0x00002000,
194
+
195
+ ThreadingAuto = 0x00000000,
196
+ ThreadingNever = 0x00010000,
197
+ ThreadingAlways = 0x00020000,
198
+
199
+ WindowStandard = 0x00000000,
200
+ WindowShort = 0x00100000,
201
+ WindowLong = 0x00200000,
202
+
203
+ SmoothingOff = 0x00000000,
204
+ SmoothingOn = 0x00800000,
205
+
206
+ FormantShifted = 0x00000000,
207
+ FormantPreserved = 0x01000000,
208
+
209
+ PitchHighSpeed = 0x00000000,
210
+ PitchHighQuality = 0x02000000,
211
+ PitchHighConsistency = 0x04000000,
212
+
213
+ ChannelsApart = 0x00000000,
214
+ ChannelsTogether = 0x10000000,
215
+
216
+ EngineFaster = 0x00000000,
217
+ EngineFiner = 0x20000000
218
+ }
219
+
220
+ export enum RubberBandPresetOption {
221
+ DefaultOptions = 0x00000000,
222
+ PercussiveOptions = 0x00102000,
223
+ }
224
+
225
+ export const defaultRubberbandOptions: RubberbandOptions = {
226
+ stretch: "elastic",
227
+ transients: "crisp",
228
+ detector: "compound",
229
+ phase: "laminar",
230
+ window: "standard",
231
+ smoothing: "off",
232
+ formant: "shited",
233
+ pitch: "high-speed",
234
+ channels: "apart",
235
+ engine: "faster"
236
+ }
237
+
238
+ export type RubberbandOptions = {
239
+ stretch?: "elastic" | "precise"
240
+ transients?: "crisp" | "mixed" | "smooth"
241
+ detector?: "compound" | "percussive" | "soft"
242
+ phase?: "laminar" | "independent"
243
+ window?: "standard" | "long" | "short"
244
+ smoothing?: "off" | "on"
245
+ formant?: "shited" | "preserved"
246
+ pitch?: "high-speed" | "high-quality" | "high-consistency"
247
+ channels?: "apart" | "together"
248
+ engine?: "faster" | "finer"
249
+ }
@@ -0,0 +1,59 @@
1
+ import { RawAudio } from "../audio/AudioUtilities.js"
2
+ import { WasmMemoryManager } from "../utilities/WasmMemoryManager.js"
3
+
4
+ let sonicInstance: any
5
+
6
+ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchScale: number) {
7
+ const sampleRate = rawAudio.sampleRate
8
+ const channelCount = rawAudio.audioChannels.length
9
+ const inputSamples = rawAudio.audioChannels[0]
10
+ const inputSampleCount = rawAudio.audioChannels[0].length
11
+
12
+ const m = await getSonicInstance()
13
+ const wasmMemory = new WasmMemoryManager(m)
14
+
15
+ const streamPtr = m._sonicCreateStream(sampleRate, channelCount)
16
+ m._sonicSetSpeed(streamPtr, speed)
17
+ m._sonicSetPitch(streamPtr, pitchScale)
18
+
19
+ const inputSamplesRef = wasmMemory.allocFloat32Array(inputSampleCount)
20
+ inputSamplesRef.view.set(inputSamples)
21
+
22
+ const writeSuccess = m._sonicWriteFloatToStream(streamPtr, inputSamplesRef.address, inputSampleCount)
23
+
24
+ if (writeSuccess != 1) {
25
+ throw new Error("Sonic error: failed write to stream")
26
+ }
27
+
28
+ const flushSuccess = m._sonicFlushStream(streamPtr)
29
+
30
+ if (flushSuccess != 1) {
31
+ throw new Error("Sonic error: failed flushing stream")
32
+ }
33
+
34
+ const samplesAvailable = m._sonicSamplesAvailable(streamPtr)
35
+
36
+ const outputSamplesRef = wasmMemory.allocFloat32Array(samplesAvailable)
37
+
38
+ const samplesRead = m._sonicReadFloatFromStream(streamPtr, outputSamplesRef.address, outputSamplesRef.length)
39
+
40
+ const outputSamples = outputSamplesRef.view.slice(0, samplesRead)
41
+
42
+ const resultAudio: RawAudio = { audioChannels: [outputSamples], sampleRate }
43
+
44
+ m._sonicDestroyStream(streamPtr)
45
+
46
+ wasmMemory.freeAll()
47
+
48
+ return resultAudio
49
+ }
50
+
51
+ async function getSonicInstance() {
52
+ if (!sonicInstance) {
53
+ const { default: SonicInitializer } = await import('@echogarden/sonic-wasm')
54
+
55
+ sonicInstance = await SonicInitializer()
56
+ }
57
+
58
+ return sonicInstance
59
+ }