From e97274c8f590e90b2d67cacaf9a5b6572810a33e Mon Sep 17 00:00:00 2001 From: "jie.tong" Date: Mon, 5 Dec 2022 22:51:44 +0800 Subject: [PATCH] fix bug --- dist/Yami.js | 209 ++++++++++++++++++++++++++++------------------ dist/Yami.umd.cjs | 2 +- index.d.ts | 59 ++++++++----- package.json | 2 +- 4 files changed, 165 insertions(+), 107 deletions(-) diff --git a/dist/Yami.js b/dist/Yami.js index dcae331..55db15a 100644 --- a/dist/Yami.js +++ b/dist/Yami.js @@ -1,7 +1,7 @@ -var U = Object.defineProperty; -var x = (a, t, e) => t in a ? U(a, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[t] = e; -var r = (a, t, e) => (x(a, typeof t != "symbol" ? t + "" : t, e), e); -class N { +var x = Object.defineProperty; +var D = (a, t, e) => t in a ? x(a, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[t] = e; +var r = (a, t, e) => (D(a, typeof t != "symbol" ? t + "" : t, e), e); +class U { constructor() { r(this, "_rate", 1); r(this, "slopeCount", 0); @@ -20,24 +20,24 @@ class N { } process(t) { const e = t.length / 2, s = t, i = new Float32Array(~~(t.length / this.rate)); - let o = 0, n = 0; + let n = 0, o = 0; for (; this.slopeCount < 1; ) - i[2 * n] = (1 - this.slopeCount) * this.prevSampleL + this.slopeCount * s[0], i[2 * n + 1] = (1 - this.slopeCount) * this.prevSampleR + this.slopeCount * s[1], n = n + 1, this.slopeCount += this.rate; + i[2 * o] = (1 - this.slopeCount) * this.prevSampleL + this.slopeCount * s[0], i[2 * o + 1] = (1 - this.slopeCount) * this.prevSampleR + this.slopeCount * s[1], o = o + 1, this.slopeCount += this.rate; if (this.slopeCount -= 1, e !== 1) { t: for (; ; ) { for (; this.slopeCount > 1; ) - if (this.slopeCount -= 1, o = o + 1, o >= e - 1) + if (this.slopeCount -= 1, n = n + 1, n >= e - 1) break t; - const h = 2 * o; - i[2 * n] = (1 - this.slopeCount) * s[h] + this.slopeCount * s[h + 2], i[2 * n + 1] = (1 - this.slopeCount) * s[h + 1] + this.slopeCount * s[h + 3], n = n + 1, this.slopeCount += this.rate; + const h = 2 * n; + i[2 * o] = (1 - this.slopeCount) * s[h] + this.slopeCount * s[h + 2], i[2 * o + 1] = (1 - this.slopeCount) * s[h + 1] + this.slopeCount * s[h + 3], o = o + 1, this.slopeCount += this.rate; } } return this.prevSampleL = s[2 * e - 2], this.prevSampleR = s[2 * e - 1], i; } } -const g = (a, t) => (a > t ? a - t : t - a) > 1e-10, B = (a, t, e) => a < t ? t : a > e ? e : a; -class l { +const B = (a, t) => (a > t ? a - t : t - a) > 1e-10, N = (a, t, e) => a < t ? t : a > e ? e : a; +class c { constructor() { r(this, "_vector", new Float32Array()); r(this, "_position", 0); @@ -68,12 +68,12 @@ class l { e = e || 0; const i = e * 2; s >= 0 || (s = (t.length - i) / 2); - const o = s * 2; + const n = s * 2; this.ensureCapacity(s + this._frameCount); - const n = this.endIndex; + const o = this.endIndex; this.vector.set( - t.subarray(i, i + o), - n + t.subarray(i, i + n), + o ), this._frameCount += s; } putBuffer(t, e, s = 0) { @@ -101,7 +101,7 @@ class l { this._position > 0 && (this._vector.set(this._vector.subarray(this.startIndex, this.endIndex)), this._position = 0); } } -const I = 0, E = I, P = 0, A = P, L = 8, d = 0.5, T = 2, m = 125, k = 50, M = (k - m) / (T - d), F = m - M * d, v = 25, R = 15, y = (R - v) / (T - d), b = v - y * d, w = [ +const P = 0, E = P, I = 0, A = I, L = 8, m = 0.5, O = 2, S = 125, k = 50, R = (k - S) / (O - m), F = S - R * m, C = 25, M = 15, y = (M - C) / (O - m), b = C - y * m, w = [ [ 124, 186, @@ -181,8 +181,8 @@ const I = 0, E = I, P = 0, A = P, L = 8, d = 0.5, T = 2, m = 125, k = 50, M = (k 0 ], [-4, -3, -2, -1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -], D = 16384; -class q { +], q = 16384; +class W { constructor() { r(this, "_tempo", 1); r(this, "autoSeqSetting", !0); @@ -199,8 +199,8 @@ class q { r(this, "sampleReq", 0); r(this, "nominalSkip", 0); r(this, "skipFract", 0); - r(this, "inputBuffer", new l()); - r(this, "outputBuffer", new l()); + r(this, "inputBuffer", new c()); + r(this, "outputBuffer", new c()); this.setParameters( 44100, E, @@ -213,15 +213,15 @@ class q { } calculateSequenceParameters() { if (this.autoSeqSetting) { - let t = F + M * this._tempo; - t = B(t, k, m), this.sequenceMs = Math.floor(t + 0.5); + let t = F + R * this._tempo; + t = N(t, k, S), this.sequenceMs = Math.floor(t + 0.5); } if (this.autoSeekSetting) { let t = b + y * this._tempo; - t = B( + t = N( t, - R, - v + M, + C ), this.seekWindowMs = Math.floor(t + 0.5); } this.seekWindowLength = Math.floor( @@ -243,14 +243,14 @@ class q { seekBestOverlapPosition() { this.preCalculateCorrelationReferenceStereo(); let t = Number.MIN_VALUE, e = 0, s = 0, i = 0; - for (let o = 0; o < 4; o = o + 1) { - let n = 0; - for (; w[o][n] && (i = s + w[o][n], !(i >= this.seekLength)); ) { + for (let n = 0; n < 4; n = n + 1) { + let o = 0; + for (; w[n][o] && (i = s + w[n][o], !(i >= this.seekLength)); ) { let h = this.calculateCrossCorrelationStereo( 2 * i, this.refMidBuffer ); - h > t && (t = h, e = i), n = n + 1; + h > t && (t = h, e = i), o = o + 1; } s = e; } @@ -266,10 +266,10 @@ class q { const s = this.inputBuffer.vector; t += this.inputBuffer.startIndex; let i = 0; - const o = 2 * this.overlapLength; - for (let n = 2; n < o; n = n + 2) { - let h = n + t; - i += s[h] * e[n] + s[h + 1] * e[n + 1]; + const n = 2 * this.overlapLength; + for (let o = 2; o < n; o = o + 2) { + let h = o + t; + i += s[h] * e[o] + s[h + 1] * e[o + 1]; } return i; } @@ -277,14 +277,14 @@ class q { let e = 2 * t; const s = this.inputBuffer.vector; e += this.inputBuffer.startIndex; - const i = this.outputBuffer.vector, o = this.outputBuffer.endIndex, n = 1 / this.overlapLength; + const i = this.outputBuffer.vector, n = this.outputBuffer.endIndex, o = 1 / this.overlapLength; for (let h = 0; h < this.overlapLength; h = h + 1) { - let c = (this.overlapLength - h) * n, u = h * n, f = 2 * h, _ = f + e, C = f + o; - i[C + 0] = s[_ + 0] * u + this.midBuffer[f + 0] * c, i[C + 1] = s[_ + 1] * u + this.midBuffer[f + 1] * c; + let l = (this.overlapLength - h) * o, f = h * o, u = 2 * h, _ = u + e, T = u + n; + i[T + 0] = s[_ + 0] * f + this.midBuffer[u + 0] * l, i[T + 1] = s[_ + 1] * f + this.midBuffer[u + 1] * l; } } process(t) { - for (this.outputBuffer = new l(), this.inputBuffer.putSamples(t, 0, -1); this.inputBuffer.frameCount >= this.sampleReq; ) { + for (this.outputBuffer = new c(), this.inputBuffer.putSamples(t, 0, -1); this.inputBuffer.frameCount >= this.sampleReq; ) { const e = this.seekBestOverlapPosition(); this.outputBuffer.ensureAdditionalCapacity(this.overlapLength), this.overlap(Math.floor(e)), this.outputBuffer.put(this.overlapLength); let s = this.seekWindowLength - 2 * this.overlapLength; @@ -297,21 +297,21 @@ class q { this.midBuffer.set( this.inputBuffer.vector.subarray(i, i + 2 * this.overlapLength) ), this.skipFract += this.nominalSkip; - const o = ~~this.skipFract; - this.skipFract -= o, this.inputBuffer.receive(o); + const n = ~~this.skipFract; + this.skipFract -= n, this.inputBuffer.receive(n); } return this.outputBuffer.vector; } } -class W { +class K { constructor() { r(this, "_rate", 0); r(this, "_tempo", 0); r(this, "virtualPitch", 1); r(this, "virtualRate", 1); r(this, "virtualTempo", 1); - r(this, "transposer", new N()); - r(this, "stretch", new q()); + r(this, "transposer", new U()); + r(this, "stretch", new W()); this.calculateEffectiveRateAndTempo(); } get rate() { @@ -343,7 +343,7 @@ class W { } calculateEffectiveRateAndTempo() { const t = this._tempo, e = this._rate; - this._tempo = this.virtualTempo / this.virtualPitch, this._rate = this.virtualRate * this.virtualPitch, g(this._tempo, t) && (this.stretch.tempo = this._tempo), g(this._rate, e) && (this.transposer.rate = this._rate); + this._tempo = this.virtualTempo / this.virtualPitch, this._rate = this.virtualRate * this.virtualPitch, B(this._tempo, t) && (this.stretch.tempo = this._tempo), B(this._rate, e) && (this.transposer.rate = this._rate); } process(t) { if (this.rate > 1) { @@ -355,44 +355,78 @@ class W { } } } -var p = /* @__PURE__ */ ((a) => (a.URL = "URL", a.BUFFER = "BUFFER", a.MICROPHONE = "MICROPHONE", a))(p || {}); -const K = D, Q = (a) => a instanceof AudioBufferSourceNode, O = (a) => a instanceof AudioBuffer; -class S { +var d = /* @__PURE__ */ ((a) => (a.URL = "URL", a.BUFFER = "BUFFER", a.MICROPHONE = "MICROPHONE", a))(d || {}); +const Q = q, p = (a) => a instanceof AudioBufferSourceNode, g = (a) => a instanceof AudioBuffer; +class v { constructor(t, e, s) { - r(this, "bufferSize", K); + r(this, "sourceDuration", { + startTime: 0, + pauseTime: 0, + lastPauseTime: 0, + offsetTime: 0 + }); + r(this, "bufferSize", Q); r(this, "sourceNode"); r(this, "scriptNode"); - r(this, "soundTouch", new W()); - r(this, "processBuffer", new l()); - this.source = t, this.audioContext = e, this.type = s, O(this.source) ? (this.sourceNode = this.audioContext.createBufferSource(), this.sourceNode.buffer = this.source) : this.sourceNode = this.audioContext.createMediaStreamSource(this.source), this.scriptNode = this.audioContext.createScriptProcessor( + r(this, "soundTouch", new K()); + r(this, "processBuffer", new c()); + r(this, "gainNode"); + this.source = t, this.audioContext = e, this.type = s, this.init(); + } + init() { + g(this.source) ? (this.sourceNode = this.audioContext.createBufferSource(), this.sourceNode.buffer = this.source, this.sourceNode.addEventListener("ended", () => { + this.release(); + })) : this.sourceNode = this.audioContext.createMediaStreamSource(this.source), this.scriptNode = this.audioContext.createScriptProcessor( this.bufferSize, 2, 2 - ), this.sourceNode.connect(this.scriptNode), this.scriptNode.connect(this.audioContext.destination); + ), this.sourceNode.connect(this.scriptNode), this.gainNode = this.audioContext.createGain(), this.scriptNode.connect(this.gainNode), this.gainNode.connect(this.audioContext.destination); + } + get pitch() { + return this.soundTouch.pitch; } set pitch(t) { this.soundTouch.pitch = t; } - get pitch() { - return this.soundTouch.pitch; + get volume() { + return this.gainNode ? this.gainNode.gain.value : -1; + } + set volume(t) { + this.gainNode && (this.gainNode.gain.value = t); + } + get duration() { + return g(this.source) ? this.source.duration : -1; } - async play() { - let t = !1; - this.scriptNode.onaudioprocess = async (e) => { - const s = e.outputBuffer, i = e.inputBuffer, o = new Float32Array(this.bufferSize * 2), n = i.getChannelData(0), h = i.numberOfChannels > 1 ? i.getChannelData(1) : i.getChannelData(0); + get currentTime() { + let t = this.audioContext.currentTime; + return this.sourceDuration.lastPauseTime && (t = this.sourceDuration.lastPauseTime), this.sourceDuration.offsetTime + t - this.sourceDuration.startTime - this.sourceDuration.pauseTime; + } + async play(t = 0) { + let e = !1; + this.scriptNode && (this.scriptNode.onaudioprocess = async (s) => { + const i = s.outputBuffer, n = s.inputBuffer, o = new Float32Array(this.bufferSize * 2), h = n.getChannelData(0), l = n.numberOfChannels > 1 ? n.getChannelData(1) : n.getChannelData(0); for (let u = 0; u < o.length; u++) - o[u * 2] = n[u], o[u * 2 + 1] = h[u]; - const c = this.soundTouch.process(o); - if (this.processBuffer.putSamples(c, 0, -1), t) { + o[u * 2] = h[u], o[u * 2 + 1] = l[u]; + const f = this.soundTouch.process(o); + if (this.processBuffer.putSamples(f, 0, -1), e) { this.processBuffer.receiveSamples(o, this.bufferSize); for (let u = 0; u < o.length; u++) - s.getChannelData(0)[u] = o[u * 2], s.getChannelData(1)[u] = o[u * 2 + 1]; + i.getChannelData(0)[u] = o[u * 2], i.getChannelData(1)[u] = o[u * 2 + 1]; } else - this.processBuffer.frameCount >= this.bufferSize * 2 && (t = !0); - }, Q(this.sourceNode) && this.sourceNode.start(); + this.processBuffer.frameCount >= this.bufferSize * 2 && (e = !0); + }), this.sourceNode && p(this.sourceNode) && (this.sourceDuration.startTime = this.audioContext.currentTime, this.sourceDuration.offsetTime = t, this.sourceNode.start(0, t)); + } + resume() { + this.sourceNode && p(this.sourceNode) && (this.sourceNode.playbackRate.value = 1, this.sourceDuration.pauseTime += this.audioContext.currentTime - this.sourceDuration.lastPauseTime, this.sourceDuration.lastPauseTime = 0); + } + pause() { + this.sourceNode && p(this.sourceNode) && (this.sourceNode.playbackRate.value = Number.MIN_VALUE, this.sourceDuration.lastPauseTime || (this.sourceDuration.lastPauseTime = this.audioContext.currentTime)); + } + seek(t) { + this.release(), this.init(), this.play(t); } process() { - if (O(this.source)) { + if (g(this.source)) { const t = new Float32Array(this.source.length * 2), e = this.source.getChannelData(0), s = this.source.numberOfChannels > 1 ? this.source.getChannelData(1) : this.source.getChannelData(0); for (let i = 0; i < t.length; i++) t[i * 2] = e[i], t[i * 2 + 1] = s[i]; @@ -400,43 +434,54 @@ class S { } else return new Float32Array(); } -} -class H { - constructor() { - r(this, "audioContext", new AudioContext()); + release() { + this.sourceNode && (this.gainNode && this.gainNode.disconnect(), this.sourceNode.disconnect(), this.scriptNode && this.scriptNode.disconnect(), p(this.sourceNode) ? (this.resetSourceDuration(), this.sourceNode.onended = null) : this.sourceNode.mediaStream.getTracks().forEach((t) => { + t.stop(); + })); + } + resetSourceDuration() { + this.processBuffer = new c(), this.sourceDuration = { + offsetTime: 0, + startTime: 0, + lastPauseTime: 0, + pauseTime: 0 + }; } +} +class V { async createURLTrack(t) { return new Promise(async (e) => { - const i = await (await fetch(t)).arrayBuffer(); - this.audioContext.decodeAudioData(i, async (o) => { - const n = new S(o, this.audioContext, p.URL); - e(n); + const s = new AudioContext(), n = await (await fetch(t)).arrayBuffer(); + s.decodeAudioData(n, async (o) => { + const h = new v(o, s, d.URL); + e(h); }); }); } async createBufferTrack(t) { return new Promise(async (e) => { - this.audioContext.decodeAudioData(t, async (s) => { - const i = new S( + const s = new AudioContext(); + s.decodeAudioData(t, async (i) => { + const n = new v( + i, s, - this.audioContext, - p.BUFFER + d.BUFFER ); - e(i); + e(n); }); }); } async createMicrophoneTrack(t = !0) { return new Promise(async (e) => { - const s = await navigator.mediaDevices.getUserMedia({ + const s = new AudioContext(), i = await navigator.mediaDevices.getUserMedia({ audio: t, video: !1 - }), i = new S(s, this.audioContext, p.MICROPHONE); - e(i); + }), n = new v(i, s, d.MICROPHONE); + e(n); }); } } export { - S as Track, - H as Yami + v as Track, + V as Yami }; diff --git a/dist/Yami.umd.cjs b/dist/Yami.umd.cjs index f4afba4..0ae4d80 100644 --- a/dist/Yami.umd.cjs +++ b/dist/Yami.umd.cjs @@ -1 +1 @@ -(function(c,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(c=typeof globalThis<"u"?globalThis:c||self,f(c.Yami={}))})(this,function(c){"use strict";var K=Object.defineProperty;var Q=(c,f,l)=>f in c?K(c,f,{enumerable:!0,configurable:!0,writable:!0,value:l}):c[f]=l;var r=(c,f,l)=>(Q(c,typeof f!="symbol"?f+"":f,l),l);class f{constructor(){r(this,"_rate",1);r(this,"slopeCount",0);r(this,"prevSampleL",0);r(this,"prevSampleR",0);this.reset()}reset(){this.slopeCount=0,this.prevSampleL=0,this.prevSampleR=0}set rate(t){this._rate=t}get rate(){return this._rate}process(t){const e=t.length/2,s=t,i=new Float32Array(~~(t.length/this.rate));let o=0,n=0;for(;this.slopeCount<1;)i[2*n]=(1-this.slopeCount)*this.prevSampleL+this.slopeCount*s[0],i[2*n+1]=(1-this.slopeCount)*this.prevSampleR+this.slopeCount*s[1],n=n+1,this.slopeCount+=this.rate;if(this.slopeCount-=1,e!==1){t:for(;;){for(;this.slopeCount>1;)if(this.slopeCount-=1,o=o+1,o>=e-1)break t;const h=2*o;i[2*n]=(1-this.slopeCount)*s[h]+this.slopeCount*s[h+2],i[2*n+1]=(1-this.slopeCount)*s[h+1]+this.slopeCount*s[h+3],n=n+1,this.slopeCount+=this.rate}}return this.prevSampleL=s[2*e-2],this.prevSampleR=s[2*e-1],i}}const l=(a,t)=>(a>t?a-t:t-a)>1e-10,E=(a,t,e)=>ae?e:a;class p{constructor(){r(this,"_vector",new Float32Array);r(this,"_position",0);r(this,"_frameCount",0)}get vector(){return this._vector}get position(){return this._position}get startIndex(){return this._position*2}get frameCount(){return this._frameCount}get endIndex(){return(this._position+this._frameCount)*2}clear(){this.receive(this._frameCount),this.rewind()}put(t){this._frameCount+=t}putSamples(t,e,s){e=e||0;const i=e*2;s>=0||(s=(t.length-i)/2);const o=s*2;this.ensureCapacity(s+this._frameCount);const n=this.endIndex;this.vector.set(t.subarray(i,i+o),n),this._frameCount+=s}putBuffer(t,e,s=0){e=e||0,s>=0||(s=t.frameCount-e),this.putSamples(t.vector,t.position+e,s)}receive(t){(!(t>=0)||t>this._frameCount)&&(t=this.frameCount),this._frameCount-=t,this._position+=t}receiveSamples(t,e=0){const s=e*2,i=this.startIndex;t.set(this._vector.subarray(i,i+s)),this.receive(e)}ensureCapacity(t=0){const e=~~(t*2);if(this._vector.length0&&(this._vector.set(this._vector.subarray(this.startIndex,this.endIndex)),this._position=0)}}const B=0,A=0,L=8,d=.5,O=2,C=125,w=50,T=(w-C)/(O-d),x=C-T*d,g=25,k=15,M=(k-g)/(O-d),I=g-M*d,R=[[124,186,248,310,372,434,496,558,620,682,744,806,868,930,992,1054,1116,1178,1240,1302,1364,1426,1488,0],[-100,-75,-50,-25,25,50,75,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[-20,-15,-10,-5,5,10,15,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[-4,-3,-2,-1,1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],P=16384;class F{constructor(){r(this,"_tempo",1);r(this,"autoSeqSetting",!0);r(this,"autoSeekSetting",!0);r(this,"overlapLength",0);r(this,"midBuffer",new Float32Array);r(this,"refMidBuffer",new Float32Array);r(this,"sampleRate",44100);r(this,"overlapMs",L);r(this,"sequenceMs",B);r(this,"seekWindowMs",A);r(this,"seekWindowLength",0);r(this,"seekLength",0);r(this,"sampleReq",0);r(this,"nominalSkip",0);r(this,"skipFract",0);r(this,"inputBuffer",new p);r(this,"outputBuffer",new p);this.setParameters(44100,B,A,L)}setParameters(t,e,s,i){t>0&&(this.sampleRate=t),i>0&&(this.overlapMs=i),e>0?(this.sequenceMs=e,this.autoSeqSetting=!1):this.autoSeqSetting=!0,s>0?(this.seekWindowMs=s,this.autoSeekSetting=!1):this.autoSeekSetting=!0,this.calculateSequenceParameters(),this.calculateOverlapLength(this.overlapMs),this.tempo=this._tempo}calculateSequenceParameters(){if(this.autoSeqSetting){let t=x+T*this._tempo;t=E(t,w,C),this.sequenceMs=Math.floor(t+.5)}if(this.autoSeekSetting){let t=I+M*this._tempo;t=E(t,k,g),this.seekWindowMs=Math.floor(t+.5)}this.seekWindowLength=Math.floor(this.sampleRate*this.sequenceMs/1e3),this.seekLength=Math.floor(this.sampleRate*this.seekWindowMs/1e3)}calculateOverlapLength(t=0){let e=this.sampleRate*t/1e3;e=e<16?16:e,e-=e%8,this.overlapLength=e,this.refMidBuffer=new Float32Array(this.overlapLength*2),this.midBuffer=new Float32Array(this.overlapLength*2)}set tempo(t){this._tempo=t,this.calculateSequenceParameters(),this.nominalSkip=this._tempo*(this.seekWindowLength-this.overlapLength);let e=Math.floor(this.nominalSkip+.5);this.sampleReq=Math.max(e+this.overlapLength,this.seekWindowLength)+this.seekLength}get tempo(){return this._tempo}seekBestOverlapPosition(){this.preCalculateCorrelationReferenceStereo();let t=Number.MIN_VALUE,e=0,s=0,i=0;for(let o=0;o<4;o=o+1){let n=0;for(;R[o][n]&&(i=s+R[o][n],!(i>=this.seekLength));){let h=this.calculateCrossCorrelationStereo(2*i,this.refMidBuffer);h>t&&(t=h,e=i),n=n+1}s=e}return e}preCalculateCorrelationReferenceStereo(){for(let t=0;t=this.sampleReq;){const e=this.seekBestOverlapPosition();this.outputBuffer.ensureAdditionalCapacity(this.overlapLength),this.overlap(Math.floor(e)),this.outputBuffer.put(this.overlapLength);let s=this.seekWindowLength-2*this.overlapLength;s>0&&this.outputBuffer.putBuffer(this.inputBuffer,e+this.overlapLength,s);const i=this.inputBuffer.startIndex+2*(e+this.seekWindowLength-this.overlapLength);this.midBuffer.set(this.inputBuffer.vector.subarray(i,i+2*this.overlapLength)),this.skipFract+=this.nominalSkip;const o=~~this.skipFract;this.skipFract-=o,this.inputBuffer.receive(o)}return this.outputBuffer.vector}}class b{constructor(){r(this,"_rate",0);r(this,"_tempo",0);r(this,"virtualPitch",1);r(this,"virtualRate",1);r(this,"virtualTempo",1);r(this,"transposer",new f);r(this,"stretch",new F);this.calculateEffectiveRateAndTempo()}get rate(){return this._rate}set rate(t){this.virtualRate=t,this.calculateEffectiveRateAndTempo()}set rateChange(t){this._rate=1+.01*t}get tempo(){return this._tempo}set tempo(t){this.virtualTempo=t,this.calculateEffectiveRateAndTempo()}set tempoChange(t){this.tempo=1+.01*t}set pitch(t){this.virtualPitch=t,this.calculateEffectiveRateAndTempo()}set pitchOctaves(t){this.pitch=Math.exp(.69314718056*t),this.calculateEffectiveRateAndTempo()}set pitchSemitones(t){this.pitchOctaves=t/12}calculateEffectiveRateAndTempo(){const t=this._tempo,e=this._rate;this._tempo=this.virtualTempo/this.virtualPitch,this._rate=this.virtualRate*this.virtualPitch,l(this._tempo,t)&&(this.stretch.tempo=this._tempo),l(this._rate,e)&&(this.transposer.rate=this._rate)}process(t){if(this.rate>1){const e=this.stretch.process(t);return this.transposer.process(e)}else{const e=this.transposer.process(t);return this.stretch.process(e)}}}var S=(a=>(a.URL="URL",a.BUFFER="BUFFER",a.MICROPHONE="MICROPHONE",a))(S||{});const D=P,W=a=>a instanceof AudioBufferSourceNode,y=a=>a instanceof AudioBuffer;class _{constructor(t,e,s){r(this,"bufferSize",D);r(this,"sourceNode");r(this,"scriptNode");r(this,"soundTouch",new b);r(this,"processBuffer",new p);this.source=t,this.audioContext=e,this.type=s,y(this.source)?(this.sourceNode=this.audioContext.createBufferSource(),this.sourceNode.buffer=this.source):this.sourceNode=this.audioContext.createMediaStreamSource(this.source),this.scriptNode=this.audioContext.createScriptProcessor(this.bufferSize,2,2),this.sourceNode.connect(this.scriptNode),this.scriptNode.connect(this.audioContext.destination)}set pitch(t){this.soundTouch.pitch=t}get pitch(){return this.soundTouch.pitch}async play(){let t=!1;this.scriptNode.onaudioprocess=async e=>{const s=e.outputBuffer,i=e.inputBuffer,o=new Float32Array(this.bufferSize*2),n=i.getChannelData(0),h=i.numberOfChannels>1?i.getChannelData(1):i.getChannelData(0);for(let u=0;u=this.bufferSize*2&&(t=!0)},W(this.sourceNode)&&this.sourceNode.start()}process(){if(y(this.source)){const t=new Float32Array(this.source.length*2),e=this.source.getChannelData(0),s=this.source.numberOfChannels>1?this.source.getChannelData(1):this.source.getChannelData(0);for(let i=0;i{const i=await(await fetch(t)).arrayBuffer();this.audioContext.decodeAudioData(i,async o=>{const n=new _(o,this.audioContext,S.URL);e(n)})})}async createBufferTrack(t){return new Promise(async e=>{this.audioContext.decodeAudioData(t,async s=>{const i=new _(s,this.audioContext,S.BUFFER);e(i)})})}async createMicrophoneTrack(t=!0){return new Promise(async e=>{const s=await navigator.mediaDevices.getUserMedia({audio:t,video:!1}),i=new _(s,this.audioContext,S.MICROPHONE);e(i)})}}c.Track=_,c.Yami=q,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); +(function(c,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(c=typeof globalThis<"u"?globalThis:c||self,f(c.Yami={}))})(this,function(c){"use strict";var K=Object.defineProperty;var Q=(c,f,l)=>f in c?K(c,f,{enumerable:!0,configurable:!0,writable:!0,value:l}):c[f]=l;var o=(c,f,l)=>(Q(c,typeof f!="symbol"?f+"":f,l),l);class f{constructor(){o(this,"_rate",1);o(this,"slopeCount",0);o(this,"prevSampleL",0);o(this,"prevSampleR",0);this.reset()}reset(){this.slopeCount=0,this.prevSampleL=0,this.prevSampleR=0}set rate(t){this._rate=t}get rate(){return this._rate}process(t){const e=t.length/2,s=t,i=new Float32Array(~~(t.length/this.rate));let n=0,r=0;for(;this.slopeCount<1;)i[2*r]=(1-this.slopeCount)*this.prevSampleL+this.slopeCount*s[0],i[2*r+1]=(1-this.slopeCount)*this.prevSampleR+this.slopeCount*s[1],r=r+1,this.slopeCount+=this.rate;if(this.slopeCount-=1,e!==1){t:for(;;){for(;this.slopeCount>1;)if(this.slopeCount-=1,n=n+1,n>=e-1)break t;const h=2*n;i[2*r]=(1-this.slopeCount)*s[h]+this.slopeCount*s[h+2],i[2*r+1]=(1-this.slopeCount)*s[h+1]+this.slopeCount*s[h+3],r=r+1,this.slopeCount+=this.rate}}return this.prevSampleL=s[2*e-2],this.prevSampleR=s[2*e-1],i}}const l=(a,t)=>(a>t?a-t:t-a)>1e-10,B=(a,t,e)=>ae?e:a;class p{constructor(){o(this,"_vector",new Float32Array);o(this,"_position",0);o(this,"_frameCount",0)}get vector(){return this._vector}get position(){return this._position}get startIndex(){return this._position*2}get frameCount(){return this._frameCount}get endIndex(){return(this._position+this._frameCount)*2}clear(){this.receive(this._frameCount),this.rewind()}put(t){this._frameCount+=t}putSamples(t,e,s){e=e||0;const i=e*2;s>=0||(s=(t.length-i)/2);const n=s*2;this.ensureCapacity(s+this._frameCount);const r=this.endIndex;this.vector.set(t.subarray(i,i+n),r),this._frameCount+=s}putBuffer(t,e,s=0){e=e||0,s>=0||(s=t.frameCount-e),this.putSamples(t.vector,t.position+e,s)}receive(t){(!(t>=0)||t>this._frameCount)&&(t=this.frameCount),this._frameCount-=t,this._position+=t}receiveSamples(t,e=0){const s=e*2,i=this.startIndex;t.set(this._vector.subarray(i,i+s)),this.receive(e)}ensureCapacity(t=0){const e=~~(t*2);if(this._vector.length0&&(this._vector.set(this._vector.subarray(this.startIndex,this.endIndex)),this._position=0)}}const N=0,A=0,L=8,d=.5,w=2,C=125,O=50,k=(O-C)/(w-d),D=C-k*d,T=25,M=15,R=(M-T)/(w-d),P=T-R*d,y=[[124,186,248,310,372,434,496,558,620,682,744,806,868,930,992,1054,1116,1178,1240,1302,1364,1426,1488,0],[-100,-75,-50,-25,25,50,75,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[-20,-15,-10,-5,5,10,15,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[-4,-3,-2,-1,1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],I=16384;class b{constructor(){o(this,"_tempo",1);o(this,"autoSeqSetting",!0);o(this,"autoSeekSetting",!0);o(this,"overlapLength",0);o(this,"midBuffer",new Float32Array);o(this,"refMidBuffer",new Float32Array);o(this,"sampleRate",44100);o(this,"overlapMs",L);o(this,"sequenceMs",N);o(this,"seekWindowMs",A);o(this,"seekWindowLength",0);o(this,"seekLength",0);o(this,"sampleReq",0);o(this,"nominalSkip",0);o(this,"skipFract",0);o(this,"inputBuffer",new p);o(this,"outputBuffer",new p);this.setParameters(44100,N,A,L)}setParameters(t,e,s,i){t>0&&(this.sampleRate=t),i>0&&(this.overlapMs=i),e>0?(this.sequenceMs=e,this.autoSeqSetting=!1):this.autoSeqSetting=!0,s>0?(this.seekWindowMs=s,this.autoSeekSetting=!1):this.autoSeekSetting=!0,this.calculateSequenceParameters(),this.calculateOverlapLength(this.overlapMs),this.tempo=this._tempo}calculateSequenceParameters(){if(this.autoSeqSetting){let t=D+k*this._tempo;t=B(t,O,C),this.sequenceMs=Math.floor(t+.5)}if(this.autoSeekSetting){let t=P+R*this._tempo;t=B(t,M,T),this.seekWindowMs=Math.floor(t+.5)}this.seekWindowLength=Math.floor(this.sampleRate*this.sequenceMs/1e3),this.seekLength=Math.floor(this.sampleRate*this.seekWindowMs/1e3)}calculateOverlapLength(t=0){let e=this.sampleRate*t/1e3;e=e<16?16:e,e-=e%8,this.overlapLength=e,this.refMidBuffer=new Float32Array(this.overlapLength*2),this.midBuffer=new Float32Array(this.overlapLength*2)}set tempo(t){this._tempo=t,this.calculateSequenceParameters(),this.nominalSkip=this._tempo*(this.seekWindowLength-this.overlapLength);let e=Math.floor(this.nominalSkip+.5);this.sampleReq=Math.max(e+this.overlapLength,this.seekWindowLength)+this.seekLength}get tempo(){return this._tempo}seekBestOverlapPosition(){this.preCalculateCorrelationReferenceStereo();let t=Number.MIN_VALUE,e=0,s=0,i=0;for(let n=0;n<4;n=n+1){let r=0;for(;y[n][r]&&(i=s+y[n][r],!(i>=this.seekLength));){let h=this.calculateCrossCorrelationStereo(2*i,this.refMidBuffer);h>t&&(t=h,e=i),r=r+1}s=e}return e}preCalculateCorrelationReferenceStereo(){for(let t=0;t=this.sampleReq;){const e=this.seekBestOverlapPosition();this.outputBuffer.ensureAdditionalCapacity(this.overlapLength),this.overlap(Math.floor(e)),this.outputBuffer.put(this.overlapLength);let s=this.seekWindowLength-2*this.overlapLength;s>0&&this.outputBuffer.putBuffer(this.inputBuffer,e+this.overlapLength,s);const i=this.inputBuffer.startIndex+2*(e+this.seekWindowLength-this.overlapLength);this.midBuffer.set(this.inputBuffer.vector.subarray(i,i+2*this.overlapLength)),this.skipFract+=this.nominalSkip;const n=~~this.skipFract;this.skipFract-=n,this.inputBuffer.receive(n)}return this.outputBuffer.vector}}class F{constructor(){o(this,"_rate",0);o(this,"_tempo",0);o(this,"virtualPitch",1);o(this,"virtualRate",1);o(this,"virtualTempo",1);o(this,"transposer",new f);o(this,"stretch",new b);this.calculateEffectiveRateAndTempo()}get rate(){return this._rate}set rate(t){this.virtualRate=t,this.calculateEffectiveRateAndTempo()}set rateChange(t){this._rate=1+.01*t}get tempo(){return this._tempo}set tempo(t){this.virtualTempo=t,this.calculateEffectiveRateAndTempo()}set tempoChange(t){this.tempo=1+.01*t}set pitch(t){this.virtualPitch=t,this.calculateEffectiveRateAndTempo()}set pitchOctaves(t){this.pitch=Math.exp(.69314718056*t),this.calculateEffectiveRateAndTempo()}set pitchSemitones(t){this.pitchOctaves=t/12}calculateEffectiveRateAndTempo(){const t=this._tempo,e=this._rate;this._tempo=this.virtualTempo/this.virtualPitch,this._rate=this.virtualRate*this.virtualPitch,l(this._tempo,t)&&(this.stretch.tempo=this._tempo),l(this._rate,e)&&(this.transposer.rate=this._rate)}process(t){if(this.rate>1){const e=this.stretch.process(t);return this.transposer.process(e)}else{const e=this.transposer.process(t);return this.stretch.process(e)}}}var m=(a=>(a.URL="URL",a.BUFFER="BUFFER",a.MICROPHONE="MICROPHONE",a))(m||{});const W=I,S=a=>a instanceof AudioBufferSourceNode,E=a=>a instanceof AudioBuffer;class g{constructor(t,e,s){o(this,"sourceDuration",{startTime:0,pauseTime:0,lastPauseTime:0,offsetTime:0});o(this,"bufferSize",W);o(this,"sourceNode");o(this,"scriptNode");o(this,"soundTouch",new F);o(this,"processBuffer",new p);o(this,"gainNode");this.source=t,this.audioContext=e,this.type=s,this.init()}init(){E(this.source)?(this.sourceNode=this.audioContext.createBufferSource(),this.sourceNode.buffer=this.source,this.sourceNode.addEventListener("ended",()=>{this.release()})):this.sourceNode=this.audioContext.createMediaStreamSource(this.source),this.scriptNode=this.audioContext.createScriptProcessor(this.bufferSize,2,2),this.sourceNode.connect(this.scriptNode),this.gainNode=this.audioContext.createGain(),this.scriptNode.connect(this.gainNode),this.gainNode.connect(this.audioContext.destination)}get pitch(){return this.soundTouch.pitch}set pitch(t){this.soundTouch.pitch=t}get volume(){return this.gainNode?this.gainNode.gain.value:-1}set volume(t){this.gainNode&&(this.gainNode.gain.value=t)}get duration(){return E(this.source)?this.source.duration:-1}get currentTime(){let t=this.audioContext.currentTime;return this.sourceDuration.lastPauseTime&&(t=this.sourceDuration.lastPauseTime),this.sourceDuration.offsetTime+t-this.sourceDuration.startTime-this.sourceDuration.pauseTime}async play(t=0){let e=!1;this.scriptNode&&(this.scriptNode.onaudioprocess=async s=>{const i=s.outputBuffer,n=s.inputBuffer,r=new Float32Array(this.bufferSize*2),h=n.getChannelData(0),v=n.numberOfChannels>1?n.getChannelData(1):n.getChannelData(0);for(let u=0;u=this.bufferSize*2&&(e=!0)}),this.sourceNode&&S(this.sourceNode)&&(this.sourceDuration.startTime=this.audioContext.currentTime,this.sourceDuration.offsetTime=t,this.sourceNode.start(0,t))}resume(){this.sourceNode&&S(this.sourceNode)&&(this.sourceNode.playbackRate.value=1,this.sourceDuration.pauseTime+=this.audioContext.currentTime-this.sourceDuration.lastPauseTime,this.sourceDuration.lastPauseTime=0)}pause(){this.sourceNode&&S(this.sourceNode)&&(this.sourceNode.playbackRate.value=Number.MIN_VALUE,this.sourceDuration.lastPauseTime||(this.sourceDuration.lastPauseTime=this.audioContext.currentTime))}seek(t){this.release(),this.init(),this.play(t)}process(){if(E(this.source)){const t=new Float32Array(this.source.length*2),e=this.source.getChannelData(0),s=this.source.numberOfChannels>1?this.source.getChannelData(1):this.source.getChannelData(0);for(let i=0;i{t.stop()}))}resetSourceDuration(){this.processBuffer=new p,this.sourceDuration={offsetTime:0,startTime:0,lastPauseTime:0,pauseTime:0}}}class q{async createURLTrack(t){return new Promise(async e=>{const s=new AudioContext,n=await(await fetch(t)).arrayBuffer();s.decodeAudioData(n,async r=>{const h=new g(r,s,m.URL);e(h)})})}async createBufferTrack(t){return new Promise(async e=>{const s=new AudioContext;s.decodeAudioData(t,async i=>{const n=new g(i,s,m.BUFFER);e(n)})})}async createMicrophoneTrack(t=!0){return new Promise(async e=>{const s=new AudioContext,i=await navigator.mediaDevices.getUserMedia({audio:t,video:!1}),n=new g(i,s,m.MICROPHONE);e(n)})}}c.Track=g,c.Yami=q,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); diff --git a/index.d.ts b/index.d.ts index 2f2b886..4f06cd9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,23 +1,3 @@ -/** - * 检查范围 - * @public - * @remarks 目标值限定在两数范围内 - * @param x - 目标值 - * @param mi - 最小值 - * @param ma - 最大值 - * @returns 输出值 - */ -export declare const checkLimits: (x: number, mi: number, ma: number) => number; - -/** - * 判定两个浮点数是否相等 - * @public - * @param a - 浮点数 a - * @param b - 浮点数 b - * @returns 是否相等 - */ -export declare const testFloatEqual: (a: number, b: number) => boolean; - /** * 音频类 * @public @@ -27,27 +7,61 @@ export declare class Track { private source; private audioContext; type: TrackType; + private sourceDuration; private bufferSize; private sourceNode; private scriptNode; private soundTouch; private processBuffer; + private gainNode; constructor(source: AudioBuffer | MediaStream, audioContext: AudioContext, type: TrackType); - set pitch(newVal: number); + init(): void; /** * 获取变调 */ get pitch(): number; + set pitch(newVal: number); + /** + * 获取音量 + */ + get volume(): number; + set volume(newVal: number); + /** + * 获取音频时长 + */ + get duration(): number; + /** + * 获取当前时间 + */ + get currentTime(): number; /** * 播放 */ - play(): Promise; + play(offset?: number): Promise; + /** + * 恢复 + */ + resume(): void; + /** + * 暂停 + */ + pause(): void; + /** + * seek + * @param time seek 指定时间 + */ + seek(time: number): void; /** * 获取数据 * @remarks 该接口会造成 UI/JS 线程阻塞 * @returns 获取处理后的数据 */ process(): Float32Array; + /** + * 释放资源 + */ + release(): void; + private resetSourceDuration; } /** @@ -75,7 +89,6 @@ export declare enum TrackType { * @remarks 主要用于创建各种音频轨 */ export declare class Yami { - private audioContext; /** * 根据 url 创建音频轨 * @param url - 音频地址 diff --git a/package.json b/package.json index 162512d..b39c22f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "yami-voice", "private": false, - "version": "0.1.0", + "version": "0.1.1", "type": "module", "files": [ "dist",