code wiki / (root) / nx_lipsync.nx

nx_lipsync.nx source

↩ module page · 72 lines · 3774 B

1// nx_lipsync.nx -- TALKING AVATAR v0 (roadmap R6, the Duix-Avatar side of the operator's ask, sovereign + no 2// neural clone): TEXT -> visemes -> an animated mouth on a rendered face. A viseme = a mouth shape (open height + 3// width). Rule-based letter->viseme (vowels open wide, plosives m/b/p close the lips, silence shuts) -- the honest 4// ceiling: letter-level (not phoneme), rule-based (not Duix's trained clone). Renders a simple 2D face directly to 5// the framebuffer (no rig/SDF) so a synthetic human can TALK. Pairs with the sovereign TTS for audio. ORIGINAL 6import "nx_syscalls.nx" 7const K_MAGIC_1000000: i64 = 1000000 8const K_MAGIC_65536: i64 = 65536 9 10// viseme for a lowercase char -> out2[0]=mouth open HEIGHT (1=shut..16=wide), out2[1]=mouth WIDTH (10..22). 11func ls_viseme(c: i64, out2: *i64) -> i64 { 12 var oh: i64 = 5; var ow: i64 = 14 // default consonant: slightly open 13 if c==97 { oh=16; ow=20 } // a -> wide open 14 if c==101 { oh=8; ow=22 } // e -> wide, flatter 15 if c==105 { oh=6; ow=22 } // i -> wide grin 16 if c==111 { oh=14; ow=12 } // o -> round 17 if c==117 { oh=13; ow=11 } // u -> round/pursed 18 if c==109 { oh=1; ow=15 } // m -> lips shut 19 if c==98 { oh=1; ow=15 } // b -> lips shut 20 if c==112 { oh=1; ow=15 } // p -> lips shut 21 if c==102 { oh=4; ow=16 } // f 22 if c==118 { oh=4; ow=16 } // v 23 if c==119 { oh=10; ow=12 } // w -> round-ish 24 if c==32 { oh=1; ow=13 } // space -> shut (pause) 25 out2[0]=oh; out2[1]=ow 26 return 0 27} 28func ls_lc(c: i64) -> i64 { if c>=65 { if c<=90 { return c+32 } } return c } 29// fill an ellipse (cx,cy) radii (rx,ry) with packed color into fb. 30func ls_ellipse(fb: *i64, w: i64, h: i64, cx: i64, cy: i64, rx: i64, ry: i64, col: i64) -> i64 { 31 if rx < 1 { return 0 } if ry < 1 { return 0 } 32 var y: i64 = cy - ry 33 while y <= cy + ry { 34 if y >= 0 { if y < h { 35 var x: i64 = cx - rx 36 while x <= cx + rx { 37 if x >= 0 { if x < w { 38 let dx: i64 = (x-cx)*1000/rx; let dy: i64 = (y-cy)*1000/ry 39 if dx*dx + dy*dy <= K_MAGIC_1000000 { fb[y*w+x] = col } 40 } } 41 x = x + 1 42 } 43 } } 44 y = y + 1 45 } 46 return 0 47} 48// draw one talking-face frame: head + eyes + a mouth of the given open height/width. 49func ls_face_frame(fb: *i64, w: i64, h: i64, openh: i64, openw: i64) -> i64 { 50 let bg: i64 = 22 + 18*256 + 28*K_MAGIC_65536 51 var i: i64 = 0 52 while i < w*h { fb[i]=bg; i=i+1 } 53 let cx: i64 = w/2; let cy: i64 = h/2 54 ls_ellipse(fb, w, h, cx, cy, w*36/100, h*44/100, 214 + 180*256 + 158*K_MAGIC_65536) // head (skin) 55 ls_ellipse(fb, w, h, cx - w*14/100, cy - h*8/100, w*5/100, h*6/100, 40+30*256+34*K_MAGIC_65536) // eye L 56 ls_ellipse(fb, w, h, cx + w*14/100, cy - h*8/100, w*5/100, h*6/100, 40+30*256+34*K_MAGIC_65536) // eye R 57 ls_ellipse(fb, w, h, cx, cy + h*22/100, w*openw/200, h*openh/200, 120+40*256+52*K_MAGIC_65536) // mouth (open amount) 58 return 0 59} 60// build the viseme timeline for text into vh[maxn],vw[maxn]; returns the count. 61func ls_text_visemes(text: *u8, vh: *i64, vw: *i64, maxn: i64) -> i64 { 62 let o2: *i64 = sys_mmap(16) as *i64 63 var n: i64 = 0 64 var i: i64 = 0 65 while text[i] != (0 as u8) { 66 if n >= maxn { return n } 67 ls_viseme(ls_lc(text[i] & 0xff), o2) 68 vh[n]=o2[0]; vw[n]=o2[1]; n=n+1 69 i = i + 1 70 } 71 return n 72}