mellon 0.0.21 → 0.0.23

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/dist/index.d.ts CHANGED
@@ -20,6 +20,12 @@ export interface DetectorConfig {
20
20
  audioProcessorPath?: string;
21
21
  ortCdnUrl?: string;
22
22
  audioUtils?: AudioUtils;
23
+ /** Enable console logging. Pass `true` for info+warn+error, or a custom logger. Defaults to silent. */
24
+ log?: boolean | {
25
+ info?: (...a: unknown[]) => void;
26
+ warn?: (...a: unknown[]) => void;
27
+ error?: (...a: unknown[]) => void;
28
+ };
23
29
  }
24
30
  export interface EnrollmentSessionConfig {
25
31
  wasmPaths?: string;
package/dist/mellon.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function Ut(m){return m&&m.__esModule&&Object.prototype.hasOwnProperty.call(m,"default")?m.default:m}var dt,pt;function Dt(){if(pt)return dt;pt=1;function m(o){if(this.size=o|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=o<<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 n=0,e=1;this.size>e;e<<=1)n++;this._width=n%2===0?n-1:n,this._bitrev=new Array(1<<this._width);for(var i=0;i<this._bitrev.length;i++){this._bitrev[i]=0;for(var r=0;r<this._width;r+=2){var h=this._width-r-2;this._bitrev[i]|=(i>>>r&3)<<h}}this._out=null,this._data=null,this._inv=0}return dt=m,m.prototype.fromComplexArray=function(t,s){for(var n=s||new Array(t.length>>>1),e=0;e<t.length;e+=2)n[e>>>1]=t[e];return n},m.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var s=0;s<t.length;s++)t[s]=0;return t},m.prototype.toComplexArray=function(t,s){for(var n=s||this.createComplexArray(),e=0;e<n.length;e+=2)n[e]=t[e>>>1],n[e+1]=0;return n},m.prototype.completeSpectrum=function(t){for(var s=this._csize,n=s>>>1,e=2;e<n;e+=2)t[s-e]=t[e],t[s-e+1]=-t[e+1]},m.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},m.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},m.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 n=0;n<t.length;n++)t[n]/=this.size;this._out=null,this._data=null},m.prototype._transform4=function(){var t=this._out,s=this._csize,n=this._width,e=1<<n,i=s/e<<1,r,h,l=this._bitrev;if(i===4)for(r=0,h=0;r<s;r+=i,h++){const u=l[h];this._singleTransform2(r,u,e)}else for(r=0,h=0;r<s;r+=i,h++){const u=l[h];this._singleTransform4(r,u,e)}var c=this._inv?-1:1,a=this.table;for(e>>=2;e>=2;e>>=2){i=s/e<<1;var d=i>>>2;for(r=0;r<s;r+=i)for(var _=r+d,v=r,f=0;v<_;v+=2,f+=e){const u=v,p=u+d,w=p+d,g=w+d,T=t[u],A=t[u+1],b=t[p],y=t[p+1],F=t[w],S=t[w+1],C=t[g],E=t[g+1],M=T,P=A,R=a[f],U=c*a[f+1],D=b*R-y*U,z=b*U+y*R,k=a[2*f],N=c*a[2*f+1],B=F*k-S*N,j=F*N+S*k,K=a[3*f],O=c*a[3*f+1],$=C*K-E*O,G=C*O+E*K,J=M+B,x=P+j,I=M-B,Y=P-j,Q=D+$,L=z+G,W=c*(D-$),V=c*(z-G),tt=J+Q,rt=x+L,ot=J-Q,nt=x-L,at=I+V,it=Y-W,ct=I-V,lt=Y+W;t[u]=tt,t[u+1]=rt,t[p]=at,t[p+1]=it,t[w]=ot,t[w+1]=nt,t[g]=ct,t[g+1]=lt}}},m.prototype._singleTransform2=function(t,s,n){const e=this._out,i=this._data,r=i[s],h=i[s+1],l=i[s+n],c=i[s+n+1],a=r+l,d=h+c,_=r-l,v=h-c;e[t]=a,e[t+1]=d,e[t+2]=_,e[t+3]=v},m.prototype._singleTransform4=function(t,s,n){const e=this._out,i=this._data,r=this._inv?-1:1,h=n*2,l=n*3,c=i[s],a=i[s+1],d=i[s+n],_=i[s+n+1],v=i[s+h],f=i[s+h+1],u=i[s+l],p=i[s+l+1],w=c+v,g=a+f,T=c-v,A=a-f,b=d+u,y=_+p,F=r*(d-u),S=r*(_-p),C=w+b,E=g+y,M=T+S,P=A-F,R=w-b,U=g-y,D=T-S,z=A+F;e[t]=C,e[t+1]=E,e[t+2]=M,e[t+3]=P,e[t+4]=R,e[t+5]=U,e[t+6]=D,e[t+7]=z},m.prototype._realTransform4=function(){var t=this._out,s=this._csize,n=this._width,e=1<<n,i=s/e<<1,r,h,l=this._bitrev;if(i===4)for(r=0,h=0;r<s;r+=i,h++){const ht=l[h];this._singleRealTransform2(r,ht>>>1,e>>>1)}else for(r=0,h=0;r<s;r+=i,h++){const ht=l[h];this._singleRealTransform4(r,ht>>>1,e>>>1)}var c=this._inv?-1:1,a=this.table;for(e>>=2;e>=2;e>>=2){i=s/e<<1;var d=i>>>1,_=d>>>1,v=_>>>1;for(r=0;r<s;r+=i)for(var f=0,u=0;f<=v;f+=2,u+=e){var p=r+f,w=p+_,g=w+_,T=g+_,A=t[p],b=t[p+1],y=t[w],F=t[w+1],S=t[g],C=t[g+1],E=t[T],M=t[T+1],P=A,R=b,U=a[u],D=c*a[u+1],z=y*U-F*D,k=y*D+F*U,N=a[2*u],B=c*a[2*u+1],j=S*N-C*B,K=S*B+C*N,O=a[3*u],$=c*a[3*u+1],G=E*O-M*$,J=E*$+M*O,x=P+j,I=R+K,Y=P-j,Q=R-K,L=z+G,W=k+J,V=c*(z-G),tt=c*(k-J),rt=x+L,ot=I+W,nt=Y+tt,at=Q-V;if(t[p]=rt,t[p+1]=ot,t[w]=nt,t[w+1]=at,f===0){var it=x-L,ct=I-W;t[g]=it,t[g+1]=ct;continue}if(f!==v){var lt=Y,yt=-Q,Tt=x,bt=-I,At=-c*tt,Ft=-c*V,St=-c*W,Ct=-c*L,Et=lt+At,Mt=yt+Ft,Pt=Tt+Ct,Rt=bt-St,ut=r+_-f,vt=r+d-f;t[ut]=Et,t[ut+1]=Mt,t[vt]=Pt,t[vt+1]=Rt}}}},m.prototype._singleRealTransform2=function(t,s,n){const e=this._out,i=this._data,r=i[s],h=i[s+n],l=r+h,c=r-h;e[t]=l,e[t+1]=0,e[t+2]=c,e[t+3]=0},m.prototype._singleRealTransform4=function(t,s,n){const e=this._out,i=this._data,r=this._inv?-1:1,h=n*2,l=n*3,c=i[s],a=i[s+n],d=i[s+h],_=i[s+l],v=c+d,f=c-d,u=a+_,p=r*(a-_),w=v+u,g=f,T=-p,A=v-u,b=f,y=p;e[t]=w,e[t+1]=0,e[t+2]=g,e[t+3]=T,e[t+4]=A,e[t+5]=0,e[t+6]=b,e[t+7]=y},dt}var zt=Dt();const xt=Ut(zt);class ft{constructor(o=16e3,t=512,s=64){this._sampleRate=o,this._nfft=t,this._nfilt=s,this._fft=new xt(t),this._melFilters=this._createMelFilterbank()}_hzToMel(o){return 2595*Math.log10(1+o/700)}_melToHz(o){return 700*(10**(o/2595)-1)}_createMelFilterbank(){const t=this._sampleRate/2,s=this._hzToMel(0),n=this._hzToMel(t),e=new Float32Array(this._nfilt+2);for(let l=0;l<this._nfilt+2;l++)e[l]=s+l*(n-s)/(this._nfilt+1);const r=e.map(l=>this._melToHz(l)).map(l=>Math.floor((this._nfft+1)*l/this._sampleRate)),h=[];for(let l=0;l<this._nfilt;l++){const c=new Float32Array(Math.floor(this._nfft/2)+1);for(let a=r[l];a<r[l+1];a++)c[a]=(a-r[l])/(r[l+1]-r[l]);for(let a=r[l+1];a<r[l+2];a++)c[a]=(r[l+2]-a)/(r[l+2]-r[l+1]);h.push(c)}return h}logfbank(o){const t=Math.floor(.025*this._sampleRate),s=Math.floor(.01*this._sampleRate),n=1+Math.ceil((o.length-t)/s),e=new Float32Array(n*this._nfilt),i=new Float32Array(this._nfft),r=this._fft.createComplexArray();for(let h=0;h<n;h++){const l=h*s;i.fill(0);for(let d=0;d<t&&l+d<o.length;d++)i[d]=o[l+d];const c=this._fft.toComplexArray(i,null);this._fft.transform(r,c);const a=new Float32Array(Math.floor(this._nfft/2)+1);for(let d=0;d<a.length;d++){const _=r[2*d],v=r[2*d+1];a[d]=1/this._nfft*(_*_+v*v),a[d]===0&&(a[d]=1e-30)}for(let d=0;d<this._nfilt;d++){let _=0;const v=this._melFilters[d];for(let f=0;f<a.length;f++)_+=a[f]*v[f];_===0&&(_=1e-30),e[h*this._nfilt+d]=Math.log(_)}}return e}maxCosineSim(o,t){let s=0;for(const n of t){let e=0;for(let r=0;r<n.length;r++)e+=o[r]*n[r];const i=(e+1)/2;i>s&&(s=i)}return s}}async function st(m=Z,o=q){const t=await import(o);return t.env.wasm.wasmPaths=m,t.env.wasm.numThreads=1,t}let _t=null;async function mt(m=et,o=Z,t=q,s){return _t||(_t=st(o,t).then(n=>s?n.InferenceSession.create(new Uint8Array(s),{executionProviders:["wasm"],graphOptimizationLevel:"all"}):n.InferenceSession.create(m,{executionProviders:["wasm"],graphOptimizationLevel:"all"}))),_t}class H{static loadWords(o=X){try{const t=localStorage.getItem(o);return t?JSON.parse(t):[]}catch{return[]}}static saveWord(o,t=X){const s=H.loadWords(t).filter(n=>n.word_name!==o.word_name);localStorage.setItem(t,JSON.stringify([...s,o]))}static deleteWord(o,t=X){try{const s=H.loadWords(t).filter(n=>n.word_name!==o);localStorage.setItem(t,JSON.stringify(s))}catch{}}}class It{constructor(o,t){this._started=!1,this._inferring=!1,this._audioCtx=null,this._stream=null,this._refEmbeddings=new Map,this._lastMatchAt=0,this._lastInferenceAt=0,this._initPromise=null;const{refsStorageKey:s=X,thresholdStorageKey:n=gt,wasmPaths:e=Z,modelPath:i=et,audioProcessorPath:r=wt,ortCdnUrl:h=q,audioUtils:l=new ft}=t||{};this._audioUtils=l,this._commands=o,this._refsStorageKey=s,this._thresholdStorageKey=n,this._audioProcessorPath=r,this._wasmPaths=e,this._modelPath=i,this._ortCdnUrl=h;try{const c=localStorage.getItem(this._thresholdStorageKey);this._threshold=c!==null?Math.max(0,Math.min(1,Number(c))):.65}catch{this._threshold=.65}}get threshold(){return this._threshold}set threshold(o){this._threshold=Math.max(0,Math.min(1,o));try{localStorage.setItem(this._thresholdStorageKey,String(this._threshold))}catch{}}get listening(){return this._started}async _trackFetch(o,t,s){const n=await fetch(o);if(!n.ok)throw new Error(`HTTP ${n.status} fetching ${o}`);const e=Number(n.headers.get("content-length")??"0");if(e>0&&(s.total+=e),!n.body){const a=await n.arrayBuffer();return s.downloaded+=a.byteLength,e||(s.total+=a.byteLength),t==null||t(s.downloaded,s.total),a}const i=n.body.getReader(),r=[];let h=0;for(;;){const{done:a,value:d}=await i.read();if(a)break;r.push(d),h+=d.length,s.downloaded+=d.length,t==null||t(s.downloaded,s.total)}e||(s.total+=h);const l=new Uint8Array(h);let c=0;for(const a of r)l.set(a,c),c+=a.length;return l.buffer}async _init(o){const t={downloaded:0,total:0},s=H.loadWords(this._refsStorageKey),n=new Set(s.map(c=>c.word_name)),e=new Set,i=[];for(const c of this._commands)for(const a of c.triggers)!e.has(a.name)&&a.defaultRefPath&&!n.has(a.name)&&(e.add(a.name),i.push({name:a.name,path:a.defaultRefPath}));const r=st(this._wasmPaths,this._ortCdnUrl),[h,...l]=await Promise.all([this._trackFetch(this._modelPath,o,t),...i.map(({path:c})=>this._trackFetch(c,o,t))]);await r,await mt(this._modelPath,this._wasmPaths,this._ortCdnUrl,h);for(let c=0;c<i.length;c++)try{const a=JSON.parse(new TextDecoder().decode(l[c]));this.addCustomWord(a),H.saveWord(a,this._refsStorageKey)}catch{console.warn(`[Mellon] failed to parse ref file: ${i[c].path}`)}for(const c of s)this._refEmbeddings.set(c.word_name,c.embeddings);console.info("[Mellon] init complete, loaded refs:",[...this._refEmbeddings.keys()])}async init(o){this._initPromise||(this._initPromise=this._init(o)),await this._initPromise}addCustomWord(o){if(!(Array.isArray(o.embeddings)&&o.embeddings.length>0))throw new Error("invalid ref file for : "+o.word_name);this._refEmbeddings.set(o.word_name,o.embeddings)}async start(){if(this._started)return;await this.init();let o;try{o=await navigator.mediaDevices.getUserMedia({audio:{noiseSuppression:!1,echoCancellation:!1,autoGainControl:!1,channelCount:1}})}catch{o=await navigator.mediaDevices.getUserMedia({audio:!0})}this._stream=o;const t=new AudioContext({sampleRate:16e3});this._audioCtx=t,await t.audioWorklet.addModule(this._audioProcessorPath);const s=t.createMediaStreamSource(o),n=new AudioWorkletNode(t,"audio-processor");n.port.onmessage=e=>{this._handleBuffer(e.data)},s.connect(n),n.connect(t.destination),this._started=!0}async stop(){if(this._started=!1,this._audioCtx&&(await this._audioCtx.close(),this._audioCtx=null),this._stream){for(const o of this._stream.getTracks())o.stop();this._stream=null}}async _handleBuffer(o){if(this._inferring)return;const t=Date.now();if(!(t-this._lastInferenceAt<300)){this._lastInferenceAt=t,this._inferring=!0;try{const[s,n]=await Promise.all([st(this._wasmPaths,this._ortCdnUrl),mt(this._modelPath,this._wasmPaths,this._ortCdnUrl)]),e=this._audioUtils.logfbank(o),i=new s.Tensor("float32",e,[1,1,149,64]),r=await n.run({input:i}),h=r[Object.keys(r)[0]].data;let l=!1;for(const c of this._commands){if(l)break;for(const a of c.triggers){const d=this._refEmbeddings.get(a.name);if(!d)continue;const _=this._audioUtils.maxCosineSim(h,d);if(_>=this._threshold&&t-this._lastMatchAt>2e3){this._lastMatchAt=t,console.info(`[Mellon] match: "${a}" sim=${_.toFixed(3)}`),typeof c.onMatch=="function"&&c.onMatch(a.name,_),l=!0;break}}}}catch(s){console.error("[Mellon] inference error:",s)}finally{this._inferring=!1}}}}class Lt{constructor(o,t){this._config={},this._samples=[],this._wordName=o,this._config.modelPath=(t==null?void 0:t.modelPath)||et,this._config.wasmPaths=(t==null?void 0:t.wasmPaths)||Z,this._config.ortCdnUrl=(t==null?void 0:t.ortCdnUrl)||q,this._audioUtils=(t==null?void 0:t.audioUtils)??new ft}async recordSample(){const o=await navigator.mediaDevices.getUserMedia({audio:!0}),t=new AudioContext({sampleRate:16e3}),s=await new Promise((i,r)=>{const h=new MediaRecorder(o),l=[];h.ondataavailable=c=>{c.data.size>0&&l.push(c.data)},h.onstop=async()=>{var c;for(const a of o.getTracks())a.stop();try{const d=await new Blob(l,{type:((c=l[0])==null?void 0:c.type)||"audio/webm"}).arrayBuffer(),_=await t.decodeAudioData(d);await t.close(),i(_.getChannelData(0).slice())}catch(a){r(a)}},h.start(),setTimeout(()=>{try{h.stop()}catch{}},1500)}),n=24e3,e=new Float32Array(n);return e.set(s.slice(0,n)),this._samples.push(e),this._samples.length}deleteSample(o){if(o<0||o>=this._samples.length)throw new RangeError(`index ${o} out of bounds (${this._samples.length} samples)`);return this._samples.splice(o,1),this._samples.length}async generateRef(){const[o,t]=await Promise.all([st(this._config.wasmPaths,this._config.ortCdnUrl),mt(this._config.modelPath,this._config.wasmPaths,this._config.ortCdnUrl)]),s=[];for(const n of this._samples){const e=this._audioUtils.logfbank(n),i=new o.Tensor("float32",e,[1,1,149,64]),r=await t.run({input:i}),h=Array.from(r[Object.keys(r)[0]].data);s.push(h)}return{word_name:this._wordName,model_type:"resnet_50_arc",embeddings:s}}}const Z="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/",q="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.wasm.min.mjs",et="https://huggingface.co/ComicScrip/mellon/resolve/main/model.onnx",wt="https://cdn.jsdelivr.net/npm/mellon@0.0.14/dist/assets/audio-processor.js",X="mellon-refs",gt="mellon-threshold";exports.AudioUtils=ft;exports.DEFAULT_AUDIO_PROCESSOR_PATH=wt;exports.DEFAULT_MODEL_PATH=et;exports.DEFAULT_ORT_CDN_URL=q;exports.DEFAULT_REFS_STORAGE_KEY=X;exports.DEFAULT_THRESHOLD_STORAGE_KEY=gt;exports.DEFAULT_WASM_PATHS=Z;exports.Detector=It;exports.EnrollmentSession=Lt;exports.Storage=H;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function Dt(m){return m&&m.__esModule&&Object.prototype.hasOwnProperty.call(m,"default")?m.default:m}var _t,pt;function zt(){if(pt)return _t;pt=1;function m(o){if(this.size=o|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=o<<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 n=0,e=1;this.size>e;e<<=1)n++;this._width=n%2===0?n-1:n,this._bitrev=new Array(1<<this._width);for(var i=0;i<this._bitrev.length;i++){this._bitrev[i]=0;for(var r=0;r<this._width;r+=2){var h=this._width-r-2;this._bitrev[i]|=(i>>>r&3)<<h}}this._out=null,this._data=null,this._inv=0}return _t=m,m.prototype.fromComplexArray=function(t,s){for(var n=s||new Array(t.length>>>1),e=0;e<t.length;e+=2)n[e>>>1]=t[e];return n},m.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var s=0;s<t.length;s++)t[s]=0;return t},m.prototype.toComplexArray=function(t,s){for(var n=s||this.createComplexArray(),e=0;e<n.length;e+=2)n[e]=t[e>>>1],n[e+1]=0;return n},m.prototype.completeSpectrum=function(t){for(var s=this._csize,n=s>>>1,e=2;e<n;e+=2)t[s-e]=t[e],t[s-e+1]=-t[e+1]},m.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},m.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},m.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 n=0;n<t.length;n++)t[n]/=this.size;this._out=null,this._data=null},m.prototype._transform4=function(){var t=this._out,s=this._csize,n=this._width,e=1<<n,i=s/e<<1,r,h,l=this._bitrev;if(i===4)for(r=0,h=0;r<s;r+=i,h++){const u=l[h];this._singleTransform2(r,u,e)}else for(r=0,h=0;r<s;r+=i,h++){const u=l[h];this._singleTransform4(r,u,e)}var d=this._inv?-1:1,a=this.table;for(e>>=2;e>=2;e>>=2){i=s/e<<1;var c=i>>>2;for(r=0;r<s;r+=i)for(var _=r+c,v=r,f=0;v<_;v+=2,f+=e){const u=v,p=u+c,w=p+c,g=w+c,T=t[u],A=t[u+1],b=t[p],y=t[p+1],F=t[w],S=t[w+1],C=t[g],E=t[g+1],R=T,P=A,M=a[f],U=d*a[f+1],D=b*M-y*U,z=b*U+y*M,H=a[2*f],k=d*a[2*f+1],O=F*H-S*k,B=F*k+S*H,j=a[3*f],K=d*a[3*f+1],$=C*j-E*K,G=C*K+E*j,J=R+O,x=P+B,I=R-O,Y=P-B,Q=D+$,L=z+G,N=d*(D-$),V=d*(z-G),tt=J+Q,ot=x+L,nt=J-Q,at=x-L,it=I+V,lt=Y-N,ct=I-V,ht=Y+N;t[u]=tt,t[u+1]=ot,t[p]=it,t[p+1]=lt,t[w]=nt,t[w+1]=at,t[g]=ct,t[g+1]=ht}}},m.prototype._singleTransform2=function(t,s,n){const e=this._out,i=this._data,r=i[s],h=i[s+1],l=i[s+n],d=i[s+n+1],a=r+l,c=h+d,_=r-l,v=h-d;e[t]=a,e[t+1]=c,e[t+2]=_,e[t+3]=v},m.prototype._singleTransform4=function(t,s,n){const e=this._out,i=this._data,r=this._inv?-1:1,h=n*2,l=n*3,d=i[s],a=i[s+1],c=i[s+n],_=i[s+n+1],v=i[s+h],f=i[s+h+1],u=i[s+l],p=i[s+l+1],w=d+v,g=a+f,T=d-v,A=a-f,b=c+u,y=_+p,F=r*(c-u),S=r*(_-p),C=w+b,E=g+y,R=T+S,P=A-F,M=w-b,U=g-y,D=T-S,z=A+F;e[t]=C,e[t+1]=E,e[t+2]=R,e[t+3]=P,e[t+4]=M,e[t+5]=U,e[t+6]=D,e[t+7]=z},m.prototype._realTransform4=function(){var t=this._out,s=this._csize,n=this._width,e=1<<n,i=s/e<<1,r,h,l=this._bitrev;if(i===4)for(r=0,h=0;r<s;r+=i,h++){const dt=l[h];this._singleRealTransform2(r,dt>>>1,e>>>1)}else for(r=0,h=0;r<s;r+=i,h++){const dt=l[h];this._singleRealTransform4(r,dt>>>1,e>>>1)}var d=this._inv?-1:1,a=this.table;for(e>>=2;e>=2;e>>=2){i=s/e<<1;var c=i>>>1,_=c>>>1,v=_>>>1;for(r=0;r<s;r+=i)for(var f=0,u=0;f<=v;f+=2,u+=e){var p=r+f,w=p+_,g=w+_,T=g+_,A=t[p],b=t[p+1],y=t[w],F=t[w+1],S=t[g],C=t[g+1],E=t[T],R=t[T+1],P=A,M=b,U=a[u],D=d*a[u+1],z=y*U-F*D,H=y*D+F*U,k=a[2*u],O=d*a[2*u+1],B=S*k-C*O,j=S*O+C*k,K=a[3*u],$=d*a[3*u+1],G=E*K-R*$,J=E*$+R*K,x=P+B,I=M+j,Y=P-B,Q=M-j,L=z+G,N=H+J,V=d*(z-G),tt=d*(H-J),ot=x+L,nt=I+N,at=Y+tt,it=Q-V;if(t[p]=ot,t[p+1]=nt,t[w]=at,t[w+1]=it,f===0){var lt=x-L,ct=I-N;t[g]=lt,t[g+1]=ct;continue}if(f!==v){var ht=Y,Tt=-Q,bt=x,At=-I,Ft=-d*tt,St=-d*V,Ct=-d*N,Et=-d*L,Rt=ht+Ft,Pt=Tt+St,Mt=bt+Et,Ut=At-Ct,ut=r+_-f,vt=r+c-f;t[ut]=Rt,t[ut+1]=Pt,t[vt]=Mt,t[vt+1]=Ut}}}},m.prototype._singleRealTransform2=function(t,s,n){const e=this._out,i=this._data,r=i[s],h=i[s+n],l=r+h,d=r-h;e[t]=l,e[t+1]=0,e[t+2]=d,e[t+3]=0},m.prototype._singleRealTransform4=function(t,s,n){const e=this._out,i=this._data,r=this._inv?-1:1,h=n*2,l=n*3,d=i[s],a=i[s+n],c=i[s+h],_=i[s+l],v=d+c,f=d-c,u=a+_,p=r*(a-_),w=v+u,g=f,T=-p,A=v-u,b=f,y=p;e[t]=w,e[t+1]=0,e[t+2]=g,e[t+3]=T,e[t+4]=A,e[t+5]=0,e[t+6]=b,e[t+7]=y},_t}var xt=zt();const It=Dt(xt);class ft{constructor(o=16e3,t=512,s=64){this._sampleRate=o,this._nfft=t,this._nfilt=s,this._fft=new It(t),this._melFilters=this._createMelFilterbank()}_hzToMel(o){return 2595*Math.log10(1+o/700)}_melToHz(o){return 700*(10**(o/2595)-1)}_createMelFilterbank(){const t=this._sampleRate/2,s=this._hzToMel(0),n=this._hzToMel(t),e=new Float32Array(this._nfilt+2);for(let l=0;l<this._nfilt+2;l++)e[l]=s+l*(n-s)/(this._nfilt+1);const r=e.map(l=>this._melToHz(l)).map(l=>Math.floor((this._nfft+1)*l/this._sampleRate)),h=[];for(let l=0;l<this._nfilt;l++){const d=new Float32Array(Math.floor(this._nfft/2)+1);for(let a=r[l];a<r[l+1];a++)d[a]=(a-r[l])/(r[l+1]-r[l]);for(let a=r[l+1];a<r[l+2];a++)d[a]=(r[l+2]-a)/(r[l+2]-r[l+1]);h.push(d)}return h}logfbank(o){const t=Math.floor(.025*this._sampleRate),s=Math.floor(.01*this._sampleRate),n=1+Math.ceil((o.length-t)/s),e=new Float32Array(n*this._nfilt),i=new Float32Array(this._nfft),r=this._fft.createComplexArray();for(let h=0;h<n;h++){const l=h*s;i.fill(0);for(let c=0;c<t&&l+c<o.length;c++)i[c]=o[l+c];const d=this._fft.toComplexArray(i,null);this._fft.transform(r,d);const a=new Float32Array(Math.floor(this._nfft/2)+1);for(let c=0;c<a.length;c++){const _=r[2*c],v=r[2*c+1];a[c]=1/this._nfft*(_*_+v*v),a[c]===0&&(a[c]=1e-30)}for(let c=0;c<this._nfilt;c++){let _=0;const v=this._melFilters[c];for(let f=0;f<a.length;f++)_+=a[f]*v[f];_===0&&(_=1e-30),e[h*this._nfilt+c]=Math.log(_)}}return e}maxCosineSim(o,t){let s=0;for(const n of t){let e=0;for(let r=0;r<n.length;r++)e+=o[r]*n[r];const i=(e+1)/2;i>s&&(s=i)}return s}}async function et(m=Z,o=q){const t=await import(o);return t.env.wasm.wasmPaths=m,t.env.wasm.numThreads=1,t}let st=null;async function mt(m=rt,o=Z,t=q,s){return st||(st=et(o,t).then(n=>s?n.InferenceSession.create(new Uint8Array(s),{executionProviders:["wasm"],graphOptimizationLevel:"all"}):n.InferenceSession.create(m,{executionProviders:["wasm"],graphOptimizationLevel:"all"}))),st}function Lt(){return st!==null}class W{static loadWords(o=X){try{const t=localStorage.getItem(o);return t?JSON.parse(t):[]}catch{return[]}}static saveWord(o,t=X){const s=W.loadWords(t).filter(n=>n.word_name!==o.word_name);localStorage.setItem(t,JSON.stringify([...s,o]))}static deleteWord(o,t=X){try{const s=W.loadWords(t).filter(n=>n.word_name!==o);localStorage.setItem(t,JSON.stringify(s))}catch{}}}const wt={info:()=>{},warn:()=>{},error:()=>{}},Nt={info:console.info.bind(console),warn:console.warn.bind(console),error:console.error.bind(console)};class Wt{constructor(o,t){this._started=!1,this._inferring=!1,this._audioCtx=null,this._stream=null,this._refEmbeddings=new Map,this._lastMatchAt=0,this._lastInferenceAt=0,this._initPromise=null;const{refsStorageKey:s=X,thresholdStorageKey:n=yt,wasmPaths:e=Z,modelPath:i=rt,audioProcessorPath:r=gt,ortCdnUrl:h=q,audioUtils:l=new ft,log:d=!1}=t||{};this._log=d===!1?wt:d===!0?Nt:{...wt,...d},this._audioUtils=l,this._commands=o,this._refsStorageKey=s,this._thresholdStorageKey=n,this._audioProcessorPath=r,this._wasmPaths=e,this._modelPath=i,this._ortCdnUrl=h;try{const a=localStorage.getItem(this._thresholdStorageKey);this._threshold=a!==null?Math.max(0,Math.min(1,Number(a))):.65}catch{this._threshold=.65}}get threshold(){return this._threshold}set threshold(o){this._threshold=Math.max(0,Math.min(1,o));try{localStorage.setItem(this._thresholdStorageKey,String(this._threshold))}catch{}}get listening(){return this._started}async _trackFetch(o,t,s){const n=await fetch(o);if(!n.ok)throw new Error(`HTTP ${n.status} fetching ${o}`);const e=Number(n.headers.get("content-length")??"0");if(e>0&&(s.total+=e),!n.body){const a=await n.arrayBuffer();return s.downloaded+=a.byteLength,e||(s.total+=a.byteLength),t==null||t(s.downloaded,s.total),a}const i=n.body.getReader(),r=[];let h=0;for(;;){const{done:a,value:c}=await i.read();if(a)break;r.push(c),h+=c.length,s.downloaded+=c.length,t==null||t(s.downloaded,s.total)}e||(s.total+=h);const l=new Uint8Array(h);let d=0;for(const a of r)l.set(a,d),d+=a.length;return l.buffer}async _init(o){const t={downloaded:0,total:0},s=W.loadWords(this._refsStorageKey),n=new Set(s.map(a=>a.word_name)),e=new Set,i=[];for(const a of this._commands)for(const c of a.triggers)!e.has(c.name)&&c.defaultRefPath&&!n.has(c.name)&&(e.add(c.name),i.push({name:c.name,path:c.defaultRefPath}));const r=et(this._wasmPaths,this._ortCdnUrl),h=Lt(),[l,...d]=await Promise.all([h?Promise.resolve(null):this._trackFetch(this._modelPath,o,t),...i.map(({path:a})=>this._trackFetch(a,o,t))]);await r,await mt(this._modelPath,this._wasmPaths,this._ortCdnUrl,h?void 0:l);for(let a=0;a<i.length;a++)try{const c=JSON.parse(new TextDecoder().decode(d[a]));this.addCustomWord(c),W.saveWord(c,this._refsStorageKey)}catch{this._log.warn(`[Mellon] failed to parse ref file: ${i[a].path}`)}for(const a of s)this._refEmbeddings.set(a.word_name,a.embeddings);this._log.info("[Mellon] init complete, loaded refs:",[...this._refEmbeddings.keys()])}async init(o){this._initPromise||(this._initPromise=this._init(o)),await this._initPromise}addCustomWord(o){if(!(Array.isArray(o.embeddings)&&o.embeddings.length>0))throw new Error("invalid ref file for : "+o.word_name);this._refEmbeddings.set(o.word_name,o.embeddings)}async start(){if(this._started)return;await this.init();let o;try{o=await navigator.mediaDevices.getUserMedia({audio:{noiseSuppression:!1,echoCancellation:!1,autoGainControl:!1,channelCount:1}})}catch{o=await navigator.mediaDevices.getUserMedia({audio:!0})}this._stream=o;const t=new AudioContext({sampleRate:16e3});this._audioCtx=t,await t.audioWorklet.addModule(this._audioProcessorPath);const s=t.createMediaStreamSource(o),n=new AudioWorkletNode(t,"audio-processor");n.port.onmessage=e=>{this._handleBuffer(e.data)},s.connect(n),n.connect(t.destination),this._started=!0}async stop(){if(this._started=!1,this._audioCtx&&(await this._audioCtx.close(),this._audioCtx=null),this._stream){for(const o of this._stream.getTracks())o.stop();this._stream=null}}async _handleBuffer(o){if(this._inferring)return;const t=Date.now();if(!(t-this._lastInferenceAt<300)){this._lastInferenceAt=t,this._inferring=!0;try{const[s,n]=await Promise.all([et(this._wasmPaths,this._ortCdnUrl),mt(this._modelPath,this._wasmPaths,this._ortCdnUrl)]),e=this._audioUtils.logfbank(o),i=new s.Tensor("float32",e,[1,1,149,64]),r=await n.run({input:i}),h=r[Object.keys(r)[0]].data;let l=!1;for(const d of this._commands){if(l)break;for(const a of d.triggers){const c=this._refEmbeddings.get(a.name);if(!c)continue;const _=this._audioUtils.maxCosineSim(h,c);if(_>=this._threshold&&t-this._lastMatchAt>2e3){this._lastMatchAt=t,this._log.info(`[Mellon] match: "${a}" sim=${_.toFixed(3)}`),typeof d.onMatch=="function"&&d.onMatch(a.name,_),l=!0;break}}}}catch(s){this._log.error("[Mellon] inference error:",s)}finally{this._inferring=!1}}}}class Ht{constructor(o,t){this._config={},this._samples=[],this._wordName=o,this._config.modelPath=(t==null?void 0:t.modelPath)||rt,this._config.wasmPaths=(t==null?void 0:t.wasmPaths)||Z,this._config.ortCdnUrl=(t==null?void 0:t.ortCdnUrl)||q,this._audioUtils=(t==null?void 0:t.audioUtils)??new ft}async recordSample(){const o=await navigator.mediaDevices.getUserMedia({audio:!0}),t=new AudioContext({sampleRate:16e3}),s=await new Promise((i,r)=>{const h=new MediaRecorder(o),l=[];h.ondataavailable=d=>{d.data.size>0&&l.push(d.data)},h.onstop=async()=>{var d;for(const a of o.getTracks())a.stop();try{const c=await new Blob(l,{type:((d=l[0])==null?void 0:d.type)||"audio/webm"}).arrayBuffer(),_=await t.decodeAudioData(c);await t.close(),i(_.getChannelData(0).slice())}catch(a){r(a)}},h.start(),setTimeout(()=>{try{h.stop()}catch{}},1500)}),n=24e3,e=new Float32Array(n);return e.set(s.slice(0,n)),this._samples.push(e),this._samples.length}deleteSample(o){if(o<0||o>=this._samples.length)throw new RangeError(`index ${o} out of bounds (${this._samples.length} samples)`);return this._samples.splice(o,1),this._samples.length}async generateRef(){const[o,t]=await Promise.all([et(this._config.wasmPaths,this._config.ortCdnUrl),mt(this._config.modelPath,this._config.wasmPaths,this._config.ortCdnUrl)]),s=[];for(const n of this._samples){const e=this._audioUtils.logfbank(n),i=new o.Tensor("float32",e,[1,1,149,64]),r=await t.run({input:i}),h=Array.from(r[Object.keys(r)[0]].data);s.push(h)}return{word_name:this._wordName,model_type:"resnet_50_arc",embeddings:s}}}const Z="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/",q="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.wasm.min.mjs",rt="https://huggingface.co/ComicScrip/mellon/resolve/main/model.onnx",gt="https://cdn.jsdelivr.net/npm/mellon@0.0.14/dist/assets/audio-processor.js",X="mellon-refs",yt="mellon-threshold";exports.AudioUtils=ft;exports.DEFAULT_AUDIO_PROCESSOR_PATH=gt;exports.DEFAULT_MODEL_PATH=rt;exports.DEFAULT_ORT_CDN_URL=q;exports.DEFAULT_REFS_STORAGE_KEY=X;exports.DEFAULT_THRESHOLD_STORAGE_KEY=yt;exports.DEFAULT_WASM_PATHS=Z;exports.Detector=Wt;exports.EnrollmentSession=Ht;exports.Storage=W;
package/dist/mellon.mjs CHANGED
@@ -1,17 +1,17 @@
1
- function Rt(m) {
1
+ function Et(m) {
2
2
  return m && m.__esModule && Object.prototype.hasOwnProperty.call(m, "default") ? m.default : m;
3
3
  }
4
- var lt, vt;
4
+ var dt, vt;
5
5
  function Ut() {
6
- if (vt) return lt;
6
+ if (vt) return dt;
7
7
  vt = 1;
8
8
  function m(o) {
9
9
  if (this.size = o | 0, this.size <= 1 || (this.size & this.size - 1) !== 0)
10
10
  throw new Error("FFT size must be a power of two and bigger than 1");
11
11
  this._csize = o << 1;
12
12
  for (var t = new Array(this.size * 2), s = 0; s < t.length; s += 2) {
13
- const h = Math.PI * s / this.size;
14
- t[s] = Math.cos(h), t[s + 1] = -Math.sin(h);
13
+ const c = Math.PI * s / this.size;
14
+ t[s] = Math.cos(c), t[s + 1] = -Math.sin(c);
15
15
  }
16
16
  this.table = t;
17
17
  for (var n = 0, e = 1; this.size > e; e <<= 1)
@@ -20,13 +20,13 @@ function Ut() {
20
20
  for (var i = 0; i < this._bitrev.length; i++) {
21
21
  this._bitrev[i] = 0;
22
22
  for (var r = 0; r < this._width; r += 2) {
23
- var l = this._width - r - 2;
24
- this._bitrev[i] |= (i >>> r & 3) << l;
23
+ var h = this._width - r - 2;
24
+ this._bitrev[i] |= (i >>> r & 3) << h;
25
25
  }
26
26
  }
27
27
  this._out = null, this._data = null, this._inv = 0;
28
28
  }
29
- return lt = m, m.prototype.fromComplexArray = function(t, s) {
29
+ return dt = m, m.prototype.fromComplexArray = function(t, s) {
30
30
  for (var n = s || new Array(t.length >>> 1), e = 0; e < t.length; e += 2)
31
31
  n[e >>> 1] = t[e];
32
32
  return n;
@@ -58,76 +58,76 @@ function Ut() {
58
58
  t[n] /= this.size;
59
59
  this._out = null, this._data = null;
60
60
  }, m.prototype._transform4 = function() {
61
- var t = this._out, s = this._csize, n = this._width, e = 1 << n, i = s / e << 1, r, l, h = this._bitrev;
61
+ var t = this._out, s = this._csize, n = this._width, e = 1 << n, i = s / e << 1, r, h, c = this._bitrev;
62
62
  if (i === 4)
63
- for (r = 0, l = 0; r < s; r += i, l++) {
64
- const u = h[l];
63
+ for (r = 0, h = 0; r < s; r += i, h++) {
64
+ const u = c[h];
65
65
  this._singleTransform2(r, u, e);
66
66
  }
67
67
  else
68
- for (r = 0, l = 0; r < s; r += i, l++) {
69
- const u = h[l];
68
+ for (r = 0, h = 0; r < s; r += i, h++) {
69
+ const u = c[h];
70
70
  this._singleTransform4(r, u, e);
71
71
  }
72
- var c = this._inv ? -1 : 1, a = this.table;
72
+ var d = this._inv ? -1 : 1, a = this.table;
73
73
  for (e >>= 2; e >= 2; e >>= 2) {
74
74
  i = s / e << 1;
75
- var d = i >>> 2;
75
+ var l = i >>> 2;
76
76
  for (r = 0; r < s; r += i)
77
- for (var _ = r + d, v = r, f = 0; v < _; v += 2, f += e) {
78
- const u = v, p = u + d, w = p + d, g = w + d, b = t[u], T = t[u + 1], F = t[p], y = t[p + 1], A = t[w], C = t[w + 1], M = t[g], S = t[g + 1], P = b, R = T, U = a[f], E = c * a[f + 1], D = F * U - y * E, x = F * E + y * U, B = a[2 * f], N = c * a[2 * f + 1], j = A * B - C * N, H = A * N + C * B, K = a[3 * f], $ = c * a[3 * f + 1], L = M * K - S * $, J = M * $ + S * K, G = P + j, z = R + H, I = P - j, Y = R - H, O = D + L, W = x + J, k = c * (D - L), Q = c * (x - J), X = G + O, et = z + W, rt = G - O, ot = z - W, nt = I + Q, at = Y - k, it = I - Q, ct = Y + k;
79
- t[u] = X, t[u + 1] = et, t[p] = nt, t[p + 1] = at, t[w] = rt, t[w + 1] = ot, t[g] = it, t[g + 1] = ct;
77
+ for (var _ = r + l, v = r, f = 0; v < _; v += 2, f += e) {
78
+ const u = v, p = u + l, w = p + l, g = w + l, b = t[u], T = t[u + 1], F = t[p], y = t[p + 1], A = t[w], C = t[w + 1], S = t[g], M = t[g + 1], P = b, R = T, E = a[f], U = d * a[f + 1], D = F * E - y * U, x = F * U + y * E, k = a[2 * f], B = d * a[2 * f + 1], j = A * k - C * B, H = A * B + C * k, K = a[3 * f], L = d * a[3 * f + 1], $ = S * K - M * L, J = S * L + M * K, G = P + j, z = R + H, I = P - j, O = R - H, Y = D + $, N = x + J, W = d * (D - $), Q = d * (x - J), X = G + Y, rt = z + N, ot = G - Y, nt = z - N, at = I + Q, it = O - W, ct = I - Q, lt = O + W;
79
+ t[u] = X, t[u + 1] = rt, t[p] = at, t[p + 1] = it, t[w] = ot, t[w + 1] = nt, t[g] = ct, t[g + 1] = lt;
80
80
  }
81
81
  }
82
82
  }, m.prototype._singleTransform2 = function(t, s, n) {
83
- const e = this._out, i = this._data, r = i[s], l = i[s + 1], h = i[s + n], c = i[s + n + 1], a = r + h, d = l + c, _ = r - h, v = l - c;
84
- e[t] = a, e[t + 1] = d, e[t + 2] = _, e[t + 3] = v;
83
+ const e = this._out, i = this._data, r = i[s], h = i[s + 1], c = i[s + n], d = i[s + n + 1], a = r + c, l = h + d, _ = r - c, v = h - d;
84
+ e[t] = a, e[t + 1] = l, e[t + 2] = _, e[t + 3] = v;
85
85
  }, m.prototype._singleTransform4 = function(t, s, n) {
86
- const e = this._out, i = this._data, r = this._inv ? -1 : 1, l = n * 2, h = n * 3, c = i[s], a = i[s + 1], d = i[s + n], _ = i[s + n + 1], v = i[s + l], f = i[s + l + 1], u = i[s + h], p = i[s + h + 1], w = c + v, g = a + f, b = c - v, T = a - f, F = d + u, y = _ + p, A = r * (d - u), C = r * (_ - p), M = w + F, S = g + y, P = b + C, R = T - A, U = w - F, E = g - y, D = b - C, x = T + A;
87
- e[t] = M, e[t + 1] = S, e[t + 2] = P, e[t + 3] = R, e[t + 4] = U, e[t + 5] = E, e[t + 6] = D, e[t + 7] = x;
86
+ const e = this._out, i = this._data, r = this._inv ? -1 : 1, h = n * 2, c = n * 3, d = i[s], a = i[s + 1], l = i[s + n], _ = i[s + n + 1], v = i[s + h], f = i[s + h + 1], u = i[s + c], p = i[s + c + 1], w = d + v, g = a + f, b = d - v, T = a - f, F = l + u, y = _ + p, A = r * (l - u), C = r * (_ - p), S = w + F, M = g + y, P = b + C, R = T - A, E = w - F, U = g - y, D = b - C, x = T + A;
87
+ e[t] = S, e[t + 1] = M, e[t + 2] = P, e[t + 3] = R, e[t + 4] = E, e[t + 5] = U, e[t + 6] = D, e[t + 7] = x;
88
88
  }, m.prototype._realTransform4 = function() {
89
- var t = this._out, s = this._csize, n = this._width, e = 1 << n, i = s / e << 1, r, l, h = this._bitrev;
89
+ var t = this._out, s = this._csize, n = this._width, e = 1 << n, i = s / e << 1, r, h, c = this._bitrev;
90
90
  if (i === 4)
91
- for (r = 0, l = 0; r < s; r += i, l++) {
92
- const ht = h[l];
91
+ for (r = 0, h = 0; r < s; r += i, h++) {
92
+ const ht = c[h];
93
93
  this._singleRealTransform2(r, ht >>> 1, e >>> 1);
94
94
  }
95
95
  else
96
- for (r = 0, l = 0; r < s; r += i, l++) {
97
- const ht = h[l];
96
+ for (r = 0, h = 0; r < s; r += i, h++) {
97
+ const ht = c[h];
98
98
  this._singleRealTransform4(r, ht >>> 1, e >>> 1);
99
99
  }
100
- var c = this._inv ? -1 : 1, a = this.table;
100
+ var d = this._inv ? -1 : 1, a = this.table;
101
101
  for (e >>= 2; e >= 2; e >>= 2) {
102
102
  i = s / e << 1;
103
- var d = i >>> 1, _ = d >>> 1, v = _ >>> 1;
103
+ var l = i >>> 1, _ = l >>> 1, v = _ >>> 1;
104
104
  for (r = 0; r < s; r += i)
105
105
  for (var f = 0, u = 0; f <= v; f += 2, u += e) {
106
- var p = r + f, w = p + _, g = w + _, b = g + _, T = t[p], F = t[p + 1], y = t[w], A = t[w + 1], C = t[g], M = t[g + 1], S = t[b], P = t[b + 1], R = T, U = F, E = a[u], D = c * a[u + 1], x = y * E - A * D, B = y * D + A * E, N = a[2 * u], j = c * a[2 * u + 1], H = C * N - M * j, K = C * j + M * N, $ = a[3 * u], L = c * a[3 * u + 1], J = S * $ - P * L, G = S * L + P * $, z = R + H, I = U + K, Y = R - H, O = U - K, W = x + J, k = B + G, Q = c * (x - J), X = c * (B - G), et = z + W, rt = I + k, ot = Y + X, nt = O - Q;
107
- if (t[p] = et, t[p + 1] = rt, t[w] = ot, t[w + 1] = nt, f === 0) {
108
- var at = z - W, it = I - k;
109
- t[g] = at, t[g + 1] = it;
106
+ var p = r + f, w = p + _, g = w + _, b = g + _, T = t[p], F = t[p + 1], y = t[w], A = t[w + 1], C = t[g], S = t[g + 1], M = t[b], P = t[b + 1], R = T, E = F, U = a[u], D = d * a[u + 1], x = y * U - A * D, k = y * D + A * U, B = a[2 * u], j = d * a[2 * u + 1], H = C * B - S * j, K = C * j + S * B, L = a[3 * u], $ = d * a[3 * u + 1], J = M * L - P * $, G = M * $ + P * L, z = R + H, I = E + K, O = R - H, Y = E - K, N = x + J, W = k + G, Q = d * (x - J), X = d * (k - G), rt = z + N, ot = I + W, nt = O + X, at = Y - Q;
107
+ if (t[p] = rt, t[p + 1] = ot, t[w] = nt, t[w + 1] = at, f === 0) {
108
+ var it = z - N, ct = I - W;
109
+ t[g] = it, t[g + 1] = ct;
110
110
  continue;
111
111
  }
112
112
  if (f !== v) {
113
- var ct = Y, wt = -O, gt = z, yt = -I, bt = -c * X, Ft = -c * Q, Tt = -c * k, At = -c * W, Ct = ct + bt, Mt = wt + Ft, St = gt + At, Pt = yt - Tt, ft = r + _ - f, ut = r + d - f;
114
- t[ft] = Ct, t[ft + 1] = Mt, t[ut] = St, t[ut + 1] = Pt;
113
+ var lt = O, gt = -Y, yt = z, bt = -I, Ft = -d * X, Tt = -d * Q, At = -d * W, Ct = -d * N, St = lt + Ft, Mt = gt + Tt, Pt = yt + Ct, Rt = bt - At, ft = r + _ - f, ut = r + l - f;
114
+ t[ft] = St, t[ft + 1] = Mt, t[ut] = Pt, t[ut + 1] = Rt;
115
115
  }
116
116
  }
117
117
  }
118
118
  }, m.prototype._singleRealTransform2 = function(t, s, n) {
119
- const e = this._out, i = this._data, r = i[s], l = i[s + n], h = r + l, c = r - l;
120
- e[t] = h, e[t + 1] = 0, e[t + 2] = c, e[t + 3] = 0;
119
+ const e = this._out, i = this._data, r = i[s], h = i[s + n], c = r + h, d = r - h;
120
+ e[t] = c, e[t + 1] = 0, e[t + 2] = d, e[t + 3] = 0;
121
121
  }, m.prototype._singleRealTransform4 = function(t, s, n) {
122
- const e = this._out, i = this._data, r = this._inv ? -1 : 1, l = n * 2, h = n * 3, c = i[s], a = i[s + n], d = i[s + l], _ = i[s + h], v = c + d, f = c - d, u = a + _, p = r * (a - _), w = v + u, g = f, b = -p, T = v - u, F = f, y = p;
122
+ const e = this._out, i = this._data, r = this._inv ? -1 : 1, h = n * 2, c = n * 3, d = i[s], a = i[s + n], l = i[s + h], _ = i[s + c], v = d + l, f = d - l, u = a + _, p = r * (a - _), w = v + u, g = f, b = -p, T = v - u, F = f, y = p;
123
123
  e[t] = w, e[t + 1] = 0, e[t + 2] = g, e[t + 3] = b, e[t + 4] = T, e[t + 5] = 0, e[t + 6] = F, e[t + 7] = y;
124
- }, lt;
124
+ }, dt;
125
125
  }
126
- var Et = Ut();
127
- const Dt = /* @__PURE__ */ Rt(Et);
128
- class pt {
126
+ var Dt = Ut();
127
+ const xt = /* @__PURE__ */ Et(Dt);
128
+ class wt {
129
129
  constructor(o = 16e3, t = 512, s = 64) {
130
- this._sampleRate = o, this._nfft = t, this._nfilt = s, this._fft = new Dt(t), this._melFilters = this._createMelFilterbank();
130
+ this._sampleRate = o, this._nfft = t, this._nfilt = s, this._fft = new xt(t), this._melFilters = this._createMelFilterbank();
131
131
  }
132
132
  _hzToMel(o) {
133
133
  return 2595 * Math.log10(1 + o / 700);
@@ -137,40 +137,40 @@ class pt {
137
137
  }
138
138
  _createMelFilterbank() {
139
139
  const t = this._sampleRate / 2, s = this._hzToMel(0), n = this._hzToMel(t), e = new Float32Array(this._nfilt + 2);
140
- for (let h = 0; h < this._nfilt + 2; h++)
141
- e[h] = s + h * (n - s) / (this._nfilt + 1);
142
- const r = e.map((h) => this._melToHz(h)).map((h) => Math.floor((this._nfft + 1) * h / this._sampleRate)), l = [];
143
- for (let h = 0; h < this._nfilt; h++) {
144
- const c = new Float32Array(Math.floor(this._nfft / 2) + 1);
145
- for (let a = r[h]; a < r[h + 1]; a++)
146
- c[a] = (a - r[h]) / (r[h + 1] - r[h]);
147
- for (let a = r[h + 1]; a < r[h + 2]; a++)
148
- c[a] = (r[h + 2] - a) / (r[h + 2] - r[h + 1]);
149
- l.push(c);
140
+ for (let c = 0; c < this._nfilt + 2; c++)
141
+ e[c] = s + c * (n - s) / (this._nfilt + 1);
142
+ const r = e.map((c) => this._melToHz(c)).map((c) => Math.floor((this._nfft + 1) * c / this._sampleRate)), h = [];
143
+ for (let c = 0; c < this._nfilt; c++) {
144
+ const d = new Float32Array(Math.floor(this._nfft / 2) + 1);
145
+ for (let a = r[c]; a < r[c + 1]; a++)
146
+ d[a] = (a - r[c]) / (r[c + 1] - r[c]);
147
+ for (let a = r[c + 1]; a < r[c + 2]; a++)
148
+ d[a] = (r[c + 2] - a) / (r[c + 2] - r[c + 1]);
149
+ h.push(d);
150
150
  }
151
- return l;
151
+ return h;
152
152
  }
153
153
  /** Returns a flat Float32Array of shape [numFrames × nfilt]. */
154
154
  logfbank(o) {
155
155
  const t = Math.floor(0.025 * this._sampleRate), s = Math.floor(0.01 * this._sampleRate), n = 1 + Math.ceil((o.length - t) / s), e = new Float32Array(n * this._nfilt), i = new Float32Array(this._nfft), r = this._fft.createComplexArray();
156
- for (let l = 0; l < n; l++) {
157
- const h = l * s;
156
+ for (let h = 0; h < n; h++) {
157
+ const c = h * s;
158
158
  i.fill(0);
159
- for (let d = 0; d < t && h + d < o.length; d++)
160
- i[d] = o[h + d];
161
- const c = this._fft.toComplexArray(i, null);
162
- this._fft.transform(r, c);
159
+ for (let l = 0; l < t && c + l < o.length; l++)
160
+ i[l] = o[c + l];
161
+ const d = this._fft.toComplexArray(i, null);
162
+ this._fft.transform(r, d);
163
163
  const a = new Float32Array(Math.floor(this._nfft / 2) + 1);
164
- for (let d = 0; d < a.length; d++) {
165
- const _ = r[2 * d], v = r[2 * d + 1];
166
- a[d] = 1 / this._nfft * (_ * _ + v * v), a[d] === 0 && (a[d] = 1e-30);
164
+ for (let l = 0; l < a.length; l++) {
165
+ const _ = r[2 * l], v = r[2 * l + 1];
166
+ a[l] = 1 / this._nfft * (_ * _ + v * v), a[l] === 0 && (a[l] = 1e-30);
167
167
  }
168
- for (let d = 0; d < this._nfilt; d++) {
168
+ for (let l = 0; l < this._nfilt; l++) {
169
169
  let _ = 0;
170
- const v = this._melFilters[d];
170
+ const v = this._melFilters[l];
171
171
  for (let f = 0; f < a.length; f++)
172
172
  _ += a[f] * v[f];
173
- _ === 0 && (_ = 1e-30), e[l * this._nfilt + d] = Math.log(_);
173
+ _ === 0 && (_ = 1e-30), e[h * this._nfilt + l] = Math.log(_);
174
174
  }
175
175
  }
176
176
  return e;
@@ -186,16 +186,16 @@ class pt {
186
186
  return s;
187
187
  }
188
188
  }
189
- async function q(m = tt, o = st) {
189
+ async function tt(m = st, o = et) {
190
190
  const t = await import(
191
191
  /* @vite-ignore */
192
192
  o
193
193
  );
194
194
  return t.env.wasm.wasmPaths = m, t.env.wasm.numThreads = 1, t;
195
195
  }
196
- let dt = null;
197
- async function _t(m = mt, o = tt, t = st, s) {
198
- return dt || (dt = q(o, t).then(
196
+ let Z = null;
197
+ async function _t(m = mt, o = st, t = et, s) {
198
+ return Z || (Z = tt(o, t).then(
199
199
  (n) => s ? n.InferenceSession.create(new Uint8Array(s), {
200
200
  executionProviders: ["wasm"],
201
201
  graphOptimizationLevel: "all"
@@ -203,10 +203,13 @@ async function _t(m = mt, o = tt, t = st, s) {
203
203
  executionProviders: ["wasm"],
204
204
  graphOptimizationLevel: "all"
205
205
  })
206
- )), dt;
206
+ )), Z;
207
+ }
208
+ function zt() {
209
+ return Z !== null;
207
210
  }
208
211
  class V {
209
- static loadWords(o = Z) {
212
+ static loadWords(o = q) {
210
213
  try {
211
214
  const t = localStorage.getItem(o);
212
215
  return t ? JSON.parse(t) : [];
@@ -214,11 +217,11 @@ class V {
214
217
  return [];
215
218
  }
216
219
  }
217
- static saveWord(o, t = Z) {
220
+ static saveWord(o, t = q) {
218
221
  const s = V.loadWords(t).filter((n) => n.word_name !== o.word_name);
219
222
  localStorage.setItem(t, JSON.stringify([...s, o]));
220
223
  }
221
- static deleteWord(o, t = Z) {
224
+ static deleteWord(o, t = q) {
222
225
  try {
223
226
  const s = V.loadWords(t).filter((n) => n.word_name !== o);
224
227
  localStorage.setItem(t, JSON.stringify(s));
@@ -226,22 +229,27 @@ class V {
226
229
  }
227
230
  }
228
231
  }
229
- class It {
232
+ const pt = { info: () => {
233
+ }, warn: () => {
234
+ }, error: () => {
235
+ } }, It = { info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console) };
236
+ class kt {
230
237
  constructor(o, t) {
231
238
  this._started = !1, this._inferring = !1, this._audioCtx = null, this._stream = null, this._refEmbeddings = /* @__PURE__ */ new Map(), this._lastMatchAt = 0, this._lastInferenceAt = 0, this._initPromise = null;
232
239
  const {
233
- refsStorageKey: s = Z,
234
- thresholdStorageKey: n = zt,
235
- wasmPaths: e = tt,
240
+ refsStorageKey: s = q,
241
+ thresholdStorageKey: n = Wt,
242
+ wasmPaths: e = st,
236
243
  modelPath: i = mt,
237
- audioProcessorPath: r = xt,
238
- ortCdnUrl: l = st,
239
- audioUtils: h = new pt()
244
+ audioProcessorPath: r = Nt,
245
+ ortCdnUrl: h = et,
246
+ audioUtils: c = new wt(),
247
+ log: d = !1
240
248
  } = t || {};
241
- this._audioUtils = h, this._commands = o, this._refsStorageKey = s, this._thresholdStorageKey = n, this._audioProcessorPath = r, this._wasmPaths = e, this._modelPath = i, this._ortCdnUrl = l;
249
+ this._log = d === !1 ? pt : d === !0 ? It : { ...pt, ...d }, this._audioUtils = c, this._commands = o, this._refsStorageKey = s, this._thresholdStorageKey = n, this._audioProcessorPath = r, this._wasmPaths = e, this._modelPath = i, this._ortCdnUrl = h;
242
250
  try {
243
- const c = localStorage.getItem(this._thresholdStorageKey);
244
- this._threshold = c !== null ? Math.max(0, Math.min(1, Number(c))) : 0.65;
251
+ const a = localStorage.getItem(this._thresholdStorageKey);
252
+ this._threshold = a !== null ? Math.max(0, Math.min(1, Number(a))) : 0.65;
245
253
  } catch {
246
254
  this._threshold = 0.65;
247
255
  }
@@ -272,39 +280,44 @@ class It {
272
280
  return s.downloaded += a.byteLength, e || (s.total += a.byteLength), t == null || t(s.downloaded, s.total), a;
273
281
  }
274
282
  const i = n.body.getReader(), r = [];
275
- let l = 0;
283
+ let h = 0;
276
284
  for (; ; ) {
277
- const { done: a, value: d } = await i.read();
285
+ const { done: a, value: l } = await i.read();
278
286
  if (a) break;
279
- r.push(d), l += d.length, s.downloaded += d.length, t == null || t(s.downloaded, s.total);
287
+ r.push(l), h += l.length, s.downloaded += l.length, t == null || t(s.downloaded, s.total);
280
288
  }
281
- e || (s.total += l);
282
- const h = new Uint8Array(l);
283
- let c = 0;
289
+ e || (s.total += h);
290
+ const c = new Uint8Array(h);
291
+ let d = 0;
284
292
  for (const a of r)
285
- h.set(a, c), c += a.length;
286
- return h.buffer;
293
+ c.set(a, d), d += a.length;
294
+ return c.buffer;
287
295
  }
288
296
  async _init(o) {
289
- const t = { downloaded: 0, total: 0 }, s = V.loadWords(this._refsStorageKey), n = new Set(s.map((c) => c.word_name)), e = /* @__PURE__ */ new Set(), i = [];
290
- for (const c of this._commands)
291
- for (const a of c.triggers)
292
- !e.has(a.name) && a.defaultRefPath && !n.has(a.name) && (e.add(a.name), i.push({ name: a.name, path: a.defaultRefPath }));
293
- const r = q(this._wasmPaths, this._ortCdnUrl), [l, ...h] = await Promise.all([
294
- this._trackFetch(this._modelPath, o, t),
295
- ...i.map(({ path: c }) => this._trackFetch(c, o, t))
297
+ const t = { downloaded: 0, total: 0 }, s = V.loadWords(this._refsStorageKey), n = new Set(s.map((a) => a.word_name)), e = /* @__PURE__ */ new Set(), i = [];
298
+ for (const a of this._commands)
299
+ for (const l of a.triggers)
300
+ !e.has(l.name) && l.defaultRefPath && !n.has(l.name) && (e.add(l.name), i.push({ name: l.name, path: l.defaultRefPath }));
301
+ const r = tt(this._wasmPaths, this._ortCdnUrl), h = zt(), [c, ...d] = await Promise.all([
302
+ h ? Promise.resolve(null) : this._trackFetch(this._modelPath, o, t),
303
+ ...i.map(({ path: a }) => this._trackFetch(a, o, t))
296
304
  ]);
297
- await r, await _t(this._modelPath, this._wasmPaths, this._ortCdnUrl, l);
298
- for (let c = 0; c < i.length; c++)
305
+ await r, await _t(
306
+ this._modelPath,
307
+ this._wasmPaths,
308
+ this._ortCdnUrl,
309
+ h ? void 0 : c
310
+ );
311
+ for (let a = 0; a < i.length; a++)
299
312
  try {
300
- const a = JSON.parse(new TextDecoder().decode(h[c]));
301
- this.addCustomWord(a), V.saveWord(a, this._refsStorageKey);
313
+ const l = JSON.parse(new TextDecoder().decode(d[a]));
314
+ this.addCustomWord(l), V.saveWord(l, this._refsStorageKey);
302
315
  } catch {
303
- console.warn(`[Mellon] failed to parse ref file: ${i[c].path}`);
316
+ this._log.warn(`[Mellon] failed to parse ref file: ${i[a].path}`);
304
317
  }
305
- for (const c of s)
306
- this._refEmbeddings.set(c.word_name, c.embeddings);
307
- console.info("[Mellon] init complete, loaded refs:", [...this._refEmbeddings.keys()]);
318
+ for (const a of s)
319
+ this._refEmbeddings.set(a.word_name, a.embeddings);
320
+ this._log.info("[Mellon] init complete, loaded refs:", [...this._refEmbeddings.keys()]);
308
321
  }
309
322
  /**
310
323
  * Loads the ONNX model and all reference embeddings.
@@ -358,50 +371,50 @@ class It {
358
371
  if (!(t - this._lastInferenceAt < 300)) {
359
372
  this._lastInferenceAt = t, this._inferring = !0;
360
373
  try {
361
- const [s, n] = await Promise.all([q(this._wasmPaths, this._ortCdnUrl), _t(this._modelPath, this._wasmPaths, this._ortCdnUrl)]), e = this._audioUtils.logfbank(o), i = new s.Tensor("float32", e, [1, 1, 149, 64]), r = await n.run({ input: i }), l = r[Object.keys(r)[0]].data;
362
- let h = !1;
363
- for (const c of this._commands) {
364
- if (h) break;
365
- for (const a of c.triggers) {
366
- const d = this._refEmbeddings.get(a.name);
367
- if (!d) continue;
368
- const _ = this._audioUtils.maxCosineSim(l, d);
374
+ const [s, n] = await Promise.all([tt(this._wasmPaths, this._ortCdnUrl), _t(this._modelPath, this._wasmPaths, this._ortCdnUrl)]), e = this._audioUtils.logfbank(o), i = new s.Tensor("float32", e, [1, 1, 149, 64]), r = await n.run({ input: i }), h = r[Object.keys(r)[0]].data;
375
+ let c = !1;
376
+ for (const d of this._commands) {
377
+ if (c) break;
378
+ for (const a of d.triggers) {
379
+ const l = this._refEmbeddings.get(a.name);
380
+ if (!l) continue;
381
+ const _ = this._audioUtils.maxCosineSim(h, l);
369
382
  if (_ >= this._threshold && t - this._lastMatchAt > 2e3) {
370
- this._lastMatchAt = t, console.info(`[Mellon] match: "${a}" sim=${_.toFixed(3)}`), typeof c.onMatch == "function" && c.onMatch(a.name, _), h = !0;
383
+ this._lastMatchAt = t, this._log.info(`[Mellon] match: "${a}" sim=${_.toFixed(3)}`), typeof d.onMatch == "function" && d.onMatch(a.name, _), c = !0;
371
384
  break;
372
385
  }
373
386
  }
374
387
  }
375
388
  } catch (s) {
376
- console.error("[Mellon] inference error:", s);
389
+ this._log.error("[Mellon] inference error:", s);
377
390
  } finally {
378
391
  this._inferring = !1;
379
392
  }
380
393
  }
381
394
  }
382
395
  }
383
- class Wt {
396
+ class Bt {
384
397
  constructor(o, t) {
385
- this._config = {}, this._samples = [], this._wordName = o, this._config.modelPath = (t == null ? void 0 : t.modelPath) || mt, this._config.wasmPaths = (t == null ? void 0 : t.wasmPaths) || tt, this._config.ortCdnUrl = (t == null ? void 0 : t.ortCdnUrl) || st, this._audioUtils = (t == null ? void 0 : t.audioUtils) ?? new pt();
398
+ this._config = {}, this._samples = [], this._wordName = o, this._config.modelPath = (t == null ? void 0 : t.modelPath) || mt, this._config.wasmPaths = (t == null ? void 0 : t.wasmPaths) || st, this._config.ortCdnUrl = (t == null ? void 0 : t.ortCdnUrl) || et, this._audioUtils = (t == null ? void 0 : t.audioUtils) ?? new wt();
386
399
  }
387
400
  /** Records 1.5 s of audio, stores the decoded PCM, returns new sample count. */
388
401
  async recordSample() {
389
402
  const o = await navigator.mediaDevices.getUserMedia({ audio: !0 }), t = new AudioContext({ sampleRate: 16e3 }), s = await new Promise((i, r) => {
390
- const l = new MediaRecorder(o), h = [];
391
- l.ondataavailable = (c) => {
392
- c.data.size > 0 && h.push(c.data);
393
- }, l.onstop = async () => {
394
- var c;
403
+ const h = new MediaRecorder(o), c = [];
404
+ h.ondataavailable = (d) => {
405
+ d.data.size > 0 && c.push(d.data);
406
+ }, h.onstop = async () => {
407
+ var d;
395
408
  for (const a of o.getTracks()) a.stop();
396
409
  try {
397
- const d = await new Blob(h, { type: ((c = h[0]) == null ? void 0 : c.type) || "audio/webm" }).arrayBuffer(), _ = await t.decodeAudioData(d);
410
+ const l = await new Blob(c, { type: ((d = c[0]) == null ? void 0 : d.type) || "audio/webm" }).arrayBuffer(), _ = await t.decodeAudioData(l);
398
411
  await t.close(), i(_.getChannelData(0).slice());
399
412
  } catch (a) {
400
413
  r(a);
401
414
  }
402
- }, l.start(), setTimeout(() => {
415
+ }, h.start(), setTimeout(() => {
403
416
  try {
404
- l.stop();
417
+ h.stop();
405
418
  } catch {
406
419
  }
407
420
  }, 1500);
@@ -416,24 +429,24 @@ class Wt {
416
429
  }
417
430
  /** Runs ONNX inference on every recorded sample to produce reference embeddings. */
418
431
  async generateRef() {
419
- const [o, t] = await Promise.all([q(this._config.wasmPaths, this._config.ortCdnUrl), _t(this._config.modelPath, this._config.wasmPaths, this._config.ortCdnUrl)]), s = [];
432
+ const [o, t] = await Promise.all([tt(this._config.wasmPaths, this._config.ortCdnUrl), _t(this._config.modelPath, this._config.wasmPaths, this._config.ortCdnUrl)]), s = [];
420
433
  for (const n of this._samples) {
421
- const e = this._audioUtils.logfbank(n), i = new o.Tensor("float32", e, [1, 1, 149, 64]), r = await t.run({ input: i }), l = Array.from(r[Object.keys(r)[0]].data);
422
- s.push(l);
434
+ const e = this._audioUtils.logfbank(n), i = new o.Tensor("float32", e, [1, 1, 149, 64]), r = await t.run({ input: i }), h = Array.from(r[Object.keys(r)[0]].data);
435
+ s.push(h);
423
436
  }
424
437
  return { word_name: this._wordName, model_type: "resnet_50_arc", embeddings: s };
425
438
  }
426
439
  }
427
- const tt = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/", st = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.wasm.min.mjs", mt = "https://huggingface.co/ComicScrip/mellon/resolve/main/model.onnx", xt = "https://cdn.jsdelivr.net/npm/mellon@0.0.14/dist/assets/audio-processor.js", Z = "mellon-refs", zt = "mellon-threshold";
440
+ const st = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/", et = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.wasm.min.mjs", mt = "https://huggingface.co/ComicScrip/mellon/resolve/main/model.onnx", Nt = "https://cdn.jsdelivr.net/npm/mellon@0.0.14/dist/assets/audio-processor.js", q = "mellon-refs", Wt = "mellon-threshold";
428
441
  export {
429
- pt as AudioUtils,
430
- xt as DEFAULT_AUDIO_PROCESSOR_PATH,
442
+ wt as AudioUtils,
443
+ Nt as DEFAULT_AUDIO_PROCESSOR_PATH,
431
444
  mt as DEFAULT_MODEL_PATH,
432
- st as DEFAULT_ORT_CDN_URL,
433
- Z as DEFAULT_REFS_STORAGE_KEY,
434
- zt as DEFAULT_THRESHOLD_STORAGE_KEY,
435
- tt as DEFAULT_WASM_PATHS,
436
- It as Detector,
437
- Wt as EnrollmentSession,
445
+ et as DEFAULT_ORT_CDN_URL,
446
+ q as DEFAULT_REFS_STORAGE_KEY,
447
+ Wt as DEFAULT_THRESHOLD_STORAGE_KEY,
448
+ st as DEFAULT_WASM_PATHS,
449
+ kt as Detector,
450
+ Bt as EnrollmentSession,
438
451
  V as Storage
439
452
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mellon",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "Offline, in-browser voice commands powered by EfficientWord-Net (ResNet-50 ArcFace).",
5
5
  "type": "module",
6
6
  "main": "./dist/mellon.cjs",