11type Tag = 'eos' | 'pause' | 'whitespace' ;
22
3+ // Punctuation Regex Patterns
34const EOS_PATTERN = / [ . ? ! ; … | । ॥ ¿ ¡ ] / ;
45const PAUSE_PATTERN = / [ , : \u2014 \u00AB \u00BB ] / ;
56const WHITESPACE_PATTERN = / \s / ;
67
8+ // Standard Partitioning Constants
79const MAX_TARGET_PHRASE_LENGTH = 120 ;
810const MIN_PARTITION_LIMIT = 10 ;
911const DEVIATION_SCALING = 0.01 ;
1012const TARGET_LENGTH_RATIO = 0.5 ;
11- const SEPARATOR_PENALTY : Record < Tag , number > = { eos : 5 , pause : 18 , whitespace : 1000 } ;
13+
14+ // TTFA Optimization Constants
15+ const INITIAL_TARGET_LENGTH_RATIO = 0.15 ;
16+ const RAMP_CHARACTER_COUNT = 150 ;
17+ const INITIAL_SHORT_DEVIATION_SCALE = 0.1 ;
18+
19+ // Separator Breakpoint Penalties (eos = sentence end, pause = comma/colon, whitespace = word space)
20+ const DEFAULT_SEPARATOR_PENALTY : Record < Tag , number > = { eos : 5 , pause : 80 , whitespace : 1000 } ;
21+
22+ /**
23+ * Configuration options for text partitioning.
24+ * @category Types
25+ */
26+ export type PartitionOptions = {
27+ /**
28+ * Whether to prioritize shorter initial segment lengths and scale up
29+ * progressively to minimize Time To First Audio (TTFA).
30+ * @default false
31+ */
32+ readonly prioritizeInitialTtfa ?: boolean ;
33+
34+ /**
35+ * Scaling multiplier for the length deviation penalty when the first segment
36+ * is shorter than target length. Lower values reduce the length penalty for
37+ * short initial chunks when prioritizeInitialTtfa is true.
38+ * @default 0.1
39+ */
40+ readonly initialShortDeviationScale ?: number ;
41+
42+ /**
43+ * Custom separator penalties for breakpoint tag types ('eos', 'pause',
44+ * 'whitespace'). Default: { eos: 5, pause: 80, whitespace: 1000 }.
45+ */
46+ readonly separatorPenalties ?: {
47+ readonly eos ?: number ;
48+ readonly pause ?: number ;
49+ readonly whitespace ?: number ;
50+ } ;
51+ } ;
1252
1353function tagFromChar ( char : string ) : Tag | undefined {
1454 if ( EOS_PATTERN . test ( char ) ) return 'eos' ;
@@ -17,6 +57,23 @@ function tagFromChar(char: string): Tag | undefined {
1757 return ;
1858}
1959
60+ function getTargetLength (
61+ startCharIdx : number ,
62+ limit : number ,
63+ prioritizeInitialTtfa : boolean
64+ ) : number {
65+ const maxTarget = Math . min ( MAX_TARGET_PHRASE_LENGTH , limit * TARGET_LENGTH_RATIO ) ;
66+ if ( ! prioritizeInitialTtfa ) {
67+ return maxTarget ;
68+ }
69+ const minTarget = Math . min (
70+ maxTarget ,
71+ Math . max ( MIN_PARTITION_LIMIT * 2.5 , limit * INITIAL_TARGET_LENGTH_RATIO )
72+ ) ;
73+ const progress = Math . min ( 1.0 , startCharIdx / RAMP_CHARACTER_COUNT ) ;
74+ return minTarget + ( maxTarget - minTarget ) * progress ;
75+ }
76+
2077function sliceAtCuts ( text : string , cutIndices : number [ ] ) : string [ ] {
2178 const chunks : string [ ] = [ ] ;
2279 let startIdx = 0 ;
@@ -34,9 +91,10 @@ function sliceAtCuts(text: string, cutIndices: number[]): string[] {
3491 * @category Utils
3592 * @param text The input text to partition.
3693 * @param limit The character limit per partition.
94+ * @param options Optional configuration for TTFA prioritization and custom penalties.
3795 * @returns An array of partitioned text segments.
3896 */
39- export function partition ( text : string , limit : number ) : string [ ] {
97+ export function partition ( text : string , limit : number , options ?: PartitionOptions ) : string [ ] {
4098 if ( ! text ) {
4199 return [ ] ;
42100 }
@@ -45,6 +103,15 @@ export function partition(text: string, limit: number): string[] {
45103 throw new Error ( `partition: limit ${ limit } is below minimum ${ MIN_PARTITION_LIMIT } ` ) ;
46104 }
47105
106+ const prioritizeInitialTtfa = options ?. prioritizeInitialTtfa ?? false ;
107+ const initialShortDeviationScale =
108+ options ?. initialShortDeviationScale ?? INITIAL_SHORT_DEVIATION_SCALE ;
109+ const separatorPenalty : Record < Tag , number > = {
110+ eos : options ?. separatorPenalties ?. eos ?? DEFAULT_SEPARATOR_PENALTY . eos ,
111+ pause : options ?. separatorPenalties ?. pause ?? DEFAULT_SEPARATOR_PENALTY . pause ,
112+ whitespace : options ?. separatorPenalties ?. whitespace ?? DEFAULT_SEPARATOR_PENALTY . whitespace ,
113+ } ;
114+
48115 const breakpoints : { idx : number ; tag : Tag } [ ] = [ ] ;
49116 let charIdx = 0 ;
50117 for ( const char of text ) {
@@ -57,8 +124,6 @@ export function partition(text: string, limit: number): string[] {
57124 breakpoints . push ( { idx : text . length - 1 , tag : 'eos' } ) ;
58125 }
59126
60- const targetLength = Math . min ( MAX_TARGET_PHRASE_LENGTH , limit * TARGET_LENGTH_RATIO ) ;
61-
62127 const length = ( currBreakIdx : number , prevBreakIdx : number ) : number => {
63128 if ( prevBreakIdx < 0 ) return breakpoints [ currBreakIdx ] ! . idx + 1 ; // no previous cuts
64129 return breakpoints [ currBreakIdx ] ! . idx - breakpoints [ prevBreakIdx ] ! . idx ;
@@ -67,10 +132,18 @@ export function partition(text: string, limit: number): string[] {
67132 const cost = ( currBreakIdx : number , prevBreakIdx : number ) : number => {
68133 const len = length ( currBreakIdx , prevBreakIdx ) ;
69134 if ( len > limit ) return Infinity ;
70- return (
71- SEPARATOR_PENALTY [ breakpoints [ currBreakIdx ] ! . tag ] +
72- DEVIATION_SCALING * ( len - targetLength ) ** 2
73- ) ;
135+
136+ const startCharIdx = prevBreakIdx < 0 ? 0 : breakpoints [ prevBreakIdx ] ! . idx + 1 ;
137+ const targetLength = getTargetLength ( startCharIdx , limit , prioritizeInitialTtfa ) ;
138+
139+ const isFirstChunk = prevBreakIdx < 0 ;
140+ const diff = len - targetLength ;
141+ const devScale =
142+ prioritizeInitialTtfa && isFirstChunk && diff < 0
143+ ? DEVIATION_SCALING * initialShortDeviationScale
144+ : DEVIATION_SCALING ;
145+
146+ return separatorPenalty [ breakpoints [ currBreakIdx ] ! . tag ] + devScale * diff ** 2 ;
74147 } ;
75148
76149 // Forward DP Recurrence Relation
0 commit comments