code wiki / _hdl_build / nx_audio_render.nx

nx_audio_render.nx source

↩ module page · 102 lines · 8706 B

1// nx_audio_render.nx -- SOVEREIGN ZERO-JS audiobook player PAGE (rung 1 of SC33-37). Operator's correction: the 2// player features are NISHI CODE, not browser JS -- the player BRAIN is a server-side Nishi audio daemon 3// (nx_audio_serve, next rung) + our own DSP pre-rendering; the browser is a dumb NATIVE <audio> client. So 4// "browser runs ZERO code" holds (same doctrine as the zero-JS text reader). This emits the static player HTML from 5// reader/<slug>/audio.json: native <audio controls> (SC33 basic playback + the browsers' native speed menu) + 6// chapters + the daemon-API affordances (speed-variant / sleep-timer / bookmark links+forms that target the Nishi 7// daemon -- the "APIs" that do the work server-side). FAIL-CLOSED: scans its OWN bytes for <script and REFUSES to 8// emit any (sovereign zero-JS, like nx_reader_render). Usage: nx_audio_render <slug>. expect_exit: 0 ORIGINAL 9import "nx_syscalls.nx" 10import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 11const K_MAGIC_1024: i64 = 1024 12const K_MAGIC_65536: i64 = 65536 13 14func ar_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 15func ar_p(s: *u8) -> i64 { sys_write(1, s, ar_slen(s)); return 0 } 16// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 17// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 18// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 19// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 20func ar_pn(v: i64) -> i64 { nxi_out(v); return 0 } 21func ensure_dir(path: *u8) -> i64 { __syscall(258, 0-100, path, 0x1ed, 0, 0, 0); return 0 } 22func cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off+i]=s[i];i=i+1} return off+i } 23func aw(o: *u8, p: *i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ o[p[0]]=s[i]; p[0]=p[0]+1; i=i+1 } return 0 } 24// HTML-escape a string into the buffer (so a malicious title can't inject markup) 25func awh(o: *u8, p: *i64, s: *u8) -> i64 { 26 var i: i64=0 27 while s[i]!=(0 as u8) { let c: i64 = s[i] as i64 28 if c==0x3c { aw(o,p,"&lt;" as *u8) } else { if c==0x3e { aw(o,p,"&gt;" as *u8) } else { if c==0x26 { aw(o,p,"&amp;" as *u8) } else { if c==0x22 { aw(o,p,"&quot;" as *u8) } else { o[p[0]]=s[i]; p[0]=p[0]+1 } } } } 29 i=i+1 } 30 return 0 31} 32func jfrom(hay: *u8, hl: i64, start: i64, needle: *u8) -> i64 { 33 let nl: i64 = ar_slen(needle); if nl==0 { return 0-1 } 34 var i: i64 = start; if i<0 { i=0 } 35 while i + nl <= hl { var k: i64=0; var hit: i64=1; while k<nl { if hay[i+k]!=needle[k]{hit=0;k=nl}else{k=k+1} } if hit==1 {return i} i=i+1 } 36 return 0-1 37} 38// extract the JSON string value after key "<key>":" into out (NUL-term). ret len or -1. 39func jval(hay: *u8, hl: i64, key: *u8, out: *u8, cap: i64) -> i64 { 40 let at: i64 = jfrom(hay, hl, 0, key) 41 if at < 0 { out[0]=0 as u8; return 0-1 } 42 var i: i64 = at + ar_slen(key) 43 var o: i64 = 0 44 while i < hl { let c: i64 = hay[i] as i64; if c==0x22 { i=hl } else { if c==0x5c { if i+1<hl { if o<cap-1 { out[o]=hay[i+1]; o=o+1 } i=i+2 } else { i=i+1 } } else { if o<cap-1 { out[o]=hay[i] as u8; o=o+1 } i=i+1 } } } 45 out[o]=0 as u8; return o 46} 47func contains(hay: *u8, hl: i64, needle: *u8) -> i64 { 48 let nl: i64 = ar_slen(needle); if nl==0 { return 0 } 49 var i: i64=0 50 while i+nl<=hl { var k: i64=0; var hit: i64=1; while k<nl { if hay[i+k]!=needle[k]{hit=0;k=nl}else{k=k+1} } if hit==1 {return 1} i=i+1 } 51 return 0 52} 53 54func main(argc: i64, argv: *i64) -> i64 { 55 var slug: *u8 = "nishi_m4b_fixture" as *u8 56 if argc >= 2 { slug = argv[1] as *u8 } 57 let dir: *u8 = sys_mmap(512); var dl: i64 = cat(dir, 0, "knowledge/staging/media/reader/" as *u8); dl = cat(dir, dl, slug); dir[dl]=0 as u8 58 let ajp: *u8 = sys_mmap(K_MAGIC_1024); var al: i64 = cat(ajp, 0, dir as *u8); al = cat(ajp, al, "/audio.json" as *u8); ajp[al]=0 as u8 59 let lp: *i64 = sys_mmap(8) as *i64; lp[0]=0 60 let aj: *u8 = sys_read_file(ajp, lp) 61 if (aj as i64)==0 { ar_p("AUDIO-RENDER-FAIL no audio.json " as *u8); ar_p(ajp); ar_p("\n" as *u8); sys_exit(1); return 1 } 62 let an: i64 = lp[0] 63 64 let title: *u8 = sys_mmap(K_MAGIC_1024); jval(aj, an, "\"title\":\"" as *u8, title, K_MAGIC_1024) 65 let narr: *u8 = sys_mmap(K_MAGIC_1024); jval(aj, an, "\"narrator\":\"" as *u8, narr, K_MAGIC_1024) 66 let album: *u8 = sys_mmap(K_MAGIC_1024); jval(aj, an, "\"album\":\"" as *u8, album, K_MAGIC_1024) 67 let track: *u8 = sys_mmap(512); jval(aj, an, "\"file\":\"" as *u8, track, 512) 68 if track[0]==(0 as u8) { cat(track, 0, "track0.mp3" as *u8) } 69 70 let H: *u8 = sys_mmap(K_MAGIC_65536); let hp: *i64 = sys_mmap(8) as *i64; hp[0]=0 71 aw(H, hp, "<!doctype html><html lang=en><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>" as *u8); awh(H, hp, title) 72 aw(H, hp, "</title><style>:root{--bg:#15110d;--fg:#efe7da;--ac:#c89b5a}body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}main{max-width:46rem;margin:0 auto;padding:1.4rem}h1{font-size:1.5rem;margin:.2rem 0}.nar{color:var(--ac);margin:.2rem 0 1rem}audio{width:100%;margin:.6rem 0}section{border:1px solid #3a2f22;border-radius:.6rem;padding:.8rem 1rem;margin:1rem 0;background:#1d1812}h2{font-size:1rem;color:var(--ac);margin:.2rem 0 .6rem}a.sp,button{display:inline-block;background:#2a2218;color:var(--fg);border:1px solid #4a3c2a;border-radius:.4rem;padding:.35rem .7rem;margin:.15rem;text-decoration:none;cursor:pointer}form{display:inline}.badge{color:#9b8e78;font-size:.82rem;border-top:1px solid #2a2218;margin-top:1.4rem;padding-top:.8rem}</style></head><body><main>" as *u8) 73 aw(H, hp, "<h1>" as *u8); awh(H, hp, title); aw(H, hp, "</h1><p class=nar>" as *u8); awh(H, hp, narr) 74 if album[0]!=(0 as u8) { aw(H, hp, " &middot; " as *u8); awh(H, hp, album) } 75 aw(H, hp, "</p>" as *u8) 76 // native player: SC33 basic playback (+ the browser's native speed menu), zero JS 77 aw(H, hp, "<audio controls preload=metadata src=\"" as *u8); awh(H, hp, track); aw(H, hp, "\"></audio>" as *u8) 78 // the daemon-API affordances (the "Nishi code APIs" -- served server-side by nx_audio_serve) 79 aw(H, hp, "<section><h2>Playback speed</h2><p>" as *u8) 80 aw(H, hp, "<a class=sp href=\"/audio/" as *u8); aw(H, hp, slug); aw(H, hp, "/speed/1.0\">1&times;</a>" as *u8) 81 aw(H, hp, "<a class=sp href=\"/audio/" as *u8); aw(H, hp, slug); aw(H, hp, "/speed/1.25\">1.25&times;</a>" as *u8) 82 aw(H, hp, "<a class=sp href=\"/audio/" as *u8); aw(H, hp, slug); aw(H, hp, "/speed/1.5\">1.5&times;</a>" as *u8) 83 aw(H, hp, "<a class=sp href=\"/audio/" as *u8); aw(H, hp, slug); aw(H, hp, "/speed/2.0\">2&times;</a>" as *u8) 84 aw(H, hp, "</p><p class=badge>Each speed is a variant our DSP renders tempo-shifted (no pitch change); the daemon serves the one you pick. No browser code.</p></section>" as *u8) 85 aw(H, hp, "<section><h2>Sleep timer &middot; bookmark</h2>" as *u8) 86 aw(H, hp, "<form method=get action=\"/audio/" as *u8); aw(H, hp, slug); aw(H, hp, "/sleep\"><label>Stop after <input type=number name=min value=30 min=1 max=240 style=\"width:4rem\"> min</label> <button>Set</button></form> " as *u8) 87 aw(H, hp, "<form method=post action=\"/audio/" as *u8); aw(H, hp, slug); aw(H, hp, "/bookmark\"><button>Bookmark here</button></form>" as *u8) 88 aw(H, hp, "<p class=badge>The Nishi daemon ends the stream after N minutes (sleep) and stores your position (bookmark / cross-device resume) server-side &mdash; no client code.</p></section>" as *u8) 89 aw(H, hp, "<footer class=badge>Sovereign Nishi audiobook player &middot; <b>0 JavaScript</b> &middot; native &lt;audio&gt; only &middot; the player logic runs in the Nishi audio daemon + our DSP, never in your browser.</footer>" as *u8) 90 aw(H, hp, "</main></body></html>" as *u8) 91 let hn: i64 = hp[0] 92 93 // FAIL-CLOSED sovereignty: refuse to emit any browser script 94 if contains(H, hn, "<script" as *u8)==1 { ar_p("AUDIO-RENDER-FAIL sovereignty: <script present\n" as *u8); sys_exit(2); return 2 } 95 96 let outp: *u8 = sys_mmap(K_MAGIC_1024); var ol: i64 = cat(outp, 0, dir as *u8); ol = cat(outp, ol, "/player.html" as *u8); outp[ol]=0 as u8 97 let fd: i64 = sys_openat_wr(outp, 0x1a4) 98 if fd < 0 { ar_p("AUDIO-RENDER-FAIL open out\n" as *u8); sys_exit(1); return 1 } 99 sys_write(fd, H, hn); sys_close(fd) 100 ar_p("AUDIO-RENDER slug=" as *u8); ar_p(slug); ar_p(" bytes=" as *u8); ar_pn(hn); ar_p(" 0-script -> " as *u8); ar_p(outp); ar_p("\n" as *u8) 101 ar_p("AUDIO-RENDER-OK\n" as *u8); sys_exit(0); return 0 102}