mellon 0.0.1 → 0.0.2

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
@@ -81,8 +81,8 @@ Refs are fetched automatically during `start()`. You can enroll your own words
81
81
  By default, the WASM runtime and model load from the jsDelivr CDN — no setup needed. For air-gapped or private-network deployments, copy the assets locally and tell the library where to find them:
82
82
 
83
83
  ```bash
84
- cp -r node_modules/mellon/dist/assets/wasm public/mellon-assets/wasm
85
- cp node_modules/mellon/dist/assets/model.onnx public/mellon-assets/model.onnx
84
+ cp -r node_modules/mellon/dist/wasm public/mellon-assets/wasm
85
+ cp node_modules/mellon/dist/models/model.onnx public/mellon-assets/model.onnx
86
86
  ```
87
87
 
88
88
  Then pass the paths to the constructor:
@@ -104,8 +104,8 @@ export default {
104
104
  plugins: [
105
105
  viteStaticCopy({
106
106
  targets: [
107
- { src: 'node_modules/mellon/dist/assets/wasm/*', dest: 'mellon-assets/wasm' },
108
- { src: 'node_modules/mellon/dist/assets/model.onnx', dest: 'mellon-assets' },
107
+ { src: 'node_modules/mellon/dist/wasm/*', dest: 'mellon-assets/wasm' },
108
+ { src: 'node_modules/mellon/dist/models/model.onnx', dest: 'mellon-assets' },
109
109
  ],
110
110
  }),
111
111
  ],
@@ -1,4 +1,4 @@
1
- (function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function e(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(s){if(s.ep)return;s.ep=!0;const o=e(s);fetch(s.href,o)}})();const mt="0.0.1",ut=[1,1,149,64],Ne=`https://cdn.jsdelivr.net/npm/mellon@${mt}/dist/assets`,ce={wasmBasePath:`${Ne}/wasm/`,modelUrl:`${Ne}/model.onnx`};let J=null,le=null,de=null;async function ht(t){return J?(t==null||t(1),J):le||(le=(async()=>{const n=ce.wasmBasePath.endsWith("/")?ce.wasmBasePath:ce.wasmBasePath+"/",e=n+"ort.all.min.mjs",r=ce.modelUrl;de=await new Function("url","return import(url)")(e),de.env.wasm.wasmPaths=n;const o=await fetch(r);if(!o.ok)throw new Error(`Failed to fetch model: ${o.status}`);const i=parseInt(o.headers.get("content-length")||"0",10),a=o.body.getReader(),d=[];let m=0;for(;;){const{done:v,value:w}=await a.read();if(v)break;d.push(w),m+=w.byteLength,i>0&&(t==null||t(m/i))}const u=new Uint8Array(m);let f=0;for(const v of d)u.set(v,f),f+=v.byteLength;return J=await de.InferenceSession.create(u.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),t==null||t(1),J})(),le)}async function Je(t){if(!J)throw new Error("Model not loaded — call loadModel() first");const n=new de.Tensor("float32",t,ut),e=await J.run({input:n}),r=Object.keys(e)[0];return e[r].data}function ft(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var Se,Ie;function pt(){if(Ie)return Se;Ie=1;function t(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 e=new Array(this.size*2),r=0;r<e.length;r+=2){const m=Math.PI*r/this.size;e[r]=Math.cos(m),e[r+1]=-Math.sin(m)}this.table=e;for(var s=0,o=1;this.size>o;o<<=1)s++;this._width=s%2===0?s-1:s,this._bitrev=new Array(1<<this._width);for(var i=0;i<this._bitrev.length;i++){this._bitrev[i]=0;for(var a=0;a<this._width;a+=2){var d=this._width-a-2;this._bitrev[i]|=(i>>>a&3)<<d}}this._out=null,this._data=null,this._inv=0}return Se=t,t.prototype.fromComplexArray=function(e,r){for(var s=r||new Array(e.length>>>1),o=0;o<e.length;o+=2)s[o>>>1]=e[o];return s},t.prototype.createComplexArray=function(){const e=new Array(this._csize);for(var r=0;r<e.length;r++)e[r]=0;return e},t.prototype.toComplexArray=function(e,r){for(var s=r||this.createComplexArray(),o=0;o<s.length;o+=2)s[o]=e[o>>>1],s[o+1]=0;return s},t.prototype.completeSpectrum=function(e){for(var r=this._csize,s=r>>>1,o=2;o<s;o+=2)e[r-o]=e[o],e[r-o+1]=-e[o+1]},t.prototype.transform=function(e,r){if(e===r)throw new Error("Input and output buffers must be different");this._out=e,this._data=r,this._inv=0,this._transform4(),this._out=null,this._data=null},t.prototype.realTransform=function(e,r){if(e===r)throw new Error("Input and output buffers must be different");this._out=e,this._data=r,this._inv=0,this._realTransform4(),this._out=null,this._data=null},t.prototype.inverseTransform=function(e,r){if(e===r)throw new Error("Input and output buffers must be different");this._out=e,this._data=r,this._inv=1,this._transform4();for(var s=0;s<e.length;s++)e[s]/=this.size;this._out=null,this._data=null},t.prototype._transform4=function(){var e=this._out,r=this._csize,s=this._width,o=1<<s,i=r/o<<1,a,d,m=this._bitrev;if(i===4)for(a=0,d=0;a<r;a+=i,d++){const p=m[d];this._singleTransform2(a,p,o)}else for(a=0,d=0;a<r;a+=i,d++){const p=m[d];this._singleTransform4(a,p,o)}var u=this._inv?-1:1,f=this.table;for(o>>=2;o>=2;o>>=2){i=r/o<<1;var v=i>>>2;for(a=0;a<r;a+=i)for(var w=a+v,L=a,g=0;L<w;L+=2,g+=o){const p=L,b=p+v,y=b+v,_=y+v,C=e[p],A=e[p+1],T=e[b],E=e[b+1],F=e[y],R=e[y+1],M=e[_],$=e[_+1],N=C,I=A,D=f[g],G=u*f[g+1],z=T*D-E*G,B=T*G+E*D,V=f[2*g],K=u*f[2*g+1],Y=F*V-R*K,Q=F*K+R*V,X=f[3*g],Z=u*f[3*g+1],ee=M*X-$*Z,te=M*Z+$*X,ne=N+Y,P=I+Q,j=N-Y,re=I-Q,oe=z+ee,U=B+te,H=u*(z-ee),se=u*(B-te),ie=ne+oe,ve=P+U,ge=ne-oe,we=P-U,be=j+se,ye=re-H,_e=j-se,Le=re+H;e[p]=ie,e[p+1]=ve,e[b]=be,e[b+1]=ye,e[y]=ge,e[y+1]=we,e[_]=_e,e[_+1]=Le}}},t.prototype._singleTransform2=function(e,r,s){const o=this._out,i=this._data,a=i[r],d=i[r+1],m=i[r+s],u=i[r+s+1],f=a+m,v=d+u,w=a-m,L=d-u;o[e]=f,o[e+1]=v,o[e+2]=w,o[e+3]=L},t.prototype._singleTransform4=function(e,r,s){const o=this._out,i=this._data,a=this._inv?-1:1,d=s*2,m=s*3,u=i[r],f=i[r+1],v=i[r+s],w=i[r+s+1],L=i[r+d],g=i[r+d+1],p=i[r+m],b=i[r+m+1],y=u+L,_=f+g,C=u-L,A=f-g,T=v+p,E=w+b,F=a*(v-p),R=a*(w-b),M=y+T,$=_+E,N=C+R,I=A-F,D=y-T,G=_-E,z=C-R,B=A+F;o[e]=M,o[e+1]=$,o[e+2]=N,o[e+3]=I,o[e+4]=D,o[e+5]=G,o[e+6]=z,o[e+7]=B},t.prototype._realTransform4=function(){var e=this._out,r=this._csize,s=this._width,o=1<<s,i=r/o<<1,a,d,m=this._bitrev;if(i===4)for(a=0,d=0;a<r;a+=i,d++){const Ee=m[d];this._singleRealTransform2(a,Ee>>>1,o>>>1)}else for(a=0,d=0;a<r;a+=i,d++){const Ee=m[d];this._singleRealTransform4(a,Ee>>>1,o>>>1)}var u=this._inv?-1:1,f=this.table;for(o>>=2;o>=2;o>>=2){i=r/o<<1;var v=i>>>1,w=v>>>1,L=w>>>1;for(a=0;a<r;a+=i)for(var g=0,p=0;g<=L;g+=2,p+=o){var b=a+g,y=b+w,_=y+w,C=_+w,A=e[b],T=e[b+1],E=e[y],F=e[y+1],R=e[_],M=e[_+1],$=e[C],N=e[C+1],I=A,D=T,G=f[p],z=u*f[p+1],B=E*G-F*z,V=E*z+F*G,K=f[2*p],Y=u*f[2*p+1],Q=R*K-M*Y,X=R*Y+M*K,Z=f[3*p],ee=u*f[3*p+1],te=$*Z-N*ee,ne=$*ee+N*Z,P=I+Q,j=D+X,re=I-Q,oe=D-X,U=B+te,H=V+ne,se=u*(B-te),ie=u*(V-ne),ve=P+U,ge=j+H,we=re+ie,be=oe-se;if(e[b]=ve,e[b+1]=ge,e[y]=we,e[y+1]=be,g===0){var ye=P-U,_e=j-H;e[_]=ye,e[_+1]=_e;continue}if(g!==L){var Le=re,et=-oe,tt=P,nt=-j,rt=-u*ie,ot=-u*se,st=-u*H,at=-u*U,it=Le+rt,ct=et+ot,lt=tt+at,dt=nt-st,Me=a+w-g,$e=a+v-g;e[Me]=it,e[Me+1]=ct,e[$e]=lt,e[$e+1]=dt}}}},t.prototype._singleRealTransform2=function(e,r,s){const o=this._out,i=this._data,a=i[r],d=i[r+s],m=a+d,u=a-d;o[e]=m,o[e+1]=0,o[e+2]=u,o[e+3]=0},t.prototype._singleRealTransform4=function(e,r,s){const o=this._out,i=this._data,a=this._inv?-1:1,d=s*2,m=s*3,u=i[r],f=i[r+s],v=i[r+d],w=i[r+m],L=u+v,g=u-v,p=f+w,b=a*(f-w),y=L+p,_=g,C=-b,A=L-p,T=g,E=b;o[e]=y,o[e+1]=0,o[e+2]=_,o[e+3]=C,o[e+4]=A,o[e+5]=0,o[e+6]=T,o[e+7]=E},Se}var vt=pt();const gt=ft(vt),me=16e3,W=512,k=64,De=Math.floor(.025*me),Ge=Math.floor(.01*me);function ze(t){return 2595*Math.log10(1+t/700)}function wt(t){return 700*(10**(t/2595)-1)}function bt(){const t=ze(0),n=ze(me/2),e=new Float64Array(k+2);for(let a=0;a<k+2;a++)e[a]=t+a*(n-t)/(k+1);const s=e.map(a=>wt(a)).map(a=>Math.floor((W+1)*a/me)),o=[],i=Math.floor(W/2)+1;for(let a=0;a<k;a++){const d=new Float32Array(i);for(let m=s[a];m<s[a+1];m++)d[m]=(m-s[a])/(s[a+1]-s[a]);for(let m=s[a+1];m<s[a+2];m++)d[m]=(s[a+2]-m)/(s[a+2]-s[a+1]);o.push(d)}return o}const yt=bt(),ue=new gt(W),Ce=new Float32Array(W),Be=ue.createComplexArray(),Te=ue.createComplexArray(),ke=new Float32Array(Math.floor(W/2)+1);function Oe(t){const n=1+Math.ceil((t.length-De)/Ge),e=new Float32Array(n*k),r=Math.floor(W/2)+1;for(let s=0;s<n;s++){const o=s*Ge;Ce.fill(0);for(let i=0;i<De&&o+i<t.length;i++)Ce[i]=t[o+i];ue.toComplexArray(Ce,Be),ue.transform(Te,Be);for(let i=0;i<r;i++){const a=Te[2*i],d=Te[2*i+1],m=(a*a+d*d)/W;ke[i]=m===0?1e-30:m}for(let i=0;i<k;i++){const a=yt[i];let d=0;for(let m=0;m<r;m++)d+=ke[m]*a[m];e[s*k+i]=Math.log(d===0?1e-30:d)}}return e}function _t(t,n){let e=0;for(let r=0;r<t.length;r++)e+=t[r]*n[r];return(e+1)/2}function Lt(t,n){let e=0;for(const r of n){const s=_t(t,r);s>e&&(e=s)}return e}class Et extends EventTarget{constructor({name:n,refEmbeddings:e,threshold:r=.65,relaxationMs:s=2e3,inferenceGapMs:o=300}){super(),this.name=n,this.refEmbeddings=e,this.threshold=r,this.relaxationMs=s,this.inferenceGapMs=o,this._lastDetectionAt=0,this._lastInferenceAt=0,this._lastScore=0}get lastScore(){return this._lastScore}async scoreFrame(n){const e=Date.now();if(e-this._lastInferenceAt<this.inferenceGapMs)return null;this._lastInferenceAt=e;const r=Oe(n),s=await Je(r),o=Lt(s,this.refEmbeddings);return this._lastScore=o,o>=this.threshold&&e-this._lastDetectionAt>=this.relaxationMs&&(this._lastDetectionAt=e,this.dispatchEvent(new CustomEvent("match",{detail:{name:this.name,confidence:o,timestamp:e}}))),o}}const We=16e3,St=1500,xe=24e3;function Pe(t){if(t.length===xe)return t;const n=new Float32Array(xe);return n.set(t.subarray(0,xe)),n}class Ct 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((e,r)=>{const s=new AudioContext({sampleRate:We}),o=new MediaRecorder(n),i=[];this.dispatchEvent(new CustomEvent("recording-start")),o.ondataavailable=a=>{a.data.size>0&&i.push(a.data)},o.onstop=async()=>{n.getTracks().forEach(a=>a.stop());try{const d=await new Blob(i,{type:"audio/webm"}).arrayBuffer(),m=await s.decodeAudioData(d);await s.close();const u=m.getChannelData(0),f=Pe(new Float32Array(u)),v=this._push(f,`Recorded #${this.samples.length}`);e(v)}catch(a){await s.close().catch(()=>{}),r(a)}},o.start(),setTimeout(()=>o.stop(),St)})}async addAudioFile(n){const e=await n.arrayBuffer(),r=new AudioContext({sampleRate:We}),s=await r.decodeAudioData(e);await r.close();const o=s.getChannelData(0),i=Pe(new Float32Array(o));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 e=0;e<this.samples.length;e++){const r=Oe(this.samples[e].audioBuffer),s=await Je(r);n.push(Array.from(s)),this.dispatchEvent(new CustomEvent("progress",{detail:{done:e+1,total:this.samples.length}}))}return{word_name:this.wordName,model_type:"resnet_50_arc",embeddings:n}}_push(n,e){this.samples.push({audioBuffer:n,name:e});const r=this.samples.length;return this.dispatchEvent(new CustomEvent("sample-added",{detail:{count:r,name:e}})),r}}const Re="mellon_custom_refs";function q(){try{const t=localStorage.getItem(Re);return t?JSON.parse(t):[]}catch{return[]}}function qe(t){const n=q().filter(e=>e.word_name!==t.word_name);n.push(t),localStorage.setItem(Re,JSON.stringify(n))}function Tt(t){const n=q().filter(e=>e.word_name!==t);localStorage.setItem(Re,JSON.stringify(n))}function Ve(t){const n=JSON.stringify(t,null,2),e=new Blob([n],{type:"application/json"}),r=URL.createObjectURL(e),s=Object.assign(document.createElement("a"),{href:r,download:`${t.word_name}_ref.json`});document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}async function xt(t){const n=await t.text();let e;try{e=JSON.parse(n)}catch{throw new Error("Invalid JSON")}if(!e.embeddings||!Array.isArray(e.embeddings)||!e.embeddings.length)throw new Error('Missing or empty "embeddings" array');if(!Array.isArray(e.embeddings[0]))throw new Error('"embeddings" must be a 2D array');return e.word_name||(e.word_name=t.name.replace(/_ref\.json$/i,"").replace(/\.json$/i,"")),e}const c={isListening:!1,audioCtx:null,processorNode:null,silentGain:null,micStream:null,detectors:new Map,selectedWords:new Set,threshold:.65,enrollSession:null,lastGeneratedRef:null,recordTimerInterval:null},h=t=>document.getElementById(t),l={loaderBar:h("model-loader"),loaderPct:h("loader-pct"),loaderFill:h("loader-fill"),orb:h("orb"),orbLabel:h("orb-label"),btnToggle:h("btn-toggle-detect"),wordSelect:h("word-select"),threshRange:h("threshold-range"),threshVal:h("threshold-val"),confPct:h("conf-pct"),confFill:h("conf-fill"),detectLog:h("detection-log"),btnClearLog:h("btn-clear-log"),enrollName:h("enroll-name"),sampleCount:h("sample-count"),btnRecord:h("btn-record"),recordLbl:h("record-lbl"),recordProgress:h("record-progress-wrap"),recordFill:h("record-progress-fill"),recordTimer:h("record-timer"),uploadSamples:h("upload-samples"),uploadRefJson:h("upload-ref-json"),samplesList:h("samples-list"),btnGenerate:h("btn-generate"),btnExport:h("btn-export"),enrollStatus:h("enroll-status"),customList:h("custom-words-list")};async function At(){Ft(),$t(),Nt(),pe(),Ae(!0);try{await ht(t=>Rt(t)),Ae(!1),l.btnToggle.disabled=!1,x("Model ready — press Start Listening")}catch(t){Ae(!1),x("⚠️ Model failed to load — check console"),console.error("[mellon] Model load error:",t)}}function Ft(){document.querySelectorAll(".tab-btn").forEach(t=>{t.addEventListener("click",()=>{const n=t.dataset.tab;document.querySelectorAll(".tab-btn").forEach(e=>{e.classList.toggle("active",e===t),e.setAttribute("aria-selected",e===t?"true":"false")}),document.querySelectorAll(".tab-panel").forEach(e=>{const r=e.id===`tab-${n}`;e.hidden=!r})})})}function Ae(t){l.loaderBar.hidden=!t}function Rt(t){const n=Math.round(t*100);l.loaderPct.textContent=`${n}%`,l.loaderFill.style.width=`${n}%`}function ae(t){l.orb.className=`orb orb--${t}`}function x(t){l.orbLabel.textContent=t}function Ke(t){const n=(t*100).toFixed(1);l.confPct.textContent=`${n}%`,l.confFill.style.width=`${n}%`}function Mt(t,n){l.detectLog.querySelectorAll(".log-empty").forEach(s=>s.remove());const e=new Date().toLocaleTimeString(),r=document.createElement("div");r.className="log-item",r.innerHTML=`
1
+ (function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function e(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(s){if(s.ep)return;s.ep=!0;const o=e(s);fetch(s.href,o)}})();const mt="0.0.2",ut=[1,1,149,64],Ne=`https://cdn.jsdelivr.net/npm/mellon@${mt}/dist`,ce={wasmBasePath:`${Ne}/wasm/`,modelUrl:`${Ne}/models/model.onnx`};let J=null,le=null,de=null;async function ht(t){return J?(t==null||t(1),J):le||(le=(async()=>{const n=ce.wasmBasePath.endsWith("/")?ce.wasmBasePath:ce.wasmBasePath+"/",e=n+"ort.all.min.mjs",r=ce.modelUrl;de=await new Function("url","return import(url)")(e),de.env.wasm.wasmPaths=n;const o=await fetch(r);if(!o.ok)throw new Error(`Failed to fetch model: ${o.status}`);const i=parseInt(o.headers.get("content-length")||"0",10),a=o.body.getReader(),d=[];let m=0;for(;;){const{done:v,value:w}=await a.read();if(v)break;d.push(w),m+=w.byteLength,i>0&&(t==null||t(m/i))}const u=new Uint8Array(m);let f=0;for(const v of d)u.set(v,f),f+=v.byteLength;return J=await de.InferenceSession.create(u.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),t==null||t(1),J})(),le)}async function Je(t){if(!J)throw new Error("Model not loaded — call loadModel() first");const n=new de.Tensor("float32",t,ut),e=await J.run({input:n}),r=Object.keys(e)[0];return e[r].data}function ft(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var Se,Ie;function pt(){if(Ie)return Se;Ie=1;function t(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 e=new Array(this.size*2),r=0;r<e.length;r+=2){const m=Math.PI*r/this.size;e[r]=Math.cos(m),e[r+1]=-Math.sin(m)}this.table=e;for(var s=0,o=1;this.size>o;o<<=1)s++;this._width=s%2===0?s-1:s,this._bitrev=new Array(1<<this._width);for(var i=0;i<this._bitrev.length;i++){this._bitrev[i]=0;for(var a=0;a<this._width;a+=2){var d=this._width-a-2;this._bitrev[i]|=(i>>>a&3)<<d}}this._out=null,this._data=null,this._inv=0}return Se=t,t.prototype.fromComplexArray=function(e,r){for(var s=r||new Array(e.length>>>1),o=0;o<e.length;o+=2)s[o>>>1]=e[o];return s},t.prototype.createComplexArray=function(){const e=new Array(this._csize);for(var r=0;r<e.length;r++)e[r]=0;return e},t.prototype.toComplexArray=function(e,r){for(var s=r||this.createComplexArray(),o=0;o<s.length;o+=2)s[o]=e[o>>>1],s[o+1]=0;return s},t.prototype.completeSpectrum=function(e){for(var r=this._csize,s=r>>>1,o=2;o<s;o+=2)e[r-o]=e[o],e[r-o+1]=-e[o+1]},t.prototype.transform=function(e,r){if(e===r)throw new Error("Input and output buffers must be different");this._out=e,this._data=r,this._inv=0,this._transform4(),this._out=null,this._data=null},t.prototype.realTransform=function(e,r){if(e===r)throw new Error("Input and output buffers must be different");this._out=e,this._data=r,this._inv=0,this._realTransform4(),this._out=null,this._data=null},t.prototype.inverseTransform=function(e,r){if(e===r)throw new Error("Input and output buffers must be different");this._out=e,this._data=r,this._inv=1,this._transform4();for(var s=0;s<e.length;s++)e[s]/=this.size;this._out=null,this._data=null},t.prototype._transform4=function(){var e=this._out,r=this._csize,s=this._width,o=1<<s,i=r/o<<1,a,d,m=this._bitrev;if(i===4)for(a=0,d=0;a<r;a+=i,d++){const p=m[d];this._singleTransform2(a,p,o)}else for(a=0,d=0;a<r;a+=i,d++){const p=m[d];this._singleTransform4(a,p,o)}var u=this._inv?-1:1,f=this.table;for(o>>=2;o>=2;o>>=2){i=r/o<<1;var v=i>>>2;for(a=0;a<r;a+=i)for(var w=a+v,L=a,g=0;L<w;L+=2,g+=o){const p=L,b=p+v,y=b+v,_=y+v,C=e[p],A=e[p+1],T=e[b],E=e[b+1],F=e[y],R=e[y+1],M=e[_],$=e[_+1],N=C,I=A,D=f[g],G=u*f[g+1],z=T*D-E*G,B=T*G+E*D,V=f[2*g],K=u*f[2*g+1],Y=F*V-R*K,Q=F*K+R*V,X=f[3*g],Z=u*f[3*g+1],ee=M*X-$*Z,te=M*Z+$*X,ne=N+Y,P=I+Q,j=N-Y,re=I-Q,oe=z+ee,U=B+te,H=u*(z-ee),se=u*(B-te),ie=ne+oe,ve=P+U,ge=ne-oe,we=P-U,be=j+se,ye=re-H,_e=j-se,Le=re+H;e[p]=ie,e[p+1]=ve,e[b]=be,e[b+1]=ye,e[y]=ge,e[y+1]=we,e[_]=_e,e[_+1]=Le}}},t.prototype._singleTransform2=function(e,r,s){const o=this._out,i=this._data,a=i[r],d=i[r+1],m=i[r+s],u=i[r+s+1],f=a+m,v=d+u,w=a-m,L=d-u;o[e]=f,o[e+1]=v,o[e+2]=w,o[e+3]=L},t.prototype._singleTransform4=function(e,r,s){const o=this._out,i=this._data,a=this._inv?-1:1,d=s*2,m=s*3,u=i[r],f=i[r+1],v=i[r+s],w=i[r+s+1],L=i[r+d],g=i[r+d+1],p=i[r+m],b=i[r+m+1],y=u+L,_=f+g,C=u-L,A=f-g,T=v+p,E=w+b,F=a*(v-p),R=a*(w-b),M=y+T,$=_+E,N=C+R,I=A-F,D=y-T,G=_-E,z=C-R,B=A+F;o[e]=M,o[e+1]=$,o[e+2]=N,o[e+3]=I,o[e+4]=D,o[e+5]=G,o[e+6]=z,o[e+7]=B},t.prototype._realTransform4=function(){var e=this._out,r=this._csize,s=this._width,o=1<<s,i=r/o<<1,a,d,m=this._bitrev;if(i===4)for(a=0,d=0;a<r;a+=i,d++){const Ee=m[d];this._singleRealTransform2(a,Ee>>>1,o>>>1)}else for(a=0,d=0;a<r;a+=i,d++){const Ee=m[d];this._singleRealTransform4(a,Ee>>>1,o>>>1)}var u=this._inv?-1:1,f=this.table;for(o>>=2;o>=2;o>>=2){i=r/o<<1;var v=i>>>1,w=v>>>1,L=w>>>1;for(a=0;a<r;a+=i)for(var g=0,p=0;g<=L;g+=2,p+=o){var b=a+g,y=b+w,_=y+w,C=_+w,A=e[b],T=e[b+1],E=e[y],F=e[y+1],R=e[_],M=e[_+1],$=e[C],N=e[C+1],I=A,D=T,G=f[p],z=u*f[p+1],B=E*G-F*z,V=E*z+F*G,K=f[2*p],Y=u*f[2*p+1],Q=R*K-M*Y,X=R*Y+M*K,Z=f[3*p],ee=u*f[3*p+1],te=$*Z-N*ee,ne=$*ee+N*Z,P=I+Q,j=D+X,re=I-Q,oe=D-X,U=B+te,H=V+ne,se=u*(B-te),ie=u*(V-ne),ve=P+U,ge=j+H,we=re+ie,be=oe-se;if(e[b]=ve,e[b+1]=ge,e[y]=we,e[y+1]=be,g===0){var ye=P-U,_e=j-H;e[_]=ye,e[_+1]=_e;continue}if(g!==L){var Le=re,et=-oe,tt=P,nt=-j,rt=-u*ie,ot=-u*se,st=-u*H,at=-u*U,it=Le+rt,ct=et+ot,lt=tt+at,dt=nt-st,Me=a+w-g,$e=a+v-g;e[Me]=it,e[Me+1]=ct,e[$e]=lt,e[$e+1]=dt}}}},t.prototype._singleRealTransform2=function(e,r,s){const o=this._out,i=this._data,a=i[r],d=i[r+s],m=a+d,u=a-d;o[e]=m,o[e+1]=0,o[e+2]=u,o[e+3]=0},t.prototype._singleRealTransform4=function(e,r,s){const o=this._out,i=this._data,a=this._inv?-1:1,d=s*2,m=s*3,u=i[r],f=i[r+s],v=i[r+d],w=i[r+m],L=u+v,g=u-v,p=f+w,b=a*(f-w),y=L+p,_=g,C=-b,A=L-p,T=g,E=b;o[e]=y,o[e+1]=0,o[e+2]=_,o[e+3]=C,o[e+4]=A,o[e+5]=0,o[e+6]=T,o[e+7]=E},Se}var vt=pt();const gt=ft(vt),me=16e3,W=512,k=64,De=Math.floor(.025*me),Ge=Math.floor(.01*me);function ze(t){return 2595*Math.log10(1+t/700)}function wt(t){return 700*(10**(t/2595)-1)}function bt(){const t=ze(0),n=ze(me/2),e=new Float64Array(k+2);for(let a=0;a<k+2;a++)e[a]=t+a*(n-t)/(k+1);const s=e.map(a=>wt(a)).map(a=>Math.floor((W+1)*a/me)),o=[],i=Math.floor(W/2)+1;for(let a=0;a<k;a++){const d=new Float32Array(i);for(let m=s[a];m<s[a+1];m++)d[m]=(m-s[a])/(s[a+1]-s[a]);for(let m=s[a+1];m<s[a+2];m++)d[m]=(s[a+2]-m)/(s[a+2]-s[a+1]);o.push(d)}return o}const yt=bt(),ue=new gt(W),Ce=new Float32Array(W),Be=ue.createComplexArray(),Te=ue.createComplexArray(),ke=new Float32Array(Math.floor(W/2)+1);function Oe(t){const n=1+Math.ceil((t.length-De)/Ge),e=new Float32Array(n*k),r=Math.floor(W/2)+1;for(let s=0;s<n;s++){const o=s*Ge;Ce.fill(0);for(let i=0;i<De&&o+i<t.length;i++)Ce[i]=t[o+i];ue.toComplexArray(Ce,Be),ue.transform(Te,Be);for(let i=0;i<r;i++){const a=Te[2*i],d=Te[2*i+1],m=(a*a+d*d)/W;ke[i]=m===0?1e-30:m}for(let i=0;i<k;i++){const a=yt[i];let d=0;for(let m=0;m<r;m++)d+=ke[m]*a[m];e[s*k+i]=Math.log(d===0?1e-30:d)}}return e}function _t(t,n){let e=0;for(let r=0;r<t.length;r++)e+=t[r]*n[r];return(e+1)/2}function Lt(t,n){let e=0;for(const r of n){const s=_t(t,r);s>e&&(e=s)}return e}class Et extends EventTarget{constructor({name:n,refEmbeddings:e,threshold:r=.65,relaxationMs:s=2e3,inferenceGapMs:o=300}){super(),this.name=n,this.refEmbeddings=e,this.threshold=r,this.relaxationMs=s,this.inferenceGapMs=o,this._lastDetectionAt=0,this._lastInferenceAt=0,this._lastScore=0}get lastScore(){return this._lastScore}async scoreFrame(n){const e=Date.now();if(e-this._lastInferenceAt<this.inferenceGapMs)return null;this._lastInferenceAt=e;const r=Oe(n),s=await Je(r),o=Lt(s,this.refEmbeddings);return this._lastScore=o,o>=this.threshold&&e-this._lastDetectionAt>=this.relaxationMs&&(this._lastDetectionAt=e,this.dispatchEvent(new CustomEvent("match",{detail:{name:this.name,confidence:o,timestamp:e}}))),o}}const We=16e3,St=1500,xe=24e3;function Pe(t){if(t.length===xe)return t;const n=new Float32Array(xe);return n.set(t.subarray(0,xe)),n}class Ct 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((e,r)=>{const s=new AudioContext({sampleRate:We}),o=new MediaRecorder(n),i=[];this.dispatchEvent(new CustomEvent("recording-start")),o.ondataavailable=a=>{a.data.size>0&&i.push(a.data)},o.onstop=async()=>{n.getTracks().forEach(a=>a.stop());try{const d=await new Blob(i,{type:"audio/webm"}).arrayBuffer(),m=await s.decodeAudioData(d);await s.close();const u=m.getChannelData(0),f=Pe(new Float32Array(u)),v=this._push(f,`Recorded #${this.samples.length}`);e(v)}catch(a){await s.close().catch(()=>{}),r(a)}},o.start(),setTimeout(()=>o.stop(),St)})}async addAudioFile(n){const e=await n.arrayBuffer(),r=new AudioContext({sampleRate:We}),s=await r.decodeAudioData(e);await r.close();const o=s.getChannelData(0),i=Pe(new Float32Array(o));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 e=0;e<this.samples.length;e++){const r=Oe(this.samples[e].audioBuffer),s=await Je(r);n.push(Array.from(s)),this.dispatchEvent(new CustomEvent("progress",{detail:{done:e+1,total:this.samples.length}}))}return{word_name:this.wordName,model_type:"resnet_50_arc",embeddings:n}}_push(n,e){this.samples.push({audioBuffer:n,name:e});const r=this.samples.length;return this.dispatchEvent(new CustomEvent("sample-added",{detail:{count:r,name:e}})),r}}const Re="mellon_custom_refs";function q(){try{const t=localStorage.getItem(Re);return t?JSON.parse(t):[]}catch{return[]}}function qe(t){const n=q().filter(e=>e.word_name!==t.word_name);n.push(t),localStorage.setItem(Re,JSON.stringify(n))}function Tt(t){const n=q().filter(e=>e.word_name!==t);localStorage.setItem(Re,JSON.stringify(n))}function Ve(t){const n=JSON.stringify(t,null,2),e=new Blob([n],{type:"application/json"}),r=URL.createObjectURL(e),s=Object.assign(document.createElement("a"),{href:r,download:`${t.word_name}_ref.json`});document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}async function xt(t){const n=await t.text();let e;try{e=JSON.parse(n)}catch{throw new Error("Invalid JSON")}if(!e.embeddings||!Array.isArray(e.embeddings)||!e.embeddings.length)throw new Error('Missing or empty "embeddings" array');if(!Array.isArray(e.embeddings[0]))throw new Error('"embeddings" must be a 2D array');return e.word_name||(e.word_name=t.name.replace(/_ref\.json$/i,"").replace(/\.json$/i,"")),e}const c={isListening:!1,audioCtx:null,processorNode:null,silentGain:null,micStream:null,detectors:new Map,selectedWords:new Set,threshold:.65,enrollSession:null,lastGeneratedRef:null,recordTimerInterval:null},h=t=>document.getElementById(t),l={loaderBar:h("model-loader"),loaderPct:h("loader-pct"),loaderFill:h("loader-fill"),orb:h("orb"),orbLabel:h("orb-label"),btnToggle:h("btn-toggle-detect"),wordSelect:h("word-select"),threshRange:h("threshold-range"),threshVal:h("threshold-val"),confPct:h("conf-pct"),confFill:h("conf-fill"),detectLog:h("detection-log"),btnClearLog:h("btn-clear-log"),enrollName:h("enroll-name"),sampleCount:h("sample-count"),btnRecord:h("btn-record"),recordLbl:h("record-lbl"),recordProgress:h("record-progress-wrap"),recordFill:h("record-progress-fill"),recordTimer:h("record-timer"),uploadSamples:h("upload-samples"),uploadRefJson:h("upload-ref-json"),samplesList:h("samples-list"),btnGenerate:h("btn-generate"),btnExport:h("btn-export"),enrollStatus:h("enroll-status"),customList:h("custom-words-list")};async function At(){Ft(),$t(),Nt(),pe(),Ae(!0);try{await ht(t=>Rt(t)),Ae(!1),l.btnToggle.disabled=!1,x("Model ready — press Start Listening")}catch(t){Ae(!1),x("⚠️ Model failed to load — check console"),console.error("[mellon] Model load error:",t)}}function Ft(){document.querySelectorAll(".tab-btn").forEach(t=>{t.addEventListener("click",()=>{const n=t.dataset.tab;document.querySelectorAll(".tab-btn").forEach(e=>{e.classList.toggle("active",e===t),e.setAttribute("aria-selected",e===t?"true":"false")}),document.querySelectorAll(".tab-panel").forEach(e=>{const r=e.id===`tab-${n}`;e.hidden=!r})})})}function Ae(t){l.loaderBar.hidden=!t}function Rt(t){const n=Math.round(t*100);l.loaderPct.textContent=`${n}%`,l.loaderFill.style.width=`${n}%`}function ae(t){l.orb.className=`orb orb--${t}`}function x(t){l.orbLabel.textContent=t}function Ke(t){const n=(t*100).toFixed(1);l.confPct.textContent=`${n}%`,l.confFill.style.width=`${n}%`}function Mt(t,n){l.detectLog.querySelectorAll(".log-empty").forEach(s=>s.remove());const e=new Date().toLocaleTimeString(),r=document.createElement("div");r.className="log-item",r.innerHTML=`
2
2
  <div class="log-item-left">
3
3
  <span class="log-item-word">${O(t.toUpperCase())}</span>
4
4
  <span class="log-item-time">${O(e)}</span>
package/dist/index.html CHANGED
@@ -8,7 +8,7 @@
8
8
  <meta name="description" content="EfficientWord-Net offline hotword detection — runs 100% in your browser" />
9
9
  <link rel="manifest" href="./manifest.json" />
10
10
  <title>Mellon — Offline Hotword Detection</title>
11
- <script type="module" crossorigin src="/assets/index-CyhUtQlr.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-CiLJGV_Q.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-B3ZBo_ZU.css">
13
13
  </head>
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mellon",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
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",