diff --git a/dist/main.js b/dist/main.js index e646d43..b5d26a9 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,135 +1 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // create a fake namespace object -/******/ // mode & 1: value is a module id, require it -/******/ // mode & 2: merge all properties of value into the ns -/******/ // mode & 4: return value when already ns object -/******/ // mode & 8|1: behave like require -/******/ __webpack_require__.t = function(value, mode) { -/******/ if(mode & 1) value = __webpack_require__(value); -/******/ if(mode & 8) return value; -/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; -/******/ var ns = Object.create(null); -/******/ __webpack_require__.r(ns); -/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); -/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); -/******/ return ns; -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./src/main.js": -/*!*********************!*\ - !*** ./src/main.js ***! - \*********************/ -/*! no exports provided */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _setup_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./setup.js */ \"./src/setup.js\");\n\n\ndocument.getElementById('choose').addEventListener('click', () => {\n document.getElementById('file').click()\n})\n\ndocument.querySelector('input').addEventListener('input', function () {\n //@ts-ignore\n fileGuard(this.files)\n})\n\nwindow.addEventListener('drop', async ev => {\n ev.preventDefault()\n fileGuard(ev.dataTransfer.files)\n})\n\n// Prevent Chrome from replacing the document when a file's dropped over it\ndocument.addEventListener('dragover', ev => {\n ev.preventDefault()\n})\n\n\n/**\n * @param {FileList} files\n */\nfunction fileGuard(files) {\n const candidates = [...files]\n const file =\n candidates.find(file => file.type.startsWith('audio/')) ||\n candidates.find(file => file.type.startsWith('video/'))\n if (!file) {\n console.log(candidates.map(f => f.type))\n alert(\"Be sure to drop an audio file your browser supports :)\")\n return\n }\n\n init(file)\n}\n\nconst select = document.querySelector('select')\nselect.addEventListener('input', () => {\n Object(_setup_js__WEBPACK_IMPORTED_MODULE_0__[\"useProcessor\"])(select.value)\n})\n\n/**\n * @param {File} audioFile\n */\nasync function init(audioFile) {\n const audioEl = document.querySelector('audio')\n audioEl.src = URL.createObjectURL(audioFile)\n Object(_setup_js__WEBPACK_IMPORTED_MODULE_0__[\"useProcessor\"])(select.value)\n\n _setup_js__WEBPACK_IMPORTED_MODULE_0__[\"audio\"].resume().then(() => audioEl.play())\n\n document.title = `D♭ • ${audioFile.name}`\n\n if (navigator.mediaSession && typeof MediaMetadata !== 'undefined') {\n navigator.mediaSession.metadata = new MediaMetadata({\n title: audioFile.name,\n album: \"D♭\"\n });\n navigator.mediaSession.setActionHandler('play', () => {\n audioEl.play()\n })\n navigator.mediaSession.setActionHandler('pause', () => {\n audioEl.pause()\n })\n navigator.mediaSession.setActionHandler('seekbackward', () => {\n audioEl.currentTime -= 5\n })\n navigator.mediaSession.setActionHandler('seekforward', () => {\n audioEl.currentTime += 5\n })\n }\n}\n\n\n//# sourceURL=webpack:///./src/main.js?"); - -/***/ }), - -/***/ "./src/processors.worklet.js": -/*!***********************************!*\ - !*** ./src/processors.worklet.js ***! - \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("module.exports = __webpack_require__.p + \"processors.worklet.js\";\n\n//# sourceURL=webpack:///./src/processors.worklet.js?"); - -/***/ }), - -/***/ "./src/setup.js": -/*!**********************!*\ - !*** ./src/setup.js ***! - \**********************/ -/*! exports provided: useProcessor, audio */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"useProcessor\", function() { return useProcessor; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"audio\", function() { return audio; });\n/* harmony import */ var _processors_worklet_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./processors.worklet.js */ \"./src/processors.worklet.js\");\n/* harmony import */ var _processors_worklet_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_processors_worklet_js__WEBPACK_IMPORTED_MODULE_0__);\n//@ts-ignore\n\n\nconst audioEl = document.querySelector('audio')\nconst audio = new AudioContext()\nconst audioSrcNode = audio.createMediaElementSource(audioEl)\n\n// Add the modules\naudio.audioWorklet.addModule('/dist/' + _processors_worklet_js__WEBPACK_IMPORTED_MODULE_0___default.a)\n\nlet dFlat = null\n// let dFlat = null new AudioWorkletNode(audio, 'subtract-overlap', {\n// processorOptions: {\n// maxChannels: audio.destination.maxChannelCount\n// }\n// })\n\n// audioSrcNode.connect(dFlat)\n// dFlat.connect(audio.destination)\n\n/**\n * Swaps out the processor\n * @param {string} name The name of the processor to use\n */\nfunction useProcessor(name) {\n if (dFlat) {\n dFlat.disconnect()\n audioSrcNode.disconnect()\n }\n\n dFlat = new AudioWorkletNode(audio, name, {\n processorOptions: {\n maxChannels: audio.destination.maxChannelCount\n }\n })\n audioSrcNode.connect(dFlat)\n dFlat.connect(audio.destination)\n}\n\n\n\n\n// const audioEl = document.querySelector('audio')\n// const audio = new AudioContext()\n// await audio.audioWorklet.addModule('/dist/' + workletUrl)\n\n// const dFlat = new AudioWorkletNode(audio, 'subtract-overlap', {\n// // const dFlat = new AudioWorkletNode(audio, 'bit-crusher', {\n// processorOptions: {\n// maxChannels: audio.destination.maxChannelCount\n// }\n// })\n// const audioSrcNode = audio.createMediaElementSource(audioEl)\n\n// audioSrcNode.connect(dFlat)\n// dFlat.connect(audio.destination)\n// audioEl.src = URL.createObjectURL(audioFile)\n\n\n//# sourceURL=webpack:///./src/setup.js?"); - -/***/ }), - -/***/ 0: -/*!***************************!*\ - !*** multi ./src/main.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("module.exports = __webpack_require__(/*! ./src/main.js */\"./src/main.js\");\n\n\n//# sourceURL=webpack:///multi_./src/main.js?"); - -/***/ }) - -/******/ }); \ No newline at end of file +!function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(e,t,n){e.exports=n.p+"processors.worklet.js"},function(e,t,n){e.exports=n(2)},function(e,t,n){"use strict";n.r(t);var i=n(0),o=n.n(i);const r=document.querySelector("audio"),a=new AudioContext,c=function(e,t){const n=e.createAnalyser(),i=new Uint8Array(n.frequencyBinCount),o=document.querySelector("canvas"),r=o.getContext("2d"),a=document.getElementById("visualizerSelect");let c=0;return a.addEventListener("input",()=>{c=+a.value}),requestAnimationFrame((function e(){if(!t.paused)switch(o.width=window.innerWidth*window.devicePixelRatio,o.height=window.innerHeight*window.devicePixelRatio,c){case 0:!function(){r.clearRect(0,0,o.width,o.height),r.fillStyle="rgba(255, 255, 255, .72)",r.strokeStyle="rgba(255, 255, 255, .72)",r.lineWidth=2*window.devicePixelRatio,n.getByteTimeDomainData(i);const e=o.width/n.frequencyBinCount;let t=0,a=0,c=0;for(;ae.type.startsWith("audio/"))||t.find(e=>e.type.startsWith("video/"));if(!n)return console.log(t.map(e=>e.type)),void alert("Be sure to drop an audio file your browser supports :)");!async function(e){const t=document.querySelector("audio");t.src=URL.createObjectURL(e),s(f.value),a.resume().then(()=>t.play()),document.title="D♭ • "+e.name,navigator.mediaSession&&"undefined"!=typeof MediaMetadata&&(navigator.mediaSession.metadata=new MediaMetadata({title:e.name,album:"D♭"}),navigator.mediaSession.setActionHandler("play",()=>{t.play()}),navigator.mediaSession.setActionHandler("pause",()=>{t.pause()}),navigator.mediaSession.setActionHandler("seekbackward",()=>{t.currentTime-=5}),navigator.mediaSession.setActionHandler("seekforward",()=>{t.currentTime+=5}))}(n)}a.audioWorklet.addModule("/dist/"+o.a),document.getElementById("choose").addEventListener("click",()=>{document.getElementById("file").click()}),document.querySelector("input").addEventListener("input",(function(){l(this.files)})),window.addEventListener("drop",async e=>{e.preventDefault(),l(e.dataTransfer.files)}),document.addEventListener("dragover",e=>{e.preventDefault()});const f=document.querySelector("select");f.addEventListener("input",()=>{s(f.value)})}]); \ No newline at end of file diff --git a/dist/processors.worklet.js b/dist/processors.worklet.js index 9ec9862..8e6f736 100644 --- a/dist/processors.worklet.js +++ b/dist/processors.worklet.js @@ -1,100 +1 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // create a fake namespace object -/******/ // mode & 1: value is a module id, require it -/******/ // mode & 2: merge all properties of value into the ns -/******/ // mode & 4: return value when already ns object -/******/ // mode & 8|1: behave like require -/******/ __webpack_require__.t = function(value, mode) { -/******/ if(mode & 1) value = __webpack_require__(value); -/******/ if(mode & 8) return value; -/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; -/******/ var ns = Object.create(null); -/******/ __webpack_require__.r(ns); -/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); -/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); -/******/ return ns; -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = "./src/processors.worklet.js"); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./src/processors.worklet.js": -/*!***********************************!*\ - !*** ./src/processors.worklet.js ***! - \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("/// \n\nconst carry = Symbol()\n\nclass NoopProcessor extends AudioWorkletProcessor {\n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n const input = inputs[0]\n const output = outputs[0]\n\n for (let channel = 0; channel < input.length; channel++) {\n output[channel].set(input[channel])\n }\n return true\n }\n}\nregisterProcessor('noop', NoopProcessor)\n\nclass Mono extends AudioWorkletProcessor {\n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n // This processor has only one input and one output.\n const input = inputs[0]\n const output = outputs[0]\n\n for (let channel = 0; channel < input.length; channel++) {\n for (let sample = 0; sample < input[0].length; sample++) {\n // Mono: (L + R) / 2\n // More generally: 1/n * (\\sum_{i = 0}^n C_i)\n output[0][sample] += input[channel][sample] * (1 / input.length)\n }\n if (channel !== 0) {\n output[channel].set(output[0])\n }\n }\n\n return true\n }\n}\nregisterProcessor('mono', Mono)\n\nclass DerivativeProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super()\n this[carry] = new Float32Array(options.processorOptions.maxChannels)\n }\n \n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n const input = inputs[0]\n const output = outputs[0]\n\n for (let channel = 0; channel < input.length; channel++) {\n output[channel][0] = this[carry][channel]\n for (let sample = 1; sample < input[channel].length; sample++) {\n // dy/dx: (s2 - s1) / time\n output[channel][sample] = input[channel][sample] - input[channel][sample - 1]\n }\n this[carry][channel] = input[channel][input.length - 1]\n }\n\n return true\n }\n}\nregisterProcessor('derivative', DerivativeProcessor)\n\nclass IntegralProcessor extends AudioWorkletProcessor {\n\n constructor(options) {\n super()\n this[carry] = new Float32Array(options.processorOptions.maxChannels)\n }\n \n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n const input = inputs[0]\n const output = outputs[0]\n\n for (let channel = 0; channel < input.length; channel++) {\n output[channel][0] = (this[carry][channel] + input[channel][0]) / 2\n for (let sample = 1; sample < input[channel].length; sample++) {\n // Integral of two samples: (time * (s1 + s2)) / 2\n output[channel][sample] = (input[channel][sample - 1] + input[channel][sample]) / 2\n }\n this[carry][channel] = input[channel][input.length - 1]\n }\n\n return true\n }\n}\nregisterProcessor('integral', IntegralProcessor)\n\n\nclass BitCrusher extends AudioWorkletProcessor {\n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n const input = inputs[0]\n const output = outputs[0]\n\n let phase = 1\n const step = 2 * Math.pow(.5, 8)\n\n for (let channel = 0; channel < input.length; channel++) {\n for (let sample = 0; sample < input[channel].length; sample++) {\n output[channel][sample] = input[channel][sample]\n }\n }\n\n return true\n }\n}\nregisterProcessor('bit-crusher', BitCrusher)\n\n\n/**\n * Inverts the amplitude of every even-numbered channel and merges them to mono.\n * Quick-and-dirty way of removing vocals\n */\nclass SubtractOverlap extends AudioWorkletProcessor {\n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n // This processor has only one input and one output.\n const input = inputs[0]\n const output = outputs[0]\n\n for (let channel = 0; channel < input.length; channel++) {\n for (let sample = 0; sample < input[channel].length; sample++) {\n output[0][sample] += (channel % 2 === 0 ? 1 : -1) * input[channel][sample]\n }\n if (channel !== 0) {\n output[channel].set(output[0])\n }\n }\n\n return true\n }\n}\nregisterProcessor('subtract-overlap', SubtractOverlap)\n\n/**\n * Like `subtract-overlap`, but isolates the overlap instead of removing it.\n * Quick-and-dirty way to isolate vocals\n */\nclass IsolateOverlap extends AudioWorkletProcessor {\n /**\n * @param {Float32Array[][]} inputs\n * @param {Float32Array[][]} outputs\n */\n process(inputs, outputs) {\n // This processor has only one input and one output.\n const input = inputs[0]\n const output = outputs[0]\n\n // FIXME: I can't get this filter to work :/\n\n for (let channel = 0; channel < input.length; channel++) {\n for (let sample = 0; sample < input[channel].length; sample++) {\n output[0][sample] += (channel % 2 === 0 ? 1 : -1) * input[channel][sample]\n }\n }\n\n for (let channel = input.length - 1; 0 <= channel; channel--) {\n for (let sample = 0; sample < input[channel].length; sample++) {\n output[channel][sample] = input[channel][sample] - output[0][sample]\n }\n }\n\n\n return true\n }\n}\nregisterProcessor('isolate-overlap', IsolateOverlap)\n\n\n//# sourceURL=webpack:///./src/processors.worklet.js?"); - -/***/ }) - -/******/ }); \ No newline at end of file +!function(e){var r={};function t(o){if(r[o])return r[o].exports;var s=r[o]={i:o,l:!1,exports:{}};return e[o].call(s.exports,s,s.exports,t),s.l=!0,s.exports}t.m=e,t.c=r,t.d=function(e,r,o){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var s in e)t.d(o,s,function(r){return e[r]}.bind(null,s));return o},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=0)}([function(e,r){const t=Symbol();class o extends AudioWorkletProcessor{process(e,r){const t=e[0],o=r[0];for(let e=0;e --> +

Drag and drop an audio file anywhere or @@ -118,6 +127,13 @@ +

diff --git a/src/setup.js b/src/setup.js index 4e343bb..f001f7d 100644 --- a/src/setup.js +++ b/src/setup.js @@ -1,22 +1,16 @@ //@ts-ignore -import workletUrl from './processors.worklet.js' +import processorUrl from './processors.worklet.js' +import visFactory from './visualizer.js' const audioEl = document.querySelector('audio') const audio = new AudioContext() +const visualizer = visFactory(audio, audioEl) const audioSrcNode = audio.createMediaElementSource(audioEl) +let dFlat; + // Add the modules -audio.audioWorklet.addModule('/dist/' + workletUrl) - -let dFlat = null -// let dFlat = null new AudioWorkletNode(audio, 'subtract-overlap', { -// processorOptions: { -// maxChannels: audio.destination.maxChannelCount -// } -// }) - -// audioSrcNode.connect(dFlat) -// dFlat.connect(audio.destination) +audio.audioWorklet.addModule('/dist/' + processorUrl) /** * Swaps out the processor @@ -26,6 +20,7 @@ export function useProcessor(name) { if (dFlat) { dFlat.disconnect() audioSrcNode.disconnect() + visualizer.disconnect() } dFlat = new AudioWorkletNode(audio, name, { @@ -34,24 +29,8 @@ export function useProcessor(name) { } }) audioSrcNode.connect(dFlat) - dFlat.connect(audio.destination) + dFlat.connect(visualizer) + visualizer.connect(audio.destination) } export { audio } - - -// const audioEl = document.querySelector('audio') -// const audio = new AudioContext() -// await audio.audioWorklet.addModule('/dist/' + workletUrl) - -// const dFlat = new AudioWorkletNode(audio, 'subtract-overlap', { -// // const dFlat = new AudioWorkletNode(audio, 'bit-crusher', { -// processorOptions: { -// maxChannels: audio.destination.maxChannelCount -// } -// }) -// const audioSrcNode = audio.createMediaElementSource(audioEl) - -// audioSrcNode.connect(dFlat) -// dFlat.connect(audio.destination) -// audioEl.src = URL.createObjectURL(audioFile) diff --git a/src/visualizer.js b/src/visualizer.js new file mode 100644 index 0000000..0d73346 --- /dev/null +++ b/src/visualizer.js @@ -0,0 +1,89 @@ +/// + +/** + * @param {AudioContext} audioContext + * @param {HTMLAudioElement} audioElement + */ +export default function visFactory(audioContext, audioElement) { + const analyzer = audioContext.createAnalyser() + const samples = new Uint8Array(analyzer.frequencyBinCount) + + const canvas = document.querySelector('canvas') + const ctx = canvas.getContext('2d') + + /** @type {HTMLSelectElement} */ + // @ts-ignore + const dropdown = document.getElementById('visualizerSelect') + + let mode = 0 + + dropdown.addEventListener('input', () => { + mode = +dropdown.value + }) + + // Animation loop + requestAnimationFrame(function rAF() { + if (!audioElement.paused) { + canvas.width = window.innerWidth * window.devicePixelRatio + canvas.height = window.innerHeight * window.devicePixelRatio + + switch (mode) { + case 0: drawTime() + break + case 1: drawFreq() + break + } + } + requestAnimationFrame(rAF) + }) + + function drawTime() { + ctx.clearRect(0, 0, canvas.width, canvas.height) + + ctx.fillStyle = 'rgba(255, 255, 255, .72)' + ctx.strokeStyle = 'rgba(255, 255, 255, .72)' + ctx.lineWidth = 2 * window.devicePixelRatio + + analyzer.getByteTimeDomainData(samples) + + const increment = canvas.width / analyzer.frequencyBinCount + + let x = 0, + i = 0, + y = 0; + + // ctx.beginPath() + for (; i < analyzer.frequencyBinCount; i++) { + y = (samples[i] / 128) * (canvas.height / 2) + ctx.lineTo(x, y) + x += increment + } + ctx.stroke() + } + + function drawFreq() { + ctx.clearRect(0, 0, canvas.width, canvas.height) + + ctx.fillStyle = 'rgba(255, 255, 255, .72)' + ctx.strokeStyle = 'rgba(255, 255, 255, .72)' + ctx.lineWidth = 2 * window.devicePixelRatio + + analyzer.getByteFrequencyData(samples) + + const increment = canvas.width / analyzer.frequencyBinCount + + let x = 0, + i = analyzer.frequencyBinCount, + y = 0; + + // ctx.beginPath() + for (; 0 < i; i--) { + y = canvas.height - (samples[i] / 128) * (canvas.height / 2) + ctx.lineTo(x, y) + x += increment + } + ctx.stroke() + } + + return analyzer +}