echogarden 2.7.0 → 2.8.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.
Files changed (110) hide show
  1. package/data/lexicons/heteronyms.en.json +6 -2
  2. package/data/lexicons/words.en.json +379 -19
  3. package/dist/alignment/SemanticTextAlignment.js +6 -7
  4. package/dist/alignment/SemanticTextAlignment.js.map +1 -1
  5. package/dist/api/Alignment.js +2 -2
  6. package/dist/api/Alignment.js.map +1 -1
  7. package/dist/api/Recognition.js +2 -2
  8. package/dist/api/Recognition.js.map +1 -1
  9. package/dist/api/SpeechTranslation.js +2 -2
  10. package/dist/api/SpeechTranslation.js.map +1 -1
  11. package/dist/api/Synthesis.js +7 -7
  12. package/dist/api/Synthesis.js.map +1 -1
  13. package/dist/api/TimelineTranslationAlignment.js +2 -2
  14. package/dist/api/TimelineTranslationAlignment.js.map +1 -1
  15. package/dist/api/TranslationAlignment.js +2 -2
  16. package/dist/api/TranslationAlignment.js.map +1 -1
  17. package/dist/audio/AudioPlayer.js +2 -2
  18. package/dist/audio/AudioPlayer.js.map +1 -1
  19. package/dist/audio/AudioUtilities.d.ts +1 -1
  20. package/dist/cli/CLI.js +4 -4
  21. package/dist/cli/CLI.js.map +1 -1
  22. package/dist/denoising/RNNoise.js +5 -5
  23. package/dist/denoising/RNNoise.js.map +1 -1
  24. package/dist/dsp/FFT.js +13 -19
  25. package/dist/dsp/FFT.js.map +1 -1
  26. package/dist/dsp/Rubberband.js +5 -5
  27. package/dist/dsp/Rubberband.js.map +1 -1
  28. package/dist/dsp/Sonic.js +6 -6
  29. package/dist/dsp/Sonic.js.map +1 -1
  30. package/dist/dsp/SpeexResampler.js +11 -11
  31. package/dist/dsp/SpeexResampler.js.map +1 -1
  32. package/dist/nlp/EspeakPhonemizer.js +1 -1
  33. package/dist/nlp/EspeakPhonemizer.js.map +1 -1
  34. package/dist/nlp/Segmentation.d.ts +13 -10
  35. package/dist/nlp/Segmentation.js +129 -106
  36. package/dist/nlp/Segmentation.js.map +1 -1
  37. package/dist/nlp/TextNormalizer.js +74 -42
  38. package/dist/nlp/TextNormalizer.js.map +1 -1
  39. package/dist/recognition/AmazonTranscribeSTT.js +2 -2
  40. package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
  41. package/dist/recognition/SileroSTT.js +2 -2
  42. package/dist/recognition/SileroSTT.js.map +1 -1
  43. package/dist/recognition/WhisperSTT.js +4 -16
  44. package/dist/recognition/WhisperSTT.js.map +1 -1
  45. package/dist/subtitles/Subtitles.js +52 -7
  46. package/dist/subtitles/Subtitles.js.map +1 -1
  47. package/dist/synthesis/ElevenLabsTTS.js +11 -10
  48. package/dist/synthesis/ElevenLabsTTS.js.map +1 -1
  49. package/dist/synthesis/EspeakTTS.js +8 -8
  50. package/dist/synthesis/EspeakTTS.js.map +1 -1
  51. package/dist/synthesis/KokoroTTS.js +21 -10
  52. package/dist/synthesis/KokoroTTS.js.map +1 -1
  53. package/dist/synthesis/SvoxPicoTTS.js +24 -23
  54. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  55. package/dist/text-translation/DeepLTextTranslation.js +4 -3
  56. package/dist/text-translation/DeepLTextTranslation.js.map +1 -1
  57. package/dist/text-translation/NLLBTextTranslation.js +7 -6
  58. package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
  59. package/dist/utilities/StringUtilities.d.ts +1 -1
  60. package/dist/utilities/StringUtilities.js +5 -5
  61. package/dist/utilities/StringUtilities.js.map +1 -1
  62. package/dist/utilities/Timeline.d.ts +2 -3
  63. package/dist/utilities/Timeline.js +96 -147
  64. package/dist/utilities/Timeline.js.map +1 -1
  65. package/dist/utilities/WikipediaReader.js +3 -3
  66. package/dist/utilities/WikipediaReader.js.map +1 -1
  67. package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
  68. package/dist/voice-activity-detection/WebRtcVAD.js.map +1 -1
  69. package/docs/Tasklist.md +1 -6
  70. package/package.json +13 -11
  71. package/src/alignment/SemanticTextAlignment.ts +6 -7
  72. package/src/api/Alignment.ts +2 -2
  73. package/src/api/Recognition.ts +2 -2
  74. package/src/api/SpeechTranslation.ts +2 -2
  75. package/src/api/Synthesis.ts +7 -7
  76. package/src/api/TimelineTranslationAlignment.ts +2 -2
  77. package/src/api/TranslationAlignment.ts +2 -2
  78. package/src/audio/AudioPlayer.ts +2 -2
  79. package/src/cli/CLI.ts +4 -4
  80. package/src/denoising/RNNoise.ts +5 -5
  81. package/src/dsp/FFT.ts +22 -19
  82. package/src/dsp/Rubberband.ts +5 -5
  83. package/src/dsp/Sonic.ts +6 -6
  84. package/src/dsp/SpeexResampler.ts +11 -11
  85. package/src/nlp/EspeakPhonemizer.ts +1 -1
  86. package/src/nlp/Segmentation.ts +146 -111
  87. package/src/nlp/TextNormalizer.ts +94 -52
  88. package/src/recognition/AmazonTranscribeSTT.ts +2 -2
  89. package/src/recognition/SileroSTT.ts +2 -2
  90. package/src/recognition/WhisperSTT.ts +4 -18
  91. package/src/subtitles/Subtitles.ts +69 -8
  92. package/src/synthesis/ElevenLabsTTS.ts +12 -11
  93. package/src/synthesis/EspeakTTS.ts +9 -8
  94. package/src/synthesis/KokoroTTS.ts +24 -12
  95. package/src/synthesis/SvoxPicoTTS.ts +26 -23
  96. package/src/text-translation/DeepLTextTranslation.ts +4 -3
  97. package/src/text-translation/NLLBTextTranslation.ts +8 -6
  98. package/src/typings/Fillers.d.ts +0 -16
  99. package/src/utilities/StringUtilities.ts +5 -6
  100. package/src/utilities/Timeline.ts +100 -170
  101. package/src/utilities/WikipediaReader.ts +3 -3
  102. package/src/voice-activity-detection/WebRtcVAD.ts +5 -4
  103. package/dist/nlp/CompromiseNLP.d.ts +0 -15
  104. package/dist/nlp/CompromiseNLP.js +0 -79
  105. package/dist/nlp/CompromiseNLP.js.map +0 -1
  106. package/dist/utilities/WasmMemoryManager.d.ts +0 -153
  107. package/dist/utilities/WasmMemoryManager.js +0 -440
  108. package/dist/utilities/WasmMemoryManager.js.map +0 -1
  109. package/src/nlp/CompromiseNLP.ts +0 -119
  110. package/src/utilities/WasmMemoryManager.ts +0 -566
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echogarden",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
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",
@@ -52,27 +52,27 @@
52
52
  "echogarden": "./dist/cli/CLILauncher.js"
53
53
  },
54
54
  "dependencies": {
55
- "@aws-sdk/client-polly": "^3.799.0",
56
- "@aws-sdk/client-transcribe-streaming": "^3.799.0",
57
- "@echogarden/audio-io": "^0.3.0",
55
+ "@aws-sdk/client-polly": "^3.810.0",
56
+ "@aws-sdk/client-transcribe-streaming": "^3.810.0",
57
+ "@echogarden/audio-io": "^0.4.1",
58
58
  "@echogarden/espeak-ng-emscripten": "^0.3.3",
59
59
  "@echogarden/fasttext-wasm": "^0.1.0",
60
60
  "@echogarden/flite-wasi": "^0.1.1",
61
61
  "@echogarden/fvad-wasm": "^0.2.0",
62
62
  "@echogarden/gnuspeech-wasm": "^0.2.1",
63
+ "@echogarden/icu-segmentation-wasm": "^0.2.1",
63
64
  "@echogarden/pffft-wasm": "^0.4.2",
64
65
  "@echogarden/rnnoise-wasm": "^0.2.0",
65
66
  "@echogarden/rubberband-wasm": "^0.2.0",
66
67
  "@echogarden/sonic-wasm": "^0.2.0",
67
68
  "@echogarden/speex-resampler-wasm": "^0.3.0",
68
69
  "@echogarden/svoxpico-wasm": "^0.2.0",
69
- "@echogarden/transformers-nodejs-lite": "^2.17.1-lite.3",
70
- "@echogarden/wave-codec": "^0.3.2",
70
+ "@echogarden/text-segmentation": "^0.3.1",
71
+ "@echogarden/transformers-nodejs-lite": "^2.17.1-lite.4",
72
+ "@echogarden/wave-codec": "^0.3.3",
71
73
  "@mozilla/readability": "^0.6.0",
72
74
  "chalk": "^5.4.1",
73
- "cldr-segmentation": "^2.2.1",
74
75
  "command-exists": "^1.2.9",
75
- "compromise": "^14.14.4",
76
76
  "fs-extra": "^11.3.0",
77
77
  "gaxios": "^6.7.1",
78
78
  "graceful-fs": "^4.2.11",
@@ -84,14 +84,16 @@
84
84
  "kuromoji": "^0.1.2",
85
85
  "microsoft-cognitiveservices-speech-sdk": "^1.43.1",
86
86
  "msgpack-lite": "^0.1.26",
87
- "onnxruntime-node": "^1.21.1",
88
- "openai": "^4.97.0",
87
+ "onnxruntime-node": "^1.22.0",
88
+ "openai": "^4.98.0",
89
+ "regexp-composer": "^0.3.0",
89
90
  "sam-js": "^0.3.1",
90
91
  "strip-ansi": "^7.1.0",
91
92
  "tar": "^7.4.3",
92
93
  "tiktoken": "^1.0.21",
93
94
  "tinyld": "^1.3.4",
94
95
  "wasm-feature-detect": "^1.8.0",
96
+ "wasm-heap-manager": "^0.2.2",
95
97
  "ws": "^8.18.2",
96
98
  "wtf_wikipedia": "^10.4.0"
97
99
  },
@@ -112,7 +114,7 @@
112
114
  "@types/graceful-fs": "^4.1.9",
113
115
  "@types/jsdom": "^21.1.7",
114
116
  "@types/msgpack-lite": "^0.1.11",
115
- "@types/node": "^22.15.3",
117
+ "@types/node": "^22.15.18",
116
118
  "@types/tar": "^6.1.13",
117
119
  "@types/ws": "^8.18.1",
118
120
  "ts-json-schema-generator": "^2.4.0",
@@ -3,7 +3,7 @@ import { Logger } from '../utilities/Logger.js'
3
3
  import { loadPackage } from '../utilities/PackageManager.js'
4
4
  import { alignDTWWindowed } from './DTWSequenceAlignmentWindowed.js'
5
5
  import { cosineDistance } from '../math/VectorMath.js'
6
- import { isPunctuation, isWord, splitToSentences, splitToWords } from '../nlp/Segmentation.js'
6
+ import { includesPunctuation, isWord, parseText } from '../nlp/Segmentation.js'
7
7
  import { Timeline, extractEntries } from '../utilities/Timeline.js'
8
8
 
9
9
  export async function alignTimelineToTextSemantically(timeline: Timeline, text: string, textLangCode: string) {
@@ -26,13 +26,12 @@ export async function alignTimelineToTextSemantically(timeline: Timeline, text:
26
26
 
27
27
  const timelineWordEntriesFiltered = timelineWordEntryGroups.flat()
28
28
 
29
- const textSentences = splitToSentences(text, textLangCode)
29
+ const segmentedText = await parseText(text, textLangCode)
30
30
 
31
31
  const textWordGroups: string[][] = []
32
32
 
33
- for (const sentenceText of textSentences) {
34
- let wordGroup = await splitToWords(sentenceText, textLangCode)
35
- wordGroup = wordGroup.filter(word => isWord(word))
33
+ for (const sentenceEntry of segmentedText.sentences) {
34
+ const wordGroup = sentenceEntry.words.nonPunctuationWords
36
35
 
37
36
  textWordGroups.push(wordGroup)
38
37
  }
@@ -197,8 +196,8 @@ export async function alignWordsToWordsSemantically(wordsGroups1: string[][], wo
197
196
 
198
197
  // Align
199
198
  function costFunction(a: TokenEmbeddingData, b: TokenEmbeddingData) {
200
- const aIsPunctuation = isPunctuation(a.text)
201
- const bIsPunctuation = isPunctuation(b.text)
199
+ const aIsPunctuation = includesPunctuation(a.text)
200
+ const bIsPunctuation = includesPunctuation(b.text)
202
201
 
203
202
  if (aIsPunctuation === bIsPunctuation) {
204
203
  return cosineDistance(a.embeddingVector, b.embeddingVector)
@@ -5,7 +5,7 @@ import { AudioSourceParam, RawAudio, ensureRawAudio, getRawAudioDuration, normal
5
5
  import { Logger } from '../utilities/Logger.js'
6
6
 
7
7
  import * as API from './API.js'
8
- import { Timeline, addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
8
+ import { Timeline, addTimeOffsetToTimeline, addWordTextOffsetsToTimelineInPlace, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
9
9
  import { formatLanguageCodeWithName, getDefaultDialectForLanguageCodeIfPossible, getShortLanguageCode, parseLangIdentifier } from '../utilities/Locale.js'
10
10
  import { type WhisperAlignmentOptions } from '../recognition/WhisperSTT.js'
11
11
  import chalk from 'chalk'
@@ -329,7 +329,7 @@ export async function align(input: AudioSourceParam, transcript: string, options
329
329
  }
330
330
 
331
331
  // Add text offsets
332
- addWordTextOffsetsToTimeline(mappedTimeline, transcript)
332
+ addWordTextOffsetsToTimelineInPlace(mappedTimeline, transcript)
333
333
 
334
334
  // Make segment timeline
335
335
  const { segmentTimeline } = await wordTimelineToSegmentSentenceTimeline(mappedTimeline, transcript, language, options.plainText?.paragraphBreaks, options.plainText?.whitespace)
@@ -5,7 +5,7 @@ import { AudioSourceParam, RawAudio, ensureRawAudio, normalizeAudioLevelInPlace,
5
5
  import { Logger } from '../utilities/Logger.js'
6
6
 
7
7
  import * as API from './API.js'
8
- import { Timeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
8
+ import { Timeline, addWordTextOffsetsToTimelineInPlace, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
9
9
  import { formatLanguageCodeWithName, parseLangIdentifier } from '../utilities/Locale.js'
10
10
  import { loadPackage } from '../utilities/PackageManager.js'
11
11
  import chalk from 'chalk'
@@ -312,7 +312,7 @@ export async function recognize(input: AudioSourceParam, options: RecognitionOpt
312
312
  }
313
313
 
314
314
  // Add text offsets
315
- addWordTextOffsetsToTimeline(timeline, transcript)
315
+ addWordTextOffsetsToTimelineInPlace(timeline, transcript)
316
316
 
317
317
  // Make segment timeline
318
318
  const { segmentTimeline } = await wordTimelineToSegmentSentenceTimeline(timeline, transcript, languageCode, 'single', 'preserve')
@@ -4,7 +4,7 @@ import { logToStderr } from '../utilities/Utilities.js'
4
4
  import { AudioSourceParam, RawAudio, ensureRawAudio, normalizeAudioLevelInPlace, trimAudioEnd } from '../audio/AudioUtilities.js'
5
5
  import { Logger } from '../utilities/Logger.js'
6
6
 
7
- import { Timeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
7
+ import { Timeline, addWordTextOffsetsToTimelineInPlace, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
8
8
  import { type WhisperOptions } from '../recognition/WhisperSTT.js'
9
9
  import { formatLanguageCodeWithName, getShortLanguageCode, normalizeIdentifierToLanguageCode, parseLangIdentifier } from '../utilities/Locale.js'
10
10
  import { EngineMetadata } from './Common.js'
@@ -201,7 +201,7 @@ export async function translateSpeech(input: AudioSourceParam, options: SpeechTr
201
201
  }
202
202
 
203
203
  if (wordTimeline) {
204
- addWordTextOffsetsToTimeline(wordTimeline, transcript)
204
+ addWordTextOffsetsToTimelineInPlace(wordTimeline, transcript)
205
205
  }
206
206
 
207
207
  if (!segmentTimeline) {
@@ -6,7 +6,7 @@ import { clip, sha256AsHex, stringifyAndFormatJson, logToStderr, yieldToEventLoo
6
6
  import { RawAudio, concatAudioSegments, downmixToMono, encodeRawAudioToWave, getSamplePeakDecibels, getEmptyRawAudio, getRawAudioDuration, trimAudioEnd, trimAudioStart, attenuateIfClippingInPlace, normalizeAudioLevelInPlace } from '../audio/AudioUtilities.js'
7
7
  import { Logger } from '../utilities/Logger.js'
8
8
 
9
- import { isWordOrSymbolWord, splitToParagraphs, splitToSentences } from '../nlp/Segmentation.js'
9
+ import { isWordOrSymbolWord, parseText, splitToParagraphs } from '../nlp/Segmentation.js'
10
10
  import { type RubberbandOptions } from '../dsp/Rubberband.js'
11
11
  import { loadLexiconsForLanguage } from '../nlp/Lexicon.js'
12
12
 
@@ -116,9 +116,9 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
116
116
  let timeOffset = 0
117
117
 
118
118
  for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
119
- const segmentText = segments[segmentIndex].trim()
119
+ const segmentText = segments[segmentIndex]
120
120
 
121
- logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}: '${segmentText}'`)
121
+ logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}: '${segmentText.trim()}'`)
122
122
 
123
123
  const segmentStartTime = timeOffset
124
124
 
@@ -132,9 +132,9 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
132
132
 
133
133
  let sentences: string[]
134
134
 
135
- if ((options.splitToSentences || options.engine == 'vits') && !options.ssml) {
136
- sentences = splitToSentences(segmentText, options.language!)
137
- sentences = sentences.filter(sentence => sentence.trim() != '')
135
+ if ((options.splitToSentences || options.engine === 'vits' || options.engine === 'kokoro') && !options.ssml) {
136
+ const parsedText = await parseText(segmentText, options.language!)
137
+ sentences = parsedText.sentences.map(sentenceEntry => sentenceEntry.text)
138
138
 
139
139
  if (sentences.length == 0) {
140
140
  sentences = ['']
@@ -156,7 +156,7 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
156
156
 
157
157
  const sentenceText = sentences[sentenceIndex].trim()
158
158
 
159
- logger.log(`\n${chalk.magentaBright(`Synthesizing sentence ${sentenceIndex + 1}/${sentences.length}`)}: "${sentenceText}"`)
159
+ logger.log(`\n${chalk.magentaBright(`Synthesizing sentence ${sentenceIndex + 1}/${sentences.length}`)}: "${sentenceText.trim()}"`)
160
160
 
161
161
  const sentenceStartTime = timeOffset
162
162
 
@@ -4,7 +4,7 @@ import { SubtitlesConfig } from '../subtitles/Subtitles.js'
4
4
  import { formatLanguageCodeWithName, getShortLanguageCode, parseLangIdentifier } from '../utilities/Locale.js'
5
5
  import { Logger } from '../utilities/Logger.js'
6
6
  import { extendDeep } from '../utilities/ObjectUtilities.js'
7
- import { Timeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
7
+ import { Timeline, addWordTextOffsetsToTimelineInPlace, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
8
8
  import * as API from './API.js'
9
9
 
10
10
  export async function alignTimelineTranslation(inputTimeline: Timeline, translatedTranscript: string, options: TimelineTranslationAlignmentOptions): Promise<TimelineTranslationAlignmentResult> {
@@ -95,7 +95,7 @@ export async function alignTimelineTranslation(inputTimeline: Timeline, translat
95
95
 
96
96
  logger.start(`Postprocess timeline`)
97
97
 
98
- addWordTextOffsetsToTimeline(mappedWordTimeline, translatedTranscript)
98
+ addWordTextOffsetsToTimelineInPlace(mappedWordTimeline, translatedTranscript)
99
99
 
100
100
  const { segmentTimeline: mappedTimeline } = await wordTimelineToSegmentSentenceTimeline(mappedWordTimeline, translatedTranscript, targetLanguage)
101
101
 
@@ -5,7 +5,7 @@ import { AudioSourceParam, RawAudio, ensureRawAudio, normalizeAudioLevelInPlace,
5
5
  import { Logger } from '../utilities/Logger.js'
6
6
 
7
7
  import * as API from './API.js'
8
- import { Timeline, addWordTextOffsetsToTimeline, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
8
+ import { Timeline, addWordTextOffsetsToTimelineInPlace, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
9
9
  import { formatLanguageCodeWithName, getShortLanguageCode, normalizeIdentifierToLanguageCode, parseLangIdentifier } from '../utilities/Locale.js'
10
10
  import { type WhisperAlignmentOptions } from '../recognition/WhisperSTT.js'
11
11
  import chalk from 'chalk'
@@ -124,7 +124,7 @@ export async function alignTranslation(input: AudioSourceParam, translatedTransc
124
124
  }
125
125
 
126
126
  // Add text offsets
127
- addWordTextOffsetsToTimeline(mappedTimeline, translatedTranscript)
127
+ addWordTextOffsetsToTimelineInPlace(mappedTimeline, translatedTranscript)
128
128
 
129
129
  // Make segment timeline
130
130
  const { segmentTimeline } = await wordTimelineToSegmentSentenceTimeline(mappedTimeline, translatedTranscript, sourceLanguage, options.plainText?.paragraphBreaks, options.plainText?.whitespace)
@@ -9,7 +9,7 @@ import { Timer } from '../utilities/Timer.js'
9
9
  import { clip, getRandomHexString, waitTimeout, writeToStderr } from '../utilities/Utilities.js'
10
10
  import { encodeToAudioBuffer, float32ToInt16Pcm, interleaveChannels } from './AudioBufferConversion.js'
11
11
  import { OpenPromise } from '../utilities/OpenPromise.js'
12
- import { Timeline, addWordTextOffsetsToTimeline } from '../utilities/Timeline.js'
12
+ import { Timeline, addWordTextOffsetsToTimelineInPlace } from '../utilities/Timeline.js'
13
13
  import { readAndParseJsonFile, readFileAsUtf8, remove, writeFile } from '../utilities/FileSystem.js'
14
14
  import { tryResolvingSoxPath } from './SoxPath.js'
15
15
  import { SignalChannel } from '../utilities/SignalChannel.js'
@@ -38,7 +38,7 @@ export async function playAudioWithWordTimeline(rawAudio: RawAudio, wordTimeline
38
38
 
39
39
  wordTimeline = deepClone(wordTimeline)
40
40
 
41
- addWordTextOffsetsToTimeline(wordTimeline, transcript)
41
+ addWordTextOffsetsToTimelineInPlace(wordTimeline, transcript)
42
42
 
43
43
  let timelineEntryIndex = 0
44
44
  let transcriptOffset = 0
package/src/cli/CLI.ts CHANGED
@@ -10,10 +10,10 @@ import { SubtitlesConfig, subtitlesToText, timelineToSubtitles } from '../subtit
10
10
  import { Logger, resetActiveLogger } from '../utilities/Logger.js'
11
11
  import { isMainThread, parentPort } from 'node:worker_threads'
12
12
  import { encodeFromChannels, getDefaultFFMpegOptionsForSpeech } from '../codecs/FFMpegTranscoder.js'
13
- import { splitToParagraphs, splitToWords, wordCharacterPattern } from '../nlp/Segmentation.js'
13
+ import { splitToParagraphs, splitToWords } from '../nlp/Segmentation.js'
14
14
  import { playAudioSamplesWithKeyboardControls, playAudioWithWordTimeline } from '../audio/AudioPlayer.js'
15
15
  import { extendDeep } from '../utilities/ObjectUtilities.js'
16
- import { Timeline, TimelineEntry, addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, roundTimelineProperties } from '../utilities/Timeline.js'
16
+ import { Timeline, TimelineEntry, addTimeOffsetToTimeline, addWordTextOffsetsToTimelineInPlace, roundTimelineProperties } from '../utilities/Timeline.js'
17
17
  import { ensureDir, existsSync, readAndParseJsonFile, readdir, readFileAsUtf8, writeFileSafe } from '../utilities/FileSystem.js'
18
18
  import { formatLanguageCodeWithName, getShortLanguageCode } from '../utilities/Locale.js'
19
19
  import { APIOptions } from '../api/APIOptions.js'
@@ -509,7 +509,7 @@ export async function speak(operationData: CLIOperationData) {
509
509
  const { audio: synthesizedAudio, timeline } = await API.synthesize(textSegments, options, onSegment, undefined)
510
510
 
511
511
  if (plainText) {
512
- addWordTextOffsetsToTimeline(timeline, plainText)
512
+ addWordTextOffsetsToTimelineInPlace(timeline, plainText)
513
513
  }
514
514
 
515
515
  if (outputFilenames.length > 0) {
@@ -1884,7 +1884,7 @@ async function checkOutputFilenames(outputFilenames: string[], acceptMediaOutput
1884
1884
  async function writeOutputFilesForSegment(outputFilenames: string[], index: number, total: number, audio: RawAudio, timeline: Timeline, text: string, language: string, allowOverwrite: boolean) {
1885
1885
  const digitCount = Math.max((total + 1).toString().length, 2)
1886
1886
 
1887
- const segmentWords = (await splitToWords(text, language)).filter(text => wordCharacterPattern.test(text))
1887
+ const segmentWords = (await splitToWords(text, language)).nonPunctuationWords
1888
1888
 
1889
1889
  const segmentJoinedWords = segmentWords.join(' ').trim()
1890
1890
 
@@ -1,6 +1,6 @@
1
1
  import { float32ToInt16Pcm } from '../audio/AudioBufferConversion.js'
2
2
  import { concatFloat32Arrays } from '../utilities/Utilities.js'
3
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
3
+ import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
4
4
  import { Logger } from '../utilities/Logger.js'
5
5
  import { RawAudio, cloneRawAudio } from '../audio/AudioUtilities.js'
6
6
 
@@ -24,15 +24,15 @@ export async function denoiseAudio(rawAudio: RawAudio) {
24
24
  const m = await getRnnoiseInstance()
25
25
 
26
26
  logger.start('Process with RNNoise')
27
- const wasmMemory = new WasmMemoryManager(m)
27
+ const wasmHeap = wrapEmscriptenModuleHeap(m)
28
28
 
29
29
  const stateSize = m._rnnoise_get_size()
30
30
  const frameSize = m._rnnoise_get_frame_size()
31
31
 
32
32
  const denoiseState = m._rnnoise_create(0)
33
33
 
34
- const inputRef = wasmMemory.allocFloat32Array(frameSize)
35
- const outputRef = wasmMemory.allocFloat32Array(frameSize)
34
+ const inputRef = wasmHeap.allocFloat32Array(frameSize)
35
+ const outputRef = wasmHeap.allocFloat32Array(frameSize)
36
36
 
37
37
  const floatSamples = rawAudio.audioChannels[0]
38
38
  const int16Samples = float32ToInt16Pcm(floatSamples)
@@ -70,7 +70,7 @@ export async function denoiseAudio(rawAudio: RawAudio) {
70
70
  outputNewFrame(outputRef.view.slice(), lastFrameVadProbability)
71
71
 
72
72
  m._rnnoise_destroy(denoiseState)
73
- wasmMemory.freeAll()
73
+ wasmHeap.freeAll()
74
74
 
75
75
  const int16DenoisedSamplesAsFloats = concatFloat32Arrays(processedFrames)
76
76
 
package/src/dsp/FFT.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ComplexNumber } from '../math/VectorMath.js'
2
2
  import { concatFloat32Arrays, isWasmSimdSupported } from '../utilities/Utilities.js'
3
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
3
+ import { createWasmHeapManager } from 'wasm-heap-manager'
4
4
 
5
5
  // Compute short-term Fourier transform (real-valued)
6
6
  export async function stftr(samples: Float32Array, fftOrder: number, windowSize: number, hopSize: number, windowType: WindowType) {
@@ -15,7 +15,7 @@ export async function stftr(samples: Float32Array, fftOrder: number, windowSize:
15
15
 
16
16
  // Incrementally generate short-term Fourier transform frames (real-valued)
17
17
  export async function* stftrGenerator(samples: Float32Array, fftOrder: number, windowSize: number, hopSize: number, windowType: WindowType) {
18
- if (fftOrder % 2 != 0 || windowSize % 2 != 0) {
18
+ if (fftOrder % 2 !== 0 || windowSize % 2 !== 0) {
19
19
  throw new Error('FFT order and window size must be multiples of 2')
20
20
  }
21
21
 
@@ -35,17 +35,19 @@ export async function* stftrGenerator(samples: Float32Array, fftOrder: number, w
35
35
  const windowWeights = getWindowWeights(windowType, windowSize)
36
36
 
37
37
  const m = await getPFFFTInstance(await isPffftSimdSupportedForFFTOrder(fftOrder))
38
- const wasmMemory = new WasmMemoryManager(m, {
39
- wasmAlloc: m._pffft_aligned_malloc,
40
- wasmFree: m._pffft_aligned_free
41
- })
38
+
39
+ const wasmHeap = createWasmHeapManager(
40
+ () => m.HEAPU8.buffer,
41
+ m._pffft_aligned_malloc,
42
+ m._pffft_aligned_free,
43
+ )
42
44
 
43
45
  const statePtr = m._pffft_new_setup(fftOrder, 0)
44
46
 
45
47
  const sampleCount = samples.length
46
- const frameBufferRef = wasmMemory.allocFloat32Array(fftOrder)
47
- const binsBufferRef = wasmMemory.allocFloat32Array(fftOrder * 2)
48
- const workBufferRef = wasmMemory.allocFloat32Array(fftOrder * 2)
48
+ const frameBufferRef = wasmHeap.allocFloat32Array(fftOrder)
49
+ const binsBufferRef = wasmHeap.allocFloat32Array(fftOrder * 2)
50
+ const workBufferRef = wasmHeap.allocFloat32Array(fftOrder * 2)
49
51
 
50
52
  for (let offset = 0; offset < sampleCount; offset += hopSize) {
51
53
  const windowSamples = samples.subarray(offset, offset + windowSize)
@@ -68,12 +70,12 @@ export async function* stftrGenerator(samples: Float32Array, fftOrder: number, w
68
70
 
69
71
  m._pffft_destroy_setup(statePtr)
70
72
 
71
- wasmMemory.freeAll()
73
+ wasmHeap.freeAll()
72
74
  }
73
75
 
74
76
  // Compute short-term inverse Fourier transform (real-valued)
75
77
  export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, windowSize: number, hopSize: number, windowType: WindowType, expectedOutputLength?: number) {
76
- if (fftOrder % 2 != 0 || windowSize % 2 != 0) {
78
+ if (fftOrder % 2 !== 0 || windowSize % 2 !== 0) {
77
79
  throw new Error('FFT order and window size must multiples of 2')
78
80
  }
79
81
 
@@ -101,16 +103,17 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
101
103
 
102
104
  const m = await getPFFFTInstance(await isPffftSimdSupportedForFFTOrder(fftOrder))
103
105
 
104
- const wasmMemory = new WasmMemoryManager(m, {
105
- wasmAlloc: m._pffft_aligned_malloc,
106
- wasmFree: m._pffft_aligned_free
107
- })
106
+ const wasmHeap = createWasmHeapManager(
107
+ () => m.HEAPU8.buffer,
108
+ m._pffft_aligned_malloc,
109
+ m._pffft_aligned_free,
110
+ )
108
111
 
109
112
  const statePtr = m._pffft_new_setup(fftOrder, 0)
110
113
 
111
- const frameBufferRef = wasmMemory.allocFloat32Array(fftOrder)
112
- const binsRef = wasmMemory.allocFloat32Array(fftOrder * 2)
113
- const workBufferRef = wasmMemory.allocFloat32Array(fftOrder * 2)
114
+ const frameBufferRef = wasmHeap.allocFloat32Array(fftOrder)
115
+ const binsRef = wasmHeap.allocFloat32Array(fftOrder * 2)
116
+ const workBufferRef = wasmHeap.allocFloat32Array(fftOrder * 2)
114
117
 
115
118
  const sumOfSquaredWeightsForSample = new Float32Array(outSampleCount)
116
119
 
@@ -140,7 +143,7 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
140
143
  }
141
144
 
142
145
  m._pffft_destroy_setup(statePtr)
143
- wasmMemory.freeAll()
146
+ wasmHeap.freeAll()
144
147
 
145
148
  // Divide each output sample by the sum of squared weights
146
149
  for (let i = 0; i < outSamples.length; i++) {
@@ -1,7 +1,7 @@
1
1
  import { RawAudio } from '../audio/AudioUtilities.js'
2
2
  import { extendDeep } from '../utilities/ObjectUtilities.js'
3
3
  import { concatFloat32Arrays } from '../utilities/Utilities.js'
4
- import { Float32ArrayRef, WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
4
+ import { Float32ArrayRef, wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
5
5
 
6
6
  let rubberbandInstance: any
7
7
 
@@ -14,7 +14,7 @@ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchS
14
14
  const sampleRate = rawAudio.sampleRate
15
15
 
16
16
  const m = await getRubberbandInstance()
17
- const wasmMemory = new WasmMemoryManager(m)
17
+ const wasmHeap = wrapEmscriptenModuleHeap(m)
18
18
 
19
19
  const optionFlags = rubberBandOptionsToFlags(options)
20
20
 
@@ -25,11 +25,11 @@ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchS
25
25
  const samplesRequired = m._rubberband_get_samples_required(statePtr)
26
26
  const bufferSize = Math.min(samplesRequired, sampleCount)
27
27
 
28
- const bufferChannelPtrsRef = wasmMemory.allocUint32Array(bufferSize)
28
+ const bufferChannelPtrsRef = wasmHeap.allocUint32Array(bufferSize)
29
29
  const bufferChannelRefs: Float32ArrayRef[] = []
30
30
 
31
31
  for (let i = 0; i < channelCount; i++) {
32
- const bufferChannelRef = wasmMemory.allocFloat32Array(bufferSize)
32
+ const bufferChannelRef = wasmHeap.allocFloat32Array(bufferSize)
33
33
  bufferChannelPtrsRef.view[i] = bufferChannelRef.address
34
34
 
35
35
  bufferChannelRefs.push(bufferChannelRef)
@@ -102,7 +102,7 @@ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchS
102
102
  }
103
103
 
104
104
  m._rubberband_delete(statePtr)
105
- wasmMemory.freeAll()
105
+ wasmHeap.freeAll()
106
106
 
107
107
  const outputAudioChannels = outputAudioChannelChunks.map(chunks => concatFloat32Arrays(chunks))
108
108
  const outputRawAudio: RawAudio = { audioChannels: outputAudioChannels, sampleRate }
package/src/dsp/Sonic.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RawAudio } from '../audio/AudioUtilities.js'
2
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
2
+ import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
3
3
 
4
4
  let sonicInstance: any
5
5
 
@@ -10,13 +10,13 @@ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchS
10
10
  const inputSampleCount = rawAudio.audioChannels[0].length
11
11
 
12
12
  const m = await getSonicInstance()
13
- const wasmMemory = new WasmMemoryManager(m)
13
+ const wasmHeap = wrapEmscriptenModuleHeap(m)
14
14
 
15
15
  const streamPtr = m._sonicCreateStream(sampleRate, channelCount)
16
16
  m._sonicSetSpeed(streamPtr, speed)
17
17
  m._sonicSetPitch(streamPtr, pitchScale)
18
18
 
19
- const inputSamplesRef = wasmMemory.allocFloat32Array(inputSampleCount)
19
+ const inputSamplesRef = wasmHeap.allocFloat32Array(inputSampleCount)
20
20
  inputSamplesRef.view.set(inputSamples)
21
21
 
22
22
  const writeSuccess = m._sonicWriteFloatToStream(streamPtr, inputSamplesRef.address, inputSampleCount)
@@ -33,9 +33,9 @@ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchS
33
33
 
34
34
  const samplesAvailable = m._sonicSamplesAvailable(streamPtr)
35
35
 
36
- const outputSamplesRef = wasmMemory.allocFloat32Array(samplesAvailable)
36
+ const outputSamplesRef = wasmHeap.allocFloat32Array(samplesAvailable)
37
37
 
38
- const samplesRead = m._sonicReadFloatFromStream(streamPtr, outputSamplesRef.address, outputSamplesRef.length)
38
+ const samplesRead = m._sonicReadFloatFromStream(streamPtr, outputSamplesRef.address, outputSamplesRef.allocatedByteCount)
39
39
 
40
40
  const outputSamples = outputSamplesRef.view.slice(0, samplesRead)
41
41
 
@@ -43,7 +43,7 @@ export async function stretchTimePitch(rawAudio: RawAudio, speed: number, pitchS
43
43
 
44
44
  m._sonicDestroyStream(streamPtr)
45
45
 
46
- wasmMemory.freeAll()
46
+ wasmHeap.freeAll()
47
47
 
48
48
  return resultAudio
49
49
  }
@@ -1,6 +1,6 @@
1
1
  import { RawAudio, cloneRawAudio } from '../audio/AudioUtilities.js'
2
2
  import { concatFloat32Arrays, isWasmSimdSupported } from '../utilities/Utilities.js'
3
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
3
+ import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
4
4
 
5
5
  let speexResamplerInstance: any
6
6
 
@@ -23,17 +23,17 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
23
23
  }
24
24
 
25
25
  const m = await getSpeexResamplerInstance()
26
- const wasmMemory = new WasmMemoryManager(m)
26
+ const wasmHeap = wrapEmscriptenModuleHeap(m)
27
27
 
28
28
  function speexResultCodeToString(resultCode: number) {
29
29
  const errorStrPtr = m._speex_resampler_strerror(resultCode)
30
- const errorStrRef = wasmMemory.wrapUint8Array(errorStrPtr, 1024)
31
- const message = errorStrRef.readAsNullTerminatedUtf8String()
30
+ const messageRef = wasmHeap.wrapNullTerminatedUtf8String(errorStrPtr)
31
+ const message = messageRef.value
32
32
 
33
33
  return message
34
34
  }
35
35
 
36
- const initErrRef = wasmMemory.allocInt32()
36
+ const initErrRef = wasmHeap.allocInt32()
37
37
  const resamplerStateAddress = m._speex_resampler_init(channelCount, inSampleRate, outSampleRate, quality, initErrRef.address)
38
38
  let resultCode = initErrRef.value
39
39
 
@@ -46,11 +46,11 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
46
46
 
47
47
  const maxChunkSize = 2 ** 20
48
48
 
49
- const inputChunkSampleCountRef = wasmMemory.allocInt32()
50
- const outputChunkSampleCountRef = wasmMemory.allocInt32()
49
+ const inputChunkSampleCountRef = wasmHeap.allocInt32()
50
+ const outputChunkSampleCountRef = wasmHeap.allocInt32()
51
51
 
52
- const inputChunkSamplesRef = wasmMemory.allocFloat32Array(maxChunkSize * 2)
53
- const outputChunkSamplesRef = wasmMemory.allocFloat32Array(Math.floor(maxChunkSize * sampleRateRatio) * 2)
52
+ const inputChunkSamplesRef = wasmHeap.allocFloat32Array(maxChunkSize * 2)
53
+ const outputChunkSamplesRef = wasmHeap.allocFloat32Array(Math.floor(maxChunkSize * sampleRateRatio) * 2)
54
54
 
55
55
  const resampledAudioChunksForChannels: Float32Array[][] = []
56
56
 
@@ -65,7 +65,7 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
65
65
  const inputPaddingSize = isLastChunk ? inputLatency : 0
66
66
  const maxSamplesToRead = Math.min(maxChunkSize, totalSampleCount - readOffset) + inputPaddingSize
67
67
 
68
- const maxSamplesToWrite = outputChunkSamplesRef.length
68
+ const maxSamplesToWrite = outputChunkSamplesRef.elementCount
69
69
 
70
70
  const inputChunkSamplesForChannel = rawAudio.audioChannels[channelIndex].slice(readOffset, readOffset + maxSamplesToRead)
71
71
 
@@ -91,7 +91,7 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
91
91
  }
92
92
 
93
93
  m._speex_resampler_destroy(resamplerStateAddress)
94
- wasmMemory.freeAll()
94
+ wasmHeap.freeAll()
95
95
 
96
96
  const resampledAudio: RawAudio = {
97
97
  audioChannels: [],
@@ -57,7 +57,7 @@ export async function phonemizeText(text: string, voice: string, substitutionMap
57
57
  .replaceAll('«', ', ')
58
58
  .replaceAll('»', ', ')
59
59
 
60
- const segmentedText = await Segmentation.parse(text, voice)
60
+ const segmentedText = await Segmentation.parseTextAndConvertToFragmentObjects(text, voice)
61
61
  const preparedPhrases: string[] = []
62
62
  const phraseBreakers: string[] = []
63
63