mellon 0.0.6 → 0.0.7
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/mellon.cjs +2 -2
- package/dist/mellon.mjs +42 -42
- package/package.json +1 -1
package/dist/mellon.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Kt="0.0.6",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 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 a=parseInt(s.headers.get("content-length")||"0",10),r=s.body.getReader(),c=[];let l=0;for(;;){const{done:m,value:_}=await r.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 n=new tt.Tensor("float32",i,Yt),t=await I.run({input:n}),e=Object.keys(t)[0];return t[e].data}function Zt(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var mt,At;function Ot(){if(At)return mt;At=1;function i(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 o=0,s=1;this.size>s;s<<=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 r=0;r<this._width;r+=2){var c=this._width-r-2;this._bitrev[a]|=(a>>>r&3)<<c}}this._out=null,this._data=null,this._inv=0}return mt=i,i.prototype.fromComplexArray=function(t,e){for(var o=e||new Array(t.length>>>1),s=0;s<t.length;s+=2)o[s>>>1]=t[s];return o},i.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var e=0;e<t.length;e++)t[e]=0;return t},i.prototype.toComplexArray=function(t,e){for(var o=e||this.createComplexArray(),s=0;s<o.length;s+=2)o[s]=t[s>>>1],o[s+1]=0;return o},i.prototype.completeSpectrum=function(t){for(var e=this._csize,o=e>>>1,s=2;s<o;s+=2)t[e-s]=t[s],t[e-s+1]=-t[s+1]},i.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},i.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},i.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 o=0;o<t.length;o++)t[o]/=this.size;this._out=null,this._data=null},i.prototype._transform4=function(){var t=this._out,e=this._csize,o=this._width,s=1<<o,a=e/s<<1,r,c,l=this._bitrev;if(a===4)for(r=0,c=0;r<e;r+=a,c++){const u=l[c];this._singleTransform2(r,u,s)}else for(r=0,c=0;r<e;r+=a,c++){const u=l[c];this._singleTransform4(r,u,s)}var h=this._inv?-1:1,d=this.table;for(s>>=2;s>=2;s>>=2){a=e/s<<1;var m=a>>>2;for(r=0;r<e;r+=a)for(var _=r+m,g=r,f=0;g<_;g+=2,f+=s){const u=g,p=u+m,v=p+m,w=v+m,b=t[u],A=t[u+1],E=t[p],y=t[p+1],F=t[v],C=t[v+1],M=t[w],T=t[w+1],x=b,R=A,z=d[f],S=h*d[f+1],N=E*z-y*S,k=E*S+y*z,P=d[2*f],L=h*d[2*f+1],G=F*P-C*L,H=F*L+C*P,J=d[3*f],K=h*d[3*f+1],Y=M*J-T*K,Q=M*K+T*J,V=x+G,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+$;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}}},i.prototype._singleTransform2=function(t,e,o){const s=this._out,a=this._data,r=a[e],c=a[e+1],l=a[e+o],h=a[e+o+1],d=r+l,m=c+h,_=r-l,g=c-h;s[t]=d,s[t+1]=m,s[t+2]=_,s[t+3]=g},i.prototype._singleTransform4=function(t,e,o){const s=this._out,a=this._data,r=this._inv?-1:1,c=o*2,l=o*3,h=a[e],d=a[e+1],m=a[e+o],_=a[e+o+1],g=a[e+c],f=a[e+c+1],u=a[e+l],p=a[e+l+1],v=h+g,w=d+f,b=h-g,A=d-f,E=m+u,y=_+p,F=r*(m-u),C=r*(_-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;s[t]=M,s[t+1]=T,s[t+2]=x,s[t+3]=R,s[t+4]=z,s[t+5]=S,s[t+6]=N,s[t+7]=k},i.prototype._realTransform4=function(){var t=this._out,e=this._csize,o=this._width,s=1<<o,a=e/s<<1,r,c,l=this._bitrev;if(a===4)for(r=0,c=0;r<e;r+=a,c++){const ut=l[c];this._singleRealTransform2(r,ut>>>1,s>>>1)}else for(r=0,c=0;r<e;r+=a,c++){const ut=l[c];this._singleRealTransform4(r,ut>>>1,s>>>1)}var h=this._inv?-1:1,d=this.table;for(s>>=2;s>=2;s>>=2){a=e/s<<1;var m=a>>>1,_=m>>>1,g=_>>>1;for(r=0;r<e;r+=a)for(var f=0,u=0;f<=g;f+=2,u+=s){var p=r+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,P=y*N+F*S,L=d[2*u],G=h*d[2*u+1],H=C*L-M*G,J=C*G+M*L,K=d[3*u],Y=h*d[3*u+1],Q=T*K-x*Y,V=T*Y+x*K,j=R+H,B=z+J,X=R-H,Z=z-J,U=k+Q,$=P+V,O=h*(k-Q),et=h*(P-V),ot=j+U,at=B+$,it=X+et,ct=Z-O;if(t[p]=ot,t[p+1]=at,t[v]=it,t[v+1]=ct,f===0){var lt=j-U,ht=B-$;t[w]=lt,t[w+1]=ht;continue}if(f!==g){var dt=X,Dt=-Z,Wt=j,jt=-B,Bt=-h*et,Ut=-h*O,$t=-h*$,Pt=-h*U,Lt=dt+Bt,Gt=Dt+Ut,Ht=Wt+Pt,Jt=jt-$t,bt=r+_-f,Et=r+m-f;t[bt]=Lt,t[bt+1]=Gt,t[Et]=Ht,t[Et+1]=Jt}}}},i.prototype._singleRealTransform2=function(t,e,o){const s=this._out,a=this._data,r=a[e],c=a[e+o],l=r+c,h=r-c;s[t]=l,s[t+1]=0,s[t+2]=h,s[t+3]=0},i.prototype._singleRealTransform4=function(t,e,o){const s=this._out,a=this._data,r=this._inv?-1:1,c=o*2,l=o*3,h=a[e],d=a[e+o],m=a[e+c],_=a[e+l],g=h+m,f=h-m,u=d+_,p=r*(d-_),v=g+u,w=f,b=-p,A=g-u,E=f,y=p;s[t]=v,s[t+1]=0,s[t+2]=w,s[t+3]=b,s[t+4]=A,s[t+5]=0,s[t+6]=E,s[t+7]=y},mt}var qt=Ot();const te=Zt(qt),nt=16e3,W=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),n=Mt(nt/2),t=new Float64Array(D+2);for(let r=0;r<D+2;r++)t[r]=i+r*(n-i)/(D+1);const o=t.map(r=>ee(r)).map(r=>Math.floor((W+1)*r/nt)),s=[],a=Math.floor(W/2)+1;for(let r=0;r<D;r++){const c=new Float32Array(a);for(let l=o[r];l<o[r+1];l++)c[l]=(l-o[r])/(o[r+1]-o[r]);for(let l=o[r+1];l<o[r+2];l++)c[l]=(o[r+2]-l)/(o[r+2]-o[r+1]);s.push(c)}return s}const ne=se(),rt=new te(W),ft=new Float32Array(W),Tt=rt.createComplexArray(),_t=rt.createComplexArray(),xt=new Float32Array(Math.floor(W/2)+1);function kt(i){const n=1+Math.ceil((i.length-Ft)/Ct),t=new Float32Array(n*D),e=Math.floor(W/2)+1;for(let o=0;o<n;o++){const s=o*Ct;ft.fill(0);for(let a=0;a<Ft&&s+a<i.length;a++)ft[a]=i[s+a];rt.toComplexArray(ft,Tt),rt.transform(_t,Tt);for(let a=0;a<e;a++){const r=_t[2*a],c=_t[2*a+1],l=(r*r+c*c)/W;xt[a]=l===0?1e-30:l}for(let a=0;a<D;a++){const r=ne[a];let c=0;for(let l=0;l<e;l++)c+=xt[l]*r[l];t[o*D+a]=Math.log(c===0?1e-30:c)}}return t}function re(i,n){let t=0;for(let e=0;e<i.length;e++)t+=i[e]*n[e];return(t+1)/2}function oe(i,n){let t=0;for(const e of n){const o=re(i,e);o>t&&(t=o)}return t}class Rt extends EventTarget{constructor({name:n,refEmbeddings:t,threshold:e=.65,relaxationMs:o=2e3,inferenceGapMs:s=300}){super(),this.name=n,this.refEmbeddings=t,this.threshold=e,this.relaxationMs=o,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),o=await Nt(e),s=oe(o,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 zt=16e3,ae=1500,pt=24e3;function St(i){if(i.length===pt)return i;const n=new Float32Array(pt);return n.set(i.subarray(0,pt)),n}class It extends EventTarget{constructor(n){super(),this.wordName=n.trim().toLowerCase(),this.samples=[]}get sampleCount(){return this.samples.length}async recordSample(){const n=await navigator.mediaDevices.getUserMedia({audio:!0});return new Promise((t,e)=>{const o=new AudioContext({sampleRate:zt}),s=new MediaRecorder(n),a=[];this.dispatchEvent(new CustomEvent("recording-start")),s.ondataavailable=r=>{r.data.size>0&&a.push(r.data)},s.onstop=async()=>{n.getTracks().forEach(r=>r.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(r){await o.close().catch(()=>{}),e(r)}},s.start(),setTimeout(()=>s.stop(),ae)})}async addAudioFile(n){const t=await n.arrayBuffer(),e=new AudioContext({sampleRate:zt}),o=await e.decodeAudioData(t);await e.close();const s=o.getChannelData(0),a=St(new Float32Array(s));return this._push(a,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),o=await Nt(e);n.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: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 vt=null;function
|
|
38
|
+
`;let vt=null;function ce(){if(!vt){const i=new Blob([ie],{type:"application/javascript"});vt=URL.createObjectURL(i)}return vt}const wt="mellon_custom_refs";function gt(){try{const i=localStorage.getItem(wt);return i?JSON.parse(i):[]}catch{return[]}}function le(i){const n=gt().filter(t=>t.word_name!==i.word_name);n.push(i),localStorage.setItem(wt,JSON.stringify(n))}function he(i){const n=gt().filter(t=>t.word_name!==i);localStorage.setItem(wt,JSON.stringify(n))}function de(i){const n=JSON.stringify(i,null,2),t=new Blob([n],{type:"application/json"}),e=URL.createObjectURL(t),o=Object.assign(document.createElement("a"),{href:e,download:`${i.word_name}_ref.json`});document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(e)}async function ue(i){const n=await i.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=i.name.replace(/_ref\.json$/i,"").replace(/\.json$/i,"")),t}class yt extends EventTarget{constructor(n={}){super(),this._opts={words:n.words??[],refs:n.refs??[],threshold:n.threshold??.65,relaxationMs:n.relaxationMs??2e3,inferenceGapMs:n.inferenceGapMs??300,assetsPath:n.assetsPath},this._refs=new Map,this._detectors=new Map,this._audioCtx=null,this._workletNode=null,this._stream=null,this._initialized=!1,this._running=!1}get isInitialized(){return this._initialized}get isRunning(){return this._running}async init(n){if(this._initialized){n==null||n(1);return}this._opts.assetsPath&&Vt({assetsPath:this._opts.assetsPath});try{await Xt(n)}catch(t){throw this.dispatchEvent(new CustomEvent("error",{detail:{error:t}})),t}for(const t of this._opts.refs)try{let e;if(console.log("fetching ref : ",t),typeof t=="string"){const o=await fetch(t);if(!o.ok)throw new Error(`HTTP ${o.status}`);e=await o.json()}else e=t;console.log({refData:e}),yt.saveWord(e),this.addCustomWord(e)}catch(e){const o=typeof t=="string"?t:t.word_name;console.warn(`[Mellon] Failed to load ref "${o}": ${e.message}`)}this._initialized=!0,this.dispatchEvent(new CustomEvent("ready"))}async start(n){this._initialized||await this.init();const t=n??this._opts.words;try{this._stream=await navigator.mediaDevices.getUserMedia({audio:!0})}catch(s){const a=new Error(`Microphone access denied: ${s.message}`);throw this.dispatchEvent(new CustomEvent("error",{detail:{error:a}})),a}this._audioCtx=new AudioContext({sampleRate:16e3});const e=ce();await this._audioCtx.audioWorklet.addModule(e);const o=this._audioCtx.createMediaStreamSource(this._stream);this._workletNode=new AudioWorkletNode(this._audioCtx,"audio-processor"),o.connect(this._workletNode),this._workletNode.connect(this._audioCtx.destination);for(const s of t){const a=this._refs.get(s);if(!a){console.warn(`[Mellon] No reference embeddings for "${s}" — skipping. Call addCustomWord() to register custom words before start().`);continue}const r=new Rt({name:s,refEmbeddings:a.embeddings,threshold:this._opts.threshold,relaxationMs:this._opts.relaxationMs,inferenceGapMs:this._opts.inferenceGapMs});r.addEventListener("match",c=>{this.dispatchEvent(new CustomEvent("match",{detail:c.detail}))}),this._detectors.set(s,r)}this._workletNode.port.onmessage=async s=>{const a=[];for(const r of this._detectors.values())a.push(r.scoreFrame(s.data));await Promise.allSettled(a)},this._running=!0}stop(){this._workletNode&&(this._workletNode.port.onmessage=null,this._workletNode.disconnect(),this._workletNode=null),this._stream&&(this._stream.getTracks().forEach(n=>n.stop()),this._stream=null),this._audioCtx&&(this._audioCtx.close(),this._audioCtx=null),this._detectors.clear(),this._running=!1}addCustomWord(n){if(this._refs.set(n.word_name,n),this._running&&this._workletNode){const t=new Rt({name:n.word_name,refEmbeddings:n.embeddings,threshold:this._opts.threshold,relaxationMs:this._opts.relaxationMs,inferenceGapMs:this._opts.inferenceGapMs});t.addEventListener("match",e=>{this.dispatchEvent(new CustomEvent("match",{detail:e.detail}))}),this._detectors.set(n.word_name,t)}}enrollWord(n){return new It(n)}static loadWords(){return gt()}static saveWord(n){le(n)}static deleteWord(n){he(n)}static importWordFile(n){return ue(n)}static exportWord(n){de(n)}}exports.EnrollmentSession=It;exports.Mellon=yt;
|
package/dist/mellon.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const
|
|
2
|
-
assetsPath: `${
|
|
1
|
+
const Jt = "0.0.6", Kt = [1, 1, 149, 64], Yt = `https://cdn.jsdelivr.net/npm/mellon@${Jt}/dist/assets`, st = {
|
|
2
|
+
assetsPath: `${Yt}`
|
|
3
3
|
};
|
|
4
4
|
let I = null, q = null, tt = null;
|
|
5
|
-
function
|
|
5
|
+
function Qt({ assetsPath: i } = {}) {
|
|
6
6
|
i !== void 0 && (st.assetsPath = i), I = null, q = null, tt = null;
|
|
7
7
|
}
|
|
8
|
-
async function
|
|
8
|
+
async function Vt(i) {
|
|
9
9
|
return I ? (i == null || i(1), I) : 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;
|
|
@@ -30,14 +30,14 @@ async function Qt(i) {
|
|
|
30
30
|
}
|
|
31
31
|
async function St(i) {
|
|
32
32
|
if (!I) throw new Error("Model not loaded — call loadModel() first");
|
|
33
|
-
const n = new tt.Tensor("float32", i,
|
|
33
|
+
const n = new tt.Tensor("float32", i, Kt), t = await I.run({ input: n }), e = Object.keys(t)[0];
|
|
34
34
|
return t[e].data;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function Xt(i) {
|
|
37
37
|
return i && i.__esModule && Object.prototype.hasOwnProperty.call(i, "default") ? i.default : i;
|
|
38
38
|
}
|
|
39
39
|
var mt, Et;
|
|
40
|
-
function
|
|
40
|
+
function Zt() {
|
|
41
41
|
if (Et) return mt;
|
|
42
42
|
Et = 1;
|
|
43
43
|
function i(n) {
|
|
@@ -145,8 +145,8 @@ function Xt() {
|
|
|
145
145
|
continue;
|
|
146
146
|
}
|
|
147
147
|
if (f !== g) {
|
|
148
|
-
var dt = X,
|
|
149
|
-
t[yt] =
|
|
148
|
+
var dt = X, It = -Z, Dt = j, Wt = -B, jt = -h * et, Bt = -h * O, Ut = -h * $, $t = -h * U, Lt = dt + jt, Pt = It + Bt, Gt = Dt + $t, Ht = Wt - Ut, yt = r + _ - f, bt = r + m - f;
|
|
149
|
+
t[yt] = Lt, t[yt + 1] = Pt, t[bt] = Gt, t[bt + 1] = Ht;
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
}
|
|
@@ -158,19 +158,19 @@ function Xt() {
|
|
|
158
158
|
s[t] = v, s[t + 1] = 0, s[t + 2] = w, s[t + 3] = b, s[t + 4] = A, s[t + 5] = 0, s[t + 6] = E, s[t + 7] = y;
|
|
159
159
|
}, mt;
|
|
160
160
|
}
|
|
161
|
-
var
|
|
162
|
-
const
|
|
161
|
+
var Ot = Zt();
|
|
162
|
+
const qt = /* @__PURE__ */ Xt(Ot), nt = 16e3, W = 512, D = 64, At = Math.floor(0.025 * nt), Ft = Math.floor(0.01 * nt);
|
|
163
163
|
function Ct(i) {
|
|
164
164
|
return 2595 * Math.log10(1 + i / 700);
|
|
165
165
|
}
|
|
166
|
-
function
|
|
166
|
+
function te(i) {
|
|
167
167
|
return 700 * (10 ** (i / 2595) - 1);
|
|
168
168
|
}
|
|
169
|
-
function
|
|
169
|
+
function ee() {
|
|
170
170
|
const i = Ct(0), n = Ct(nt / 2), t = new Float64Array(D + 2);
|
|
171
171
|
for (let r = 0; r < D + 2; r++)
|
|
172
172
|
t[r] = i + r * (n - i) / (D + 1);
|
|
173
|
-
const o = t.map((r) =>
|
|
173
|
+
const o = t.map((r) => te(r)).map((r) => Math.floor((W + 1) * r / nt)), s = [], a = Math.floor(W / 2) + 1;
|
|
174
174
|
for (let r = 0; r < D; r++) {
|
|
175
175
|
const c = new Float32Array(a);
|
|
176
176
|
for (let l = o[r]; l < o[r + 1]; l++) c[l] = (l - o[r]) / (o[r + 1] - o[r]);
|
|
@@ -179,7 +179,7 @@ function te() {
|
|
|
179
179
|
}
|
|
180
180
|
return s;
|
|
181
181
|
}
|
|
182
|
-
const
|
|
182
|
+
const se = ee(), rt = new qt(W), ft = new Float32Array(W), Mt = rt.createComplexArray(), _t = rt.createComplexArray(), Tt = new Float32Array(Math.floor(W / 2) + 1);
|
|
183
183
|
function Nt(i) {
|
|
184
184
|
const n = 1 + Math.ceil((i.length - At) / Ft), t = new Float32Array(n * D), e = Math.floor(W / 2) + 1;
|
|
185
185
|
for (let o = 0; o < n; o++) {
|
|
@@ -193,7 +193,7 @@ function Nt(i) {
|
|
|
193
193
|
Tt[a] = l === 0 ? 1e-30 : l;
|
|
194
194
|
}
|
|
195
195
|
for (let a = 0; a < D; a++) {
|
|
196
|
-
const r =
|
|
196
|
+
const r = se[a];
|
|
197
197
|
let c = 0;
|
|
198
198
|
for (let l = 0; l < e; l++) c += Tt[l] * r[l];
|
|
199
199
|
t[o * D + a] = Math.log(c === 0 ? 1e-30 : c);
|
|
@@ -201,15 +201,15 @@ function Nt(i) {
|
|
|
201
201
|
}
|
|
202
202
|
return t;
|
|
203
203
|
}
|
|
204
|
-
function
|
|
204
|
+
function ne(i, n) {
|
|
205
205
|
let t = 0;
|
|
206
206
|
for (let e = 0; e < i.length; e++) t += i[e] * n[e];
|
|
207
207
|
return (t + 1) / 2;
|
|
208
208
|
}
|
|
209
|
-
function
|
|
209
|
+
function re(i, n) {
|
|
210
210
|
let t = 0;
|
|
211
211
|
for (const e of n) {
|
|
212
|
-
const o =
|
|
212
|
+
const o = ne(i, e);
|
|
213
213
|
o > t && (t = o);
|
|
214
214
|
}
|
|
215
215
|
return t;
|
|
@@ -239,19 +239,19 @@ class xt extends EventTarget {
|
|
|
239
239
|
const t = Date.now();
|
|
240
240
|
if (t - this._lastInferenceAt < this.inferenceGapMs) return null;
|
|
241
241
|
this._lastInferenceAt = t;
|
|
242
|
-
const e = Nt(n), o = await St(e), s =
|
|
242
|
+
const e = Nt(n), o = await St(e), s = re(o, this.refEmbeddings);
|
|
243
243
|
return this._lastScore = s, s >= this.threshold && t - this._lastDetectionAt >= this.relaxationMs && (this._lastDetectionAt = t, this.dispatchEvent(new CustomEvent("match", {
|
|
244
244
|
detail: { name: this.name, confidence: s, timestamp: t }
|
|
245
245
|
}))), s;
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
const Rt = 16e3,
|
|
248
|
+
const Rt = 16e3, oe = 1500, pt = 24e3;
|
|
249
249
|
function zt(i) {
|
|
250
250
|
if (i.length === pt) return i;
|
|
251
251
|
const n = new Float32Array(pt);
|
|
252
252
|
return n.set(i.subarray(0, pt)), n;
|
|
253
253
|
}
|
|
254
|
-
class
|
|
254
|
+
class ae extends EventTarget {
|
|
255
255
|
/** @param {string} wordName — the wake word label */
|
|
256
256
|
constructor(n) {
|
|
257
257
|
super(), this.wordName = n.trim().toLowerCase(), this.samples = [];
|
|
@@ -283,7 +283,7 @@ class oe extends EventTarget {
|
|
|
283
283
|
await o.close().catch(() => {
|
|
284
284
|
}), e(r);
|
|
285
285
|
}
|
|
286
|
-
}, s.start(), setTimeout(() => s.stop(),
|
|
286
|
+
}, s.start(), setTimeout(() => s.stop(), oe);
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
289
|
// ─── Upload ────────────────────────────────────────────────────────────────
|
|
@@ -341,7 +341,7 @@ class oe extends EventTarget {
|
|
|
341
341
|
return this.dispatchEvent(new CustomEvent("sample-added", { detail: { count: e, name: t } })), e;
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
-
const
|
|
344
|
+
const ie = `/**
|
|
345
345
|
* public/audio-processor.js
|
|
346
346
|
* AudioWorklet that runs at 16 kHz and continuously emits the last
|
|
347
347
|
* 1.5-second window (24 000 samples) via a circular buffer.
|
|
@@ -380,9 +380,9 @@ class AudioProcessor extends AudioWorkletProcessor {
|
|
|
380
380
|
registerProcessor('audio-processor', AudioProcessor)
|
|
381
381
|
`;
|
|
382
382
|
let vt = null;
|
|
383
|
-
function
|
|
383
|
+
function ce() {
|
|
384
384
|
if (!vt) {
|
|
385
|
-
const i = new Blob([
|
|
385
|
+
const i = new Blob([ie], { type: "application/javascript" });
|
|
386
386
|
vt = URL.createObjectURL(i);
|
|
387
387
|
}
|
|
388
388
|
return vt;
|
|
@@ -396,22 +396,22 @@ function gt() {
|
|
|
396
396
|
return [];
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
|
-
function
|
|
399
|
+
function le(i) {
|
|
400
400
|
const n = gt().filter((t) => t.word_name !== i.word_name);
|
|
401
401
|
n.push(i), localStorage.setItem(wt, JSON.stringify(n));
|
|
402
402
|
}
|
|
403
|
-
function
|
|
403
|
+
function he(i) {
|
|
404
404
|
const n = gt().filter((t) => t.word_name !== i);
|
|
405
405
|
localStorage.setItem(wt, JSON.stringify(n));
|
|
406
406
|
}
|
|
407
|
-
function
|
|
407
|
+
function de(i) {
|
|
408
408
|
const n = JSON.stringify(i, null, 2), t = new Blob([n], { type: "application/json" }), e = URL.createObjectURL(t), o = Object.assign(document.createElement("a"), {
|
|
409
409
|
href: e,
|
|
410
410
|
download: `${i.word_name}_ref.json`
|
|
411
411
|
});
|
|
412
412
|
document.body.appendChild(o), o.click(), document.body.removeChild(o), URL.revokeObjectURL(e);
|
|
413
413
|
}
|
|
414
|
-
async function
|
|
414
|
+
async function ue(i) {
|
|
415
415
|
const n = await i.text();
|
|
416
416
|
let t;
|
|
417
417
|
try {
|
|
@@ -425,7 +425,7 @@ async function de(i) {
|
|
|
425
425
|
throw new Error('"embeddings" must be a 2D array');
|
|
426
426
|
return t.word_name || (t.word_name = i.name.replace(/_ref\.json$/i, "").replace(/\.json$/i, "")), t;
|
|
427
427
|
}
|
|
428
|
-
class
|
|
428
|
+
class kt extends EventTarget {
|
|
429
429
|
/**
|
|
430
430
|
* @param {object} [opts]
|
|
431
431
|
* @param {string[]} [opts.words] Words to detect (must have refs loaded via addCustomWord())
|
|
@@ -469,9 +469,9 @@ class ue extends EventTarget {
|
|
|
469
469
|
n == null || n(1);
|
|
470
470
|
return;
|
|
471
471
|
}
|
|
472
|
-
this._opts.assetsPath &&
|
|
472
|
+
this._opts.assetsPath && Qt({ assetsPath: this._opts.assetsPath });
|
|
473
473
|
try {
|
|
474
|
-
await
|
|
474
|
+
await Vt(n);
|
|
475
475
|
} catch (t) {
|
|
476
476
|
throw this.dispatchEvent(new CustomEvent("error", { detail: { error: t } })), t;
|
|
477
477
|
}
|
|
@@ -484,7 +484,7 @@ class ue extends EventTarget {
|
|
|
484
484
|
e = await o.json();
|
|
485
485
|
} else
|
|
486
486
|
e = t;
|
|
487
|
-
this.addCustomWord(e);
|
|
487
|
+
console.log({ refData: e }), kt.saveWord(e), this.addCustomWord(e);
|
|
488
488
|
} catch (e) {
|
|
489
489
|
const o = typeof t == "string" ? t : t.word_name;
|
|
490
490
|
console.warn(`[Mellon] Failed to load ref "${o}": ${e.message}`);
|
|
@@ -507,7 +507,7 @@ class ue extends EventTarget {
|
|
|
507
507
|
throw this.dispatchEvent(new CustomEvent("error", { detail: { error: a } })), a;
|
|
508
508
|
}
|
|
509
509
|
this._audioCtx = new AudioContext({ sampleRate: 16e3 });
|
|
510
|
-
const e =
|
|
510
|
+
const e = ce();
|
|
511
511
|
await this._audioCtx.audioWorklet.addModule(e);
|
|
512
512
|
const o = this._audioCtx.createMediaStreamSource(this._stream);
|
|
513
513
|
this._workletNode = new AudioWorkletNode(this._audioCtx, "audio-processor"), o.connect(this._workletNode), this._workletNode.connect(this._audioCtx.destination);
|
|
@@ -578,7 +578,7 @@ class ue extends EventTarget {
|
|
|
578
578
|
* stt.addCustomWord(ref)
|
|
579
579
|
*/
|
|
580
580
|
enrollWord(n) {
|
|
581
|
-
return new
|
|
581
|
+
return new ae(n);
|
|
582
582
|
}
|
|
583
583
|
// ─── Persistence (static) ────────────────────────────────────────────────
|
|
584
584
|
/** Return all custom word refs stored in localStorage. */
|
|
@@ -587,25 +587,25 @@ class ue extends EventTarget {
|
|
|
587
587
|
}
|
|
588
588
|
/** Persist a word ref to localStorage (replaces any existing entry with the same name). */
|
|
589
589
|
static saveWord(n) {
|
|
590
|
-
|
|
590
|
+
le(n);
|
|
591
591
|
}
|
|
592
592
|
/** Delete a word ref from localStorage by name. */
|
|
593
593
|
static deleteWord(n) {
|
|
594
|
-
|
|
594
|
+
he(n);
|
|
595
595
|
}
|
|
596
596
|
/**
|
|
597
597
|
* Parse an uploaded ref JSON file and return a RefData object.
|
|
598
598
|
* @param {File} file
|
|
599
599
|
*/
|
|
600
600
|
static importWordFile(n) {
|
|
601
|
-
return
|
|
601
|
+
return ue(n);
|
|
602
602
|
}
|
|
603
603
|
/** Trigger a browser download of a ref as a JSON file. */
|
|
604
604
|
static exportWord(n) {
|
|
605
|
-
|
|
605
|
+
de(n);
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
export {
|
|
609
|
-
|
|
610
|
-
|
|
609
|
+
ae as EnrollmentSession,
|
|
610
|
+
kt as Mellon
|
|
611
611
|
};
|
package/package.json
CHANGED