// Streaming recognition of the existing synthetic coded-pulse protocol. // No samples/images are retained. A match identifies a diagnostic signal, // not arbitrary content, a transport timestamp or a calibrated browser clock. const tones=[620,780,940,1120,1320,1540,1780,2040,2320,2620,2960,3340,3760,4220,4740,5320]; const colors=[[255,45,45],[0,230,118],[41,121,255],[255,179,0], [216,27,96],[0,188,212],[205,220,57],[126,87,194], [255,112,67],[38,166,154],[255,64,129],[92,107,192], [255,235,59],[105,240,174],[171,71,188],[3,169,244]]; const normalized=colors.map(rgb=>rgb.map(v=>v/Math.max(...rgb))); export function colorCode(rgba) { if(!rgba || rgba.length<16 || rgba.length>4096 || rgba.length%4)return 0; const mean=[0,0,0];const low=[255,255,255],high=[0,0,0]; for(let i=0;i255)return 0; mean[c]+=v;low[c]=Math.min(low[c],v);high[c]=Math.max(high[c],v); } for(let c=0;c<3;c++)mean[c]/=rgba.length/4; const max=Math.max(...mean),min=Math.min(...mean); if(max<70 || max-min<50 || high.some((v,i)=>v-low[i]>45))return 0; const rgb=mean.map(v=>v/max); const distances=normalized.map((p,index)=>({code:index+1, distance:Math.sqrt(p.reduce((sum,v,i)=>sum+(rgb[i]-v)**2,0))})).sort((a,b)=>a.distance-b.distance); if(distances[0].distance>.13 || distances[1].distance-distances[0].distance<.035)return 0; return distances[0].code; } // Two consecutive observations confirm an onset; short detector dropouts do // not manufacture another event. start/end bound the FIRST matching window. export class PulseEdges { constructor(){this.active=0;this.candidate=null;this.off=0;this.last=-Infinity;this.lastStart=-Infinity;} observe(code,startMs,endMs) { if(!Number.isInteger(code)||code<0||code>16||!Number.isFinite(startMs)|| !Number.isFinite(endMs)||startMs<0||endMs=2){this.active=0;this.candidate=null;}return null;} this.off=0; if(code===this.active)return null; if(this.candidate?.code!==code){this.candidate={code,startMs,endMs};return null;} const result=this.candidate;this.active=code;this.candidate=null;return result; } } export class ToneDetector { constructor(rate) { if(!Number.isFinite(rate)||rate<16000||rate>192000)throw new RangeError('sample rate'); this.rate=rate;this.size=Math.round(rate*.01);this.coeff=tones.map(f=>2*Math.cos(2*Math.PI*f/rate)); this.q1=new Float64Array(16);this.q2=new Float64Array(16); this.edges=new PulseEdges();this.expected=null;this.reset(); } reset(){this.q1.fill(0);this.q2.fill(0);this.count=0;this.energy=0;} // frame is AudioWorklet currentFrame, not message delivery time. process(samples,frame) { if(!Number.isSafeInteger(frame)||frame<0||samples.length>8192)throw new RangeError('audio block'); if(this.expected!==null&&frame!==this.expected){this.reset();this.edges=new PulseEdges();} this.expected=frame+samples.length; const events=[]; for(let n=0;nbest){second=best;best=power;code=i+1;}else second=Math.max(second,power); } if(this.energy/this.size<1e-6 || 2*best/(this.size*this.energy)<.75 || best