120 lines
3.6 KiB
JavaScript
120 lines
3.6 KiB
JavaScript
/* 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);
|