ez-web-audio
Version:
Making the Web Audio API super EZ since 2024.
2 lines (1 loc) • 16.6 kB
JavaScript
(function(r,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(r=typeof globalThis<"u"?globalThis:r||self,l(r["ez-audio"]={}))})(this,function(r){"use strict";var te=Object.defineProperty;var ne=(r,l,d)=>l in r?te(r,l,{enumerable:!0,configurable:!0,writable:!0,value:d}):r[l]=d;var c=(r,l,d)=>ne(r,typeof l!="symbol"?l+"":l,d);function l(o){const e=Math.floor(o);return e<10?`0${e}`:`${e}`}function d(o,e,t){return{raw:o,string:`${l(e)}:${l(t)}`,pojo:{minutes:e,seconds:t}}}function y(o){let e=[],t=1;function n(){return o.currentTime*1e3}function i(){const a=n();e.forEach(s=>{s.due<=a&&s.fn()}),e=e.filter(s=>s.due>a),e.length>0&&window.requestAnimationFrame(i)}return{setTimeout(a,s){const h=t;return t+=1,e.push({id:h,due:n()+s,fn:a}),e.length===1&&window.requestAnimationFrame(i),h},clearTimeout(a){e=e.filter(s=>s.id!==a)}}}function k(o,e,t){return o<e?e:o>t?t:o}class A{constructor(e,t,n){c(this,"startingValues",[]);c(this,"valuesAtTime",[]);c(this,"exponentialValues",[]);c(this,"linearValues",[]);this.audioSource=e,this.gainNode=t,this.pannerNode=n}get gain(){return this.gainNode.gain.value}set gain(e){this.gainNode.gain.value=e}get pan(){return this.pannerNode.pan.value}set pan(e){this.pannerNode.pan.value=e}updateGainNode(e){e.gain.value=this.gain,this.gainNode=e}updatePannerNode(e){e.pan.value=this.pan,this.pannerNode=e}_update(e,t){switch(e){case"pan":this.pan=t;break;case"gain":this.gain=t;break;case"detune":if(!this.audioSource.detune)throw new Error("Audio source does not support detune");this.audioSource.detune.value=t;break;default:throw new Error(`Control type '${e}' not supported`)}}update(e){return{to:t=>({from:n=>{switch(n){case"ratio":this._update(e,t);break;case"inverseRatio":this._update(e,1-t);break;case"percent":this._update(e,t/100);break;default:throw new Error(`Control method '${n}' not supported`)}}})}}onPlaySet(e){return{to:t=>{const n={type:e,value:t};return this.startingValues.push(n),{at:i=>{this.removeStartingValue(n),this.valuesAtTime.push({...n,time:i})},endingAt:(i,a="exponential")=>{this.removeStartingValue(n),this.addRampValue({...n,time:i},a)}}}}}onPlayRamp(e,t){return{from:n=>({to:i=>({in:a=>{this.onPlaySet(e).to(n),this.onPlaySet(e).to(i).endingAt(a,t)}})})}}removeStartingValue(e){this.startingValues=this.startingValues.filter(t=>t!==e)}addRampValue(e,t){switch(t){case"exponential":this.exponentialValues.push(e);break;case"linear":this.linearValues.push(e);break;default:throw new Error(`Unsupported ramp type: ${t}`)}}}class g{constructor(e,t){c(this,"gainNode");c(this,"pannerNode");c(this,"bufferSourceNode");c(this,"controller");c(this,"_isPlaying",!1);c(this,"_startedPlayingAt",0);c(this,"startOffset",0);c(this,"connections",[]);this.audioContext=e,this.audioBuffer=t;const n=this.audioContext.createBufferSource(),i=e.createGain(),a=e.createStereoPanner();this.gainNode=i,this.pannerNode=a,this.bufferSourceNode=n,this.audioBuffer=t,n.buffer=t,this.controller=new E(n,i,a)}setup(){const e=this.audioContext.createBufferSource();e.buffer=this.audioBuffer,this.bufferSourceNode=e,this.wireConnections(),this.controller.setValuesAtTimes()}wireConnections(){const e=[this.bufferSourceNode],{connections:t,pannerNode:n}=this;for(let i=0;i<t.length;i++)e.push(t[i].audioNode);e.push(this.gainNode),e.push(n);for(let i=0;i<e.length-1;i++)e[i].connect(e[i+1]);n.connect(this.audioContext.destination)}addConnection(e){this.connections.push(e),this.wireConnections()}removeConnection(e){const t=this.getConnection(e);if(t){const n=this.connections.indexOf(t);n>-1&&(this.connections.splice(n,1),this.wireConnections())}}getConnection(e){return this.connections.find(t=>t.name===e)}getNodeFrom(e){var t;return(t=this.getConnection(e))==null?void 0:t.audioNode}get audioSourceNode(){return this.bufferSourceNode}update(e){return this.controller.update(e)}changePanTo(e){this.controller.update("pan").to(e).from("ratio")}changeGainTo(e){return this.controller.update("gain").to(e)}onPlaySet(e){return this.controller.onPlaySet(e)}onPlayRamp(e,t){return this.controller.onPlayRamp(e,t)}play(){this.playAt(this.audioContext.currentTime)}playFor(e){const{setTimeout:t}=y(this.audioContext);this.playAt(this.audioContext.currentTime),t(()=>this.stop(),e*1e3)}playAt(e){const{audioContext:t}=this,{currentTime:n}=t,{setTimeout:i}=y(t);this.setup(),this.bufferSourceNode.start(e,this.startOffset),this._startedPlayingAt=e,i(()=>this._isPlaying=!1,this.duration.pojo.seconds*1e3),e<=n?this._isPlaying=!0:i(()=>{this._isPlaying=!0},(e-n)*1e3)}stop(){this.bufferSourceNode.stop(),this._isPlaying=!1}get isPlaying(){return this._isPlaying}get duration(){const e=this.bufferSourceNode.buffer;if(e===null)return d(0,0,0);const{duration:t}=e,n=Math.floor(t/60),i=t%60;return d(t,n,i)}get percentGain(){return this.controller.gain*100}seek(e){const t=this.duration.raw,n=i=>{const a=this._isPlaying,s=k(i,0,t);a?(this.stop(),this.startOffset=s,this.play()):this.startOffset=s};return{from(i){switch(i){case"ratio":n(e*t);break;case"percent":n(e*t*.01);break;case"inverseRatio":n(t-e*t);break;case"seconds":n(e);break}}}}}class E extends A{constructor(e,t,n){super(e,t,n),this.bufferSourceNode=e,this.gainNode=t,this.pannerNode=n}updateAudioSource(e){this.bufferSourceNode=e}setValuesAtTimes(){const{bufferSourceNode:e}=this,t=e.context.currentTime;this.applyValues(this.startingValues,t),this.applyValues(this.valuesAtTime,t),this.applyRampValues(this.exponentialValues,t,"exponential"),this.applyRampValues(this.linearValues,t,"linear")}applyValues(e,t){e.forEach(n=>{switch(n.type){case"detune":this.bufferSourceNode.detune.setValueAtTime(n.value,t);break;case"gain":this.gainNode.gain.setValueAtTime(n.value,t);break;default:throw new Error(`Unsupported control type: ${n.type}`)}})}applyRampValues(e,t,n){e.forEach(i=>{const a=t+i.time;switch(i.type){case"detune":switch(n){case"exponential":this.bufferSourceNode.detune.exponentialRampToValueAtTime(i.value,a);break;case"linear":this.bufferSourceNode.detune.linearRampToValueAtTime(i.value,a);break;default:throw new Error(`Unsupported ramp type: ${n}`)}break;case"gain":switch(n){case"exponential":this.gainNode.gain.exponentialRampToValueAtTime(i.value,a);break;case"linear":this.gainNode.gain.linearRampToValueAtTime(i.value,a);break;default:throw new Error(`Unsupported ramp type: ${n}`)}break;default:throw new Error(`ControlType of ${i.type} not supported`)}})}}const p={C0:16.35,Db0:17.32,D0:18.35,Eb0:19.45,E0:20.6,F0:21.83,Gb0:23.12,G0:24.5,Ab0:25.96,A0:27.5,Bb0:29.14,B0:30.87,C1:32.7,Db1:34.65,D1:36.71,Eb1:38.89,E1:41.2,F1:43.65,Gb1:46.25,G1:49,Ab1:51.91,A1:55,Bb1:58.27,B1:61.74,C2:65.41,Db2:69.3,D2:73.42,Eb2:77.78,E2:82.41,F2:87.31,Gb2:92.5,G2:98,Ab2:103.83,A2:110,Bb2:116.54,B2:123.47,C3:130.81,Db3:138.59,D3:146.83,Eb3:155.56,E3:164.81,F3:174.61,Gb3:185,G3:196,Ab3:207.65,A3:220,Bb3:233.08,B3:246.94,C4:261.63,Db4:277.18,D4:293.66,Eb4:311.13,E4:329.63,F4:349.23,Gb4:369.99,G4:392,Ab4:415.3,A4:440,Bb4:466.16,B4:493.88,C5:523.25,Db5:554.37,D5:587.33,Eb5:622.25,E5:659.26,F5:698.46,Gb5:739.99,G5:783.99,Ab5:830.61,A5:880,Bb5:932.33,B5:987.77,C6:1046.5,Db6:1108.73,D6:1174.66,Eb6:1244.51,E6:1318.51,F6:1396.91,Gb6:1479.98,G6:1567.98,Ab6:1661.22,A6:1760,Bb6:1864.66,B6:1975.53,C7:2093,Db7:2217.46,D7:2349.32,Eb7:2489.02,E7:2637.02,F7:2793.83,Gb7:2959.96,G7:3135.96,Ab7:3322.44,A7:3520,Bb7:3729.31,B7:3951.07,C8:4186.01,Db8:4434.92,D8:4698.64,Eb8:4978.03};function b(o,e){return e.split(".").reduce((t,n)=>t&&t[n],o)}const{warn:O}=console;function m(o){return class extends o{constructor(...n){super(...n);c(this,"letter","A");c(this,"accidental","");c(this,"octave","0");const i=n[n.length-1];if(i){const{identifier:a,frequency:s,letter:h,accidental:w,octave:N}=i;(a&&s||(a||s)&&(h||w||N))&&O("ez-audio: upon instantiation, multiple note identifiers were provided which might be a mistake and ez-audio has no way to determine which should be preferred",i,this),a&&(this.identifier=a),s&&(this.frequency=s),h&&(this.letter=h),w&&(this.accidental=w),N&&(this.octave=N)}}get name(){const{accidental:n,letter:i}=this;return n?`${i}${n}`:i}get frequency(){const{identifier:n}=this;return n&&b(p,n)||0}set frequency(n){let i;for(i in p)n===b(p,i)&&(this.identifier=i)}get identifier(){const{accidental:n,letter:i,octave:a}=this;let s="A0";if(n?s=`${i}${n}${a}`:s=`${i}${a}`,b(p,s))return s;throw new Error(`Invalid musical identifier: ${s}`)}set identifier(n){const[i]=n,a=n[2]||n[1];let s;n[2]?s=n[1]:s="",this.letter=i,this.accidental=s,this.octave=a}}}class S extends m(g){}class T{constructor(e){this.notes=e}getNote(e){return this.notes.find(t=>t.identifier===e)}play(e){this.getNote(e).play()}}function _(o){return new Uint8Array(atob(o).split("").map(e=>e.charCodeAt(0)))}function B(o){const e=o.indexOf("=",o.indexOf("MIDI.Soundfont."))+2,t=o.lastIndexOf('"')+1,n=`${o.slice(e,t)}}`.replace(/data:audio\/mp3;base64,/g,"").replace(/data:audio\/mpeg;base64,/g,"").replace(/data:audio\/ogg;base64,/g,"");return JSON.parse(n)}function q(o,e){const t=o.slice(0,e),n=o.slice(e,o.length);return n.push(...t),n}function G(o){return[...new Set(o)]}function D(o){let e=$(o);e=I(e);let t=M(e);return t=F(t),t=R(t),t.flat()}function R(o){const e=o.shift()||[],t=o[0].map(s=>s.name),n=e[e.length-1].name,i=t.lastIndexOf(n)+1,a=o.map(s=>q(s,i));return a.unshift(e),a}function F(o){return o.map(e=>e.sort(U))}function $(o){return[o,o.map(e=>e.octave)]}function I([o,e]){return[o,G(e).sort()]}function M([o,e]){return e.map(t=>o.filter(n=>n.octave===t))}function U(o,e){const t=o.letter,n=e.letter;return t<n||t===n&&o.accidental==="b"?-1:1}function j(o,e){const t=[];async function n(i,a){const s=await o.decodeAudioData(a);return[i,s]}for(const i in e)if(Object.prototype.hasOwnProperty.call(e,i)){const a=_(e[i]);t.push(n(i,a.buffer))}return Promise.all(t)}function z(o,e){const t=e.map(n=>{const[i,a]=n,s=new S(o,a);return s.identifier=i,s});return D(t)}class P{constructor(e){c(this,"gain",1);c(this,"pan",0);c(this,"_soundIterator");c(this,"sounds");const t=new Set(e);this.sounds=t,this._soundIterator=e.values()}play(){this._getNextSound().play()}playIn(e){}playAt(e){this._getNextSound().playAt(e)}_getNextSound(){let e=this._soundIterator,t;return t=e.next(),t.done&&(e=this.sounds.values(),t=e.next()),this._soundIterator=e,this._setGainAndPan(t.value)}_setGainAndPan(e){return e.changePanTo(this.pan),e}}const W=["highpass","bandpass","lowpass","lowshelf","highshelf","peaking","notch","allpass"];class V{constructor(e,t){c(this,"filters",[]);c(this,"type");c(this,"freq");c(this,"controller");c(this,"oscillator");c(this,"gainNode");c(this,"pannerNode");c(this,"connections",[]);c(this,"_isPlaying",!1);this.audioContext=e,this.type=(t==null?void 0:t.type)||"sine",this.freq=(t==null?void 0:t.frequency)||440,this.oscillator=e.createOscillator(),this.gainNode=e.createGain(),this.pannerNode=e.createStereoPanner(),this.controller=new L(this.oscillator,this.gainNode,this.pannerNode),t&&t.gain!==void 0&&this.changeGainTo(t.gain),W.forEach(n=>{const i=b(t,n);if(i){const a=e.createBiquadFilter();a.type=n,a.frequency.setValueAtTime(i.frequency||440,e.currentTime),a.Q.setValueAtTime(i.q||1,e.currentTime),this.filters.push(a)}})}onPlaySet(e){return this.controller.onPlaySet(e)}onPlayRamp(e,t){return this.controller.onPlayRamp(e,t)}setup(){const e=this.audioContext.createOscillator();e.type=this.type||"sine",e.frequency.setValueAtTime(this.freq||440,this.audioContext.currentTime),this.oscillator=e;const t=this.audioContext.createGain();this.gainNode=t,this.controller.updateAudioSource(e),this.controller.updateGainNode(t),this.wireConnections(),this.controller.setValuesAtTimes()}wireConnections(){const e=[this.oscillator],{connections:t,filters:n,pannerNode:i}=this;for(let a=0;a<n.length;a++)e.push(n[a]);for(let a=0;a<t.length;a++)e.push(t[a].audioNode);e.push(this.gainNode),e.push(i);for(let a=0;a<e.length-1;a++)e[a].connect(e[a+1]);i.connect(this.audioContext.destination)}addConnection(e){this.connections.push(e),this.wireConnections()}removeConnection(e){const t=this.getConnection(e);if(t){const n=this.connections.indexOf(t);n>-1&&(this.connections.splice(n,1),this.wireConnections())}}getConnection(e){return this.connections.find(t=>t.name===e)}getNodeFrom(e){var t;return(t=this.getConnection(e))==null?void 0:t.audioNode}get audioSourceNode(){return this.oscillator}update(e){return this.controller.update(e)}changePanTo(e){this.controller.update("pan").to(e).from("ratio")}changeGainTo(e){this.controller.update("gain").to(e).from("ratio")}play(){this.playAt(this.audioContext.currentTime)}playFor(e){const{setTimeout:t}=y(this.audioContext);this.playAt(this.audioContext.currentTime),t(()=>this.stop(),e*1e3)}playAt(e){const{audioContext:t}=this,{currentTime:n}=t,{setTimeout:i}=y(t);this.setup(),this.oscillator.start(e),e<=n?this._isPlaying=!0:i(()=>{this._isPlaying=!0},(e-n)*1e3)}stop(){this._isPlaying=!1,this.oscillator.stop()}get isPlaying(){return this._isPlaying}get duration(){return d(0,0,0)}get percentGain(){return this.gainNode.gain.value*100}}class L extends A{constructor(e,t,n){super(e,t,n),this.oscillator=e,this.gainNode=t,this.pannerNode=n}updateAudioSource(e){this.oscillator=e}_update(e,t){switch(e){case"frequency":this.oscillator.frequency.value=t;break;default:super._update(e,t)}}setValuesAtTimes(){const{oscillator:{context:{currentTime:e}}}=this;this.applyValues(this.startingValues,e),this.applyValues(this.valuesAtTime,e),this.applyRampValues(this.exponentialValues,e,"exponential"),this.applyRampValues(this.linearValues,e,"linear")}applyValues(e,t){const{oscillator:n,gainNode:i}=this;e.forEach(a=>{switch(a.type){case"frequency":n.frequency.setValueAtTime(a.value,t);break;case"gain":i.gain.setValueAtTime(a.value,t);break;default:throw new Error(`Unsupported control type: ${a.type}`)}})}applyRampValues(e,t,n){const{oscillator:i,gainNode:a}=this;e.forEach(s=>{const h=t+s.time;switch(s.type){case"frequency":switch(n){case"exponential":i.frequency.exponentialRampToValueAtTime(s.value,h);break;case"linear":i.frequency.linearRampToValueAtTime(s.value,h);break;default:throw new Error(`Unsupported ramp type: ${n}`)}break;case"gain":switch(n){case"exponential":a.gain.exponentialRampToValueAtTime(s.value,h);break;case"linear":a.gain.linearRampToValueAtTime(s.value,h);break;default:throw new Error(`Unsupported ramp type: ${n}`)}break;default:throw new Error(`ControlType of ${s.type} not supported`)}})}}class C extends g{get position(){const e=this.startOffset,t=Math.floor(e/60),n=e-t*60;return d(e,t,n)}get percentPlayed(){return this.startOffset/this.duration.raw*100}play(){super.play(),this.audioSourceNode.onended=()=>this.stop(),this._trackPlayPosition()}pause(){if(this._isPlaying){const e=this.audioSourceNode;e.onended=function(){},e.stop(),this._isPlaying=!1}}stop(){this.startOffset=0,this._isPlaying&&(this.audioSourceNode.onended=function(){},super.stop())}_trackPlayPosition(){const e=this.audioContext,t=this.startOffset,n=this._startedPlayingAt,i=()=>{this._isPlaying&&(this.startOffset=t+e.currentTime-n,requestAnimationFrame(i))};requestAnimationFrame(i)}}class x extends m(class{}){constructor(e){super(),this.letter=(e==null?void 0:e.letter)||"A",this.accidental=(e==null?void 0:e.accidental)||"",this.octave=(e==null?void 0:e.octave)||"0"}}let u;function f(){if(!u)throw new Error("The audio context does not exist yet! You must call `initAudio()` in response to a user interaction before performing this action.")}async function J(){u||(u=new AudioContext),u.state==="suspended"&&await u.resume()}function K(){return f(),u}function Q(o){const e=[];o||(o=p);for(const t in o){const n=o[t],i=new x;i.frequency=n,e.push(i)}return e}async function v(o){f();const t=await(await fetch(o)).arrayBuffer(),n=await u.decodeAudioData(t);return new g(u,n)}async function Y(o){f();const t=await(await fetch(o)).arrayBuffer(),n=await u.decodeAudioData(t);return new C(u,n)}async function H(o){f();const e=await Promise.all(o.map(async t=>v(t)));return new P(e)}function X(o){return f(),new V(u,o)}async function Z(o){f();const t=await(await fetch(o)).text(),n=B(t),i=await j(u,n),a=z(u,i);return new T(a)}function ee(){f();const o=u.sampleRate,e=u.createBuffer(1,o,o),t=e.getChannelData(0);for(let n=0;n<o;n++)t[n]=Math.random()*2-1;return new g(u,e)}r.Font=T,r.MusicallyAware=m,r.Note=x,r.Oscillator=V,r.SampledNote=S,r.Sampler=P,r.Sound=g,r.Track=C,r.createFont=Z,r.createNotes=Q,r.createOscillator=X,r.createSampler=H,r.createSound=v,r.createTrack=Y,r.createWhiteNoise=ee,r.frequencyMap=p,r.getAudioContext=K,r.initAudio=J,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});