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
@@ -6,7 +6,8 @@ import { charactersToWriteAhead } from '../audio/AudioPlayer.js'
6
6
  import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
7
7
  import { readFileAsUtf8 } from '../utilities/FileSystem.js'
8
8
  import { deepClone } from '../utilities/ObjectUtilities.js'
9
- import { formatHMS, formatMS, startsWithAnyOf } from '../utilities/StringUtilities.js'
9
+ import { formatHMS, formatMS } from '../utilities/StringUtilities.js'
10
+ import { anyOf, buildRegExp, capture, digit, inputStart, matches, notUnicodeProperty, oneOrMore, zeroOrMore } from 'regexp-composer'
10
11
 
11
12
  export async function subtitlesFileToText(filename: string) {
12
13
  return subtitlesToText(await readFileAsUtf8(filename))
@@ -257,14 +258,12 @@ function getCuesFromTimeline_IsolateSegmentSentence(timeline: Timeline, config:
257
258
  const lineLengthWithNextWord = nextWordExtendedEndOffset - lineStartOffset
258
259
  const wordsRemaining = wordTimeline.length - wordIndex - 1
259
260
 
260
- const phraseSeparators = [',', ',', '、', ';', ':', '),', '",', '”,', '.', '".', '”.', '."', '.”', '。']
261
-
262
261
  const lineLengthWithNextWordExceedsMaxLineWidth = lineLengthWithNextWord >= maxLineWidth
263
262
  const lineLengthExceedsHalfMaxLineWidth = lineLength >= maxLineWidth / 2
264
263
 
265
264
  const wordsRemainingAreEqualOrLessToMinimumWordsInLine = wordsRemaining <= config.minWordsInLine!
266
265
  const remainingTextExceedsMaxLineWidth = entryText.length - lineStartOffset > maxLineWidth
267
- const followingSubstringIsPhraseSeparator = startsWithAnyOf(entryText.substring(wordEndOffset), phraseSeparators)
266
+ const followingSubstringIsPhraseSeparator = phraseSeparatorRegExp.test(entryText.substring(wordEndOffset))
268
267
 
269
268
  const shouldAddNewLine =
270
269
  isLastWord ||
@@ -434,8 +433,7 @@ function getCuesFromTimeline_IsolateLines(timeline: Timeline, config: SubtitlesC
434
433
  }
435
434
 
436
435
  export function tryParseTimeRangePatternWithHours(line: string) {
437
- const timeRangePatternWithHours = /^(\d+)\:(\d+)\:(\d+)[\.,](\d+)[ ]*-->[ ]*(\d+)\:(\d+)\:(\d+)[\.,](\d+)/
438
- const match = timeRangePatternWithHours.exec(line)
436
+ const match = timeRangeWithHoursRegExp.exec(line)
439
437
 
440
438
  if (!match) {
441
439
  return { startTime: -1, endTime: -1, succeeded: false }
@@ -458,8 +456,7 @@ export function tryParseTimeRangePatternWithHours(line: string) {
458
456
  }
459
457
 
460
458
  export function tryParseTimeRangePatternWithoutHours(line: string) {
461
- const timeRangePatternWithHours = /^(\d+)\:(\d+)[\.,](\d+)[ ]*-->[ ]*(\d+)\:(\d+)[\.,](\d+)/
462
- const match = timeRangePatternWithHours.exec(line)
459
+ const match = timeRangeWithoutHoursRegExp.exec(line)
463
460
 
464
461
  if (!match) {
465
462
  return { startTime: -1, endTime: -1, succeeded: false }
@@ -514,6 +511,70 @@ function cueObjectToText(cue: Cue, cueIndex: number, config: SubtitlesConfig) {
514
511
  return outText
515
512
  }
516
513
 
514
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
515
+ // Patterns
516
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
517
+ const timeRangeWithHoursRegExp = buildRegExp([
518
+ inputStart,
519
+ capture(oneOrMore(digit)),
520
+ ':',
521
+ capture(oneOrMore(digit)),
522
+ ':',
523
+ capture(oneOrMore(digit)),
524
+
525
+ anyOf('.', ','),
526
+ capture(oneOrMore(digit)),
527
+
528
+ zeroOrMore(' '),
529
+ '-->',
530
+ zeroOrMore(' '),
531
+
532
+ capture(oneOrMore(digit)),
533
+ ':',
534
+ capture(oneOrMore(digit)),
535
+ ':',
536
+ capture(oneOrMore(digit)),
537
+
538
+ anyOf('.', ','),
539
+ capture(oneOrMore(digit)),
540
+ ])
541
+
542
+ const timeRangeWithoutHoursRegExp = buildRegExp([
543
+ inputStart,
544
+ capture(oneOrMore(digit)),
545
+ ':',
546
+ capture(oneOrMore(digit)),
547
+
548
+ anyOf('.', ','),
549
+ capture(oneOrMore(digit)),
550
+
551
+ zeroOrMore(' '),
552
+ '-->',
553
+ zeroOrMore(' '),
554
+
555
+ capture(oneOrMore(digit)),
556
+ ':',
557
+ capture(oneOrMore(digit)),
558
+
559
+ anyOf('.', ','),
560
+ capture(oneOrMore(digit)),
561
+ ])
562
+
563
+ const phraseSeparatorCharacters = [',', ',', '、', ';', ':', '),', '",', '”,']
564
+
565
+ const phraseSeparatorRegExp = buildRegExp(
566
+ matches([
567
+ inputStart,
568
+ anyOf(...phraseSeparatorCharacters)
569
+ ], {
570
+ ifPrecededBy: notUnicodeProperty('Decimal_Number'),
571
+ ifFollowedBy: notUnicodeProperty('Decimal_Number'),
572
+ })
573
+ )
574
+
575
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
576
+ // Types
577
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
517
578
  export type Cue = {
518
579
  lines: string[]
519
580
  startTime: number
@@ -5,7 +5,7 @@ import { Logger } from '../utilities/Logger.js'
5
5
  import { logToStderr } from '../utilities/Utilities.js'
6
6
  import { extendDeep } from '../utilities/ObjectUtilities.js'
7
7
  import { decodeBase64 } from '../encodings/Base64.js'
8
- import { isWordOrSymbolWord, splitToWords } from '../nlp/Segmentation.js'
8
+ import { splitToWords } from '../nlp/Segmentation.js'
9
9
  import { Timeline } from '../utilities/Timeline.js'
10
10
 
11
11
  const log = logToStderr
@@ -79,24 +79,25 @@ export async function synthesize(text: string, voiceId: string, language: string
79
79
  logger.start('Create timeline from returned character timings')
80
80
 
81
81
  const referenceText = characters.join('')
82
- const words = (await splitToWords(referenceText, language)).filter(w => isWordOrSymbolWord(w))
82
+ const wordSequence = await splitToWords(referenceText, language)
83
83
 
84
84
  timeline = []
85
85
 
86
- let offset = 0
86
+ for (const wordEntry of wordSequence.entries) {
87
+ const wordText = wordEntry.text
87
88
 
88
- for (const word of words) {
89
- const wordStartIndex = referenceText.indexOf(word, offset)
90
- const wordEndIndex = wordStartIndex + word.length
89
+ const wordStartOffset = wordEntry.startOffset
90
+ const wordEndOffset = wordEntry.endOffset
91
+
92
+ const startTime = characterStartTimes[wordStartOffset]
93
+ const endTime = characterEndTimes[wordEndOffset] ?? characterEndTimes[wordEndOffset - 1]
91
94
 
92
95
  timeline.push({
93
96
  type: 'word',
94
- text: word,
95
- startTime: characterStartTimes[wordStartIndex],
96
- endTime: characterEndTimes[wordEndIndex] ?? characterEndTimes[wordEndIndex - 1]
97
+ text: wordText,
98
+ startTime,
99
+ endTime,
97
100
  })
98
-
99
- offset = wordEndIndex
100
101
  }
101
102
  }
102
103
 
@@ -1,17 +1,18 @@
1
1
  import { concatFloat32Arrays, logToStderr, formatObjectToString } from '../utilities/Utilities.js'
2
2
  import { int16PcmToFloat32 } from '../audio/AudioBufferConversion.js'
3
3
  import { Logger } from '../utilities/Logger.js'
4
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
5
4
  import { RawAudio, getEmptyRawAudio } from '../audio/AudioUtilities.js'
6
5
  import { getNormalizedFragmentsForSpeech, simplifyPunctuationCharacters } from '../nlp/TextNormalizer.js'
7
6
  import { ipaPhoneToKirshenbaum } from '../nlp/PhoneConversion.js'
8
- import { splitToWords, wordCharacterPattern } from '../nlp/Segmentation.js'
7
+ import { splitToWords, wordCharacterRegExp } from '../nlp/Segmentation.js'
9
8
  import { Lexicon, tryGetFirstLexiconSubstitution } from '../nlp/Lexicon.js'
10
9
  import { phonemizeSentence } from '../nlp/EspeakPhonemizer.js'
11
10
  import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
12
11
  import { extendDeep } from '../utilities/ObjectUtilities.js'
13
12
  import { escapeHtml } from '../encodings/HtmlEscape.js'
14
13
 
14
+ import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
15
+
15
16
  const log = logToStderr
16
17
 
17
18
  let espeakInstance: any
@@ -37,7 +38,7 @@ export async function preprocessAndSynthesize(text: string, language: string, es
37
38
  fragments = []
38
39
  preprocessedFragments = []
39
40
 
40
- let words = await splitToWords(text, language)
41
+ let words = (await splitToWords(text, language)).wordArray
41
42
 
42
43
  // Merge repeating non-words to a single word to work around eSpeak bug
43
44
  {
@@ -51,7 +52,7 @@ export async function preprocessAndSynthesize(text: string, language: string, es
51
52
  i > 0 &&
52
53
  currentWord === previousWord &&
53
54
  !['[', ']'].includes(currentWord) && // Work around eSpeak-NG marker bug with repeating squared brackets
54
- !wordCharacterPattern.test(currentWord)) {
55
+ !wordCharacterRegExp.test(currentWord)) {
55
56
 
56
57
  wordsWithMerges[wordsWithMerges.length - 1] += currentWord
57
58
  } else {
@@ -515,12 +516,12 @@ export async function textToPhonemes(text: string, voice: string, useIPA = true)
515
516
  const { instance, module } = await getEspeakInstance()
516
517
  const textPtr = instance.convert_to_phonemes(text, useIPA)
517
518
 
518
- const wasmMemory = new WasmMemoryManager(module)
519
+ const wasmHeap = wrapEmscriptenModuleHeap(module)
519
520
 
520
- const resultRef = wasmMemory.wrapNullTerminatedUtf8String(textPtr.ptr)
521
- const result = resultRef.getValue()
521
+ const resultRef = wasmHeap.wrapNullTerminatedUtf8String(textPtr.ptr)
522
+ const result = resultRef.value
522
523
 
523
- wasmMemory.freeAll()
524
+ resultRef.free()
524
525
 
525
526
  return result
526
527
  }
@@ -108,9 +108,6 @@ export class KokoroTTS {
108
108
  const {
109
109
  referenceSynthesizedAudio,
110
110
  referenceTimeline,
111
- fragments,
112
- phonemizedFragmentsSubstitutions,
113
- phonemizedSentence
114
111
  } = await Espeak.preprocessAndSynthesize(sentenceText, voiceLanguage, espeakOptions, lexicons)
115
112
 
116
113
  logger.end()
@@ -132,13 +129,28 @@ export class KokoroTTS {
132
129
 
133
130
  const allTokenIds: number[] = []
134
131
 
135
- for (let phraseIndex = 0; phraseIndex < phonemizedSentence.length; phraseIndex++) {
136
- const phrase = phonemizedSentence[phraseIndex]
132
+ for (let clauseIndex = 0; clauseIndex < referenceTimeline.length; clauseIndex++) {
133
+ const clauseEntry = referenceTimeline[clauseIndex]
137
134
 
138
- for (let wordIndex = 0; wordIndex < phrase.length; wordIndex++) {
139
- const word = phrase[wordIndex]
135
+ if (!clauseEntry.timeline) {
136
+ continue
137
+ }
138
+
139
+ const wordTimelineForClause = clauseEntry.timeline
140
+
141
+ for (let wordIndex = 0; wordIndex < wordTimelineForClause.length; wordIndex++) {
142
+ const wordEntry = wordTimelineForClause[wordIndex]
143
+ const wordText = wordEntry.text
144
+
145
+ if (!wordEntry.timeline) {
146
+ continue
147
+ }
148
+
149
+ const tokenTimelineForWord = wordEntry.timeline
150
+ const phonemeTimelineForWord = tokenTimelineForWord.flatMap(tokenEntry => tokenEntry.timeline ?? [])
140
151
 
141
- for (const phoneme of word) {
152
+ for (const phonemeEntry of phonemeTimelineForWord) {
153
+ const phoneme = phonemeEntry.text
142
154
  let processedPhoneme = phoneme
143
155
 
144
156
  if (voicePrimaryLanguageShort === 'en') {
@@ -179,7 +191,7 @@ export class KokoroTTS {
179
191
 
180
192
  if (false) {
181
193
  // Workaround a word having only 'I' not being pronounced at some cases
182
- if (processedPhoneme === 'I' && word.length === 1) {
194
+ if (processedPhoneme === 'I' && wordText.length === 1) {
183
195
  processedPhoneme = 'aɪ'
184
196
  //processedPhoneme = 'ˌI'
185
197
  }
@@ -196,10 +208,10 @@ export class KokoroTTS {
196
208
  }
197
209
  }
198
210
 
199
- if (wordIndex < phrase.length - 1) {
211
+ if (wordIndex < wordTimelineForClause.length - 1) {
200
212
  allTokenIds.push(wordBreakTokenId)
201
213
  } else {
202
- if (phraseIndex < phonemizedSentence.length - 1) {
214
+ if (clauseIndex < referenceTimeline.length - 1) {
203
215
  allTokenIds.push(phraseBreakTokenId)
204
216
  allTokenIds.push(wordBreakTokenId)
205
217
  }
@@ -209,7 +221,7 @@ export class KokoroTTS {
209
221
 
210
222
  allTokenIds.push(sentenceEndTokenId)
211
223
 
212
- const maxPartLength = 509
224
+ const maxPartLength = 500
213
225
 
214
226
  const parts: number[][] = []
215
227
 
@@ -2,11 +2,12 @@ import { SynthesisVoice } from '../api/API.js'
2
2
  import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
3
3
  import { bandwidthToQFactor } from '../dsp/BiquadFilter.js'
4
4
  import { Logger } from '../utilities/Logger.js'
5
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
6
5
  import { RawAudio } from '../audio/AudioUtilities.js'
7
6
  import { readFileAsBinary } from '../utilities/FileSystem.js'
8
7
  import { concatUint8Arrays } from '../utilities/Utilities.js'
9
8
 
9
+ import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
10
+
10
11
  let svoxPicoInstance: any
11
12
 
12
13
  export async function synthesize(text: string, textAnalysisFilePath: string, signalGenerationFilePath: string, postprocessOutput = true) {
@@ -17,7 +18,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
17
18
 
18
19
  logger.start('Initialize pico engine')
19
20
 
20
- const wasmMemory = new WasmMemoryManager(m)
21
+ const wasmHeap = wrapEmscriptenModuleHeap(m)
21
22
 
22
23
  const pico_initialize = m._pico_initialize
23
24
  const picoext_setTraceLevel = m._picoext_setTraceLevel
@@ -37,11 +38,11 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
37
38
  const pico_unloadResource = m._pico_unloadResource
38
39
 
39
40
  const picoMemSize = 2500000
40
- const picoMemAreaRef = wasmMemory.allocUint8Array(picoMemSize)
41
+ const picoMemAreaRef = wasmHeap.allocUint8Array(picoMemSize)
41
42
 
42
- const systemPtrRef = wasmMemory.allocPointer()
43
+ const systemPtrRef = wasmHeap.allocPointer32()
43
44
 
44
- let resultCode = pico_initialize(picoMemAreaRef.address, picoMemAreaRef.length, systemPtrRef.address)
45
+ let resultCode = pico_initialize(picoMemAreaRef.address, picoMemAreaRef.allocatedByteCount, systemPtrRef.address)
45
46
  const systemPtr = systemPtrRef.value
46
47
 
47
48
  throwErrorIfFailed(resultCode, 'Failed Pico initialization.')
@@ -54,8 +55,8 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
54
55
  const fileData = await readFileAsBinary(localFilePath)
55
56
  m.FS.writeFile(virtualFilePath, fileData)
56
57
 
57
- const virtualFilePathRef = wasmMemory.allocNullTerminatedUtf8String(virtualFilePath)
58
- const resourcePtrRef = wasmMemory.allocPointer()
58
+ const virtualFilePathRef = wasmHeap.allocNullTerminatedUtf8String(virtualFilePath)
59
+ const resourcePtrRef = wasmHeap.allocPointer32()
59
60
 
60
61
  resultCode = pico_loadResource(systemPtr, virtualFilePathRef.address, resourcePtrRef.address)
61
62
  const resourcePtr = resourcePtrRef.value
@@ -69,12 +70,12 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
69
70
  const { resourcePtr: signalGenerationResourcePtr, resourcePtrRef: signalGenerationResourcePtrRef } = await loadResource(signalGenerationFilePath)
70
71
 
71
72
  function getResourceName(resourcePtr: number) {
72
- const resourceNameRef = wasmMemory.allocUint8Array(32)
73
+ const resourceNameRef = wasmHeap.allocNullTerminatedUtf8String(32)
73
74
  resultCode = pico_getResourceName(systemPtr, resourcePtr, resourceNameRef.address)
74
75
 
75
76
  throwErrorIfFailed(resultCode, `Failed getting Pico resource name.`)
76
77
 
77
- const resourceName = resourceNameRef.readAsNullTerminatedUtf8String()
78
+ const resourceName = resourceNameRef.value
78
79
 
79
80
  return { resourceName, resourceNameRef }
80
81
  }
@@ -82,7 +83,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
82
83
  const { resourceName: textAnalysisResourceName, resourceNameRef: textAnalysisResourceNameRef } = getResourceName(textAnalysisResourcePtr)
83
84
  const { resourceName: signalGenerationResourceName, resourceNameRef: signalGenerationResourceNameRef } = getResourceName(signalGenerationResourcePtr)
84
85
 
85
- const voiceNameRef = wasmMemory.allocNullTerminatedUtf8String('PicoVoice')
86
+ const voiceNameRef = wasmHeap.allocNullTerminatedUtf8String('PicoVoice')
86
87
 
87
88
  resultCode = pico_createVoiceDefinition(systemPtr, voiceNameRef.address)
88
89
 
@@ -95,7 +96,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
95
96
  addResourceToVoiceDefinition(textAnalysisResourceNameRef.address)
96
97
  addResourceToVoiceDefinition(signalGenerationResourceNameRef.address)
97
98
 
98
- const enginePtrRef = wasmMemory.allocPointer()
99
+ const enginePtrRef = wasmHeap.allocPointer32()
99
100
  resultCode = pico_newEngine(systemPtr, voiceNameRef.address, enginePtrRef.address)
100
101
 
101
102
  throwErrorIfFailed(resultCode, `Failed creating new engine.`)
@@ -104,15 +105,17 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
104
105
 
105
106
  logger.start('Synthesize with pico')
106
107
 
107
- const textRef = wasmMemory.allocNullTerminatedUtf8String(text)
108
+ const textRef = wasmHeap.allocNullTerminatedUtf8String(text)
108
109
 
109
- const bytesWrittenRef = wasmMemory.allocInt32()
110
+ const bytesWrittenRef = wasmHeap.allocInt32()
110
111
 
111
112
  const audioParts: Uint8Array[] = []
112
113
 
113
- for (let textByteOffset = 0; textByteOffset < textRef.length;) {
114
+ const textRefEncodedByteCountIncludingTerminator = textRef.encodedByteCount + 1
115
+
116
+ for (let textByteOffset = 0; textByteOffset < textRefEncodedByteCountIncludingTerminator;) {
114
117
  bytesWrittenRef.value = 0
115
- resultCode = pico_putTextUtf8(enginePtr, textRef.address + textByteOffset, textRef.length - textByteOffset, bytesWrittenRef.address)
118
+ resultCode = pico_putTextUtf8(enginePtr, textRef.address + textByteOffset, textRefEncodedByteCountIncludingTerminator - textByteOffset, bytesWrittenRef.address)
116
119
  const bytesWritten = bytesWrittenRef.value
117
120
 
118
121
  throwErrorIfFailed(resultCode, `Failed writing text to engine.`)
@@ -129,13 +132,13 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
129
132
  const outBuffers: Uint8Array[] = []
130
133
 
131
134
  const outBufferLength = 16384
132
- const outBufferRef = wasmMemory.allocUint8Array(outBufferLength)
135
+ const outBufferRef = wasmHeap.allocUint8Array(outBufferLength)
133
136
 
134
- const outByteCountRef = wasmMemory.allocInt16()
135
- const outDataTypeRef = wasmMemory.allocInt16()
137
+ const outByteCountRef = wasmHeap.allocInt16()
138
+ const outDataTypeRef = wasmHeap.allocInt16()
136
139
 
137
140
  while (true) {
138
- resultCode = pico_getData(enginePtr, outBufferRef.address, outBufferRef.length, outByteCountRef.address, outDataTypeRef.address)
141
+ resultCode = pico_getData(enginePtr, outBufferRef.address, outBufferRef.allocatedByteCount, outByteCountRef.address, outDataTypeRef.address)
139
142
 
140
143
  throwErrorIfFailed(resultCode, `Failed getting audio data from engine.`, [200, 201])
141
144
 
@@ -147,7 +150,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
147
150
  }
148
151
 
149
152
  if (outByteCount > 0) {
150
- outBuffers.push(outBufferRef.slice(0, outByteCount) as Uint8Array)
153
+ outBuffers.push(outBufferRef.view.slice(0, outByteCount))
151
154
  }
152
155
  }
153
156
 
@@ -179,7 +182,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
179
182
 
180
183
  pico_terminate(systemPtrRef.address)
181
184
 
182
- wasmMemory.freeAll()
185
+ wasmHeap.freeAll()
183
186
  }
184
187
 
185
188
  function throwErrorIfFailed(resultCode: number, title: string, successCodes = [0]) {
@@ -187,9 +190,9 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
187
190
  return
188
191
  }
189
192
 
190
- const picoErrorMessageRef = wasmMemory.allocUint8Array(200)
193
+ const picoErrorMessageRef = wasmHeap.allocNullTerminatedUtf8String(200)
191
194
  pico_getSystemStatusMessage(systemPtr, resultCode, picoErrorMessageRef)
192
- const picoErrorMessage = picoErrorMessageRef.readAsNullTerminatedUtf8String()
195
+ const picoErrorMessage = picoErrorMessageRef.value
193
196
 
194
197
  dispose()
195
198
  throw new Error(`${title} ${picoErrorMessage}`)
@@ -1,6 +1,6 @@
1
1
  import { request } from "gaxios"
2
2
  import { Logger } from "../utilities/Logger.js"
3
- import { splitToSentences } from "../nlp/Segmentation.js"
3
+ import { parseText } from "../nlp/Segmentation.js"
4
4
  import { TranslationPair } from "../api/TextTranslation.js"
5
5
  import { getChromeOnWindowsHeaders } from "../utilities/BrowserRequestHeaders.js"
6
6
 
@@ -9,7 +9,8 @@ export async function translateText(text: string, sourceLanguage: string, target
9
9
 
10
10
  logger.start(`Prepare request`)
11
11
 
12
- const textSentences = splitToSentences(text, sourceLanguage)
12
+ const segmentedText = await parseText(text, sourceLanguage)
13
+ const sentences = segmentedText.sentences
13
14
 
14
15
  const requestBodyObject = {
15
16
  jsonrpc: '2.0',
@@ -20,7 +21,7 @@ export async function translateText(text: string, sourceLanguage: string, target
20
21
  kind: 'default',
21
22
  sentences: [
22
23
  {
23
- text: textSentences[0],
24
+ text: sentences[0].text,
24
25
  id: 1,
25
26
  prefix: '',
26
27
  }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk'
2
2
  import { TranslationPair } from '../api/TextTranslation.js'
3
- import { splitToSentences } from '../nlp/Segmentation.js'
3
+ import { parseText } from '../nlp/Segmentation.js'
4
4
  import { Logger } from '../utilities/Logger.js'
5
5
  import { loadPackage } from '../utilities/PackageManager.js'
6
6
 
@@ -30,19 +30,21 @@ export async function translateText(sourceText: string, sourceLanguage: string,
30
30
  tgt_lang: 'eng_Latn'
31
31
  }
32
32
 
33
- const sentences = splitToSentences(sourceText, sourceLanguage)
33
+ const segmentedText = await parseText(sourceText, sourceLanguage)
34
+
35
+ const sentences = segmentedText.sentences
34
36
 
35
37
  logger.end()
36
38
 
37
39
  const translationPairs: TranslationPair[] = []
38
40
 
39
41
  for (let i = 0; i < sentences.length; i++) {
40
- const sentence = sentences[i]
42
+ const sentenceText = sentences[i].text
41
43
 
42
- logger.logTitledMessage(`Translate sentence ${i + 1}/${sentences.length}`, `"${sentence.trim()}"`, chalk.magentaBright)
44
+ logger.logTitledMessage(`Translate sentence ${i + 1}/${sentences.length}`, `"${sentenceText.trim()}"`, chalk.magentaBright)
43
45
 
44
46
  logger.start(`Tokenize sentence`)
45
- const inputs = (tokenizer as any)._build_translation_inputs(sentence, {
47
+ const inputs = (tokenizer as any)._build_translation_inputs(sentenceText, {
46
48
  padding: true,
47
49
  truncation: true,
48
50
  }, config)
@@ -67,7 +69,7 @@ export async function translateText(sourceText: string, sourceLanguage: string,
67
69
  .trim()
68
70
 
69
71
  translationPairs.push({
70
- sourceText: sentence,
72
+ sourceText: sentenceText,
71
73
  translatedText
72
74
  })
73
75
 
@@ -1,5 +1,3 @@
1
- declare module 'moving-median'
2
-
3
1
  declare module 'html-to-text' {
4
2
  export function htmlToText(html: string, options: {
5
3
  wordwrap: boolean | number
@@ -7,20 +5,6 @@ declare module 'html-to-text' {
7
5
  })
8
6
  }
9
7
 
10
- declare module 'cldr-segmentation' {
11
- export function sentenceSplit(text: string, suppressions: any)
12
- export function wordSplit(text: string, suppressions: any)
13
- export const suppressions: Record<string, Suppressions>
14
-
15
- export class Suppressions {
16
- constructor(forwardTrie: any, backwardTrie: any, list: string)
17
-
18
- merge(other: Suppressions)
19
-
20
- static create(list: string[]): Suppressions
21
- }
22
- }
23
-
24
8
  declare module 'winax'
25
9
 
26
10
  declare module 'tinyld' {
@@ -52,7 +52,7 @@ export function formatListWithQuotedElements(strings: string[], quoteSymbol = `'
52
52
 
53
53
  export function getUTF32Chars(str: string) {
54
54
  const utf32chars: string[] = []
55
- const mapping: number[] = []
55
+ const utf16To32Mapping: number[] = []
56
56
 
57
57
  let utf32Index = 0
58
58
 
@@ -60,15 +60,15 @@ export function getUTF32Chars(str: string) {
60
60
  utf32chars.push(utf32char)
61
61
 
62
62
  for (let i = 0; i < utf32char.length; i++) {
63
- mapping.push(utf32Index)
63
+ utf16To32Mapping.push(utf32Index)
64
64
  }
65
65
 
66
66
  utf32Index += 1
67
67
  }
68
68
 
69
- mapping.push(utf32Index)
69
+ utf16To32Mapping.push(utf32Index)
70
70
 
71
- return { utf32chars, mapping }
71
+ return { utf32chars, utf16To32Mapping }
72
72
  }
73
73
 
74
74
  export function containsInvalidCodepoint(str: string) {
@@ -81,10 +81,9 @@ export function containsInvalidCodepoint(str: string) {
81
81
  return false
82
82
  }
83
83
 
84
-
85
84
  export function splitAndPreserveSeparators(text: string, separatorRegex: RegExp): string[] {
86
85
  if (!separatorRegex.flags.includes('g')) {
87
- throw new Error('RegExp must be global')
86
+ throw new Error('Separator regular expression must have a global flag')
88
87
  }
89
88
 
90
89
  // Use the match method to find all matches for the separators