speaker-verification
audio
nemotron
tyger
experimental
File size: 3,023 Bytes
b21cfae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Timestamp mapping, independent of network arrival time. Apache-2.0 integration.
export class PlaybackClock {
  constructor(context){this.context=context;this.segments=[];this.inputLatency=null;this.acousticLatency=0;this.delay=0;this.method='unavailable';this.missing=[];}
  observe(now=performance.now()){
    const c=this.context,t=c.getOutputTimestamp?.();
    const plausible=t&&t.contextTime>0&&t.performanceTime>0&&Math.abs(now-t.performanceTime)<1500;
    const presented=plausible?t.contextTime+(now-t.performanceTime)/1000:NaN;
    const measured=c.currentTime-presented;
    if(plausible&&measured>=-0.015&&measured<1.5){this.delay=Math.max(0,measured);this.method='output timestamp';}
    else{this.delay=Math.max(0,(c.baseLatency||0)+(c.outputLatency||0));this.method='reported latency fallback';}
    this.missing=[];
    if(this.inputLatency===null)this.missing.push('input latency');
    if(!plausible&&!c.outputLatency)this.missing.push('output timestamp/latency');
    return this.delay;
  }
  outputHead(now=performance.now()){this.observe(now);return this.context.currentTime-this.delay;}
  position(now=performance.now()){
    const head=this.outputHead(now),s=this.segments.find(v=>v.t0<=head&&head<v.t1);
    return s?{id:s.id,sample:s.first+Math.floor((head-s.t0)*24000)}:null;
  }
  heardTime(captureTime,now=performance.now()){
    this.observe(now);return captureTime-this.delay-(this.inputLatency||0)-this.acousticLatency;
  }
  add(id,t0,count,first){this.segments.push({id,t0,t1:t0+count/24000,first});}
  tag(captureTime,now=performance.now()){
    const heard=this.heardTime(captureTime,now);
    const segment=this.segments.find(s=>s.t0<=heard&&heard<s.t1);
    this.segments=this.segments.filter(s=>s.t1>this.context.currentTime-12);
    return segment?[segment.id,segment.first+Math.floor((heard-segment.t0)*24000)]:[0,-1];
  }
  pieces(captureTime,count,rate=16000,now=performance.now()){
    const heard=this.heardTime(captureTime,now),cuts=new Set([0,count]);
    for(const s of this.segments)for(const edge of [s.t0,s.t1]){
      const index=Math.ceil((edge-heard)*rate-1e-7);if(index>0&&index<count)cuts.add(index);
    }
    const positions=[...cuts].sort((a,b)=>a-b),out=[];
    for(let j=0;j<positions.length-1;j++){
      const first=positions[j],end=positions[j+1],at=heard+first/rate;
      const s=this.segments.find(v=>v.t0<=at+1e-9&&at<v.t1-1e-9);
      out.push({first,end,id:s?s.id:0,at:s?s.first+Math.max(0,Math.floor((at-s.t0)*24000+1e-7)):-1});
    }
    this.segments=this.segments.filter(s=>s.t1>this.context.currentTime-12);
    return out;
  }
  stop(at=this.context.currentTime){
    // Keep already played history: delayed mic blocks can still refer to it.
    for(const s of this.segments)s.t1=Math.min(s.t1,at);
    this.segments=this.segments.filter(s=>s.t1>s.t0);
  }
  report(){return {output_ms:this.delay*1000,input_ms:this.inputLatency===null?null:this.inputLatency*1000,
    method:this.method,uncertain:this.missing.length>0,missing:this.missing};}
}