2026-10-02 17:02:43 -05:00
|
|
|
// A cumulative, one-credit metadata reporter: never copies or sends audio.
|
|
|
|
|
class Activity extends AudioWorkletProcessor {
|
2026-10-22 21:47:13 -05:00
|
|
|
constructor(options) {
|
2026-10-02 17:02:43 -05:00
|
|
|
super(); this.frames=0; this.inputFrames=0; this.zeroFrames=0;
|
|
|
|
|
this.energy=0; this.peak=0; this.credit=true; this.next=0; this.stopped=false;
|
2026-10-22 21:47:13 -05:00
|
|
|
this.watch=options?.processorOptions?.continuityWatch===true;
|
|
|
|
|
this.startedFrame=this.watch?currentFrame:0;this.lastAckFrame=this.startedFrame;
|
|
|
|
|
this.port.onmessage=({data})=>{
|
|
|
|
|
if(data==='ack'){this.credit=true;if(this.watch)this.lastAckFrame=currentFrame;}
|
|
|
|
|
if(data==='stop')this.stopped=true;
|
|
|
|
|
};
|
2026-10-02 17:02:43 -05:00
|
|
|
}
|
|
|
|
|
process(inputs,outputs) {
|
2026-10-22 21:47:13 -05:00
|
|
|
if(this.stopped)return false;
|
|
|
|
|
if(this.watch) {
|
|
|
|
|
// Long watches require a live UI owner. Withheld ACKs stop work
|
|
|
|
|
// autonomously; they never renew the fixed ten-hour deadline.
|
|
|
|
|
const age=currentFrame-this.startedFrame,unacked=currentFrame-this.lastAckFrame;
|
|
|
|
|
if(!Number.isSafeInteger(age)||age<0||unacked<0||age>=sampleRate*36025||unacked>sampleRate*10) {
|
|
|
|
|
this.stopped=true;this.port.postMessage({watchStopped:true});return false;
|
|
|
|
|
}
|
|
|
|
|
} else if(this.frames >= sampleRate*125)return false;
|
2026-10-02 17:02:43 -05:00
|
|
|
const block=outputs[0]?.[0]?.length ?? 128;
|
|
|
|
|
const samples=inputs[0]?.[0];
|
|
|
|
|
this.frames+=block;
|
|
|
|
|
if(samples) for(const value of samples) {
|
|
|
|
|
this.inputFrames++; this.energy+=value*value;
|
|
|
|
|
this.peak=Math.max(this.peak,Math.abs(value));
|
|
|
|
|
if(value===0)this.zeroFrames++;
|
|
|
|
|
}
|
|
|
|
|
// Output is left at zero: no monitoring/playback or acoustic feedback.
|
|
|
|
|
if(this.credit && this.frames>=this.next) {
|
|
|
|
|
this.port.postMessage({inputFrames:this.inputFrames,processedFrames:this.frames,
|
|
|
|
|
exactZeroFrames:this.zeroFrames,rms:this.inputFrames ? Math.sqrt(this.energy/this.inputFrames) : 0,
|
|
|
|
|
peak:this.peak,sampleRate});
|
|
|
|
|
this.credit=false; this.next=this.frames+sampleRate;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
registerProcessor('av-activity',Activity);
|