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 +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/mellon.cjs +2 -2
- package/dist/mellon.mjs +90 -90
- package/package.json +1 -1
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.
|
|
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
|
|
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.
|
|
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
|
|
4
|
+
let D = null, q = null, tt = null;
|
|
5
5
|
function Qt({ assetsPath: a } = {}) {
|
|
6
|
-
a !== void 0 && (st.assetsPath = a),
|
|
6
|
+
a !== void 0 && (st.assetsPath = a), D = null, q = null, tt = null;
|
|
7
7
|
}
|
|
8
8
|
async function Vt(a) {
|
|
9
|
-
return
|
|
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:
|
|
17
|
+
const { done: m, value: f } = await o.read();
|
|
18
18
|
if (m) break;
|
|
19
|
-
c.push(
|
|
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
|
|
25
|
+
return D = await tt.InferenceSession.create(h.buffer, {
|
|
26
26
|
executionProviders: ["wasm"],
|
|
27
27
|
graphOptimizationLevel: "all"
|
|
28
|
-
}), a == null || a(1),
|
|
28
|
+
}), a == null || a(1), D;
|
|
29
29
|
})(), q);
|
|
30
30
|
}
|
|
31
|
-
async function
|
|
32
|
-
if (!
|
|
33
|
-
const n = new tt.Tensor("float32", a, Kt), t = await
|
|
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
|
|
39
|
+
var mt, Et;
|
|
40
40
|
function Zt() {
|
|
41
|
-
if (Et) return
|
|
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
|
|
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
|
|
113
|
-
const u = g, p = u + m, v = p + m, w = v + m, b = t[u],
|
|
114
|
-
t[u] = et, t[u + 1] =
|
|
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,
|
|
119
|
-
s[t] = d, s[t + 1] = m, s[t + 2] =
|
|
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],
|
|
122
|
-
s[t] =
|
|
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
|
|
128
|
-
this._singleRealTransform2(o,
|
|
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
|
|
133
|
-
this._singleRealTransform4(o,
|
|
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,
|
|
138
|
+
var m = i >>> 1, f = m >>> 1, g = f >>> 1;
|
|
139
139
|
for (o = 0; o < e; o += i)
|
|
140
|
-
for (var
|
|
141
|
-
var p = o +
|
|
142
|
-
if (t[p] =
|
|
143
|
-
var
|
|
144
|
-
t[w] =
|
|
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 (
|
|
148
|
-
var
|
|
149
|
-
t[
|
|
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],
|
|
158
|
-
s[t] = v, s[t + 1] = 0, s[t + 2] = w, s[t + 3] = b, s[t + 4] =
|
|
159
|
-
},
|
|
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,
|
|
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(
|
|
171
|
-
for (let o = 0; o <
|
|
172
|
-
t[o] = a + o * (n - a) / (
|
|
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 <
|
|
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),
|
|
183
|
-
function
|
|
184
|
-
const n = 1 + Math.ceil((a.length -
|
|
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 <
|
|
188
|
+
for (let i = 0; i < Mt && s + i < a.length; i++)
|
|
189
189
|
_t[i] = a[s + i];
|
|
190
|
-
rt.toComplexArray(_t,
|
|
190
|
+
rt.toComplexArray(_t, Tt), rt.transform(ft, Tt);
|
|
191
191
|
for (let i = 0; i < e; i++) {
|
|
192
|
-
const o =
|
|
193
|
-
|
|
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 <
|
|
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 +=
|
|
199
|
-
t[r *
|
|
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
|
|
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 =
|
|
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
|
|
249
|
-
function
|
|
250
|
-
if (a.length ===
|
|
251
|
-
const n = new Float32Array(
|
|
252
|
-
return n.set(a.subarray(0,
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
382
|
+
let vt = null;
|
|
383
383
|
function ce() {
|
|
384
|
-
if (!
|
|
384
|
+
if (!vt) {
|
|
385
385
|
const a = new Blob([ie], { type: "application/javascript" });
|
|
386
|
-
|
|
386
|
+
vt = URL.createObjectURL(a);
|
|
387
387
|
}
|
|
388
|
-
return
|
|
388
|
+
return vt;
|
|
389
389
|
}
|
|
390
390
|
const gt = "mellon_custom_refs";
|
|
391
|
-
function
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
609
|
+
wt as Mellon
|
|
610
610
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mellon",
|
|
3
|
-
"version": "0.0.
|
|
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",
|