mellon 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,14 +66,15 @@ const stt = new Mellon({
66
66
  ],
67
67
  })
68
68
 
69
- await stt.start() // fetches refs, then opens the mic
69
+ await stt.init() // fetches refs and loads the model
70
+ await stt.start() // opens the mic and listens for all registered refs
70
71
 
71
72
  stt.addEventListener('match', (e) => {
72
73
  console.log(`Detected "${e.detail.name}" (${(e.detail.confidence * 100).toFixed(1)}%)`)
73
74
  })
74
75
  ```
75
76
 
76
- Refs are fetched automatically during `start()`. You can enroll your own words — see [Enrolling custom words](#enrolling-custom-words).
77
+ Refs are fetched and registered during `init()`. `start()` then listens for every registered word automatically. You can enroll your own words — see [Enrolling custom words](#enrolling-custom-words).
77
78
 
78
79
  ---
79
80
 
@@ -106,7 +107,7 @@ class Mellon extends EventTarget {
106
107
  readonly isRunning: boolean
107
108
 
108
109
  init(onProgress?: (pct: number) => void): Promise<void>
109
- start(words?: string[]): Promise<void>
110
+ start(): Promise<void>
110
111
  stop(): void
111
112
  addCustomWord(refData: RefData): void
112
113
  enrollWord(wordName: string): EnrollmentSession
@@ -124,7 +125,6 @@ class Mellon extends EventTarget {
124
125
  | Option | Type | Default | Description |
125
126
  |---|---|---|---|
126
127
  | `refs` | `(string \| RefData)[]` | `[]` | Refs to preload — URL strings are fetched during `init()` |
127
- | `words` | `string[]` | `[]` | Subset of loaded refs to activate (defaults to all loaded refs) |
128
128
  | `threshold` | `number` | `0.65` | Detection threshold (0–1) |
129
129
  | `relaxationMs` | `number` | `2000` | Min ms between match events |
130
130
  | `inferenceGapMs` | `number` | `300` | Min ms between inference runs |
@@ -259,6 +259,11 @@ Cross-Origin-Embedder-Policy: require-corp
259
259
 
260
260
  ---
261
261
 
262
+ ## Science behind the software
263
+
264
+ If you're interested, check out [this paper](https://arxiv.org/pdf/2111.00379).
265
+
266
+
262
267
  ## License
263
268
 
264
269
  MIT
package/dist/mellon.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Kt="0.0.9",Yt=[1,1,149,64],Qt=`https://cdn.jsdelivr.net/npm/mellon@${Kt}/dist/assets`,st={assetsPath:`${Qt}`};let I=null,q=null,tt=null;function Vt({assetsPath:i}={}){i!==void 0&&(st.assetsPath=i),I=null,q=null,tt=null}async function Xt(i){return I?(i==null||i(1),I):q||(q=(async()=>{const r=st.assetsPath.endsWith("/")?st.assetsPath:st.assetsPath+"/",t=r+"ort.all.min.mjs",s=r+"model.onnx";tt=await new Function("url","return import(url)")(t),tt.env.wasm.wasmPaths=r;const e=await fetch(s);if(!e.ok)throw new Error(`Failed to fetch model: ${e.status}`);const a=parseInt(e.headers.get("content-length")||"0",10),n=e.body.getReader(),c=[];let l=0;for(;;){const{done:m,value:_}=await n.read();if(m)break;c.push(_),l+=_.byteLength,a>0&&(i==null||i(l/a))}const h=new Uint8Array(l);let d=0;for(const m of c)h.set(m,d),d+=m.byteLength;return I=await tt.InferenceSession.create(h.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),i==null||i(1),I})(),q)}async function Nt(i){if(!I)throw new Error("Model not loaded — call loadModel() first");const r=new tt.Tensor("float32",i,Yt),t=await I.run({input:r}),s=Object.keys(t)[0];return t[s].data}function Zt(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var ft,At;function Ot(){if(At)return ft;At=1;function i(r){if(this.size=r|0,this.size<=1||(this.size&this.size-1)!==0)throw new Error("FFT size must be a power of two and bigger than 1");this._csize=r<<1;for(var t=new Array(this.size*2),s=0;s<t.length;s+=2){const l=Math.PI*s/this.size;t[s]=Math.cos(l),t[s+1]=-Math.sin(l)}this.table=t;for(var o=0,e=1;this.size>e;e<<=1)o++;this._width=o%2===0?o-1:o,this._bitrev=new Array(1<<this._width);for(var a=0;a<this._bitrev.length;a++){this._bitrev[a]=0;for(var n=0;n<this._width;n+=2){var c=this._width-n-2;this._bitrev[a]|=(a>>>n&3)<<c}}this._out=null,this._data=null,this._inv=0}return ft=i,i.prototype.fromComplexArray=function(t,s){for(var o=s||new Array(t.length>>>1),e=0;e<t.length;e+=2)o[e>>>1]=t[e];return o},i.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var s=0;s<t.length;s++)t[s]=0;return t},i.prototype.toComplexArray=function(t,s){for(var o=s||this.createComplexArray(),e=0;e<o.length;e+=2)o[e]=t[e>>>1],o[e+1]=0;return o},i.prototype.completeSpectrum=function(t){for(var s=this._csize,o=s>>>1,e=2;e<o;e+=2)t[s-e]=t[e],t[s-e+1]=-t[e+1]},i.prototype.transform=function(t,s){if(t===s)throw new Error("Input and output buffers must be different");this._out=t,this._data=s,this._inv=0,this._transform4(),this._out=null,this._data=null},i.prototype.realTransform=function(t,s){if(t===s)throw new Error("Input and output buffers must be different");this._out=t,this._data=s,this._inv=0,this._realTransform4(),this._out=null,this._data=null},i.prototype.inverseTransform=function(t,s){if(t===s)throw new Error("Input and output buffers must be different");this._out=t,this._data=s,this._inv=1,this._transform4();for(var o=0;o<t.length;o++)t[o]/=this.size;this._out=null,this._data=null},i.prototype._transform4=function(){var t=this._out,s=this._csize,o=this._width,e=1<<o,a=s/e<<1,n,c,l=this._bitrev;if(a===4)for(n=0,c=0;n<s;n+=a,c++){const u=l[c];this._singleTransform2(n,u,e)}else for(n=0,c=0;n<s;n+=a,c++){const u=l[c];this._singleTransform4(n,u,e)}var h=this._inv?-1:1,d=this.table;for(e>>=2;e>=2;e>>=2){a=s/e<<1;var m=a>>>2;for(n=0;n<s;n+=a)for(var _=n+m,g=n,f=0;g<_;g+=2,f+=e){const u=g,p=u+m,v=p+m,w=v+m,b=t[u],A=t[u+1],E=t[p],y=t[p+1],F=t[v],C=t[v+1],M=t[w],T=t[w+1],x=b,R=A,z=d[f],S=h*d[f+1],N=E*z-y*S,k=E*S+y*z,L=d[2*f],P=h*d[2*f+1],G=F*L-C*P,H=F*P+C*L,J=d[3*f],K=h*d[3*f+1],Y=M*J-T*K,Q=M*K+T*J,V=x+G,W=R+H,B=x-G,X=R-H,Z=N+Y,U=k+Q,$=h*(N-Y),O=h*(k-Q),et=V+Z,at=W+U,it=V-Z,ct=W-U,lt=B+O,ht=X-$,dt=B-O,ut=X+$;t[u]=et,t[u+1]=at,t[p]=lt,t[p+1]=ht,t[v]=it,t[v+1]=ct,t[w]=dt,t[w+1]=ut}}},i.prototype._singleTransform2=function(t,s,o){const e=this._out,a=this._data,n=a[s],c=a[s+1],l=a[s+o],h=a[s+o+1],d=n+l,m=c+h,_=n-l,g=c-h;e[t]=d,e[t+1]=m,e[t+2]=_,e[t+3]=g},i.prototype._singleTransform4=function(t,s,o){const e=this._out,a=this._data,n=this._inv?-1:1,c=o*2,l=o*3,h=a[s],d=a[s+1],m=a[s+o],_=a[s+o+1],g=a[s+c],f=a[s+c+1],u=a[s+l],p=a[s+l+1],v=h+g,w=d+f,b=h-g,A=d-f,E=m+u,y=_+p,F=n*(m-u),C=n*(_-p),M=v+E,T=w+y,x=b+C,R=A-F,z=v-E,S=w-y,N=b-C,k=A+F;e[t]=M,e[t+1]=T,e[t+2]=x,e[t+3]=R,e[t+4]=z,e[t+5]=S,e[t+6]=N,e[t+7]=k},i.prototype._realTransform4=function(){var t=this._out,s=this._csize,o=this._width,e=1<<o,a=s/e<<1,n,c,l=this._bitrev;if(a===4)for(n=0,c=0;n<s;n+=a,c++){const mt=l[c];this._singleRealTransform2(n,mt>>>1,e>>>1)}else for(n=0,c=0;n<s;n+=a,c++){const mt=l[c];this._singleRealTransform4(n,mt>>>1,e>>>1)}var h=this._inv?-1:1,d=this.table;for(e>>=2;e>=2;e>>=2){a=s/e<<1;var m=a>>>1,_=m>>>1,g=_>>>1;for(n=0;n<s;n+=a)for(var f=0,u=0;f<=g;f+=2,u+=e){var p=n+f,v=p+_,w=v+_,b=w+_,A=t[p],E=t[p+1],y=t[v],F=t[v+1],C=t[w],M=t[w+1],T=t[b],x=t[b+1],R=A,z=E,S=d[u],N=h*d[u+1],k=y*S-F*N,L=y*N+F*S,P=d[2*u],G=h*d[2*u+1],H=C*P-M*G,J=C*G+M*P,K=d[3*u],Y=h*d[3*u+1],Q=T*K-x*Y,V=T*Y+x*K,W=R+H,B=z+J,X=R-H,Z=z-J,U=k+Q,$=L+V,O=h*(k-Q),et=h*(L-V),at=W+U,it=B+$,ct=X+et,lt=Z-O;if(t[p]=at,t[p+1]=it,t[v]=ct,t[v+1]=lt,f===0){var ht=W-U,dt=B-$;t[w]=ht,t[w+1]=dt;continue}if(f!==g){var ut=X,Dt=-Z,jt=W,Wt=-B,Bt=-h*et,Ut=-h*O,$t=-h*$,Lt=-h*U,Pt=ut+Bt,Gt=Dt+Ut,Ht=jt+Lt,Jt=Wt-$t,bt=n+_-f,Et=n+m-f;t[bt]=Pt,t[bt+1]=Gt,t[Et]=Ht,t[Et+1]=Jt}}}},i.prototype._singleRealTransform2=function(t,s,o){const e=this._out,a=this._data,n=a[s],c=a[s+o],l=n+c,h=n-c;e[t]=l,e[t+1]=0,e[t+2]=h,e[t+3]=0},i.prototype._singleRealTransform4=function(t,s,o){const e=this._out,a=this._data,n=this._inv?-1:1,c=o*2,l=o*3,h=a[s],d=a[s+o],m=a[s+c],_=a[s+l],g=h+m,f=h-m,u=d+_,p=n*(d-_),v=g+u,w=f,b=-p,A=g-u,E=f,y=p;e[t]=v,e[t+1]=0,e[t+2]=w,e[t+3]=b,e[t+4]=A,e[t+5]=0,e[t+6]=E,e[t+7]=y},ft}var qt=Ot();const te=Zt(qt),nt=16e3,j=512,D=64,Ft=Math.floor(.025*nt),Ct=Math.floor(.01*nt);function Mt(i){return 2595*Math.log10(1+i/700)}function ee(i){return 700*(10**(i/2595)-1)}function se(){const i=Mt(0),r=Mt(nt/2),t=new Float64Array(D+2);for(let n=0;n<D+2;n++)t[n]=i+n*(r-i)/(D+1);const o=t.map(n=>ee(n)).map(n=>Math.floor((j+1)*n/nt)),e=[],a=Math.floor(j/2)+1;for(let n=0;n<D;n++){const c=new Float32Array(a);for(let l=o[n];l<o[n+1];l++)c[l]=(l-o[n])/(o[n+1]-o[n]);for(let l=o[n+1];l<o[n+2];l++)c[l]=(o[n+2]-l)/(o[n+2]-o[n+1]);e.push(c)}return e}const ne=se(),rt=new te(j),_t=new Float32Array(j),Tt=rt.createComplexArray(),pt=rt.createComplexArray(),xt=new Float32Array(Math.floor(j/2)+1);function kt(i){const r=1+Math.ceil((i.length-Ft)/Ct),t=new Float32Array(r*D),s=Math.floor(j/2)+1;for(let o=0;o<r;o++){const e=o*Ct;_t.fill(0);for(let a=0;a<Ft&&e+a<i.length;a++)_t[a]=i[e+a];rt.toComplexArray(_t,Tt),rt.transform(pt,Tt);for(let a=0;a<s;a++){const n=pt[2*a],c=pt[2*a+1],l=(n*n+c*c)/j;xt[a]=l===0?1e-30:l}for(let a=0;a<D;a++){const n=ne[a];let c=0;for(let l=0;l<s;l++)c+=xt[l]*n[l];t[o*D+a]=Math.log(c===0?1e-30:c)}}return t}function re(i,r){let t=0;for(let s=0;s<i.length;s++)t+=i[s]*r[s];return(t+1)/2}function oe(i,r){let t=0;for(const s of r){const o=re(i,s);o>t&&(t=o)}return t}class Rt extends EventTarget{constructor({name:r,refEmbeddings:t,threshold:s=.65,relaxationMs:o=2e3,inferenceGapMs:e=300}){super(),this.name=r,this.refEmbeddings=t,this.threshold=s,this.relaxationMs=o,this.inferenceGapMs=e,this._lastDetectionAt=0,this._lastInferenceAt=0,this._lastScore=0}get lastScore(){return this._lastScore}async scoreFrame(r){const t=Date.now();if(t-this._lastInferenceAt<this.inferenceGapMs)return null;this._lastInferenceAt=t;const s=kt(r),o=await Nt(s),e=oe(o,this.refEmbeddings);return this._lastScore=e,e>=this.threshold&&t-this._lastDetectionAt>=this.relaxationMs&&(this._lastDetectionAt=t,this.dispatchEvent(new CustomEvent("match",{detail:{name:this.name,confidence:e,timestamp:t}}))),e}}const zt=16e3,ae=1500,vt=24e3;function St(i){if(i.length===vt)return i;const r=new Float32Array(vt);return r.set(i.subarray(0,vt)),r}class It extends EventTarget{constructor(r){super(),this.wordName=r.trim().toLowerCase(),this.samples=[]}get sampleCount(){return this.samples.length}async recordSample(){const r=await navigator.mediaDevices.getUserMedia({audio:!0});return new Promise((t,s)=>{const o=new AudioContext({sampleRate:zt}),e=new MediaRecorder(r),a=[];this.dispatchEvent(new CustomEvent("recording-start")),e.ondataavailable=n=>{n.data.size>0&&a.push(n.data)},e.onstop=async()=>{r.getTracks().forEach(n=>n.stop());try{const c=await new Blob(a,{type:"audio/webm"}).arrayBuffer(),l=await o.decodeAudioData(c);await o.close();const h=l.getChannelData(0),d=St(new Float32Array(h)),m=this._push(d,`Recorded #${this.samples.length}`);t(m)}catch(n){await o.close().catch(()=>{}),s(n)}},e.start(),setTimeout(()=>e.stop(),ae)})}async addAudioFile(r){const t=await r.arrayBuffer(),s=new AudioContext({sampleRate:zt}),o=await s.decodeAudioData(t);await s.close();const e=o.getChannelData(0),a=St(new Float32Array(e));return this._push(a,r.name)}removeSample(r){this.samples.splice(r,1),this.dispatchEvent(new CustomEvent("samples-changed",{detail:{count:this.samples.length}}))}clearSamples(){this.samples=[],this.dispatchEvent(new CustomEvent("samples-changed",{detail:{count:0}}))}async generateRef(){if(this.samples.length<3)throw new Error(`Need at least 3 samples (currently have ${this.samples.length})`);this.dispatchEvent(new CustomEvent("generating",{detail:{total:this.samples.length}}));const r=[];for(let t=0;t<this.samples.length;t++){const s=kt(this.samples[t].audioBuffer),o=await Nt(s);r.push(Array.from(o)),this.dispatchEvent(new CustomEvent("progress",{detail:{done:t+1,total:this.samples.length}}))}return{word_name:this.wordName,model_type:"resnet_50_arc",embeddings:r}}_push(r,t){this.samples.push({audioBuffer:r,name:t});const s=this.samples.length;return this.dispatchEvent(new CustomEvent("sample-added",{detail:{count:s,name:t}})),s}}const ie=`/**
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Kt="0.0.10",Yt=[1,1,149,64],Qt=`https://cdn.jsdelivr.net/npm/mellon@${Kt}/dist/assets`,st={assetsPath:`${Qt}`};let I=null,q=null,tt=null;function Vt({assetsPath:a}={}){a!==void 0&&(st.assetsPath=a),I=null,q=null,tt=null}async function Xt(a){return I?(a==null||a(1),I):q||(q=(async()=>{const n=st.assetsPath.endsWith("/")?st.assetsPath:st.assetsPath+"/",t=n+"ort.all.min.mjs",e=n+"model.onnx";tt=await new Function("url","return import(url)")(t),tt.env.wasm.wasmPaths=n;const s=await fetch(e);if(!s.ok)throw new Error(`Failed to fetch model: ${s.status}`);const i=parseInt(s.headers.get("content-length")||"0",10),o=s.body.getReader(),c=[];let l=0;for(;;){const{done:m,value:_}=await o.read();if(m)break;c.push(_),l+=_.byteLength,i>0&&(a==null||a(l/i))}const h=new Uint8Array(l);let d=0;for(const m of c)h.set(m,d),d+=m.byteLength;return I=await tt.InferenceSession.create(h.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),a==null||a(1),I})(),q)}async function Nt(a){if(!I)throw new Error("Model not loaded — call loadModel() first");const n=new tt.Tensor("float32",a,Yt),t=await I.run({input:n}),e=Object.keys(t)[0];return t[e].data}function Zt(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var ft,At;function Ot(){if(At)return ft;At=1;function a(n){if(this.size=n|0,this.size<=1||(this.size&this.size-1)!==0)throw new Error("FFT size must be a power of two and bigger than 1");this._csize=n<<1;for(var t=new Array(this.size*2),e=0;e<t.length;e+=2){const l=Math.PI*e/this.size;t[e]=Math.cos(l),t[e+1]=-Math.sin(l)}this.table=t;for(var r=0,s=1;this.size>s;s<<=1)r++;this._width=r%2===0?r-1:r,this._bitrev=new Array(1<<this._width);for(var i=0;i<this._bitrev.length;i++){this._bitrev[i]=0;for(var o=0;o<this._width;o+=2){var c=this._width-o-2;this._bitrev[i]|=(i>>>o&3)<<c}}this._out=null,this._data=null,this._inv=0}return ft=a,a.prototype.fromComplexArray=function(t,e){for(var r=e||new Array(t.length>>>1),s=0;s<t.length;s+=2)r[s>>>1]=t[s];return r},a.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var e=0;e<t.length;e++)t[e]=0;return t},a.prototype.toComplexArray=function(t,e){for(var r=e||this.createComplexArray(),s=0;s<r.length;s+=2)r[s]=t[s>>>1],r[s+1]=0;return r},a.prototype.completeSpectrum=function(t){for(var e=this._csize,r=e>>>1,s=2;s<r;s+=2)t[e-s]=t[s],t[e-s+1]=-t[s+1]},a.prototype.transform=function(t,e){if(t===e)throw new Error("Input and output buffers must be different");this._out=t,this._data=e,this._inv=0,this._transform4(),this._out=null,this._data=null},a.prototype.realTransform=function(t,e){if(t===e)throw new Error("Input and output buffers must be different");this._out=t,this._data=e,this._inv=0,this._realTransform4(),this._out=null,this._data=null},a.prototype.inverseTransform=function(t,e){if(t===e)throw new Error("Input and output buffers must be different");this._out=t,this._data=e,this._inv=1,this._transform4();for(var r=0;r<t.length;r++)t[r]/=this.size;this._out=null,this._data=null},a.prototype._transform4=function(){var t=this._out,e=this._csize,r=this._width,s=1<<r,i=e/s<<1,o,c,l=this._bitrev;if(i===4)for(o=0,c=0;o<e;o+=i,c++){const u=l[c];this._singleTransform2(o,u,s)}else for(o=0,c=0;o<e;o+=i,c++){const u=l[c];this._singleTransform4(o,u,s)}var h=this._inv?-1:1,d=this.table;for(s>>=2;s>=2;s>>=2){i=e/s<<1;var m=i>>>2;for(o=0;o<e;o+=i)for(var _=o+m,g=o,f=0;g<_;g+=2,f+=s){const u=g,p=u+m,v=p+m,w=v+m,b=t[u],A=t[u+1],E=t[p],y=t[p+1],F=t[v],C=t[v+1],M=t[w],T=t[w+1],x=b,R=A,S=d[f],z=h*d[f+1],N=E*S-y*z,k=E*z+y*S,P=d[2*f],L=h*d[2*f+1],G=F*P-C*L,H=F*L+C*P,J=d[3*f],K=h*d[3*f+1],Y=M*J-T*K,Q=M*K+T*J,V=x+G,W=R+H,B=x-G,X=R-H,Z=N+Y,U=k+Q,$=h*(N-Y),O=h*(k-Q),et=V+Z,at=W+U,it=V-Z,ct=W-U,lt=B+O,ht=X-$,dt=B-O,ut=X+$;t[u]=et,t[u+1]=at,t[p]=lt,t[p+1]=ht,t[v]=it,t[v+1]=ct,t[w]=dt,t[w+1]=ut}}},a.prototype._singleTransform2=function(t,e,r){const s=this._out,i=this._data,o=i[e],c=i[e+1],l=i[e+r],h=i[e+r+1],d=o+l,m=c+h,_=o-l,g=c-h;s[t]=d,s[t+1]=m,s[t+2]=_,s[t+3]=g},a.prototype._singleTransform4=function(t,e,r){const s=this._out,i=this._data,o=this._inv?-1:1,c=r*2,l=r*3,h=i[e],d=i[e+1],m=i[e+r],_=i[e+r+1],g=i[e+c],f=i[e+c+1],u=i[e+l],p=i[e+l+1],v=h+g,w=d+f,b=h-g,A=d-f,E=m+u,y=_+p,F=o*(m-u),C=o*(_-p),M=v+E,T=w+y,x=b+C,R=A-F,S=v-E,z=w-y,N=b-C,k=A+F;s[t]=M,s[t+1]=T,s[t+2]=x,s[t+3]=R,s[t+4]=S,s[t+5]=z,s[t+6]=N,s[t+7]=k},a.prototype._realTransform4=function(){var t=this._out,e=this._csize,r=this._width,s=1<<r,i=e/s<<1,o,c,l=this._bitrev;if(i===4)for(o=0,c=0;o<e;o+=i,c++){const mt=l[c];this._singleRealTransform2(o,mt>>>1,s>>>1)}else for(o=0,c=0;o<e;o+=i,c++){const mt=l[c];this._singleRealTransform4(o,mt>>>1,s>>>1)}var h=this._inv?-1:1,d=this.table;for(s>>=2;s>=2;s>>=2){i=e/s<<1;var m=i>>>1,_=m>>>1,g=_>>>1;for(o=0;o<e;o+=i)for(var f=0,u=0;f<=g;f+=2,u+=s){var p=o+f,v=p+_,w=v+_,b=w+_,A=t[p],E=t[p+1],y=t[v],F=t[v+1],C=t[w],M=t[w+1],T=t[b],x=t[b+1],R=A,S=E,z=d[u],N=h*d[u+1],k=y*z-F*N,P=y*N+F*z,L=d[2*u],G=h*d[2*u+1],H=C*L-M*G,J=C*G+M*L,K=d[3*u],Y=h*d[3*u+1],Q=T*K-x*Y,V=T*Y+x*K,W=R+H,B=S+J,X=R-H,Z=S-J,U=k+Q,$=P+V,O=h*(k-Q),et=h*(P-V),at=W+U,it=B+$,ct=X+et,lt=Z-O;if(t[p]=at,t[p+1]=it,t[v]=ct,t[v+1]=lt,f===0){var ht=W-U,dt=B-$;t[w]=ht,t[w+1]=dt;continue}if(f!==g){var ut=X,Dt=-Z,jt=W,Wt=-B,Bt=-h*et,Ut=-h*O,$t=-h*$,Pt=-h*U,Lt=ut+Bt,Gt=Dt+Ut,Ht=jt+Pt,Jt=Wt-$t,bt=o+_-f,Et=o+m-f;t[bt]=Lt,t[bt+1]=Gt,t[Et]=Ht,t[Et+1]=Jt}}}},a.prototype._singleRealTransform2=function(t,e,r){const s=this._out,i=this._data,o=i[e],c=i[e+r],l=o+c,h=o-c;s[t]=l,s[t+1]=0,s[t+2]=h,s[t+3]=0},a.prototype._singleRealTransform4=function(t,e,r){const s=this._out,i=this._data,o=this._inv?-1:1,c=r*2,l=r*3,h=i[e],d=i[e+r],m=i[e+c],_=i[e+l],g=h+m,f=h-m,u=d+_,p=o*(d-_),v=g+u,w=f,b=-p,A=g-u,E=f,y=p;s[t]=v,s[t+1]=0,s[t+2]=w,s[t+3]=b,s[t+4]=A,s[t+5]=0,s[t+6]=E,s[t+7]=y},ft}var qt=Ot();const te=Zt(qt),nt=16e3,j=512,D=64,Ft=Math.floor(.025*nt),Ct=Math.floor(.01*nt);function Mt(a){return 2595*Math.log10(1+a/700)}function ee(a){return 700*(10**(a/2595)-1)}function se(){const a=Mt(0),n=Mt(nt/2),t=new Float64Array(D+2);for(let o=0;o<D+2;o++)t[o]=a+o*(n-a)/(D+1);const r=t.map(o=>ee(o)).map(o=>Math.floor((j+1)*o/nt)),s=[],i=Math.floor(j/2)+1;for(let o=0;o<D;o++){const c=new Float32Array(i);for(let l=r[o];l<r[o+1];l++)c[l]=(l-r[o])/(r[o+1]-r[o]);for(let l=r[o+1];l<r[o+2];l++)c[l]=(r[o+2]-l)/(r[o+2]-r[o+1]);s.push(c)}return s}const ne=se(),rt=new te(j),_t=new Float32Array(j),Tt=rt.createComplexArray(),pt=rt.createComplexArray(),xt=new Float32Array(Math.floor(j/2)+1);function kt(a){const n=1+Math.ceil((a.length-Ft)/Ct),t=new Float32Array(n*D),e=Math.floor(j/2)+1;for(let r=0;r<n;r++){const s=r*Ct;_t.fill(0);for(let i=0;i<Ft&&s+i<a.length;i++)_t[i]=a[s+i];rt.toComplexArray(_t,Tt),rt.transform(pt,Tt);for(let i=0;i<e;i++){const o=pt[2*i],c=pt[2*i+1],l=(o*o+c*c)/j;xt[i]=l===0?1e-30:l}for(let i=0;i<D;i++){const o=ne[i];let c=0;for(let l=0;l<e;l++)c+=xt[l]*o[l];t[r*D+i]=Math.log(c===0?1e-30:c)}}return t}function re(a,n){let t=0;for(let e=0;e<a.length;e++)t+=a[e]*n[e];return(t+1)/2}function oe(a,n){let t=0;for(const e of n){const r=re(a,e);r>t&&(t=r)}return t}class Rt extends EventTarget{constructor({name:n,refEmbeddings:t,threshold:e=.65,relaxationMs:r=2e3,inferenceGapMs:s=300}){super(),this.name=n,this.refEmbeddings=t,this.threshold=e,this.relaxationMs=r,this.inferenceGapMs=s,this._lastDetectionAt=0,this._lastInferenceAt=0,this._lastScore=0}get lastScore(){return this._lastScore}async scoreFrame(n){const t=Date.now();if(t-this._lastInferenceAt<this.inferenceGapMs)return null;this._lastInferenceAt=t;const e=kt(n),r=await Nt(e),s=oe(r,this.refEmbeddings);return this._lastScore=s,s>=this.threshold&&t-this._lastDetectionAt>=this.relaxationMs&&(this._lastDetectionAt=t,this.dispatchEvent(new CustomEvent("match",{detail:{name:this.name,confidence:s,timestamp:t}}))),s}}const St=16e3,ae=1500,vt=24e3;function zt(a){if(a.length===vt)return a;const n=new Float32Array(vt);return n.set(a.subarray(0,vt)),n}class It extends EventTarget{constructor(n){super(),this.wordName=n.trim().toLowerCase(),this.samples=[]}get sampleCount(){return this.samples.length}async recordSample(){const n=await navigator.mediaDevices.getUserMedia({audio:!0});return new Promise((t,e)=>{const r=new AudioContext({sampleRate:St}),s=new MediaRecorder(n),i=[];this.dispatchEvent(new CustomEvent("recording-start")),s.ondataavailable=o=>{o.data.size>0&&i.push(o.data)},s.onstop=async()=>{n.getTracks().forEach(o=>o.stop());try{const c=await new Blob(i,{type:"audio/webm"}).arrayBuffer(),l=await r.decodeAudioData(c);await r.close();const h=l.getChannelData(0),d=zt(new Float32Array(h)),m=this._push(d,`Recorded #${this.samples.length}`);t(m)}catch(o){await r.close().catch(()=>{}),e(o)}},s.start(),setTimeout(()=>s.stop(),ae)})}async addAudioFile(n){const t=await n.arrayBuffer(),e=new AudioContext({sampleRate:St}),r=await e.decodeAudioData(t);await e.close();const s=r.getChannelData(0),i=zt(new Float32Array(s));return this._push(i,n.name)}removeSample(n){this.samples.splice(n,1),this.dispatchEvent(new CustomEvent("samples-changed",{detail:{count:this.samples.length}}))}clearSamples(){this.samples=[],this.dispatchEvent(new CustomEvent("samples-changed",{detail:{count:0}}))}async generateRef(){if(this.samples.length<3)throw new Error(`Need at least 3 samples (currently have ${this.samples.length})`);this.dispatchEvent(new CustomEvent("generating",{detail:{total:this.samples.length}}));const n=[];for(let t=0;t<this.samples.length;t++){const e=kt(this.samples[t].audioBuffer),r=await Nt(e);n.push(Array.from(r)),this.dispatchEvent(new CustomEvent("progress",{detail:{done:t+1,total:this.samples.length}}))}return{word_name:this.wordName,model_type:"resnet_50_arc",embeddings:n}}_push(n,t){this.samples.push({audioBuffer:n,name:t});const e=this.samples.length;return this.dispatchEvent(new CustomEvent("sample-added",{detail:{count:e,name:t}})),e}}const ie=`/**
2
2
  * public/audio-processor.js
3
3
  * AudioWorklet that runs at 16 kHz and continuously emits the last
4
4
  * 1.5-second window (24 000 samples) via a circular buffer.
@@ -35,4 +35,4 @@ class AudioProcessor extends AudioWorkletProcessor {
35
35
  }
36
36
 
37
37
  registerProcessor('audio-processor', AudioProcessor)
38
- `;let wt=null;function ce(){if(!wt){const i=new Blob([ie],{type:"application/javascript"});wt=URL.createObjectURL(i)}return wt}const gt="mellon_custom_refs";function ot(){try{const i=localStorage.getItem(gt);return i?JSON.parse(i):[]}catch{return[]}}function le(i){const r=ot().filter(t=>t.word_name!==i.word_name);r.push(i),localStorage.setItem(gt,JSON.stringify(r))}function he(i){const r=ot().filter(t=>t.word_name!==i);localStorage.setItem(gt,JSON.stringify(r))}function de(i){const r=JSON.stringify(i,null,2),t=new Blob([r],{type:"application/json"}),s=URL.createObjectURL(t),o=Object.assign(document.createElement("a"),{href:s,download:`${i.word_name}_ref.json`});document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(s)}async function ue(i){const r=await i.text();let t;try{t=JSON.parse(r)}catch{throw new Error("Invalid JSON")}if(!t.embeddings||!Array.isArray(t.embeddings)||!t.embeddings.length)throw new Error('Missing or empty "embeddings" array');if(!Array.isArray(t.embeddings[0]))throw new Error('"embeddings" must be a 2D array');return t.word_name||(t.word_name=i.name.replace(/_ref\.json$/i,"").replace(/\.json$/i,"")),t}class yt extends EventTarget{constructor(r={}){super(),this._opts={words:r.words??[],refs:r.refs??[],threshold:r.threshold??.65,relaxationMs:r.relaxationMs??2e3,inferenceGapMs:r.inferenceGapMs??300,assetsPath:r.assetsPath},this._refs=new Map,this._detectors=new Map,this._audioCtx=null,this._workletNode=null,this._stream=null,this._initialized=!1,this._running=!1}get isInitialized(){return this._initialized}get isRunning(){return this._running}async init(r){var s;if(this._initialized){r==null||r(1);return}this._opts.assetsPath&&Vt({assetsPath:this._opts.assetsPath});try{await Xt(r)}catch(o){throw this.dispatchEvent(new CustomEvent("error",{detail:{error:o}})),o}const t=await ot();for(const o of this._opts.refs){const e=(s=o.match(/\/([^/]+?)_ref\.json$/))==null?void 0:s[1];if(!!t.find(n=>n.word_name===e))break;try{let n;if(typeof o=="string"){console.log("fetching ref : ",o);const c=await fetch(o);if(!c.ok)throw new Error(`HTTP ${c.status}`);n=await c.json()}else n=o;yt.saveWord(n),this.addCustomWord(n)}catch(n){const c=typeof o=="string"?o:o.word_name;console.warn(`[Mellon] Failed to load ref "${c}": ${n.message}`)}}this._initialized=!0,this.dispatchEvent(new CustomEvent("ready"))}async start(r){this._initialized||await this.init();const t=r??this._opts.words;try{this._stream=await navigator.mediaDevices.getUserMedia({audio:!0})}catch(e){const a=new Error(`Microphone access denied: ${e.message}`);throw this.dispatchEvent(new CustomEvent("error",{detail:{error:a}})),a}this._audioCtx=new AudioContext({sampleRate:16e3});const s=ce();await this._audioCtx.audioWorklet.addModule(s);const o=this._audioCtx.createMediaStreamSource(this._stream);this._workletNode=new AudioWorkletNode(this._audioCtx,"audio-processor"),o.connect(this._workletNode),this._workletNode.connect(this._audioCtx.destination);for(const e of t){const a=this._refs.get(e);if(!a){console.warn(`[Mellon] No reference embeddings for "${e}" — skipping. Call addCustomWord() to register custom words before start().`);continue}const n=new Rt({name:e,refEmbeddings:a.embeddings,threshold:this._opts.threshold,relaxationMs:this._opts.relaxationMs,inferenceGapMs:this._opts.inferenceGapMs});n.addEventListener("match",c=>{this.dispatchEvent(new CustomEvent("match",{detail:c.detail}))}),this._detectors.set(e,n)}this._workletNode.port.onmessage=async e=>{const a=[];for(const n of this._detectors.values())a.push(n.scoreFrame(e.data));await Promise.allSettled(a)},this._running=!0}stop(){this._workletNode&&(this._workletNode.port.onmessage=null,this._workletNode.disconnect(),this._workletNode=null),this._stream&&(this._stream.getTracks().forEach(r=>r.stop()),this._stream=null),this._audioCtx&&(this._audioCtx.close(),this._audioCtx=null),this._detectors.clear(),this._running=!1}addCustomWord(r){if(this._refs.set(r.word_name,r),this._running&&this._workletNode){const t=new Rt({name:r.word_name,refEmbeddings:r.embeddings,threshold:this._opts.threshold,relaxationMs:this._opts.relaxationMs,inferenceGapMs:this._opts.inferenceGapMs});t.addEventListener("match",s=>{this.dispatchEvent(new CustomEvent("match",{detail:s.detail}))}),this._detectors.set(r.word_name,t)}}enrollWord(r){return new It(r)}static loadWords(){return ot()}static saveWord(r){le(r)}static deleteWord(r){he(r)}static importWordFile(r){return ue(r)}static exportWord(r){de(r)}}exports.EnrollmentSession=It;exports.Mellon=yt;
38
+ `;let wt=null;function ce(){if(!wt){const a=new Blob([ie],{type:"application/javascript"});wt=URL.createObjectURL(a)}return wt}const gt="mellon_custom_refs";function ot(){try{const a=localStorage.getItem(gt);return a?JSON.parse(a):[]}catch{return[]}}function le(a){const n=ot().filter(t=>t.word_name!==a.word_name);n.push(a),localStorage.setItem(gt,JSON.stringify(n))}function he(a){const n=ot().filter(t=>t.word_name!==a);localStorage.setItem(gt,JSON.stringify(n))}function de(a){const n=JSON.stringify(a,null,2),t=new Blob([n],{type:"application/json"}),e=URL.createObjectURL(t),r=Object.assign(document.createElement("a"),{href:e,download:`${a.word_name}_ref.json`});document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(e)}async function ue(a){const n=await a.text();let t;try{t=JSON.parse(n)}catch{throw new Error("Invalid JSON")}if(!t.embeddings||!Array.isArray(t.embeddings)||!t.embeddings.length)throw new Error('Missing or empty "embeddings" array');if(!Array.isArray(t.embeddings[0]))throw new Error('"embeddings" must be a 2D array');return t.word_name||(t.word_name=a.name.replace(/_ref\.json$/i,"").replace(/\.json$/i,"")),t}class yt extends EventTarget{constructor(n={}){super(),this._opts={refs:n.refs??[],threshold:n.threshold??.65,relaxationMs:n.relaxationMs??2e3,inferenceGapMs:n.inferenceGapMs??300,assetsPath:n.assetsPath},this._refs=new Map,this._detectors=new Map,this._audioCtx=null,this._workletNode=null,this._stream=null,this._initialized=!1,this._running=!1}get isInitialized(){return this._initialized}get isRunning(){return this._running}async init(n){var e;if(this._initialized){n==null||n(1);return}this._opts.assetsPath&&Vt({assetsPath:this._opts.assetsPath});try{await Xt(n)}catch(r){throw this.dispatchEvent(new CustomEvent("error",{detail:{error:r}})),r}const t=await ot();for(const r of this._opts.refs){const s=(e=r.match(/\/([^/]+?)_ref\.json$/))==null?void 0:e[1],i=t.find(o=>o.word_name===s);try{let o;if(i){this.addCustomWord(i);break}else if(typeof r=="string"){console.log("fetching ref : ",r);const c=await fetch(r);if(!c.ok)throw new Error(`HTTP ${c.status}`);o=await c.json()}else o=r;yt.saveWord(o),this.addCustomWord(o)}catch(o){const c=typeof r=="string"?r:r.word_name;console.warn(`[Mellon] Failed to load ref "${c}": ${o.message}`)}}this._initialized=!0,this.dispatchEvent(new CustomEvent("ready"))}async start(){this._initialized||await this.init();try{this._stream=await navigator.mediaDevices.getUserMedia({audio:!0})}catch(e){const r=new Error(`Microphone access denied: ${e.message}`);throw this.dispatchEvent(new CustomEvent("error",{detail:{error:r}})),r}this._audioCtx=new AudioContext({sampleRate:16e3});const n=ce();await this._audioCtx.audioWorklet.addModule(n);const t=this._audioCtx.createMediaStreamSource(this._stream);this._workletNode=new AudioWorkletNode(this._audioCtx,"audio-processor"),t.connect(this._workletNode),this._workletNode.connect(this._audioCtx.destination);for(const[e,r]of this._refs){const s=new Rt({name:e,refEmbeddings:r.embeddings,threshold:this._opts.threshold,relaxationMs:this._opts.relaxationMs,inferenceGapMs:this._opts.inferenceGapMs});s.addEventListener("match",i=>{this.dispatchEvent(new CustomEvent("match",{detail:i.detail}))}),this._detectors.set(e,s)}this._workletNode.port.onmessage=async e=>{const r=[];for(const s of this._detectors.values())r.push(s.scoreFrame(e.data));await Promise.allSettled(r)},this._running=!0}stop(){this._workletNode&&(this._workletNode.port.onmessage=null,this._workletNode.disconnect(),this._workletNode=null),this._stream&&(this._stream.getTracks().forEach(n=>n.stop()),this._stream=null),this._audioCtx&&(this._audioCtx.close(),this._audioCtx=null),this._detectors.clear(),this._running=!1}addCustomWord(n){if(this._refs.set(n.word_name,n),this._running&&this._workletNode){const t=new Rt({name:n.word_name,refEmbeddings:n.embeddings,threshold:this._opts.threshold,relaxationMs:this._opts.relaxationMs,inferenceGapMs:this._opts.inferenceGapMs});t.addEventListener("match",e=>{this.dispatchEvent(new CustomEvent("match",{detail:e.detail}))}),this._detectors.set(n.word_name,t)}}enrollWord(n){return new It(n)}static loadWords(){return ot()}static saveWord(n){le(n)}static deleteWord(n){he(n)}static importWordFile(n){return ue(n)}static exportWord(n){de(n)}}exports.EnrollmentSession=It;exports.Mellon=yt;
package/dist/mellon.mjs CHANGED
@@ -1,22 +1,22 @@
1
- const Jt = "0.0.9", Kt = [1, 1, 149, 64], Yt = `https://cdn.jsdelivr.net/npm/mellon@${Jt}/dist/assets`, st = {
1
+ const Jt = "0.0.10", Kt = [1, 1, 149, 64], Yt = `https://cdn.jsdelivr.net/npm/mellon@${Jt}/dist/assets`, st = {
2
2
  assetsPath: `${Yt}`
3
3
  };
4
4
  let I = null, q = null, tt = null;
5
- function Qt({ assetsPath: i } = {}) {
6
- i !== void 0 && (st.assetsPath = i), I = null, q = null, tt = null;
5
+ function Qt({ assetsPath: a } = {}) {
6
+ a !== void 0 && (st.assetsPath = a), I = null, q = null, tt = null;
7
7
  }
8
- async function Vt(i) {
9
- return I ? (i == null || i(1), I) : q || (q = (async () => {
10
- const r = st.assetsPath.endsWith("/") ? st.assetsPath : st.assetsPath + "/", t = r + "ort.all.min.mjs", s = r + "model.onnx";
11
- tt = await new Function("url", "return import(url)")(t), tt.env.wasm.wasmPaths = r;
12
- const e = await fetch(s);
13
- if (!e.ok) throw new Error(`Failed to fetch model: ${e.status}`);
14
- const a = parseInt(e.headers.get("content-length") || "0", 10), n = e.body.getReader(), c = [];
8
+ async function Vt(a) {
9
+ return I ? (a == null || a(1), I) : q || (q = (async () => {
10
+ const n = st.assetsPath.endsWith("/") ? st.assetsPath : st.assetsPath + "/", t = n + "ort.all.min.mjs", e = n + "model.onnx";
11
+ tt = await new Function("url", "return import(url)")(t), tt.env.wasm.wasmPaths = n;
12
+ const s = await fetch(e);
13
+ if (!s.ok) throw new Error(`Failed to fetch model: ${s.status}`);
14
+ const i = parseInt(s.headers.get("content-length") || "0", 10), o = s.body.getReader(), c = [];
15
15
  let l = 0;
16
16
  for (; ; ) {
17
- const { done: m, value: _ } = await n.read();
17
+ const { done: m, value: _ } = await o.read();
18
18
  if (m) break;
19
- c.push(_), l += _.byteLength, a > 0 && (i == null || i(l / a));
19
+ c.push(_), l += _.byteLength, i > 0 && (a == null || a(l / i));
20
20
  }
21
21
  const h = new Uint8Array(l);
22
22
  let d = 0;
@@ -25,192 +25,192 @@ async function Vt(i) {
25
25
  return I = await tt.InferenceSession.create(h.buffer, {
26
26
  executionProviders: ["wasm"],
27
27
  graphOptimizationLevel: "all"
28
- }), i == null || i(1), I;
28
+ }), a == null || a(1), I;
29
29
  })(), q);
30
30
  }
31
- async function St(i) {
31
+ async function St(a) {
32
32
  if (!I) throw new Error("Model not loaded — call loadModel() first");
33
- const r = new tt.Tensor("float32", i, Kt), t = await I.run({ input: r }), s = Object.keys(t)[0];
34
- return t[s].data;
33
+ const n = new tt.Tensor("float32", a, Kt), t = await I.run({ input: n }), e = Object.keys(t)[0];
34
+ return t[e].data;
35
35
  }
36
- function Xt(i) {
37
- return i && i.__esModule && Object.prototype.hasOwnProperty.call(i, "default") ? i.default : i;
36
+ function Xt(a) {
37
+ return a && a.__esModule && Object.prototype.hasOwnProperty.call(a, "default") ? a.default : a;
38
38
  }
39
- var ft, At;
39
+ var ft, Et;
40
40
  function Zt() {
41
- if (At) return ft;
42
- At = 1;
43
- function i(r) {
44
- if (this.size = r | 0, this.size <= 1 || (this.size & this.size - 1) !== 0)
41
+ if (Et) return ft;
42
+ Et = 1;
43
+ function a(n) {
44
+ if (this.size = n | 0, this.size <= 1 || (this.size & this.size - 1) !== 0)
45
45
  throw new Error("FFT size must be a power of two and bigger than 1");
46
- this._csize = r << 1;
47
- for (var t = new Array(this.size * 2), s = 0; s < t.length; s += 2) {
48
- const l = Math.PI * s / this.size;
49
- t[s] = Math.cos(l), t[s + 1] = -Math.sin(l);
46
+ this._csize = n << 1;
47
+ for (var t = new Array(this.size * 2), e = 0; e < t.length; e += 2) {
48
+ const l = Math.PI * e / this.size;
49
+ t[e] = Math.cos(l), t[e + 1] = -Math.sin(l);
50
50
  }
51
51
  this.table = t;
52
- for (var o = 0, e = 1; this.size > e; e <<= 1)
53
- o++;
54
- this._width = o % 2 === 0 ? o - 1 : o, this._bitrev = new Array(1 << this._width);
55
- for (var a = 0; a < this._bitrev.length; a++) {
56
- this._bitrev[a] = 0;
57
- for (var n = 0; n < this._width; n += 2) {
58
- var c = this._width - n - 2;
59
- this._bitrev[a] |= (a >>> n & 3) << c;
52
+ for (var r = 0, s = 1; this.size > s; s <<= 1)
53
+ r++;
54
+ this._width = r % 2 === 0 ? r - 1 : r, this._bitrev = new Array(1 << this._width);
55
+ for (var i = 0; i < this._bitrev.length; i++) {
56
+ this._bitrev[i] = 0;
57
+ for (var o = 0; o < this._width; o += 2) {
58
+ var c = this._width - o - 2;
59
+ this._bitrev[i] |= (i >>> o & 3) << c;
60
60
  }
61
61
  }
62
62
  this._out = null, this._data = null, this._inv = 0;
63
63
  }
64
- return ft = i, i.prototype.fromComplexArray = function(t, s) {
65
- for (var o = s || new Array(t.length >>> 1), e = 0; e < t.length; e += 2)
66
- o[e >>> 1] = t[e];
67
- return o;
68
- }, i.prototype.createComplexArray = function() {
64
+ return ft = a, a.prototype.fromComplexArray = function(t, e) {
65
+ for (var r = e || new Array(t.length >>> 1), s = 0; s < t.length; s += 2)
66
+ r[s >>> 1] = t[s];
67
+ return r;
68
+ }, a.prototype.createComplexArray = function() {
69
69
  const t = new Array(this._csize);
70
- for (var s = 0; s < t.length; s++)
71
- t[s] = 0;
70
+ for (var e = 0; e < t.length; e++)
71
+ t[e] = 0;
72
72
  return t;
73
- }, i.prototype.toComplexArray = function(t, s) {
74
- for (var o = s || this.createComplexArray(), e = 0; e < o.length; e += 2)
75
- o[e] = t[e >>> 1], o[e + 1] = 0;
76
- return o;
77
- }, i.prototype.completeSpectrum = function(t) {
78
- for (var s = this._csize, o = s >>> 1, e = 2; e < o; e += 2)
79
- t[s - e] = t[e], t[s - e + 1] = -t[e + 1];
80
- }, i.prototype.transform = function(t, s) {
81
- if (t === s)
73
+ }, a.prototype.toComplexArray = function(t, e) {
74
+ for (var r = e || this.createComplexArray(), s = 0; s < r.length; s += 2)
75
+ r[s] = t[s >>> 1], r[s + 1] = 0;
76
+ return r;
77
+ }, a.prototype.completeSpectrum = function(t) {
78
+ for (var e = this._csize, r = e >>> 1, s = 2; s < r; s += 2)
79
+ t[e - s] = t[s], t[e - s + 1] = -t[s + 1];
80
+ }, a.prototype.transform = function(t, e) {
81
+ if (t === e)
82
82
  throw new Error("Input and output buffers must be different");
83
- this._out = t, this._data = s, this._inv = 0, this._transform4(), this._out = null, this._data = null;
84
- }, i.prototype.realTransform = function(t, s) {
85
- if (t === s)
83
+ this._out = t, this._data = e, this._inv = 0, this._transform4(), this._out = null, this._data = null;
84
+ }, a.prototype.realTransform = function(t, e) {
85
+ if (t === e)
86
86
  throw new Error("Input and output buffers must be different");
87
- this._out = t, this._data = s, this._inv = 0, this._realTransform4(), this._out = null, this._data = null;
88
- }, i.prototype.inverseTransform = function(t, s) {
89
- if (t === s)
87
+ this._out = t, this._data = e, this._inv = 0, this._realTransform4(), this._out = null, this._data = null;
88
+ }, a.prototype.inverseTransform = function(t, e) {
89
+ if (t === e)
90
90
  throw new Error("Input and output buffers must be different");
91
- this._out = t, this._data = s, this._inv = 1, this._transform4();
92
- for (var o = 0; o < t.length; o++)
93
- t[o] /= this.size;
91
+ this._out = t, this._data = e, this._inv = 1, this._transform4();
92
+ for (var r = 0; r < t.length; r++)
93
+ t[r] /= this.size;
94
94
  this._out = null, this._data = null;
95
- }, i.prototype._transform4 = function() {
96
- var t = this._out, s = this._csize, o = this._width, e = 1 << o, a = s / e << 1, n, c, l = this._bitrev;
97
- if (a === 4)
98
- for (n = 0, c = 0; n < s; n += a, c++) {
95
+ }, a.prototype._transform4 = function() {
96
+ var t = this._out, e = this._csize, r = this._width, s = 1 << r, i = e / s << 1, o, c, l = this._bitrev;
97
+ if (i === 4)
98
+ for (o = 0, c = 0; o < e; o += i, c++) {
99
99
  const u = l[c];
100
- this._singleTransform2(n, u, e);
100
+ this._singleTransform2(o, u, s);
101
101
  }
102
102
  else
103
- for (n = 0, c = 0; n < s; n += a, c++) {
103
+ for (o = 0, c = 0; o < e; o += i, c++) {
104
104
  const u = l[c];
105
- this._singleTransform4(n, u, e);
105
+ this._singleTransform4(o, u, s);
106
106
  }
107
107
  var h = this._inv ? -1 : 1, d = this.table;
108
- for (e >>= 2; e >= 2; e >>= 2) {
109
- a = s / e << 1;
110
- var m = a >>> 2;
111
- for (n = 0; n < s; n += a)
112
- for (var _ = n + m, g = n, f = 0; g < _; g += 2, f += e) {
113
- const u = g, p = u + m, v = p + m, w = v + m, b = t[u], E = t[u + 1], A = t[p], y = t[p + 1], F = t[v], C = t[v + 1], M = t[w], T = t[w + 1], x = b, R = E, z = d[f], S = h * d[f + 1], N = A * z - y * S, k = A * S + y * z, L = d[2 * f], P = h * d[2 * f + 1], G = F * L - C * P, H = F * P + C * L, J = d[3 * f], K = h * d[3 * f + 1], Y = M * J - T * K, Q = M * K + T * J, V = x + G, j = R + H, B = x - G, X = R - H, Z = N + Y, U = k + Q, $ = h * (N - Y), O = h * (k - Q), et = V + Z, at = j + U, it = V - Z, ct = j - U, lt = B + O, ht = X - $, dt = B - O, ut = X + $;
108
+ for (s >>= 2; s >= 2; s >>= 2) {
109
+ i = e / s << 1;
110
+ var m = i >>> 2;
111
+ for (o = 0; o < e; o += i)
112
+ for (var _ = o + m, g = o, f = 0; g < _; g += 2, f += s) {
113
+ const u = g, p = u + m, v = p + m, w = v + m, b = t[u], A = t[u + 1], E = t[p], y = t[p + 1], F = t[v], C = t[v + 1], M = t[w], T = t[w + 1], x = b, R = A, z = d[f], S = h * d[f + 1], N = E * z - y * S, k = E * S + y * z, L = d[2 * f], P = h * d[2 * f + 1], G = F * L - C * P, H = F * P + C * L, J = d[3 * f], K = h * d[3 * f + 1], Y = M * J - T * K, Q = M * K + T * J, V = x + G, j = R + H, B = x - G, X = R - H, Z = N + Y, U = k + Q, $ = h * (N - Y), O = h * (k - Q), et = V + Z, at = j + U, it = V - Z, ct = j - U, lt = B + O, ht = X - $, dt = B - O, ut = X + $;
114
114
  t[u] = et, t[u + 1] = at, t[p] = lt, t[p + 1] = ht, t[v] = it, t[v + 1] = ct, t[w] = dt, t[w + 1] = ut;
115
115
  }
116
116
  }
117
- }, i.prototype._singleTransform2 = function(t, s, o) {
118
- const e = this._out, a = this._data, n = a[s], c = a[s + 1], l = a[s + o], h = a[s + o + 1], d = n + l, m = c + h, _ = n - l, g = c - h;
119
- e[t] = d, e[t + 1] = m, e[t + 2] = _, e[t + 3] = g;
120
- }, i.prototype._singleTransform4 = function(t, s, o) {
121
- const e = this._out, a = this._data, n = this._inv ? -1 : 1, c = o * 2, l = o * 3, h = a[s], d = a[s + 1], m = a[s + o], _ = a[s + o + 1], g = a[s + c], f = a[s + c + 1], u = a[s + l], p = a[s + l + 1], v = h + g, w = d + f, b = h - g, E = d - f, A = m + u, y = _ + p, F = n * (m - u), C = n * (_ - p), M = v + A, T = w + y, x = b + C, R = E - F, z = v - A, S = w - y, N = b - C, k = E + F;
122
- e[t] = M, e[t + 1] = T, e[t + 2] = x, e[t + 3] = R, e[t + 4] = z, e[t + 5] = S, e[t + 6] = N, e[t + 7] = k;
123
- }, i.prototype._realTransform4 = function() {
124
- var t = this._out, s = this._csize, o = this._width, e = 1 << o, a = s / e << 1, n, c, l = this._bitrev;
125
- if (a === 4)
126
- for (n = 0, c = 0; n < s; n += a, c++) {
117
+ }, a.prototype._singleTransform2 = function(t, e, r) {
118
+ const s = this._out, i = this._data, o = i[e], c = i[e + 1], l = i[e + r], h = i[e + r + 1], d = o + l, m = c + h, _ = o - l, g = c - h;
119
+ s[t] = d, s[t + 1] = m, s[t + 2] = _, s[t + 3] = g;
120
+ }, a.prototype._singleTransform4 = function(t, e, r) {
121
+ const s = this._out, i = this._data, o = this._inv ? -1 : 1, c = r * 2, l = r * 3, h = i[e], d = i[e + 1], m = i[e + r], _ = i[e + r + 1], g = i[e + c], f = i[e + c + 1], u = i[e + l], p = i[e + l + 1], v = h + g, w = d + f, b = h - g, A = d - f, E = m + u, y = _ + p, F = o * (m - u), C = o * (_ - p), M = v + E, T = w + y, x = b + C, R = A - F, z = v - E, S = w - y, N = b - C, k = A + F;
122
+ s[t] = M, s[t + 1] = T, s[t + 2] = x, s[t + 3] = R, s[t + 4] = z, s[t + 5] = S, s[t + 6] = N, s[t + 7] = k;
123
+ }, a.prototype._realTransform4 = function() {
124
+ var t = this._out, e = this._csize, r = this._width, s = 1 << r, i = e / s << 1, o, c, l = this._bitrev;
125
+ if (i === 4)
126
+ for (o = 0, c = 0; o < e; o += i, c++) {
127
127
  const mt = l[c];
128
- this._singleRealTransform2(n, mt >>> 1, e >>> 1);
128
+ this._singleRealTransform2(o, mt >>> 1, s >>> 1);
129
129
  }
130
130
  else
131
- for (n = 0, c = 0; n < s; n += a, c++) {
131
+ for (o = 0, c = 0; o < e; o += i, c++) {
132
132
  const mt = l[c];
133
- this._singleRealTransform4(n, mt >>> 1, e >>> 1);
133
+ this._singleRealTransform4(o, mt >>> 1, s >>> 1);
134
134
  }
135
135
  var h = this._inv ? -1 : 1, d = this.table;
136
- for (e >>= 2; e >= 2; e >>= 2) {
137
- a = s / e << 1;
138
- var m = a >>> 1, _ = m >>> 1, g = _ >>> 1;
139
- for (n = 0; n < s; n += a)
140
- for (var f = 0, u = 0; f <= g; f += 2, u += e) {
141
- var p = n + f, v = p + _, w = v + _, b = w + _, E = t[p], A = t[p + 1], y = t[v], F = t[v + 1], C = t[w], M = t[w + 1], T = t[b], x = t[b + 1], R = E, z = A, S = d[u], N = h * d[u + 1], k = y * S - F * N, L = y * N + F * S, P = d[2 * u], G = h * d[2 * u + 1], H = C * P - M * G, J = C * G + M * P, K = d[3 * u], Y = h * d[3 * u + 1], Q = T * K - x * Y, V = T * Y + x * K, j = R + H, B = z + J, X = R - H, Z = z - J, U = k + Q, $ = L + V, O = h * (k - Q), et = h * (L - V), at = j + U, it = B + $, ct = X + et, lt = Z - O;
136
+ for (s >>= 2; s >= 2; s >>= 2) {
137
+ i = e / s << 1;
138
+ var m = i >>> 1, _ = m >>> 1, g = _ >>> 1;
139
+ for (o = 0; o < e; o += i)
140
+ for (var f = 0, u = 0; f <= g; f += 2, u += s) {
141
+ var p = o + f, v = p + _, w = v + _, b = w + _, A = t[p], E = t[p + 1], y = t[v], F = t[v + 1], C = t[w], M = t[w + 1], T = t[b], x = t[b + 1], R = A, z = E, S = d[u], N = h * d[u + 1], k = y * S - F * N, L = y * N + F * S, P = d[2 * u], G = h * d[2 * u + 1], H = C * P - M * G, J = C * G + M * P, K = d[3 * u], Y = h * d[3 * u + 1], Q = T * K - x * Y, V = T * Y + x * K, j = R + H, B = z + J, X = R - H, Z = z - J, U = k + Q, $ = L + V, O = h * (k - Q), et = h * (L - V), at = j + U, it = B + $, ct = X + et, lt = Z - O;
142
142
  if (t[p] = at, t[p + 1] = it, t[v] = ct, t[v + 1] = lt, f === 0) {
143
143
  var ht = j - U, dt = B - $;
144
144
  t[w] = ht, t[w + 1] = dt;
145
145
  continue;
146
146
  }
147
147
  if (f !== g) {
148
- var ut = X, It = -Z, Dt = j, Wt = -B, jt = -h * et, Bt = -h * O, Ut = -h * $, $t = -h * U, Lt = ut + jt, Pt = It + Bt, Gt = Dt + $t, Ht = Wt - Ut, yt = n + _ - f, bt = n + m - f;
148
+ var ut = X, It = -Z, Dt = j, Wt = -B, jt = -h * et, Bt = -h * O, Ut = -h * $, $t = -h * U, Lt = ut + jt, Pt = It + Bt, Gt = Dt + $t, Ht = Wt - Ut, yt = o + _ - f, bt = o + m - f;
149
149
  t[yt] = Lt, t[yt + 1] = Pt, t[bt] = Gt, t[bt + 1] = Ht;
150
150
  }
151
151
  }
152
152
  }
153
- }, i.prototype._singleRealTransform2 = function(t, s, o) {
154
- const e = this._out, a = this._data, n = a[s], c = a[s + o], l = n + c, h = n - c;
155
- e[t] = l, e[t + 1] = 0, e[t + 2] = h, e[t + 3] = 0;
156
- }, i.prototype._singleRealTransform4 = function(t, s, o) {
157
- const e = this._out, a = this._data, n = this._inv ? -1 : 1, c = o * 2, l = o * 3, h = a[s], d = a[s + o], m = a[s + c], _ = a[s + l], g = h + m, f = h - m, u = d + _, p = n * (d - _), v = g + u, w = f, b = -p, E = g - u, A = f, y = p;
158
- e[t] = v, e[t + 1] = 0, e[t + 2] = w, e[t + 3] = b, e[t + 4] = E, e[t + 5] = 0, e[t + 6] = A, e[t + 7] = y;
153
+ }, a.prototype._singleRealTransform2 = function(t, e, r) {
154
+ const s = this._out, i = this._data, o = i[e], c = i[e + r], l = o + c, h = o - c;
155
+ s[t] = l, s[t + 1] = 0, s[t + 2] = h, s[t + 3] = 0;
156
+ }, a.prototype._singleRealTransform4 = function(t, e, r) {
157
+ const s = this._out, i = this._data, o = this._inv ? -1 : 1, c = r * 2, l = r * 3, h = i[e], d = i[e + r], m = i[e + c], _ = i[e + l], g = h + m, f = h - m, u = d + _, p = o * (d - _), v = g + u, w = f, b = -p, A = g - u, E = f, y = p;
158
+ s[t] = v, s[t + 1] = 0, s[t + 2] = w, s[t + 3] = b, s[t + 4] = A, s[t + 5] = 0, s[t + 6] = E, s[t + 7] = y;
159
159
  }, ft;
160
160
  }
161
161
  var Ot = Zt();
162
- const qt = /* @__PURE__ */ Xt(Ot), nt = 16e3, W = 512, D = 64, Et = Math.floor(0.025 * nt), Ft = Math.floor(0.01 * nt);
163
- function Ct(i) {
164
- return 2595 * Math.log10(1 + i / 700);
162
+ const qt = /* @__PURE__ */ Xt(Ot), nt = 16e3, W = 512, D = 64, At = Math.floor(0.025 * nt), Ft = Math.floor(0.01 * nt);
163
+ function Ct(a) {
164
+ return 2595 * Math.log10(1 + a / 700);
165
165
  }
166
- function te(i) {
167
- return 700 * (10 ** (i / 2595) - 1);
166
+ function te(a) {
167
+ return 700 * (10 ** (a / 2595) - 1);
168
168
  }
169
169
  function ee() {
170
- const i = Ct(0), r = Ct(nt / 2), t = new Float64Array(D + 2);
171
- for (let n = 0; n < D + 2; n++)
172
- t[n] = i + n * (r - i) / (D + 1);
173
- const o = t.map((n) => te(n)).map((n) => Math.floor((W + 1) * n / nt)), e = [], a = Math.floor(W / 2) + 1;
174
- for (let n = 0; n < D; n++) {
175
- const c = new Float32Array(a);
176
- for (let l = o[n]; l < o[n + 1]; l++) c[l] = (l - o[n]) / (o[n + 1] - o[n]);
177
- for (let l = o[n + 1]; l < o[n + 2]; l++) c[l] = (o[n + 2] - l) / (o[n + 2] - o[n + 1]);
178
- e.push(c);
170
+ const a = Ct(0), n = Ct(nt / 2), t = new Float64Array(D + 2);
171
+ for (let o = 0; o < D + 2; o++)
172
+ t[o] = a + o * (n - a) / (D + 1);
173
+ const r = t.map((o) => te(o)).map((o) => Math.floor((W + 1) * o / nt)), s = [], i = Math.floor(W / 2) + 1;
174
+ for (let o = 0; o < D; o++) {
175
+ const c = new Float32Array(i);
176
+ for (let l = r[o]; l < r[o + 1]; l++) c[l] = (l - r[o]) / (r[o + 1] - r[o]);
177
+ for (let l = r[o + 1]; l < r[o + 2]; l++) c[l] = (r[o + 2] - l) / (r[o + 2] - r[o + 1]);
178
+ s.push(c);
179
179
  }
180
- return e;
180
+ return s;
181
181
  }
182
182
  const se = ee(), rt = new qt(W), _t = new Float32Array(W), Mt = rt.createComplexArray(), pt = rt.createComplexArray(), Tt = new Float32Array(Math.floor(W / 2) + 1);
183
- function Nt(i) {
184
- const r = 1 + Math.ceil((i.length - Et) / Ft), t = new Float32Array(r * D), s = Math.floor(W / 2) + 1;
185
- for (let o = 0; o < r; o++) {
186
- const e = o * Ft;
183
+ function Nt(a) {
184
+ const n = 1 + Math.ceil((a.length - At) / Ft), t = new Float32Array(n * D), e = Math.floor(W / 2) + 1;
185
+ for (let r = 0; r < n; r++) {
186
+ const s = r * Ft;
187
187
  _t.fill(0);
188
- for (let a = 0; a < Et && e + a < i.length; a++)
189
- _t[a] = i[e + a];
188
+ for (let i = 0; i < At && s + i < a.length; i++)
189
+ _t[i] = a[s + i];
190
190
  rt.toComplexArray(_t, Mt), rt.transform(pt, Mt);
191
- for (let a = 0; a < s; a++) {
192
- const n = pt[2 * a], c = pt[2 * a + 1], l = (n * n + c * c) / W;
193
- Tt[a] = l === 0 ? 1e-30 : l;
191
+ for (let i = 0; i < e; i++) {
192
+ const o = pt[2 * i], c = pt[2 * i + 1], l = (o * o + c * c) / W;
193
+ Tt[i] = l === 0 ? 1e-30 : l;
194
194
  }
195
- for (let a = 0; a < D; a++) {
196
- const n = se[a];
195
+ for (let i = 0; i < D; i++) {
196
+ const o = se[i];
197
197
  let c = 0;
198
- for (let l = 0; l < s; l++) c += Tt[l] * n[l];
199
- t[o * D + a] = Math.log(c === 0 ? 1e-30 : c);
198
+ for (let l = 0; l < e; l++) c += Tt[l] * o[l];
199
+ t[r * D + i] = Math.log(c === 0 ? 1e-30 : c);
200
200
  }
201
201
  }
202
202
  return t;
203
203
  }
204
- function ne(i, r) {
204
+ function ne(a, n) {
205
205
  let t = 0;
206
- for (let s = 0; s < i.length; s++) t += i[s] * r[s];
206
+ for (let e = 0; e < a.length; e++) t += a[e] * n[e];
207
207
  return (t + 1) / 2;
208
208
  }
209
- function re(i, r) {
209
+ function re(a, n) {
210
210
  let t = 0;
211
- for (const s of r) {
212
- const o = ne(i, s);
213
- o > t && (t = o);
211
+ for (const e of n) {
212
+ const r = ne(a, e);
213
+ r > t && (t = r);
214
214
  }
215
215
  return t;
216
216
  }
@@ -223,8 +223,8 @@ class xt extends EventTarget {
223
223
  * @param {number} [opts.relaxationMs=2000] Min ms between events
224
224
  * @param {number} [opts.inferenceGapMs=300] Min ms between inferences
225
225
  */
226
- constructor({ name: r, refEmbeddings: t, threshold: s = 0.65, relaxationMs: o = 2e3, inferenceGapMs: e = 300 }) {
227
- super(), this.name = r, this.refEmbeddings = t, this.threshold = s, this.relaxationMs = o, this.inferenceGapMs = e, this._lastDetectionAt = 0, this._lastInferenceAt = 0, this._lastScore = 0;
226
+ constructor({ name: n, refEmbeddings: t, threshold: e = 0.65, relaxationMs: r = 2e3, inferenceGapMs: s = 300 }) {
227
+ super(), this.name = n, this.refEmbeddings = t, this.threshold = e, this.relaxationMs = r, this.inferenceGapMs = s, this._lastDetectionAt = 0, this._lastInferenceAt = 0, this._lastScore = 0;
228
228
  }
229
229
  get lastScore() {
230
230
  return this._lastScore;
@@ -235,26 +235,26 @@ class xt extends EventTarget {
235
235
  * @param {Float32Array} audioBuffer 24 000 samples at 16 kHz
236
236
  * @returns {Promise<number|null>} Similarity score, or null if rate-limited
237
237
  */
238
- async scoreFrame(r) {
238
+ async scoreFrame(n) {
239
239
  const t = Date.now();
240
240
  if (t - this._lastInferenceAt < this.inferenceGapMs) return null;
241
241
  this._lastInferenceAt = t;
242
- const s = Nt(r), o = await St(s), e = re(o, this.refEmbeddings);
243
- return this._lastScore = e, e >= this.threshold && t - this._lastDetectionAt >= this.relaxationMs && (this._lastDetectionAt = t, this.dispatchEvent(new CustomEvent("match", {
244
- detail: { name: this.name, confidence: e, timestamp: t }
245
- }))), e;
242
+ const e = Nt(n), r = await St(e), s = re(r, this.refEmbeddings);
243
+ return this._lastScore = s, s >= this.threshold && t - this._lastDetectionAt >= this.relaxationMs && (this._lastDetectionAt = t, this.dispatchEvent(new CustomEvent("match", {
244
+ detail: { name: this.name, confidence: s, timestamp: t }
245
+ }))), s;
246
246
  }
247
247
  }
248
248
  const Rt = 16e3, oe = 1500, vt = 24e3;
249
- function zt(i) {
250
- if (i.length === vt) return i;
251
- const r = new Float32Array(vt);
252
- return r.set(i.subarray(0, vt)), r;
249
+ function zt(a) {
250
+ if (a.length === vt) return a;
251
+ const n = new Float32Array(vt);
252
+ return n.set(a.subarray(0, vt)), n;
253
253
  }
254
254
  class ae extends EventTarget {
255
255
  /** @param {string} wordName — the wake word label */
256
- constructor(r) {
257
- super(), this.wordName = r.trim().toLowerCase(), this.samples = [];
256
+ constructor(n) {
257
+ super(), this.wordName = n.trim().toLowerCase(), this.samples = [];
258
258
  }
259
259
  get sampleCount() {
260
260
  return this.samples.length;
@@ -267,23 +267,23 @@ class ae extends EventTarget {
267
267
  * @returns {Promise<number>} Index (1-based) of the new sample
268
268
  */
269
269
  async recordSample() {
270
- const r = await navigator.mediaDevices.getUserMedia({ audio: !0 });
271
- return new Promise((t, s) => {
272
- const o = new AudioContext({ sampleRate: Rt }), e = new MediaRecorder(r), a = [];
273
- this.dispatchEvent(new CustomEvent("recording-start")), e.ondataavailable = (n) => {
274
- n.data.size > 0 && a.push(n.data);
275
- }, e.onstop = async () => {
276
- r.getTracks().forEach((n) => n.stop());
270
+ const n = await navigator.mediaDevices.getUserMedia({ audio: !0 });
271
+ return new Promise((t, e) => {
272
+ const r = new AudioContext({ sampleRate: Rt }), s = new MediaRecorder(n), i = [];
273
+ this.dispatchEvent(new CustomEvent("recording-start")), s.ondataavailable = (o) => {
274
+ o.data.size > 0 && i.push(o.data);
275
+ }, s.onstop = async () => {
276
+ n.getTracks().forEach((o) => o.stop());
277
277
  try {
278
- const c = await new Blob(a, { type: "audio/webm" }).arrayBuffer(), l = await o.decodeAudioData(c);
279
- await o.close();
278
+ const c = await new Blob(i, { type: "audio/webm" }).arrayBuffer(), l = await r.decodeAudioData(c);
279
+ await r.close();
280
280
  const h = l.getChannelData(0), d = zt(new Float32Array(h)), m = this._push(d, `Recorded #${this.samples.length}`);
281
281
  t(m);
282
- } catch (n) {
283
- await o.close().catch(() => {
284
- }), s(n);
282
+ } catch (o) {
283
+ await r.close().catch(() => {
284
+ }), e(o);
285
285
  }
286
- }, e.start(), setTimeout(() => e.stop(), oe);
286
+ }, s.start(), setTimeout(() => s.stop(), oe);
287
287
  });
288
288
  }
289
289
  // ─── Upload ────────────────────────────────────────────────────────────────
@@ -293,19 +293,19 @@ class ae extends EventTarget {
293
293
  * @param {File} file
294
294
  * @returns {Promise<number>} Index (1-based) of the new sample
295
295
  */
296
- async addAudioFile(r) {
297
- const t = await r.arrayBuffer(), s = new AudioContext({ sampleRate: Rt }), o = await s.decodeAudioData(t);
298
- await s.close();
299
- const e = o.getChannelData(0), a = zt(new Float32Array(e));
300
- return this._push(a, r.name);
296
+ async addAudioFile(n) {
297
+ const t = await n.arrayBuffer(), e = new AudioContext({ sampleRate: Rt }), r = await e.decodeAudioData(t);
298
+ await e.close();
299
+ const s = r.getChannelData(0), i = zt(new Float32Array(s));
300
+ return this._push(i, n.name);
301
301
  }
302
302
  // ─── Manage ────────────────────────────────────────────────────────────────
303
303
  /**
304
304
  * Remove a sample by 0-based index.
305
305
  * @param {number} idx
306
306
  */
307
- removeSample(r) {
308
- this.samples.splice(r, 1), this.dispatchEvent(new CustomEvent("samples-changed", { detail: { count: this.samples.length } }));
307
+ removeSample(n) {
308
+ this.samples.splice(n, 1), this.dispatchEvent(new CustomEvent("samples-changed", { detail: { count: this.samples.length } }));
309
309
  }
310
310
  clearSamples() {
311
311
  this.samples = [], this.dispatchEvent(new CustomEvent("samples-changed", { detail: { count: 0 } }));
@@ -321,24 +321,24 @@ class ae extends EventTarget {
321
321
  if (this.samples.length < 3)
322
322
  throw new Error(`Need at least 3 samples (currently have ${this.samples.length})`);
323
323
  this.dispatchEvent(new CustomEvent("generating", { detail: { total: this.samples.length } }));
324
- const r = [];
324
+ const n = [];
325
325
  for (let t = 0; t < this.samples.length; t++) {
326
- const s = Nt(this.samples[t].audioBuffer), o = await St(s);
327
- r.push(Array.from(o)), this.dispatchEvent(new CustomEvent("progress", {
326
+ const e = Nt(this.samples[t].audioBuffer), r = await St(e);
327
+ n.push(Array.from(r)), this.dispatchEvent(new CustomEvent("progress", {
328
328
  detail: { done: t + 1, total: this.samples.length }
329
329
  }));
330
330
  }
331
331
  return {
332
332
  word_name: this.wordName,
333
333
  model_type: "resnet_50_arc",
334
- embeddings: r
334
+ embeddings: n
335
335
  };
336
336
  }
337
337
  // ─── Private ───────────────────────────────────────────────────────────────
338
- _push(r, t) {
339
- this.samples.push({ audioBuffer: r, name: t });
340
- const s = this.samples.length;
341
- return this.dispatchEvent(new CustomEvent("sample-added", { detail: { count: s, name: t } })), s;
338
+ _push(n, t) {
339
+ this.samples.push({ audioBuffer: n, name: t });
340
+ const e = this.samples.length;
341
+ return this.dispatchEvent(new CustomEvent("sample-added", { detail: { count: e, name: t } })), e;
342
342
  }
343
343
  }
344
344
  const ie = `/**
@@ -382,40 +382,40 @@ registerProcessor('audio-processor', AudioProcessor)
382
382
  let wt = null;
383
383
  function ce() {
384
384
  if (!wt) {
385
- const i = new Blob([ie], { type: "application/javascript" });
386
- wt = URL.createObjectURL(i);
385
+ const a = new Blob([ie], { type: "application/javascript" });
386
+ wt = URL.createObjectURL(a);
387
387
  }
388
388
  return wt;
389
389
  }
390
390
  const gt = "mellon_custom_refs";
391
391
  function ot() {
392
392
  try {
393
- const i = localStorage.getItem(gt);
394
- return i ? JSON.parse(i) : [];
393
+ const a = localStorage.getItem(gt);
394
+ return a ? JSON.parse(a) : [];
395
395
  } catch {
396
396
  return [];
397
397
  }
398
398
  }
399
- function le(i) {
400
- const r = ot().filter((t) => t.word_name !== i.word_name);
401
- r.push(i), localStorage.setItem(gt, JSON.stringify(r));
399
+ function le(a) {
400
+ const n = ot().filter((t) => t.word_name !== a.word_name);
401
+ n.push(a), localStorage.setItem(gt, JSON.stringify(n));
402
402
  }
403
- function he(i) {
404
- const r = ot().filter((t) => t.word_name !== i);
405
- localStorage.setItem(gt, JSON.stringify(r));
403
+ function he(a) {
404
+ const n = ot().filter((t) => t.word_name !== a);
405
+ localStorage.setItem(gt, JSON.stringify(n));
406
406
  }
407
- function de(i) {
408
- const r = JSON.stringify(i, null, 2), t = new Blob([r], { type: "application/json" }), s = URL.createObjectURL(t), o = Object.assign(document.createElement("a"), {
409
- href: s,
410
- download: `${i.word_name}_ref.json`
407
+ function de(a) {
408
+ const n = JSON.stringify(a, null, 2), t = new Blob([n], { type: "application/json" }), e = URL.createObjectURL(t), r = Object.assign(document.createElement("a"), {
409
+ href: e,
410
+ download: `${a.word_name}_ref.json`
411
411
  });
412
- document.body.appendChild(o), o.click(), document.body.removeChild(o), URL.revokeObjectURL(s);
412
+ document.body.appendChild(r), r.click(), document.body.removeChild(r), URL.revokeObjectURL(e);
413
413
  }
414
- async function ue(i) {
415
- const r = await i.text();
414
+ async function ue(a) {
415
+ const n = await a.text();
416
416
  let t;
417
417
  try {
418
- t = JSON.parse(r);
418
+ t = JSON.parse(n);
419
419
  } catch {
420
420
  throw new Error("Invalid JSON");
421
421
  }
@@ -423,12 +423,11 @@ async function ue(i) {
423
423
  throw new Error('Missing or empty "embeddings" array');
424
424
  if (!Array.isArray(t.embeddings[0]))
425
425
  throw new Error('"embeddings" must be a 2D array');
426
- return t.word_name || (t.word_name = i.name.replace(/_ref\.json$/i, "").replace(/\.json$/i, "")), t;
426
+ return t.word_name || (t.word_name = a.name.replace(/_ref\.json$/i, "").replace(/\.json$/i, "")), t;
427
427
  }
428
428
  class kt extends EventTarget {
429
429
  /**
430
430
  * @param {object} [opts]
431
- * @param {string[]} [opts.words] Words to detect (must have refs loaded via addCustomWord())
432
431
  * @param {Array<string|{word_name:string,embeddings:number[][]}>} [opts.refs]
433
432
  * Reference data to preload. Each entry is either a URL string pointing to a
434
433
  * hosted `_ref.json` file, or an inline RefData object.
@@ -438,14 +437,13 @@ class kt extends EventTarget {
438
437
  * @param {number} [opts.inferenceGapMs=300] Min ms between inference runs
439
438
  * @param {string} [opts.assetsPath]
440
439
  */
441
- constructor(r = {}) {
440
+ constructor(n = {}) {
442
441
  super(), this._opts = {
443
- words: r.words ?? [],
444
- refs: r.refs ?? [],
445
- threshold: r.threshold ?? 0.65,
446
- relaxationMs: r.relaxationMs ?? 2e3,
447
- inferenceGapMs: r.inferenceGapMs ?? 300,
448
- assetsPath: r.assetsPath
442
+ refs: n.refs ?? [],
443
+ threshold: n.threshold ?? 0.65,
444
+ relaxationMs: n.relaxationMs ?? 2e3,
445
+ inferenceGapMs: n.inferenceGapMs ?? 300,
446
+ assetsPath: n.assetsPath
449
447
  }, this._refs = /* @__PURE__ */ new Map(), this._detectors = /* @__PURE__ */ new Map(), this._audioCtx = null, this._workletNode = null, this._stream = null, this._initialized = !1, this._running = !1;
450
448
  }
451
449
  /** Whether init() has completed successfully. */
@@ -464,88 +462,83 @@ class kt extends EventTarget {
464
462
  *
465
463
  * @param {(progress: number) => void} [onProgress] 0.0 → 1.0
466
464
  */
467
- async init(r) {
468
- var s;
465
+ async init(n) {
466
+ var e;
469
467
  if (this._initialized) {
470
- r == null || r(1);
468
+ n == null || n(1);
471
469
  return;
472
470
  }
473
471
  this._opts.assetsPath && Qt({ assetsPath: this._opts.assetsPath });
474
472
  try {
475
- await Vt(r);
476
- } catch (o) {
477
- throw this.dispatchEvent(new CustomEvent("error", { detail: { error: o } })), o;
473
+ await Vt(n);
474
+ } catch (r) {
475
+ throw this.dispatchEvent(new CustomEvent("error", { detail: { error: r } })), r;
478
476
  }
479
477
  const t = await ot();
480
- for (const o of this._opts.refs) {
481
- const e = (s = o.match(/\/([^/]+?)_ref\.json$/)) == null ? void 0 : s[1];
482
- if (!!t.find((n) => n.word_name === e)) break;
478
+ for (const r of this._opts.refs) {
479
+ const s = (e = r.match(/\/([^/]+?)_ref\.json$/)) == null ? void 0 : e[1], i = t.find((o) => o.word_name === s);
483
480
  try {
484
- let n;
485
- if (typeof o == "string") {
486
- console.log("fetching ref : ", o);
487
- const c = await fetch(o);
481
+ let o;
482
+ if (i) {
483
+ this.addCustomWord(i);
484
+ break;
485
+ } else if (typeof r == "string") {
486
+ console.log("fetching ref : ", r);
487
+ const c = await fetch(r);
488
488
  if (!c.ok) throw new Error(`HTTP ${c.status}`);
489
- n = await c.json();
489
+ o = await c.json();
490
490
  } else
491
- n = o;
492
- kt.saveWord(n), this.addCustomWord(n);
493
- } catch (n) {
494
- const c = typeof o == "string" ? o : o.word_name;
495
- console.warn(`[Mellon] Failed to load ref "${c}": ${n.message}`);
491
+ o = r;
492
+ kt.saveWord(o), this.addCustomWord(o);
493
+ } catch (o) {
494
+ const c = typeof r == "string" ? r : r.word_name;
495
+ console.warn(`[Mellon] Failed to load ref "${c}": ${o.message}`);
496
496
  }
497
497
  }
498
498
  this._initialized = !0, this.dispatchEvent(new CustomEvent("ready"));
499
499
  }
500
500
  /**
501
501
  * Request microphone access and start hotword detection.
502
+ * Listens for all words that have registered reference embeddings.
502
503
  * Emits 'match' CustomEvents when a word is detected.
503
- *
504
- * @param {string[]} [words] Subset of words to listen for; defaults to opts.words
505
504
  */
506
- async start(r) {
505
+ async start() {
507
506
  this._initialized || await this.init();
508
- const t = r ?? this._opts.words;
509
507
  try {
510
508
  this._stream = await navigator.mediaDevices.getUserMedia({ audio: !0 });
511
509
  } catch (e) {
512
- const a = new Error(`Microphone access denied: ${e.message}`);
513
- throw this.dispatchEvent(new CustomEvent("error", { detail: { error: a } })), a;
510
+ const r = new Error(`Microphone access denied: ${e.message}`);
511
+ throw this.dispatchEvent(new CustomEvent("error", { detail: { error: r } })), r;
514
512
  }
515
513
  this._audioCtx = new AudioContext({ sampleRate: 16e3 });
516
- const s = ce();
517
- await this._audioCtx.audioWorklet.addModule(s);
518
- const o = this._audioCtx.createMediaStreamSource(this._stream);
519
- this._workletNode = new AudioWorkletNode(this._audioCtx, "audio-processor"), o.connect(this._workletNode), this._workletNode.connect(this._audioCtx.destination);
520
- for (const e of t) {
521
- const a = this._refs.get(e);
522
- if (!a) {
523
- console.warn(`[Mellon] No reference embeddings for "${e}" — skipping. Call addCustomWord() to register custom words before start().`);
524
- continue;
525
- }
526
- const n = new xt({
514
+ const n = ce();
515
+ await this._audioCtx.audioWorklet.addModule(n);
516
+ const t = this._audioCtx.createMediaStreamSource(this._stream);
517
+ this._workletNode = new AudioWorkletNode(this._audioCtx, "audio-processor"), t.connect(this._workletNode), this._workletNode.connect(this._audioCtx.destination);
518
+ for (const [e, r] of this._refs) {
519
+ const s = new xt({
527
520
  name: e,
528
- refEmbeddings: a.embeddings,
521
+ refEmbeddings: r.embeddings,
529
522
  threshold: this._opts.threshold,
530
523
  relaxationMs: this._opts.relaxationMs,
531
524
  inferenceGapMs: this._opts.inferenceGapMs
532
525
  });
533
- n.addEventListener("match", (c) => {
534
- this.dispatchEvent(new CustomEvent("match", { detail: c.detail }));
535
- }), this._detectors.set(e, n);
526
+ s.addEventListener("match", (i) => {
527
+ this.dispatchEvent(new CustomEvent("match", { detail: i.detail }));
528
+ }), this._detectors.set(e, s);
536
529
  }
537
530
  this._workletNode.port.onmessage = async (e) => {
538
- const a = [];
539
- for (const n of this._detectors.values())
540
- a.push(n.scoreFrame(e.data));
541
- await Promise.allSettled(a);
531
+ const r = [];
532
+ for (const s of this._detectors.values())
533
+ r.push(s.scoreFrame(e.data));
534
+ await Promise.allSettled(r);
542
535
  }, this._running = !0;
543
536
  }
544
537
  /**
545
538
  * Stop detection and release the microphone and AudioContext.
546
539
  */
547
540
  stop() {
548
- this._workletNode && (this._workletNode.port.onmessage = null, this._workletNode.disconnect(), this._workletNode = null), this._stream && (this._stream.getTracks().forEach((r) => r.stop()), this._stream = null), this._audioCtx && (this._audioCtx.close(), this._audioCtx = null), this._detectors.clear(), this._running = !1;
541
+ this._workletNode && (this._workletNode.port.onmessage = null, this._workletNode.disconnect(), this._workletNode = null), this._stream && (this._stream.getTracks().forEach((n) => n.stop()), this._stream = null), this._audioCtx && (this._audioCtx.close(), this._audioCtx = null), this._detectors.clear(), this._running = !1;
549
542
  }
550
543
  // ─── Custom words ────────────────────────────────────────────────────────
551
544
  /**
@@ -555,18 +548,18 @@ class kt extends EventTarget {
555
548
  *
556
549
  * @param {{ word_name: string, model_type: string, embeddings: number[][] }} refData
557
550
  */
558
- addCustomWord(r) {
559
- if (this._refs.set(r.word_name, r), this._running && this._workletNode) {
551
+ addCustomWord(n) {
552
+ if (this._refs.set(n.word_name, n), this._running && this._workletNode) {
560
553
  const t = new xt({
561
- name: r.word_name,
562
- refEmbeddings: r.embeddings,
554
+ name: n.word_name,
555
+ refEmbeddings: n.embeddings,
563
556
  threshold: this._opts.threshold,
564
557
  relaxationMs: this._opts.relaxationMs,
565
558
  inferenceGapMs: this._opts.inferenceGapMs
566
559
  });
567
- t.addEventListener("match", (s) => {
568
- this.dispatchEvent(new CustomEvent("match", { detail: s.detail }));
569
- }), this._detectors.set(r.word_name, t);
560
+ t.addEventListener("match", (e) => {
561
+ this.dispatchEvent(new CustomEvent("match", { detail: e.detail }));
562
+ }), this._detectors.set(n.word_name, t);
570
563
  }
571
564
  }
572
565
  /**
@@ -583,8 +576,8 @@ class kt extends EventTarget {
583
576
  * const ref = await session.generateRef()
584
577
  * stt.addCustomWord(ref)
585
578
  */
586
- enrollWord(r) {
587
- return new ae(r);
579
+ enrollWord(n) {
580
+ return new ae(n);
588
581
  }
589
582
  // ─── Persistence (static) ────────────────────────────────────────────────
590
583
  /** Return all custom word refs stored in localStorage. */
@@ -592,23 +585,23 @@ class kt extends EventTarget {
592
585
  return ot();
593
586
  }
594
587
  /** Persist a word ref to localStorage (replaces any existing entry with the same name). */
595
- static saveWord(r) {
596
- le(r);
588
+ static saveWord(n) {
589
+ le(n);
597
590
  }
598
591
  /** Delete a word ref from localStorage by name. */
599
- static deleteWord(r) {
600
- he(r);
592
+ static deleteWord(n) {
593
+ he(n);
601
594
  }
602
595
  /**
603
596
  * Parse an uploaded ref JSON file and return a RefData object.
604
597
  * @param {File} file
605
598
  */
606
- static importWordFile(r) {
607
- return ue(r);
599
+ static importWordFile(n) {
600
+ return ue(n);
608
601
  }
609
602
  /** Trigger a browser download of a ref as a JSON file. */
610
- static exportWord(r) {
611
- de(r);
603
+ static exportWord(n) {
604
+ de(n);
612
605
  }
613
606
  }
614
607
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mellon",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Offline, in-browser hotword detection powered by EfficientWord-Net (ResNet-50 ArcFace). Works as a standalone app or npm library.",
5
5
  "type": "module",
6
6
  "main": "./dist/mellon.cjs",