atlas-iac/dockerfiles/hermes-webui-atlas-voice-worklet.js

120 lines
3.6 KiB
JavaScript
Raw Normal View History

/* PCM capture/playback processors for the optional low-latency voice path. */
'use strict';
class AtlasPcmCaptureProcessor extends AudioWorkletProcessor {
constructor(){
super();
this.pending=[];
this.pendingLength=0;
this.port.onmessage=(event)=>{
if((event.data||{}).type!=='flush') return;
this.flush();
this.port.postMessage({type:'flushed'});
};
}
flush(){
if(!this.pendingLength) return;
const samples=new Float32Array(this.pendingLength);
let offset=0;
this.pending.forEach(function(chunk){samples.set(chunk,offset);offset+=chunk.length;});
this.pending=[];
this.pendingLength=0;
this.port.postMessage({type:'pcm',samples:samples.buffer},[samples.buffer]);
}
process(inputs){
const input=inputs[0]&&inputs[0][0];
if(!input||!input.length) return true;
this.pending.push(new Float32Array(input));
this.pendingLength+=input.length;
if(this.pendingLength>=1024){
this.flush();
}
return true;
}
}
class AtlasPcmPlaybackProcessor extends AudioWorkletProcessor {
constructor(){
super();
this.queue=[];
this.queueOffset=0;
this.phase=0;
this.sourceRate=22050;
this.ended=false;
this.cancelled=false;
this.queuedFrames=0;
this.reportCountdown=0;
this.drainedSent=false;
this.port.onmessage=(event)=>{
const data=event.data||{};
if(data.type==='push'&&data.samples){
const pcm=new Int16Array(data.samples);
const samples=new Float32Array(pcm.length);
for(let index=0;index<pcm.length;index+=1) samples[index]=pcm[index]/32768;
this.sourceRate=Number(data.sampleRate)||this.sourceRate;
this.queue.push(samples);
this.queuedFrames+=samples.length;
this.drainedSent=false;
}else if(data.type==='end'){
this.ended=true;
}else if(data.type==='cancel'){
this.cancelled=true;
this.queue=[];
this.queuedFrames=0;
}
};
}
currentSample(){
while(this.queue.length&&this.queueOffset>=this.queue[0].length){
this.queue.shift();
this.queueOffset=0;
}
return this.queue.length?this.queue[0][this.queueOffset]:null;
}
nextSample(){
if(!this.queue.length) return null;
if(this.queueOffset+1<this.queue[0].length) return this.queue[0][this.queueOffset+1];
return this.queue.length>1&&this.queue[1].length?this.queue[1][0]:this.queue[0][this.queueOffset];
}
advance(){
if(!this.queue.length) return;
this.queueOffset+=1;
this.queuedFrames=Math.max(0,this.queuedFrames-1);
if(this.queueOffset>=this.queue[0].length){this.queue.shift();this.queueOffset=0;}
}
process(_inputs,outputs){
const output=outputs[0]&&outputs[0][0];
if(!output) return !this.cancelled;
output.fill(0);
if(this.cancelled) return false;
const ratio=this.sourceRate/sampleRate;
for(let index=0;index<output.length;index+=1){
const left=this.currentSample();
if(left===null) break;
const right=this.nextSample();
output[index]=right===null?left:left+((right-left)*this.phase);
this.phase+=ratio;
while(this.phase>=1){this.advance();this.phase-=1;}
}
this.reportCountdown-=1;
if(this.reportCountdown<=0){
this.reportCountdown=20;
this.port.postMessage({type:'buffer',frames:this.queuedFrames});
}
if(this.ended&&!this.queue.length&&!this.drainedSent){
this.drainedSent=true;
this.port.postMessage({type:'drained'});
}
return true;
}
}
registerProcessor('atlas-pcm-capture',AtlasPcmCaptureProcessor);
registerProcessor('atlas-pcm-playback',AtlasPcmPlaybackProcessor);