From 145c6041160749e7e79daf8f4da2ffa5f051f97b Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Sun, 21 Jul 2024 10:27:00 +0200 Subject: [PATCH] Docs: phase vocoder example (#3791) * Docs: phase vocoder example * Simplify code --- examples/phase-vocoder/index.js | 64 +++++++++++++++++++++ examples/phase-vocoder/phase-vocoder.min.js | 1 + 2 files changed, 65 insertions(+) create mode 100644 examples/phase-vocoder/index.js create mode 100644 examples/phase-vocoder/phase-vocoder.min.js diff --git a/examples/phase-vocoder/index.js b/examples/phase-vocoder/index.js new file mode 100644 index 000000000..8f0737aa7 --- /dev/null +++ b/examples/phase-vocoder/index.js @@ -0,0 +1,64 @@ +// WebAudio speed control with pitch preservation + +import WaveSurfer from 'wavesurfer.js' + +// Init wavesurfer +const wavesurfer = WaveSurfer.create({ + backend: 'WebAudio', + container: document.body, + waveColor: 'violet', + progressColor: 'purple', + url: '/examples/audio/librivox.mp3', +}) + +// Wait for the audio to be ready +wavesurfer.on('ready', async () => { + const webAudioPlayer = wavesurfer.getMediaElement() + const gainNode = webAudioPlayer.getGainNode() + const audioContext = gainNode.context + + // Load the phase vocoder audio worklet + await audioContext.audioWorklet.addModule('/examples/phase-vocoder/phase-vocoder.min.js') + const phaseVocoderNode = new AudioWorkletNode(audioContext, 'phase-vocoder-processor') + + // Connect the worklet to the wavesurfer audio + gainNode.disconnect() + gainNode.connect(phaseVocoderNode) + phaseVocoderNode.connect(audioContext.destination) + + // Speed slider + document.querySelector('input[type="range"]').addEventListener('input', (e) => { + const speed = e.target.valueAsNumber + document.querySelector('#rate').textContent = speed.toFixed(2) + wavesurfer.setPlaybackRate(speed) + const pitchFactorParam = phaseVocoderNode.parameters.get('pitchFactor') + pitchFactorParam.value = 1 / speed + }) + + // Play/pause button + document.querySelector('button').addEventListener('click', () => { + wavesurfer.playPause() + }) +}) + +/* + +
+ + + + + +
+ +

+ 📖 Based on github.com/olvb/phaze +

+ +*/ diff --git a/examples/phase-vocoder/phase-vocoder.min.js b/examples/phase-vocoder/phase-vocoder.min.js new file mode 100644 index 000000000..ee9da8459 --- /dev/null +++ b/examples/phase-vocoder/phase-vocoder.min.js @@ -0,0 +1 @@ +!function t(e,s,r){function i(o,f){if(!s[o]){if(!e[o]){var h="function"==typeof require&&require;if(!f&&h)return h(o,!0);if(n)return n(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var a=s[o]={exports:{}};e[o][0].call(a.exports,(function(t){return i(e[o][1][t]||t)}),a,a.exports,t,e,s,r)}return s[o].exports}for(var n="function"==typeof require&&require,o=0;oi;i<<=1)r++;this._width=r%2==0?r-1:r,this._bitrev=new Array(1<>>o&3)<>>1),r=0;r>>1]=t[r];return s},r.prototype.createComplexArray=function(){const t=new Array(this._csize);for(var e=0;e>>1],s[r+1]=0;return s},r.prototype.completeSpectrum=function(t){for(var e=this._csize,s=e>>>1,r=2;r>=2;i>=2;i>>=2){var u=(n=r/i<<1)>>>2;for(t=0;t>>1,i>>>1)}else for(t=0,e=0;t>>1,i>>>1)}var f=this._inv?-1:1,h=this.table;for(i>>=2;i>=2;i>>=2){var u=(n=r/i<<1)>>>1,a=u>>>1,p=a>>>1;for(t=0;t=e||this.magnitudes[t-2]>=e?t++:this.magnitudes[t+1]>=e||this.magnitudes[t+2]>=e?t++:(this.peakIndexes[this.nbPeaks]=t,this.nbPeaks++,t+=2)}}shiftPeaks(t){this.freqComplexBufferShifted.fill(0);for(var e=0;ethis.magnitudes.length)break;var s=0,r=this.fftSize;if(e>0){let t=this.peakIndexes[e-1];s=n-Math.floor((n-t)/2)}if(e=this.magnitudes.length)break;let s=2*Math.PI*(e-t)/this.fftSize,r=Math.cos(s*this.timeCursor),f=Math.sin(s*this.timeCursor),h=2*t,u=h+1,a=this.freqComplexBuffer[h],p=this.freqComplexBuffer[u],l=a*r-p*f,c=a*f+p*r,d=2*e,v=d+1;this.freqComplexBufferShifted[d]+=l,this.freqComplexBufferShifted[v]+=c}}}})},{"./ola-processor.js":2,"fft.js":1}]},{},[3]); \ No newline at end of file