mellon 0.0.10 → 0.0.12

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
@@ -126,8 +126,9 @@ class Mellon extends EventTarget {
126
126
  |---|---|---|---|
127
127
  | `refs` | `(string \| RefData)[]` | `[]` | Refs to preload — URL strings are fetched during `init()` |
128
128
  | `threshold` | `number` | `0.65` | Detection threshold (0–1) |
129
- | `relaxationMs` | `number` | `2000` | Min ms between match events |
129
+ | `relaxationMs` | `number` | `2000` | Min ms between match events for a single word |
130
130
  | `inferenceGapMs` | `number` | `300` | Min ms between inference runs |
131
+ | `matchDebounceMs` | `number` | `1000` | Min ms between any two match events across all words |
131
132
  | `assetsPath` | `string` | — | Base URL for mellon assets |
132
133
 
133
134
  #### Events
package/dist/index.d.ts CHANGED
@@ -199,6 +199,8 @@ export interface MellonOptions {
199
199
  relaxationMs?: number
200
200
  /** Minimum milliseconds between consecutive inference runs. Default: 300 */
201
201
  inferenceGapMs?: number
202
+ /** Minimum milliseconds between any two match events across all words. Default: 1000 */
203
+ matchDebounceMs?: number
202
204
  assetsPath?: string
203
205
  }
204
206
 
package/dist/mellon.cjs CHANGED
@@ -1,4 +1,4 @@
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=`/**
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Kt="0.0.12",Yt=[1,1,149,64],Qt=`https://cdn.jsdelivr.net/npm/mellon@${Kt}/dist/assets`,st={assetsPath:`${Qt}`};let D=null,q=null,tt=null;function Vt({assetsPath:a}={}){a!==void 0&&(st.assetsPath=a),D=null,q=null,tt=null}async function Xt(a){return D?(a==null||a(1),D):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:f}=await o.read();if(m)break;c.push(f),l+=f.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 D=await tt.InferenceSession.create(h.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),a==null||a(1),D})(),q)}async function Nt(a){if(!D)throw new Error("Model not loaded — call loadModel() first");const n=new tt.Tensor("float32",a,Yt),t=await D.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 _t,Et;function Ot(){if(Et)return _t;Et=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 _t=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 f=o+m,g=o,_=0;g<f;g+=2,_+=s){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],M=t[v],F=t[v+1],C=t[w],T=t[w+1],x=b,R=E,S=d[_],z=h*d[_+1],N=A*S-y*z,k=A*z+y*S,P=d[2*_],L=h*d[2*_+1],G=M*P-F*L,H=M*L+F*P,J=d[3*_],K=h*d[3*_+1],Y=C*J-T*K,Q=C*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+$;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,f=o-l,g=c-h;s[t]=d,s[t+1]=m,s[t+2]=f,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],f=i[e+r+1],g=i[e+c],_=i[e+c+1],u=i[e+l],p=i[e+l+1],v=h+g,w=d+_,b=h-g,E=d-_,A=m+u,y=f+p,M=o*(m-u),F=o*(f-p),C=v+A,T=w+y,x=b+F,R=E-M,S=v-A,z=w-y,N=b-F,k=E+M;s[t]=C,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,f=m>>>1,g=f>>>1;for(o=0;o<e;o+=i)for(var _=0,u=0;_<=g;_+=2,u+=s){var p=o+_,v=p+f,w=v+f,b=w+f,E=t[p],A=t[p+1],y=t[v],M=t[v+1],F=t[w],C=t[w+1],T=t[b],x=t[b+1],R=E,S=A,z=d[u],N=h*d[u+1],k=y*z-M*N,P=y*N+M*z,L=d[2*u],G=h*d[2*u+1],H=F*L-C*G,J=F*G+C*L,K=d[3*u],Y=h*d[3*u+1],Q=T*K-x*Y,V=T*Y+x*K,j=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=j+U,it=B+$,ct=X+et,lt=Z-O;if(t[p]=at,t[p+1]=it,t[v]=ct,t[v+1]=lt,_===0){var ht=j-U,dt=B-$;t[w]=ht,t[w+1]=dt;continue}if(_!==g){var ut=X,It=-Z,Wt=j,jt=-B,Bt=-h*et,Ut=-h*O,$t=-h*$,Pt=-h*U,Lt=ut+Bt,Gt=It+Ut,Ht=Wt+Pt,Jt=jt-$t,bt=o+f-_,At=o+m-_;t[bt]=Lt,t[bt+1]=Gt,t[At]=Ht,t[At+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],f=i[e+l],g=h+m,_=h-m,u=d+f,p=o*(d-f),v=g+u,w=_,b=-p,E=g-u,A=_,y=p;s[t]=v,s[t+1]=0,s[t+2]=w,s[t+3]=b,s[t+4]=E,s[t+5]=0,s[t+6]=A,s[t+7]=y},_t}var qt=Ot();const te=Zt(qt),nt=16e3,W=512,I=64,Mt=Math.floor(.025*nt),Ft=Math.floor(.01*nt);function Ct(a){return 2595*Math.log10(1+a/700)}function ee(a){return 700*(10**(a/2595)-1)}function se(){const a=Ct(0),n=Ct(nt/2),t=new Float64Array(I+2);for(let o=0;o<I+2;o++)t[o]=a+o*(n-a)/(I+1);const r=t.map(o=>ee(o)).map(o=>Math.floor((W+1)*o/nt)),s=[],i=Math.floor(W/2)+1;for(let o=0;o<I;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(W),ft=new Float32Array(W),Tt=rt.createComplexArray(),pt=rt.createComplexArray(),xt=new Float32Array(Math.floor(W/2)+1);function kt(a){const n=1+Math.ceil((a.length-Mt)/Ft),t=new Float32Array(n*I),e=Math.floor(W/2)+1;for(let r=0;r<n;r++){const s=r*Ft;ft.fill(0);for(let i=0;i<Mt&&s+i<a.length;i++)ft[i]=a[s+i];rt.toComplexArray(ft,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)/W;xt[i]=l===0?1e-30:l}for(let i=0;i<I;i++){const o=ne[i];let c=0;for(let l=0;l<e;l++)c+=xt[l]*o[l];t[r*I+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 Dt 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 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;
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 yt(){try{const a=localStorage.getItem(gt);return a?JSON.parse(a):[]}catch{return[]}}function le(a){const n=yt().filter(t=>t.word_name!==a.word_name);n.push(a),localStorage.setItem(gt,JSON.stringify(n))}function he(a){const n=yt().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 ot extends EventTarget{constructor(n={}){super(),this._opts={refs:n.refs??[],threshold:n.threshold??.65,relaxationMs:n.relaxationMs??2e3,inferenceGapMs:n.inferenceGapMs??300,matchDebounceMs:n.matchDebounceMs??1e3,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,this._lastMatchAt=0}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.loadWords();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)continue;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;ot.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}`)}}t.forEach(r=>this.addCustomWord(r)),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._onMatch(i)),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._onMatch(e)),this._detectors.set(n.word_name,t)}}_onMatch(n){const t=Date.now();t-this._lastMatchAt<this._opts.matchDebounceMs||(this._lastMatchAt=t,this.dispatchEvent(new CustomEvent("match",{detail:n.detail})))}enrollWord(n){return new Dt(n)}static loadWords(){return yt()}static saveWord(n){le(n)}static deleteWord(n){he(n)}static importWordFile(n){return ue(n)}static exportWord(n){de(n)}}exports.EnrollmentSession=Dt;exports.Mellon=ot;
package/dist/mellon.mjs CHANGED
@@ -1,12 +1,12 @@
1
- const Jt = "0.0.10", Kt = [1, 1, 149, 64], Yt = `https://cdn.jsdelivr.net/npm/mellon@${Jt}/dist/assets`, st = {
1
+ const Jt = "0.0.12", Kt = [1, 1, 149, 64], Yt = `https://cdn.jsdelivr.net/npm/mellon@${Jt}/dist/assets`, st = {
2
2
  assetsPath: `${Yt}`
3
3
  };
4
- let I = null, q = null, tt = null;
4
+ let D = null, q = null, tt = null;
5
5
  function Qt({ assetsPath: a } = {}) {
6
- a !== void 0 && (st.assetsPath = a), I = null, q = null, tt = null;
6
+ a !== void 0 && (st.assetsPath = a), D = null, q = null, tt = null;
7
7
  }
8
8
  async function Vt(a) {
9
- return I ? (a == null || a(1), I) : q || (q = (async () => {
9
+ return D ? (a == null || a(1), D) : q || (q = (async () => {
10
10
  const n = st.assetsPath.endsWith("/") ? st.assetsPath : st.assetsPath + "/", t = n + "ort.all.min.mjs", e = n + "model.onnx";
11
11
  tt = await new Function("url", "return import(url)")(t), tt.env.wasm.wasmPaths = n;
12
12
  const s = await fetch(e);
@@ -14,31 +14,31 @@ async function Vt(a) {
14
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 o.read();
17
+ const { done: m, value: f } = await o.read();
18
18
  if (m) break;
19
- c.push(_), l += _.byteLength, i > 0 && (a == null || a(l / i));
19
+ c.push(f), l += f.byteLength, i > 0 && (a == null || a(l / i));
20
20
  }
21
21
  const h = new Uint8Array(l);
22
22
  let d = 0;
23
23
  for (const m of c)
24
24
  h.set(m, d), d += m.byteLength;
25
- return I = await tt.InferenceSession.create(h.buffer, {
25
+ return D = await tt.InferenceSession.create(h.buffer, {
26
26
  executionProviders: ["wasm"],
27
27
  graphOptimizationLevel: "all"
28
- }), a == null || a(1), I;
28
+ }), a == null || a(1), D;
29
29
  })(), q);
30
30
  }
31
- async function St(a) {
32
- if (!I) throw new Error("Model not loaded — call loadModel() first");
33
- const n = new tt.Tensor("float32", a, Kt), t = await I.run({ input: n }), e = Object.keys(t)[0];
31
+ async function Nt(a) {
32
+ if (!D) throw new Error("Model not loaded — call loadModel() first");
33
+ const n = new tt.Tensor("float32", a, Kt), t = await D.run({ input: n }), e = Object.keys(t)[0];
34
34
  return t[e].data;
35
35
  }
36
36
  function Xt(a) {
37
37
  return a && a.__esModule && Object.prototype.hasOwnProperty.call(a, "default") ? a.default : a;
38
38
  }
39
- var ft, Et;
39
+ var mt, Et;
40
40
  function Zt() {
41
- if (Et) return ft;
41
+ if (Et) return mt;
42
42
  Et = 1;
43
43
  function a(n) {
44
44
  if (this.size = n | 0, this.size <= 1 || (this.size & this.size - 1) !== 0)
@@ -61,7 +61,7 @@ function Zt() {
61
61
  }
62
62
  this._out = null, this._data = null, this._inv = 0;
63
63
  }
64
- return ft = a, a.prototype.fromComplexArray = function(t, e) {
64
+ return mt = a, a.prototype.fromComplexArray = function(t, e) {
65
65
  for (var r = e || new Array(t.length >>> 1), s = 0; s < t.length; s += 2)
66
66
  r[s >>> 1] = t[s];
67
67
  return r;
@@ -109,44 +109,44 @@ function Zt() {
109
109
  i = e / s << 1;
110
110
  var m = i >>> 2;
111
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
- 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;
112
+ for (var f = o + m, g = o, _ = 0; g < f; g += 2, _ += s) {
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], M = t[v], F = t[v + 1], C = t[w], T = t[w + 1], x = b, R = E, z = d[_], S = h * d[_ + 1], N = A * z - y * S, k = A * S + y * z, L = d[2 * _], P = h * d[2 * _ + 1], G = M * L - F * P, H = M * P + F * L, J = d[3 * _], K = h * d[3 * _ + 1], Y = C * J - T * K, Q = C * 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, ot = j + U, at = V - Z, it = j - U, ct = B + O, lt = X - $, ht = B - O, dt = X + $;
114
+ t[u] = et, t[u + 1] = ot, t[p] = ct, t[p + 1] = lt, t[v] = at, t[v + 1] = it, t[w] = ht, t[w + 1] = dt;
115
115
  }
116
116
  }
117
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;
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, f = o - l, g = c - h;
119
+ s[t] = d, s[t + 1] = m, s[t + 2] = f, s[t + 3] = g;
120
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;
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], f = i[e + r + 1], g = i[e + c], _ = i[e + c + 1], u = i[e + l], p = i[e + l + 1], v = h + g, w = d + _, b = h - g, E = d - _, A = m + u, y = f + p, M = o * (m - u), F = o * (f - p), C = v + A, T = w + y, x = b + F, R = E - M, z = v - A, S = w - y, N = b - F, k = E + M;
122
+ s[t] = C, 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
123
  }, a.prototype._realTransform4 = function() {
124
124
  var t = this._out, e = this._csize, r = this._width, s = 1 << r, i = e / s << 1, o, c, l = this._bitrev;
125
125
  if (i === 4)
126
126
  for (o = 0, c = 0; o < e; o += i, c++) {
127
- const mt = l[c];
128
- this._singleRealTransform2(o, mt >>> 1, s >>> 1);
127
+ const ut = l[c];
128
+ this._singleRealTransform2(o, ut >>> 1, s >>> 1);
129
129
  }
130
130
  else
131
131
  for (o = 0, c = 0; o < e; o += i, c++) {
132
- const mt = l[c];
133
- this._singleRealTransform4(o, mt >>> 1, s >>> 1);
132
+ const ut = l[c];
133
+ this._singleRealTransform4(o, ut >>> 1, s >>> 1);
134
134
  }
135
135
  var h = this._inv ? -1 : 1, d = this.table;
136
136
  for (s >>= 2; s >= 2; s >>= 2) {
137
137
  i = e / s << 1;
138
- var m = i >>> 1, _ = m >>> 1, g = _ >>> 1;
138
+ var m = i >>> 1, f = m >>> 1, g = f >>> 1;
139
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
- if (t[p] = at, t[p + 1] = it, t[v] = ct, t[v + 1] = lt, f === 0) {
143
- var ht = j - U, dt = B - $;
144
- t[w] = ht, t[w + 1] = dt;
140
+ for (var _ = 0, u = 0; _ <= g; _ += 2, u += s) {
141
+ var p = o + _, v = p + f, w = v + f, b = w + f, E = t[p], A = t[p + 1], y = t[v], M = t[v + 1], F = t[w], C = 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 - M * N, L = y * N + M * S, P = d[2 * u], G = h * d[2 * u + 1], H = F * P - C * G, J = F * G + C * 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), ot = j + U, at = B + $, it = X + et, ct = Z - O;
142
+ if (t[p] = ot, t[p + 1] = at, t[v] = it, t[v + 1] = ct, _ === 0) {
143
+ var lt = j - U, ht = B - $;
144
+ t[w] = lt, t[w + 1] = ht;
145
145
  continue;
146
146
  }
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 = o + _ - f, bt = o + m - f;
149
- t[yt] = Lt, t[yt + 1] = Pt, t[bt] = Gt, t[bt + 1] = Ht;
147
+ if (_ !== g) {
148
+ var dt = X, Dt = -Z, It = j, Wt = -B, jt = -h * et, Bt = -h * O, Ut = -h * $, $t = -h * U, Lt = dt + jt, Pt = Dt + Bt, Gt = It + $t, Ht = Wt - Ut, bt = o + f - _, At = o + m - _;
149
+ t[bt] = Lt, t[bt + 1] = Pt, t[At] = Gt, t[At + 1] = Ht;
150
150
  }
151
151
  }
152
152
  }
@@ -154,12 +154,12 @@ function Zt() {
154
154
  const s = this._out, i = this._data, o = i[e], c = i[e + r], l = o + c, h = o - c;
155
155
  s[t] = l, s[t + 1] = 0, s[t + 2] = h, s[t + 3] = 0;
156
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
- }, ft;
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], f = i[e + l], g = h + m, _ = h - m, u = d + f, p = o * (d - f), v = g + u, w = _, b = -p, E = g - u, A = _, y = p;
158
+ s[t] = v, s[t + 1] = 0, s[t + 2] = w, s[t + 3] = b, s[t + 4] = E, s[t + 5] = 0, s[t + 6] = A, s[t + 7] = y;
159
+ }, mt;
160
160
  }
161
161
  var Ot = Zt();
162
- const qt = /* @__PURE__ */ Xt(Ot), nt = 16e3, W = 512, D = 64, At = Math.floor(0.025 * nt), Ft = Math.floor(0.01 * nt);
162
+ const qt = /* @__PURE__ */ Xt(Ot), nt = 16e3, W = 512, I = 64, Mt = Math.floor(0.025 * nt), Ft = Math.floor(0.01 * nt);
163
163
  function Ct(a) {
164
164
  return 2595 * Math.log10(1 + a / 700);
165
165
  }
@@ -167,11 +167,11 @@ function te(a) {
167
167
  return 700 * (10 ** (a / 2595) - 1);
168
168
  }
169
169
  function ee() {
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);
170
+ const a = Ct(0), n = Ct(nt / 2), t = new Float64Array(I + 2);
171
+ for (let o = 0; o < I + 2; o++)
172
+ t[o] = a + o * (n - a) / (I + 1);
173
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++) {
174
+ for (let o = 0; o < I; o++) {
175
175
  const c = new Float32Array(i);
176
176
  for (let l = r[o]; l < r[o + 1]; l++) c[l] = (l - r[o]) / (r[o + 1] - r[o]);
177
177
  for (let l = r[o + 1]; l < r[o + 2]; l++) c[l] = (r[o + 2] - l) / (r[o + 2] - r[o + 1]);
@@ -179,24 +179,24 @@ function ee() {
179
179
  }
180
180
  return s;
181
181
  }
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(a) {
184
- const n = 1 + Math.ceil((a.length - At) / Ft), t = new Float32Array(n * D), e = Math.floor(W / 2) + 1;
182
+ const se = ee(), rt = new qt(W), _t = new Float32Array(W), Tt = rt.createComplexArray(), ft = rt.createComplexArray(), xt = new Float32Array(Math.floor(W / 2) + 1);
183
+ function kt(a) {
184
+ const n = 1 + Math.ceil((a.length - Mt) / Ft), t = new Float32Array(n * I), e = Math.floor(W / 2) + 1;
185
185
  for (let r = 0; r < n; r++) {
186
186
  const s = r * Ft;
187
187
  _t.fill(0);
188
- for (let i = 0; i < At && s + i < a.length; i++)
188
+ for (let i = 0; i < Mt && s + i < a.length; i++)
189
189
  _t[i] = a[s + i];
190
- rt.toComplexArray(_t, Mt), rt.transform(pt, Mt);
190
+ rt.toComplexArray(_t, Tt), rt.transform(ft, Tt);
191
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;
192
+ const o = ft[2 * i], c = ft[2 * i + 1], l = (o * o + c * c) / W;
193
+ xt[i] = l === 0 ? 1e-30 : l;
194
194
  }
195
- for (let i = 0; i < D; i++) {
195
+ for (let i = 0; i < I; i++) {
196
196
  const o = se[i];
197
197
  let c = 0;
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);
198
+ for (let l = 0; l < e; l++) c += xt[l] * o[l];
199
+ t[r * I + i] = Math.log(c === 0 ? 1e-30 : c);
200
200
  }
201
201
  }
202
202
  return t;
@@ -214,7 +214,7 @@ function re(a, n) {
214
214
  }
215
215
  return t;
216
216
  }
217
- class xt extends EventTarget {
217
+ class Rt extends EventTarget {
218
218
  /**
219
219
  * @param {object} opts
220
220
  * @param {string} opts.name Human-readable label
@@ -239,17 +239,17 @@ class xt extends EventTarget {
239
239
  const t = Date.now();
240
240
  if (t - this._lastInferenceAt < this.inferenceGapMs) return null;
241
241
  this._lastInferenceAt = t;
242
- const e = Nt(n), r = await St(e), s = re(r, this.refEmbeddings);
242
+ const e = kt(n), r = await Nt(e), s = re(r, this.refEmbeddings);
243
243
  return this._lastScore = s, s >= this.threshold && t - this._lastDetectionAt >= this.relaxationMs && (this._lastDetectionAt = t, this.dispatchEvent(new CustomEvent("match", {
244
244
  detail: { name: this.name, confidence: s, timestamp: t }
245
245
  }))), s;
246
246
  }
247
247
  }
248
- const Rt = 16e3, oe = 1500, vt = 24e3;
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;
248
+ const zt = 16e3, oe = 1500, pt = 24e3;
249
+ function St(a) {
250
+ if (a.length === pt) return a;
251
+ const n = new Float32Array(pt);
252
+ return n.set(a.subarray(0, pt)), n;
253
253
  }
254
254
  class ae extends EventTarget {
255
255
  /** @param {string} wordName — the wake word label */
@@ -269,7 +269,7 @@ class ae extends EventTarget {
269
269
  async recordSample() {
270
270
  const n = await navigator.mediaDevices.getUserMedia({ audio: !0 });
271
271
  return new Promise((t, e) => {
272
- const r = new AudioContext({ sampleRate: Rt }), s = new MediaRecorder(n), i = [];
272
+ const r = new AudioContext({ sampleRate: zt }), s = new MediaRecorder(n), i = [];
273
273
  this.dispatchEvent(new CustomEvent("recording-start")), s.ondataavailable = (o) => {
274
274
  o.data.size > 0 && i.push(o.data);
275
275
  }, s.onstop = async () => {
@@ -277,7 +277,7 @@ class ae extends EventTarget {
277
277
  try {
278
278
  const c = await new Blob(i, { type: "audio/webm" }).arrayBuffer(), l = await r.decodeAudioData(c);
279
279
  await r.close();
280
- const h = l.getChannelData(0), d = zt(new Float32Array(h)), m = this._push(d, `Recorded #${this.samples.length}`);
280
+ const h = l.getChannelData(0), d = St(new Float32Array(h)), m = this._push(d, `Recorded #${this.samples.length}`);
281
281
  t(m);
282
282
  } catch (o) {
283
283
  await r.close().catch(() => {
@@ -294,9 +294,9 @@ class ae extends EventTarget {
294
294
  * @returns {Promise<number>} Index (1-based) of the new sample
295
295
  */
296
296
  async addAudioFile(n) {
297
- const t = await n.arrayBuffer(), e = new AudioContext({ sampleRate: Rt }), r = await e.decodeAudioData(t);
297
+ const t = await n.arrayBuffer(), e = new AudioContext({ sampleRate: zt }), r = await e.decodeAudioData(t);
298
298
  await e.close();
299
- const s = r.getChannelData(0), i = zt(new Float32Array(s));
299
+ const s = r.getChannelData(0), i = St(new Float32Array(s));
300
300
  return this._push(i, n.name);
301
301
  }
302
302
  // ─── Manage ────────────────────────────────────────────────────────────────
@@ -323,7 +323,7 @@ class ae extends EventTarget {
323
323
  this.dispatchEvent(new CustomEvent("generating", { detail: { total: this.samples.length } }));
324
324
  const n = [];
325
325
  for (let t = 0; t < this.samples.length; t++) {
326
- const e = Nt(this.samples[t].audioBuffer), r = await St(e);
326
+ const e = kt(this.samples[t].audioBuffer), r = await Nt(e);
327
327
  n.push(Array.from(r)), this.dispatchEvent(new CustomEvent("progress", {
328
328
  detail: { done: t + 1, total: this.samples.length }
329
329
  }));
@@ -379,16 +379,16 @@ class AudioProcessor extends AudioWorkletProcessor {
379
379
 
380
380
  registerProcessor('audio-processor', AudioProcessor)
381
381
  `;
382
- let wt = null;
382
+ let vt = null;
383
383
  function ce() {
384
- if (!wt) {
384
+ if (!vt) {
385
385
  const a = new Blob([ie], { type: "application/javascript" });
386
- wt = URL.createObjectURL(a);
386
+ vt = URL.createObjectURL(a);
387
387
  }
388
- return wt;
388
+ return vt;
389
389
  }
390
390
  const gt = "mellon_custom_refs";
391
- function ot() {
391
+ function yt() {
392
392
  try {
393
393
  const a = localStorage.getItem(gt);
394
394
  return a ? JSON.parse(a) : [];
@@ -397,11 +397,11 @@ function ot() {
397
397
  }
398
398
  }
399
399
  function le(a) {
400
- const n = ot().filter((t) => t.word_name !== a.word_name);
400
+ const n = yt().filter((t) => t.word_name !== a.word_name);
401
401
  n.push(a), localStorage.setItem(gt, JSON.stringify(n));
402
402
  }
403
403
  function he(a) {
404
- const n = ot().filter((t) => t.word_name !== a);
404
+ const n = yt().filter((t) => t.word_name !== a);
405
405
  localStorage.setItem(gt, JSON.stringify(n));
406
406
  }
407
407
  function de(a) {
@@ -425,7 +425,7 @@ async function ue(a) {
425
425
  throw new Error('"embeddings" must be a 2D array');
426
426
  return t.word_name || (t.word_name = a.name.replace(/_ref\.json$/i, "").replace(/\.json$/i, "")), t;
427
427
  }
428
- class kt extends EventTarget {
428
+ class wt extends EventTarget {
429
429
  /**
430
430
  * @param {object} [opts]
431
431
  * @param {Array<string|{word_name:string,embeddings:number[][]}>} [opts.refs]
@@ -443,8 +443,9 @@ class kt extends EventTarget {
443
443
  threshold: n.threshold ?? 0.65,
444
444
  relaxationMs: n.relaxationMs ?? 2e3,
445
445
  inferenceGapMs: n.inferenceGapMs ?? 300,
446
+ matchDebounceMs: n.matchDebounceMs ?? 1e3,
446
447
  assetsPath: n.assetsPath
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;
448
+ }, 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, this._lastMatchAt = 0;
448
449
  }
449
450
  /** Whether init() has completed successfully. */
450
451
  get isInitialized() {
@@ -474,28 +475,27 @@ class kt extends EventTarget {
474
475
  } catch (r) {
475
476
  throw this.dispatchEvent(new CustomEvent("error", { detail: { error: r } })), r;
476
477
  }
477
- const t = await ot();
478
+ const t = await wt.loadWords();
478
479
  for (const r of this._opts.refs) {
479
480
  const s = (e = r.match(/\/([^/]+?)_ref\.json$/)) == null ? void 0 : e[1], i = t.find((o) => o.word_name === s);
480
481
  try {
481
482
  let o;
482
- if (i) {
483
- this.addCustomWord(i);
484
- break;
485
- } else if (typeof r == "string") {
483
+ if (i)
484
+ continue;
485
+ if (typeof r == "string") {
486
486
  console.log("fetching ref : ", r);
487
487
  const c = await fetch(r);
488
488
  if (!c.ok) throw new Error(`HTTP ${c.status}`);
489
489
  o = await c.json();
490
490
  } else
491
491
  o = r;
492
- kt.saveWord(o), this.addCustomWord(o);
492
+ wt.saveWord(o), this.addCustomWord(o);
493
493
  } catch (o) {
494
494
  const c = typeof r == "string" ? r : r.word_name;
495
495
  console.warn(`[Mellon] Failed to load ref "${c}": ${o.message}`);
496
496
  }
497
497
  }
498
- this._initialized = !0, this.dispatchEvent(new CustomEvent("ready"));
498
+ t.forEach((r) => this.addCustomWord(r)), this._initialized = !0, this.dispatchEvent(new CustomEvent("ready"));
499
499
  }
500
500
  /**
501
501
  * Request microphone access and start hotword detection.
@@ -516,16 +516,14 @@ class kt extends EventTarget {
516
516
  const t = this._audioCtx.createMediaStreamSource(this._stream);
517
517
  this._workletNode = new AudioWorkletNode(this._audioCtx, "audio-processor"), t.connect(this._workletNode), this._workletNode.connect(this._audioCtx.destination);
518
518
  for (const [e, r] of this._refs) {
519
- const s = new xt({
519
+ const s = new Rt({
520
520
  name: e,
521
521
  refEmbeddings: r.embeddings,
522
522
  threshold: this._opts.threshold,
523
523
  relaxationMs: this._opts.relaxationMs,
524
524
  inferenceGapMs: this._opts.inferenceGapMs
525
525
  });
526
- s.addEventListener("match", (i) => {
527
- this.dispatchEvent(new CustomEvent("match", { detail: i.detail }));
528
- }), this._detectors.set(e, s);
526
+ s.addEventListener("match", (i) => this._onMatch(i)), this._detectors.set(e, s);
529
527
  }
530
528
  this._workletNode.port.onmessage = async (e) => {
531
529
  const r = [];
@@ -550,16 +548,14 @@ class kt extends EventTarget {
550
548
  */
551
549
  addCustomWord(n) {
552
550
  if (this._refs.set(n.word_name, n), this._running && this._workletNode) {
553
- const t = new xt({
551
+ const t = new Rt({
554
552
  name: n.word_name,
555
553
  refEmbeddings: n.embeddings,
556
554
  threshold: this._opts.threshold,
557
555
  relaxationMs: this._opts.relaxationMs,
558
556
  inferenceGapMs: this._opts.inferenceGapMs
559
557
  });
560
- t.addEventListener("match", (e) => {
561
- this.dispatchEvent(new CustomEvent("match", { detail: e.detail }));
562
- }), this._detectors.set(n.word_name, t);
558
+ t.addEventListener("match", (e) => this._onMatch(e)), this._detectors.set(n.word_name, t);
563
559
  }
564
560
  }
565
561
  /**
@@ -576,13 +572,17 @@ class kt extends EventTarget {
576
572
  * const ref = await session.generateRef()
577
573
  * stt.addCustomWord(ref)
578
574
  */
575
+ _onMatch(n) {
576
+ const t = Date.now();
577
+ t - this._lastMatchAt < this._opts.matchDebounceMs || (this._lastMatchAt = t, this.dispatchEvent(new CustomEvent("match", { detail: n.detail })));
578
+ }
579
579
  enrollWord(n) {
580
580
  return new ae(n);
581
581
  }
582
582
  // ─── Persistence (static) ────────────────────────────────────────────────
583
583
  /** Return all custom word refs stored in localStorage. */
584
584
  static loadWords() {
585
- return ot();
585
+ return yt();
586
586
  }
587
587
  /** Persist a word ref to localStorage (replaces any existing entry with the same name). */
588
588
  static saveWord(n) {
@@ -606,5 +606,5 @@ class kt extends EventTarget {
606
606
  }
607
607
  export {
608
608
  ae as EnrollmentSession,
609
- kt as Mellon
609
+ wt as Mellon
610
610
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mellon",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
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",