echogarden 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/Releases.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Release notes
2
2
 
3
- ## `1.0.0` (April 4, 2024)
3
+ ## `1.0.0` (April 12, 2024)
4
4
 
5
5
  **New features**:
6
6
  * Add support for [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp), a C++ port of OpenAI's Whisper speech recognition architecture. It is faster than the integrated `whisper` engine, supports large models, and GPU processing. It can now be used for recognition, speech translation and alignment (via `dtw-ra`), though its word timestamps are less accurate than the integrated `whisper` engine.
@@ -13,6 +13,11 @@
13
13
  * Expose several new configuration options for the Whisper engine: a settings for maximum tokens per part, and a setting to enable/disable repetition suppression, set custom random seed, disable/enable decoding of timestamp tokens
14
14
  * Expose more options for the Elevenlabs engine
15
15
 
16
+ **Enhancements**:
17
+ * `detect-voice-activity` now also outputs the cropped voice when an audio file is given as output, with `.cropped` suffix added
18
+ * Improved word segmentation of whisper engine. Words spanning multiple recognized time segments are now split. Word timestamp ranges don't overlap non-speech sections.
19
+ * Whisper and Whisper.cpp outputs now include timestamps for individual recognized tokens
20
+
16
21
  **Behavioral and breaking changes**:
17
22
  * Minimal required node version changed to `18.0.0`
18
23
  * All recognition, alignment, translation and language recognition operations first apply the new adaptive gate VAD (can be changed to any other VAD engine via the `vad.` option prefix) and remove any sections that are not identified as containing voice, before starting processing. This should improve results in most cases, and reduce processing time
@@ -30,11 +35,6 @@
30
35
  * `maxIncreaseDb` post-processing and denoising option renamed to `maxGainIncrease`
31
36
  * `dryMixGainDb` denoising option renamed to `dryMixGain`
32
37
 
33
- **Enhancements**:
34
- * `detect-voice-activity` now also outputs the cropped voice when an audio file is given as output, with `.cropped` suffix added
35
- * Improved word segmentation of whisper engine. Words spanning multiple recognized time segments are now split. Word timestamp ranges don't overlap non-speech sections.
36
- * Whisper and Whisper.cpp outputs now include timestamps for individual recognized tokens
37
-
38
38
  **Fixes**:
39
39
  * Integrated `whisper` engine now uses `tiktoken` to tokenize text, which produces near identical tokenization compared to the official Python implementation. This fixes issues with decoding Chinese characters, correctly encoding prompts, and several others languages with characters that span multiple tokens
40
40
  * Fix voice language lists for multilingual voices in Elevenlabs TTS engine
@@ -43,14 +43,15 @@
43
43
  * Fix issue with identifying legacy IEEE Float wave format
44
44
  * Fix issue with language detection failing with empty inputs
45
45
  * Fix issue with Whisper model failing when no tokens are detected in a part
46
- * Fix issue with phone alignment not getting the right DTW window size, producing warnings like `all cost directions are equal to infinity
46
+ * Fix issue with phone alignment not getting the right DTW window size, producing warnings like `all cost directions are equal to infinity`
47
47
  * Fix phone timelines in DTW-RA
48
48
  * Add more workarounds for eSpeak tokenization bugs
49
49
  * Various other fixes
50
50
 
51
51
  **Documentation**:
52
- * Add new 'Releases' page. Releases before `1.0.x` were retroactively documented based on commit history (may not exactly detail all historical changes)
53
- * Options reference page restructured and updated with some missing information
52
+ * Add new [release notes](./Releases.md) page. Releases before `1.0.x` were retroactively documented based on commit history (may not exactly detail all historical changes)
53
+ * Add new [development page](./Development.md) with a quick guide for setting up a development environment to make changes to the Echogarden codebase
54
+ * Options reference page reorganized and updated with some missing information
54
55
 
55
56
  ## `0.12.x` (March 16, 2024)
56
57
 
@@ -67,6 +68,7 @@
67
68
  **Other**:
68
69
  * Include `package-lock.json` in repository
69
70
 
71
+
70
72
  ## `0.11.x` (August 17, 2023)
71
73
 
72
74
  Many features, enhancements, and fixes were incrementally added over the span of 7 months, up to March 2024.
@@ -87,6 +89,7 @@ Many features, enhancements, and fixes were incrementally added over the span of
87
89
  * Fix audio playback in macOS
88
90
  * Many other fixes
89
91
 
92
+
90
93
  ## `0.10.x` (August 2, 2023)
91
94
 
92
95
  **New features**:
@@ -100,6 +103,7 @@ Many features, enhancements, and fixes were incrementally added over the span of
100
103
  **Fixes**:
101
104
  * Many fixes
102
105
 
106
+
103
107
  ## `0.9.x` (July 29, 2023)
104
108
 
105
109
  **New features**:
@@ -114,6 +118,7 @@ Many features, enhancements, and fixes were incrementally added over the span of
114
118
  * Work around several eSpeak bugs
115
119
  * Many fixes
116
120
 
121
+
117
122
  ## `0.8.x` (July 25, 2023)
118
123
 
119
124
  **New features**:
@@ -122,26 +127,25 @@ Many features, enhancements, and fixes were incrementally added over the span of
122
127
  * Add punctuation thresholds to Whisper decoder
123
128
  * Expose subtitle configuration options to CLI
124
129
 
125
- **Behavioral changes**:
126
- * Rename `subtitles.minWords` to `subtitles.minWordsInLine`
127
-
128
130
  **Enhancements**:
129
131
  * Improve speech language detection to work with arbitrary length audio. Split audio to overlapping parts and detect each part individually. Then average the results.
130
132
  * Add more heteronyms
131
133
 
134
+ **Behavioral changes**:
135
+ * Rename `subtitles.minWords` to `subtitles.minWordsInLine`
136
+
132
137
  **Fixes**:
133
138
  * Improve numerical stability of softmax function
134
139
  * Work around eSpeak bug with markers in long inputs
135
140
  * Various fixes
136
141
 
142
+
137
143
  ## `0.7.x` (July 23, 2023)
138
144
 
139
145
  **New features**:
140
146
  * Alignment: Add option to accept custom lexicons
141
147
  * Implement and add decoder temperature option to Whisper model
142
148
 
143
- **Behavioral changes**:
144
-
145
149
  **Enhancements**:
146
150
  * Alignment: use preprocessing and lexicons
147
151
  * Add language detection to speech translation
@@ -151,10 +155,13 @@ Many features, enhancements, and fixes were incrementally added over the span of
151
155
  * Add colors to log messages
152
156
  * Warn when maximum DTW window duration is smaller than source audio duration.
153
157
 
158
+ **Behavioral changes**:
159
+
154
160
  **Fixes**:
155
161
  * Fix support for SSML input in eSpeak engine
156
162
  * Various fixes
157
163
 
164
+
158
165
  ## `0.6.x` (July 20, 2023)
159
166
 
160
167
  **New features**:
@@ -165,16 +172,16 @@ Many features, enhancements, and fixes were incrementally added over the span of
165
172
  * Add arguments to customize paragraph parsing
166
173
  * Change whitespace option to include option for collapsing all whitespace
167
174
 
168
- **Behavioral changes**:
169
- * Set Elevenlabs defaults to mid-values
170
- * Split plain text to paragraphs using double line breaks by default.
171
- * Rename `awsPolly` options to `amazonPolly` to be more consistent with documentation
172
-
173
175
  **Enhancements**:
174
176
  * Change whitespace option to include option for collapsing all whitespace
175
177
  * Add awareness of guillemets
176
178
  * Log full language of selected voice
177
179
 
180
+ **Behavioral changes**:
181
+ * Set Elevenlabs defaults to mid-values
182
+ * Split plain text to paragraphs using double line breaks by default.
183
+ * Rename `awsPolly` options to `amazonPolly` to be more consistent with documentation
184
+
178
185
  **Fixes**:
179
186
  * Fix and update Elevenlabs engine
180
187
  * Fix incorrect gender properties for some VITS voices
@@ -184,26 +191,25 @@ Many features, enhancements, and fixes were incrementally added over the span of
184
191
  **Other**:
185
192
  * Remove `package-lock.json` from the repository
186
193
 
194
+
187
195
  ## `0.5.x` (July, 19 2023)
188
196
 
189
197
  **New features**:
190
198
 
191
- **Behavioral changes**:
192
- * Move to new package system supporting version tags, and hosted in a Hugging Face repository
193
-
194
199
  **Enhancements**:
195
200
  * Add large numbers of VITS voices
196
201
 
202
+ **Behavioral changes**:
203
+ * Move to new package system supporting version tags, and hosted in a Hugging Face repository
204
+
197
205
  **Fixes**:
198
206
  * Various fixes
199
207
 
208
+
200
209
  ## `0.4.x` (July 9, 2023)
201
210
 
202
211
  **New features**:
203
212
 
204
- **Behavioral changes**:
205
- * Remove support for `afplay` and `aplay` for playback. Only SoX is used now.
206
-
207
213
  **Enhancements**:
208
214
  * Improve text normalization and add support for currencies
209
215
  * Improve heteronym lexicons
@@ -212,32 +218,34 @@ Many features, enhancements, and fixes were incrementally added over the span of
212
218
  * Add some British English (RP) pronunciations to heteronym lexicon
213
219
  * Add large numbers of VITS voices
214
220
 
221
+ **Behavioral changes**:
222
+ * Remove support for `afplay` and `aplay` for playback. Only SoX is used now.
223
+
215
224
  **Fixes**:
216
225
  * Various fixes
217
226
 
227
+
218
228
  ## `0.3.x` (July 1, 2023)
219
229
 
220
230
  **New features**:
221
231
 
222
- **Behavioral changes**:
223
-
224
232
  **Enhancements**:
225
233
  * Upgrade heteronym disambiguation to an improved, rule-based approach, which doesn't use POS tagging
226
234
  * Extend decade normalization
227
235
  * Improve text normalization and add support to currencies
228
236
  * Update the heteronym lexicon
229
237
 
238
+ **Behavioral changes**:
239
+
230
240
  **Fixes**:
231
241
  * Remove sentences containing only whitespace when synthesizing
232
242
  * Many fixes
233
243
 
244
+
234
245
  ## `0.2.x` (May 10, 2023)
235
246
 
236
247
  **New features**:
237
248
 
238
- **Behavioral changes**:
239
- * Remove dependency on `xregexp` package. Use Unicode RegExp instead.
240
-
241
249
  **Enhancements**:
242
250
  * Show current sentence and segment in synthesis log.
243
251
  * Add check for cancellation flag.
@@ -245,6 +253,9 @@ Many features, enhancements, and fixes were incrementally added over the span of
245
253
  * Improve auto TTS engine selection
246
254
  * Many Enhancements
247
255
 
256
+ **Behavioral changes**:
257
+ * Remove dependency on `xregexp` package. Use Unicode RegExp instead.
258
+
248
259
  **Fixes**:
249
260
  * Many fixes
250
261
 
package/docs/Tasklist.md CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  ## Bugs
4
4
 
5
+ ### Alignment
6
+
5
7
  * In DTW-RA, recognition transcript including something like "Question 2.What does Juan", where "2.What" has a point in the middle, is breaking playback of the timeline.
8
+ * DTW-RA will not work correctly with Polish language texts, due to issues with the eSpeak engine pronouncing `|` characters, which are intended to be used as separators and ignored by all other eSpeak languages.
6
9
 
7
10
  ### Synthesis
8
11
 
@@ -108,8 +111,8 @@
108
111
  * See if it's possible to reliably use eSpeak as a segmentation engine
109
112
 
110
113
  ### Subtitles
114
+ * Split long words if needed. This is especially important for Chinese
111
115
  * If a subtitle is too short and at the end of the audio, try to extend it back if possible (for example, if the previous subtitle is already extended, take back from it)
112
- * Split long words if needed
113
116
  * Decide how many punctuation characters to allow before breaking to a new line (currently it's infinite)
114
117
  * Add more clause separators, for even more special cases
115
118
  * Add option to output usable word or phoneme-level caption files (investigate how it's done on YouTube auto-captions)
@@ -155,10 +158,10 @@
155
158
  * Currently, when input is set to be SSML, it is wrapped in a `<speak>` tag. Handle the case where the user made their own SSML document wrapped with a `<speak>` tag as well. Currently, it may send invalid input to Azure
156
159
 
157
160
  ### Recognition
161
+ * Recognized word entries that span VAD segment boundaries can be split
158
162
  * Show alternatives when playing in the CLI. Clear current line and rewrite already printed text for alternatives during the speech recognition process
159
163
  * Option to split recognized audio to segments or sentences, as is done with synthesized audio
160
164
  * Try to exclude the timing for trailing punctuation tokens in words that contain them. This can help narrow down the end timestamp to cover the word more tightly
161
- * Recognized word entries that span VAD boundaries may be split
162
165
 
163
166
  ### Recognition / Whisper
164
167
  * May get stuck in a token repeat loop when silence or non-speech segment encountered in audio. Decide what to do
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echogarden",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "An integrated speech toolset, providing a variety of synthesis, recognition, alignment, and other processing tools, designed to be directly accessible to end-users.",
5
5
  "author": "Rotem Dan",
6
6
  "license": "GPL-3.0",
@@ -90,11 +90,11 @@
90
90
  "ndarray": "^1.0.19",
91
91
  "ndarray-ops": "^1.2.2",
92
92
  "onnxruntime-node": "^1.17.0",
93
- "openai": "^4.33.0",
93
+ "openai": "^4.33.1",
94
94
  "sam-js": "^0.2.1",
95
95
  "strip-ansi": "^7.1.0",
96
96
  "tar": "^7.0.0",
97
- "tiktoken": "^1.0.13",
97
+ "tiktoken": "^1.0.14",
98
98
  "tinyld": "^1.3.4",
99
99
  "ws": "^8.16.0",
100
100
  "wtf_wikipedia": "^10.3.0"
@@ -127,7 +127,7 @@
127
127
  "@types/recursive-readdir": "^2.2.4",
128
128
  "@types/tar": "^6.1.12",
129
129
  "@types/ws": "^8.5.10",
130
- "ts-json-schema-generator": "^1.5.1",
130
+ "ts-json-schema-generator": "^2.0.0",
131
131
  "typescript": "^5.4.5"
132
132
  }
133
133
  }
@@ -151,7 +151,7 @@ export async function alignUsingDtw(
151
151
  return mappedTimeline
152
152
  }
153
153
 
154
- export async function alignUsingDtwWithRecognitionReference(
154
+ export async function alignUsingDtwWithRecognition(
155
155
  sourceRawAudio: RawAudio,
156
156
  referenceRawAudio: RawAudio,
157
157
  referenceTimeline: Timeline,
@@ -272,6 +272,7 @@ export async function alignUsingDtwWithRecognitionReference(
272
272
 
273
273
  let currentSynthesizedToRecognizedMappingIndex = 0
274
274
 
275
+ // Map from synthesized reference timestamps to the recognition timestamps
275
276
  function mapSynthesizedToRecognizedTimeAndAdvance(synthesizedTime: number) {
276
277
  for (; ; currentSynthesizedToRecognizedMappingIndex += 1) {
277
278
  const left = synthesizedToRecognizedTimeMapping[currentSynthesizedToRecognizedMappingIndex].synthesized
@@ -12,6 +12,7 @@ import chalk from 'chalk'
12
12
  import { DtwGranularity, createAlignmentReferenceUsingEspeak } from '../alignment/SpeechAlignment.js'
13
13
  import { SubtitlesConfig, defaultSubtitlesBaseConfig } from '../subtitles/Subtitles.js'
14
14
  import { EspeakOptions, defaultEspeakOptions } from '../synthesis/EspeakTTS.js'
15
+ import { isWord } from '../nlp/Segmentation.js'
15
16
 
16
17
  const log = logToStderr
17
18
 
@@ -85,7 +86,7 @@ export async function align(input: AudioSourceParam, transcript: string, options
85
86
 
86
87
  logger.start('Load alignment module')
87
88
 
88
- const { alignUsingDtwWithRecognitionReference, alignUsingDtw } = await import('../alignment/SpeechAlignment.js')
89
+ const { alignUsingDtwWithRecognition: alignUsingDtwWithRecognitionReference, alignUsingDtw } = await import('../alignment/SpeechAlignment.js')
89
90
 
90
91
  function getDtwWindowDurationsAndGranularities() {
91
92
  let granularities: DtwGranularity[]
@@ -145,7 +146,12 @@ export async function align(input: AudioSourceParam, transcript: string, options
145
146
  logger.end()
146
147
 
147
148
  // Recognize source audio
148
- const { transcript: recognizedTranscript, wordTimeline: recognitionTimeline } = await API.recognize(sourceRawAudio, recognitionOptions)
149
+ let { wordTimeline: recognitionTimeline } = await API.recognize(sourceRawAudio, recognitionOptions)
150
+
151
+ logger.log('')
152
+
153
+ // Remove non-word entries from recognition timeline
154
+ recognitionTimeline = recognitionTimeline.filter(entry => isWord(entry.text))
149
155
 
150
156
  // Synthesize the ground-truth transcript and get its timeline
151
157
  logger.start('Synthesize ground-truth transcript with eSpeak')
@@ -154,14 +160,14 @@ export async function align(input: AudioSourceParam, transcript: string, options
154
160
  referenceRawAudio,
155
161
  referenceTimeline,
156
162
  espeakVoice,
157
- } = await createAlignmentReferenceUsingEspeak(transcript, language, options.plainText, options.customLexiconPaths, true)
163
+ } = await createAlignmentReferenceUsingEspeak(transcript, language, options.plainText, options.customLexiconPaths, false)
158
164
 
159
165
  logger.end()
160
166
 
161
167
  const { windowDurations, granularities } = getDtwWindowDurationsAndGranularities()
162
168
 
163
169
  const phoneAlignmentMethod = options.dtw!.phoneAlignmentMethod!
164
-
170
+
165
171
  const espeakOptions: EspeakOptions = {
166
172
  ...defaultEspeakOptions,
167
173
  voice: espeakVoice,
@@ -345,10 +351,10 @@ export const defaultAlignmentOptions: AlignmentOptions = {
345
351
  topCandidateCount: 5,
346
352
  punctuationThreshold: 0.2,
347
353
  maxTokensPerPart: 250,
348
- autoPromptParts: true,
354
+ autoPromptParts: false,
349
355
  suppressRepetition: true,
356
+ decodeTimestampTokens: true,
350
357
  seed: undefined,
351
- decodeTimestampTokens: false,
352
358
  }
353
359
  },
354
360
 
package/src/dsp/FFT.ts CHANGED
@@ -122,7 +122,7 @@ export async function stiftr(binsForFrames: Float32Array[], fftOrder: number, wi
122
122
 
123
123
  wasmMemory.freeAll()
124
124
 
125
- // Divide by sum of weight squares for each samples
125
+ // Divide each output sample by the sum of squared weights
126
126
  for (let i = 0; i < outSamples.length; i++) {
127
127
  outSamples[i] /= sumOfSquaredWeightsForSample[i] + 1e-8
128
128
  }
@@ -8,8 +8,9 @@ import { ParagraphBreakType, WhitespaceProcessing } from '../api/Common.js'
8
8
 
9
9
  const log = logToStderr
10
10
 
11
- export const wordCharacterPattern = /[\p{Letter}\p{Number}]/u
12
- export const punctuationPattern = /[\p{Punctuation}]/u
11
+ export const wordCharacterPattern = /[\p{Letter}\p{Number}]+/u
12
+ export const punctuationPattern = /[\p{Punctuation}]+/u
13
+
13
14
  export const phraseSeparators = [',', ';', ':']
14
15
  export const sentenceSeparators = ['.', '?', '!']
15
16
  export const symbolWords = ['$', '€', '¢', '£', '¥', '©', '®', '™', '%', '&', '#', '~', '@', '+', '±', '÷', '/', '*', '=', '¼', '½', '¾']
@@ -18,13 +19,20 @@ export function isWordOrSymbolWord(str: string) {
18
19
  return isWord(str) || symbolWords.includes(str)
19
20
  }
20
21
 
22
+ export function isSymbolWord(str: string) {
23
+ return symbolWords.includes(str.trim())
24
+ }
25
+
21
26
  export function isWord(str: string) {
22
- str = str.trim()
23
- return wordCharacterPattern.test(str) || symbolWords.includes(str)
27
+ return wordCharacterPattern.test(str.trim())
24
28
  }
25
29
 
26
30
  export function isPunctuation(str: string) {
27
- return punctuationPattern.test(str)
31
+ return punctuationPattern.test(str.trim())
32
+ }
33
+
34
+ export function isWhitespace(str: string) {
35
+ return str.trim().length === 0
28
36
  }
29
37
 
30
38
  export class Sentence {
@@ -288,13 +288,14 @@ async function parseResultObject(resultObject: WhisperCppVerboseResult, modelNam
288
288
 
289
289
  const allTokenIds = tokenTimeline.map(entry => entry.id!)
290
290
  const transcript = whisper.tokensToText(allTokenIds).trim()
291
+ const language = resultObject.result.language
291
292
 
292
- let timeline = whisper.tokenTimelineToWordTimeline(tokenTimeline)
293
+ const timeline = whisper.tokenTimelineToWordTimeline(tokenTimeline, language)
293
294
 
294
295
  return {
295
296
  transcript,
296
297
  timeline,
297
- language: resultObject.result.language
298
+ language
298
299
  }
299
300
  }
300
301
 
@@ -19,6 +19,7 @@ import chalk from 'chalk'
19
19
  import { XorShift32RNG } from '../utilities/RandomGenerator.js'
20
20
  import { detectSpeechLanguageByParts } from '../api/LanguageDetection.js'
21
21
  import { type Tiktoken } from 'tiktoken/lite'
22
+ import { isPunctuation, isWhitespace } from '../nlp/Segmentation.js'
22
23
 
23
24
  export async function recognize(sourceRawAudio: RawAudio, modelName: WhisperModelName, modelDir: string, task: WhisperTask, sourceLanguage: string, options: WhisperOptions) {
24
25
  if (sourceRawAudio.sampleRate != 16000) {
@@ -398,7 +399,7 @@ export class Whisper {
398
399
  logger.end()
399
400
  }
400
401
 
401
- timeline = this.tokenTimelineToWordTimeline(timeline)
402
+ timeline = this.tokenTimelineToWordTimeline(timeline, language)
402
403
 
403
404
  const transcript = this.tokensToText(allDecodedTokens).trim()
404
405
 
@@ -438,7 +439,7 @@ export class Whisper {
438
439
  const alignmentPath = await this.findAlignmentPathFromQKs(crossAttentionQKs, tokens, 0, audioFrameCount)//, this.getAlignmentHeadIndexes())
439
440
  let timeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, tokens, 0, audioDuration)
440
441
 
441
- timeline = this.tokenTimelineToWordTimeline(timeline)
442
+ timeline = this.tokenTimelineToWordTimeline(timeline, language)
442
443
 
443
444
  logger.end()
444
445
 
@@ -1022,33 +1023,40 @@ export class Whisper {
1022
1023
  }
1023
1024
  }
1024
1025
 
1025
- tokenTimelineToWordTimeline(tokenTimeline: Timeline) {
1026
- const separatorChars =
1027
- [' ', '–', '一', ',', '、', '|', '/', '\\', ';', '"', '“', '”', '…', '(', ')', '[', ']', '{', '}']
1026
+ tokenTimelineToWordTimeline(tokenTimeline: Timeline, language: string): Timeline {
1027
+ function isSeparatorCharacter(char: string) {
1028
+ const nonSeparatingPunctuation = [`'`, `-`, `.`, `·`, `•`]
1028
1029
 
1029
- function startsWithSeparatingPunctuation(text: string) {
1030
- return separatorChars.some(char => text.startsWith(char))
1030
+ if (nonSeparatingPunctuation.includes(char)) {
1031
+ return false
1032
+ }
1033
+
1034
+ return isWhitespace(char) || isPunctuation(char)
1035
+ }
1036
+
1037
+ function startsWithSeparatorCharacter(text: string) {
1038
+ return isSeparatorCharacter(text[0])
1031
1039
  }
1032
1040
 
1033
- function isSeparatorPunctuation(text: string) {
1034
- return separatorChars.includes(text)
1041
+ function endsWithSeparatorCharacter(text: string) {
1042
+ return isSeparatorCharacter(text[text.length - 1])
1035
1043
  }
1036
1044
 
1037
1045
  const resultTimeline: Timeline = []
1038
1046
 
1039
- const groups: TimelineEntry[][] = []
1047
+ let groups: TimelineEntry[][] = []
1040
1048
 
1041
- for (let i = 0; i < tokenTimeline.length; i++) {
1042
- const entry = tokenTimeline[i]
1043
- const previousEntry = i > 0 ? tokenTimeline[i - 1] : undefined
1049
+ for (let tokenIndex = 0; tokenIndex < tokenTimeline.length; tokenIndex++) {
1050
+ const entry = tokenTimeline[tokenIndex]
1051
+ const previousEntry = tokenIndex > 0 ? tokenTimeline[tokenIndex - 1] : undefined
1044
1052
 
1045
1053
  const text = entry.text
1046
1054
  const previousEntryText = previousEntry?.text
1047
1055
 
1048
1056
  if (groups.length == 0 ||
1049
1057
  text === '' ||
1050
- startsWithSeparatingPunctuation(text) ||
1051
- (previousEntryText != null && isSeparatorPunctuation(previousEntryText))) {
1058
+ startsWithSeparatorCharacter(text) ||
1059
+ (previousEntryText != null && endsWithSeparatorCharacter(previousEntryText))) {
1052
1060
 
1053
1061
  groups.push([entry])
1054
1062
  } else {
@@ -1056,8 +1064,28 @@ export class Whisper {
1056
1064
  }
1057
1065
  }
1058
1066
 
1067
+ const newGroups: TimelineEntry[][] = []
1068
+
1069
+ for (let groupIndex = 0; groupIndex < groups.length - 1; groupIndex++) {
1070
+ const group = groups[groupIndex]
1071
+ const nextGroup = groups[groupIndex + 1]
1072
+
1073
+ if (
1074
+ group.length > 1 &&
1075
+ group[group.length - 1].text === '.' &&
1076
+ [' ', '['].includes(nextGroup[0].text[0])) {
1077
+
1078
+ newGroups.push(group.slice(0, group.length - 1))
1079
+ newGroups.push(group.slice(group.length - 1))
1080
+ } else {
1081
+ newGroups.push(group)
1082
+ }
1083
+ }
1084
+
1085
+ groups = newGroups
1086
+
1059
1087
  for (const group of groups) {
1060
- const groupText = this.tokensToText(group.map(entry => entry.id!))
1088
+ let groupText = this.tokensToText(group.map(entry => entry.id!))
1061
1089
 
1062
1090
  if (groupText === '') {
1063
1091
  continue
@@ -1755,8 +1783,8 @@ export interface WhisperOptions {
1755
1783
  autoPromptParts?: boolean
1756
1784
  maxTokensPerPart?: number
1757
1785
  suppressRepetition?: boolean
1758
- seed?: number
1759
1786
  decodeTimestampTokens?: boolean
1787
+ seed?: number
1760
1788
  }
1761
1789
 
1762
1790
  export const defaultWhisperOptions: WhisperOptions = {
@@ -1768,6 +1796,6 @@ export const defaultWhisperOptions: WhisperOptions = {
1768
1796
  autoPromptParts: true,
1769
1797
  maxTokensPerPart: 250,
1770
1798
  suppressRepetition: true,
1799
+ decodeTimestampTokens: true,
1771
1800
  seed: undefined,
1772
- decodeTimestampTokens: false,
1773
1801
  }
@@ -200,7 +200,7 @@ function getCuesFromTimeline_IsolateSegmentSentence(timeline: Timeline, config:
200
200
  continue
201
201
  }
202
202
 
203
- const wordTimeline = entry.timeline!.filter(entry => isWord(entry.text))
203
+ const wordTimeline = entry.timeline!.filter(entry => isWordOrSymbolWord(entry.text))
204
204
 
205
205
  // First, add word start and end offsets for all word entries
206
206
  let lastWordEndOffset = 0
package/src/tests/Test.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { getRepetitionScoreRelativeToFirstSubstring, logToStderr, setupProgramTerminationListeners } from '../utilities/Utilities.js'
1
+ import { getRepetitionScoreRelativeToFirstSubstring, logToStderr, setupProgramTerminationListeners, writeToStderr } from '../utilities/Utilities.js'
2
2
  import { makeTarballsForInstalledPackages } from '../utilities/TarballMaker.js'
3
3
  import { testEspeakSynthesisWithPrePhonemizedInputs, testKirshenbaumPhonemization } from '../synthesis/EspeakTTS.js'
4
+ import { isPunctuation } from '../nlp/Segmentation.js'
4
5
 
5
6
  const log = logToStderr
6
7
 
@@ -21,4 +22,19 @@ setupProgramTerminationListeners()
21
22
  //getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'c', 'a', 'b', 'a', 'c', 'a'])
22
23
  //getRepetitionScoreRelativeToFirstSubstring(['a', 'a', 'a', 'b', 'b', 'a', 'a', 'a', 'b'])
23
24
 
25
+ /*
26
+ const allPunctuationChars: string[] = []
27
+
28
+ for (let i = 0; i < 65536; i++) {
29
+ const char = String.fromCodePoint(i)
30
+
31
+ if (isPunctuation(char)) {
32
+ allPunctuationChars.push(char)
33
+
34
+ writeToStderr(`${char} `)
35
+ }
36
+ }
37
+ */
38
+
24
39
  process.exit(0)
40
+