mellon 0.0.8 → 0.0.9
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 +5 -61
- package/dist/mellon.cjs +1 -1
- package/dist/mellon.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
What's the elvish word for "friend" ?
|
|
2
|
+
|
|
3
|
+
# Mellon
|
|
2
4
|
|
|
3
5
|
Offline, fully in-browser **hotword / wake-word detection** powered by [EfficientWord-Net](https://github.com/Ant-Brain/EfficientWord-Net) (ResNet-50 ArcFace).
|
|
4
6
|
|
|
@@ -80,32 +82,13 @@ Refs are fetched automatically during `start()`. You can enroll your own words
|
|
|
80
82
|
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:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
83
|
-
cp -r node_modules/mellon/dist/assets
|
|
85
|
+
cp -r node_modules/mellon/dist/assets public/mellon-assets/
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
Then pass the paths to the constructor:
|
|
87
89
|
|
|
88
90
|
```js
|
|
89
|
-
new Mellon({
|
|
90
|
-
assetsPath: '/mellon-assets', // trailing slash required
|
|
91
|
-
})
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
For Vite projects add the copy step once in your config:
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
// vite.config.js
|
|
98
|
-
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
|
99
|
-
|
|
100
|
-
export default {
|
|
101
|
-
plugins: [
|
|
102
|
-
viteStaticCopy({
|
|
103
|
-
targets: [
|
|
104
|
-
{ src: 'node_modules/mellon/dist/assets/*', dest: 'mellon-assets' }
|
|
105
|
-
],
|
|
106
|
-
}),
|
|
107
|
-
],
|
|
108
|
-
}
|
|
91
|
+
new Mellon({ assetsPath: '/mellon-assets'})
|
|
109
92
|
```
|
|
110
93
|
|
|
111
94
|
---
|
|
@@ -263,45 +246,6 @@ Cross-Origin-Opener-Policy: same-origin
|
|
|
263
246
|
Cross-Origin-Embedder-Policy: require-corp
|
|
264
247
|
```
|
|
265
248
|
|
|
266
|
-
### Vite dev server
|
|
267
|
-
|
|
268
|
-
Already configured in the demo app's `vite.config.js`. For your own project:
|
|
269
|
-
|
|
270
|
-
```js
|
|
271
|
-
// vite.config.js
|
|
272
|
-
export default {
|
|
273
|
-
server: { headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' } },
|
|
274
|
-
preview: { headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' } },
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Express
|
|
279
|
-
|
|
280
|
-
```js
|
|
281
|
-
app.use((req, res, next) => {
|
|
282
|
-
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
283
|
-
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
|
|
284
|
-
next()
|
|
285
|
-
})
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### Nginx
|
|
289
|
-
|
|
290
|
-
```nginx
|
|
291
|
-
add_header Cross-Origin-Opener-Policy "same-origin";
|
|
292
|
-
add_header Cross-Origin-Embedder-Policy "require-corp";
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Netlify (`public/_headers`)
|
|
296
|
-
|
|
297
|
-
```
|
|
298
|
-
/*
|
|
299
|
-
Cross-Origin-Opener-Policy: same-origin
|
|
300
|
-
Cross-Origin-Embedder-Policy: require-corp
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
249
|
## Browser support
|
|
306
250
|
|
|
307
251
|
| Browser | Supported | Notes |
|
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.9",Yt=[1,1,149,64],Qt=`https://cdn.jsdelivr.net/npm/mellon@${Kt}/dist/assets`,st={assetsPath:`${Qt}`};let I=null,q=null,tt=null;function Vt({assetsPath:i}={}){i!==void 0&&(st.assetsPath=i),I=null,q=null,tt=null}async function Xt(i){return I?(i==null||i(1),I):q||(q=(async()=>{const r=st.assetsPath.endsWith("/")?st.assetsPath:st.assetsPath+"/",t=r+"ort.all.min.mjs",s=r+"model.onnx";tt=await new Function("url","return import(url)")(t),tt.env.wasm.wasmPaths=r;const e=await fetch(s);if(!e.ok)throw new Error(`Failed to fetch model: ${e.status}`);const a=parseInt(e.headers.get("content-length")||"0",10),n=e.body.getReader(),c=[];let l=0;for(;;){const{done:m,value:_}=await n.read();if(m)break;c.push(_),l+=_.byteLength,a>0&&(i==null||i(l/a))}const h=new Uint8Array(l);let d=0;for(const m of c)h.set(m,d),d+=m.byteLength;return I=await tt.InferenceSession.create(h.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),i==null||i(1),I})(),q)}async function Nt(i){if(!I)throw new Error("Model not loaded — call loadModel() first");const r=new tt.Tensor("float32",i,Yt),t=await I.run({input:r}),s=Object.keys(t)[0];return t[s].data}function Zt(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var ft,At;function Ot(){if(At)return ft;At=1;function i(r){if(this.size=r|0,this.size<=1||(this.size&this.size-1)!==0)throw new Error("FFT size must be a power of two and bigger than 1");this._csize=r<<1;for(var t=new Array(this.size*2),s=0;s<t.length;s+=2){const l=Math.PI*s/this.size;t[s]=Math.cos(l),t[s+1]=-Math.sin(l)}this.table=t;for(var o=0,e=1;this.size>e;e<<=1)o++;this._width=o%2===0?o-1:o,this._bitrev=new Array(1<<this._width);for(var a=0;a<this._bitrev.length;a++){this._bitrev[a]=0;for(var n=0;n<this._width;n+=2){var c=this._width-n-2;this._bitrev[a]|=(a>>>n&3)<<c}}this._out=null,this._data=null,this._inv=0}return ft=i,i.prototype.fromComplexArray=function(t,s){for(var o=s||new Array(t.length>>>1),e=0;e<t.length;e+=2)o[e>>>1]=t[e];return o},i.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var s=0;s<t.length;s++)t[s]=0;return t},i.prototype.toComplexArray=function(t,s){for(var o=s||this.createComplexArray(),e=0;e<o.length;e+=2)o[e]=t[e>>>1],o[e+1]=0;return o},i.prototype.completeSpectrum=function(t){for(var s=this._csize,o=s>>>1,e=2;e<o;e+=2)t[s-e]=t[e],t[s-e+1]=-t[e+1]},i.prototype.transform=function(t,s){if(t===s)throw new Error("Input and output buffers must be different");this._out=t,this._data=s,this._inv=0,this._transform4(),this._out=null,this._data=null},i.prototype.realTransform=function(t,s){if(t===s)throw new Error("Input and output buffers must be different");this._out=t,this._data=s,this._inv=0,this._realTransform4(),this._out=null,this._data=null},i.prototype.inverseTransform=function(t,s){if(t===s)throw new Error("Input and output buffers must be different");this._out=t,this._data=s,this._inv=1,this._transform4();for(var o=0;o<t.length;o++)t[o]/=this.size;this._out=null,this._data=null},i.prototype._transform4=function(){var t=this._out,s=this._csize,o=this._width,e=1<<o,a=s/e<<1,n,c,l=this._bitrev;if(a===4)for(n=0,c=0;n<s;n+=a,c++){const u=l[c];this._singleTransform2(n,u,e)}else for(n=0,c=0;n<s;n+=a,c++){const u=l[c];this._singleTransform4(n,u,e)}var h=this._inv?-1:1,d=this.table;for(e>>=2;e>=2;e>>=2){a=s/e<<1;var m=a>>>2;for(n=0;n<s;n+=a)for(var _=n+m,g=n,f=0;g<_;g+=2,f+=e){const u=g,p=u+m,v=p+m,w=v+m,b=t[u],A=t[u+1],E=t[p],y=t[p+1],F=t[v],C=t[v+1],M=t[w],T=t[w+1],x=b,R=A,z=d[f],S=h*d[f+1],N=E*z-y*S,k=E*S+y*z,L=d[2*f],P=h*d[2*f+1],G=F*L-C*P,H=F*P+C*L,J=d[3*f],K=h*d[3*f+1],Y=M*J-T*K,Q=M*K+T*J,V=x+G,W=R+H,B=x-G,X=R-H,Z=N+Y,U=k+Q,$=h*(N-Y),O=h*(k-Q),et=V+Z,at=W+U,it=V-Z,ct=W-U,lt=B+O,ht=X-$,dt=B-O,ut=X+$;t[u]=et,t[u+1]=at,t[p]=lt,t[p+1]=ht,t[v]=it,t[v+1]=ct,t[w]=dt,t[w+1]=ut}}},i.prototype._singleTransform2=function(t,s,o){const e=this._out,a=this._data,n=a[s],c=a[s+1],l=a[s+o],h=a[s+o+1],d=n+l,m=c+h,_=n-l,g=c-h;e[t]=d,e[t+1]=m,e[t+2]=_,e[t+3]=g},i.prototype._singleTransform4=function(t,s,o){const e=this._out,a=this._data,n=this._inv?-1:1,c=o*2,l=o*3,h=a[s],d=a[s+1],m=a[s+o],_=a[s+o+1],g=a[s+c],f=a[s+c+1],u=a[s+l],p=a[s+l+1],v=h+g,w=d+f,b=h-g,A=d-f,E=m+u,y=_+p,F=n*(m-u),C=n*(_-p),M=v+E,T=w+y,x=b+C,R=A-F,z=v-E,S=w-y,N=b-C,k=A+F;e[t]=M,e[t+1]=T,e[t+2]=x,e[t+3]=R,e[t+4]=z,e[t+5]=S,e[t+6]=N,e[t+7]=k},i.prototype._realTransform4=function(){var t=this._out,s=this._csize,o=this._width,e=1<<o,a=s/e<<1,n,c,l=this._bitrev;if(a===4)for(n=0,c=0;n<s;n+=a,c++){const mt=l[c];this._singleRealTransform2(n,mt>>>1,e>>>1)}else for(n=0,c=0;n<s;n+=a,c++){const mt=l[c];this._singleRealTransform4(n,mt>>>1,e>>>1)}var h=this._inv?-1:1,d=this.table;for(e>>=2;e>=2;e>>=2){a=s/e<<1;var m=a>>>1,_=m>>>1,g=_>>>1;for(n=0;n<s;n+=a)for(var f=0,u=0;f<=g;f+=2,u+=e){var p=n+f,v=p+_,w=v+_,b=w+_,A=t[p],E=t[p+1],y=t[v],F=t[v+1],C=t[w],M=t[w+1],T=t[b],x=t[b+1],R=A,z=E,S=d[u],N=h*d[u+1],k=y*S-F*N,L=y*N+F*S,P=d[2*u],G=h*d[2*u+1],H=C*P-M*G,J=C*G+M*P,K=d[3*u],Y=h*d[3*u+1],Q=T*K-x*Y,V=T*Y+x*K,W=R+H,B=z+J,X=R-H,Z=z-J,U=k+Q,$=L+V,O=h*(k-Q),et=h*(L-V),at=W+U,it=B+$,ct=X+et,lt=Z-O;if(t[p]=at,t[p+1]=it,t[v]=ct,t[v+1]=lt,f===0){var ht=W-U,dt=B-$;t[w]=ht,t[w+1]=dt;continue}if(f!==g){var ut=X,Dt=-Z,jt=W,Wt=-B,Bt=-h*et,Ut=-h*O,$t=-h*$,Lt=-h*U,Pt=ut+Bt,Gt=Dt+Ut,Ht=jt+Lt,Jt=Wt-$t,bt=n+_-f,Et=n+m-f;t[bt]=Pt,t[bt+1]=Gt,t[Et]=Ht,t[Et+1]=Jt}}}},i.prototype._singleRealTransform2=function(t,s,o){const e=this._out,a=this._data,n=a[s],c=a[s+o],l=n+c,h=n-c;e[t]=l,e[t+1]=0,e[t+2]=h,e[t+3]=0},i.prototype._singleRealTransform4=function(t,s,o){const e=this._out,a=this._data,n=this._inv?-1:1,c=o*2,l=o*3,h=a[s],d=a[s+o],m=a[s+c],_=a[s+l],g=h+m,f=h-m,u=d+_,p=n*(d-_),v=g+u,w=f,b=-p,A=g-u,E=f,y=p;e[t]=v,e[t+1]=0,e[t+2]=w,e[t+3]=b,e[t+4]=A,e[t+5]=0,e[t+6]=E,e[t+7]=y},ft}var qt=Ot();const te=Zt(qt),nt=16e3,j=512,D=64,Ft=Math.floor(.025*nt),Ct=Math.floor(.01*nt);function Mt(i){return 2595*Math.log10(1+i/700)}function ee(i){return 700*(10**(i/2595)-1)}function se(){const i=Mt(0),r=Mt(nt/2),t=new Float64Array(D+2);for(let n=0;n<D+2;n++)t[n]=i+n*(r-i)/(D+1);const o=t.map(n=>ee(n)).map(n=>Math.floor((j+1)*n/nt)),e=[],a=Math.floor(j/2)+1;for(let n=0;n<D;n++){const c=new Float32Array(a);for(let l=o[n];l<o[n+1];l++)c[l]=(l-o[n])/(o[n+1]-o[n]);for(let l=o[n+1];l<o[n+2];l++)c[l]=(o[n+2]-l)/(o[n+2]-o[n+1]);e.push(c)}return e}const ne=se(),rt=new te(j),_t=new Float32Array(j),Tt=rt.createComplexArray(),pt=rt.createComplexArray(),xt=new Float32Array(Math.floor(j/2)+1);function kt(i){const r=1+Math.ceil((i.length-Ft)/Ct),t=new Float32Array(r*D),s=Math.floor(j/2)+1;for(let o=0;o<r;o++){const e=o*Ct;_t.fill(0);for(let a=0;a<Ft&&e+a<i.length;a++)_t[a]=i[e+a];rt.toComplexArray(_t,Tt),rt.transform(pt,Tt);for(let a=0;a<s;a++){const n=pt[2*a],c=pt[2*a+1],l=(n*n+c*c)/j;xt[a]=l===0?1e-30:l}for(let a=0;a<D;a++){const n=ne[a];let c=0;for(let l=0;l<s;l++)c+=xt[l]*n[l];t[o*D+a]=Math.log(c===0?1e-30:c)}}return t}function re(i,r){let t=0;for(let s=0;s<i.length;s++)t+=i[s]*r[s];return(t+1)/2}function oe(i,r){let t=0;for(const s of r){const o=re(i,s);o>t&&(t=o)}return t}class Rt extends EventTarget{constructor({name:r,refEmbeddings:t,threshold:s=.65,relaxationMs:o=2e3,inferenceGapMs:e=300}){super(),this.name=r,this.refEmbeddings=t,this.threshold=s,this.relaxationMs=o,this.inferenceGapMs=e,this._lastDetectionAt=0,this._lastInferenceAt=0,this._lastScore=0}get lastScore(){return this._lastScore}async scoreFrame(r){const t=Date.now();if(t-this._lastInferenceAt<this.inferenceGapMs)return null;this._lastInferenceAt=t;const s=kt(r),o=await Nt(s),e=oe(o,this.refEmbeddings);return this._lastScore=e,e>=this.threshold&&t-this._lastDetectionAt>=this.relaxationMs&&(this._lastDetectionAt=t,this.dispatchEvent(new CustomEvent("match",{detail:{name:this.name,confidence:e,timestamp:t}}))),e}}const zt=16e3,ae=1500,vt=24e3;function St(i){if(i.length===vt)return i;const r=new Float32Array(vt);return r.set(i.subarray(0,vt)),r}class It extends EventTarget{constructor(r){super(),this.wordName=r.trim().toLowerCase(),this.samples=[]}get sampleCount(){return this.samples.length}async recordSample(){const r=await navigator.mediaDevices.getUserMedia({audio:!0});return new Promise((t,s)=>{const o=new AudioContext({sampleRate:zt}),e=new MediaRecorder(r),a=[];this.dispatchEvent(new CustomEvent("recording-start")),e.ondataavailable=n=>{n.data.size>0&&a.push(n.data)},e.onstop=async()=>{r.getTracks().forEach(n=>n.stop());try{const c=await new Blob(a,{type:"audio/webm"}).arrayBuffer(),l=await o.decodeAudioData(c);await o.close();const h=l.getChannelData(0),d=St(new Float32Array(h)),m=this._push(d,`Recorded #${this.samples.length}`);t(m)}catch(n){await o.close().catch(()=>{}),s(n)}},e.start(),setTimeout(()=>e.stop(),ae)})}async addAudioFile(r){const t=await r.arrayBuffer(),s=new AudioContext({sampleRate:zt}),o=await s.decodeAudioData(t);await s.close();const e=o.getChannelData(0),a=St(new Float32Array(e));return this._push(a,r.name)}removeSample(r){this.samples.splice(r,1),this.dispatchEvent(new CustomEvent("samples-changed",{detail:{count:this.samples.length}}))}clearSamples(){this.samples=[],this.dispatchEvent(new CustomEvent("samples-changed",{detail:{count:0}}))}async generateRef(){if(this.samples.length<3)throw new Error(`Need at least 3 samples (currently have ${this.samples.length})`);this.dispatchEvent(new CustomEvent("generating",{detail:{total:this.samples.length}}));const r=[];for(let t=0;t<this.samples.length;t++){const s=kt(this.samples[t].audioBuffer),o=await Nt(s);r.push(Array.from(o)),this.dispatchEvent(new CustomEvent("progress",{detail:{done:t+1,total:this.samples.length}}))}return{word_name:this.wordName,model_type:"resnet_50_arc",embeddings:r}}_push(r,t){this.samples.push({audioBuffer:r,name:t});const s=this.samples.length;return this.dispatchEvent(new CustomEvent("sample-added",{detail:{count:s,name:t}})),s}}const ie=`/**
|
|
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.
|
package/dist/mellon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const Jt = "0.0.
|
|
1
|
+
const Jt = "0.0.9", Kt = [1, 1, 149, 64], Yt = `https://cdn.jsdelivr.net/npm/mellon@${Jt}/dist/assets`, st = {
|
|
2
2
|
assetsPath: `${Yt}`
|
|
3
3
|
};
|
|
4
4
|
let I = null, q = null, tt = null;
|
package/package.json
CHANGED