@logue/sf2synth
Version:
SoundFont2 Synthesizer
11 lines (10 loc) • 58.8 kB
JavaScript
/**
* @logue/sf2synth
*
* @description SoundFont2 Synthesizer
* @author iyama, Logue
* @license MIT
* @version 0.9.0
* @see {@link https://github.com/logue/sf2synth.js}
*/
var SoundFont=(function(){var e={version:`0.9.0`,date:`2026-05-17T05:49:55.145Z`},t=class e{static CHUNK_ID_SIZE=4;static CHUNK_SIZE_BYTES=4;static SHIFT_8_BITS=8;static SHIFT_16_BITS=16;static SHIFT_24_BITS=24;static UNSIGNED_32_BIT_MASK=0;constructor(e,t={}){this.input=e instanceof Uint8Array?e:new Uint8Array(e),this.ip=t.index||0,this.length=t.length||e.byteLength-this.ip,this.chunkList=[],this.offset=this.ip,this.padding=t.padding===void 0?!0:t.padding,this.bigEndian=t.bigEndian===void 0?!1:t.bigEndian}parse(){let e=this.length+this.offset;for(this.chunkList=[];this.ip<e;)this.parseChunk()}readChunkId(e,t){return String.fromCharCode(e[t],e[t+1],e[t+2],e[t+3])}readUInt32(t,n){return this.bigEndian?(t[n]<<e.SHIFT_24_BITS|t[n+1]<<e.SHIFT_16_BITS|t[n+2]<<e.SHIFT_8_BITS|t[n+3])>>>e.UNSIGNED_32_BIT_MASK:(t[n]|t[n+1]<<e.SHIFT_8_BITS|t[n+2]<<e.SHIFT_16_BITS|t[n+3]<<e.SHIFT_24_BITS)>>>e.UNSIGNED_32_BIT_MASK}parseChunk(){let t=this.input,r=this.ip,i=this.readChunkId(t,r);r+=e.CHUNK_ID_SIZE;let a=this.readUInt32(t,r);r+=e.CHUNK_SIZE_BYTES,this.chunkList.push(new n(i,a,r)),r+=a,this.padding&&(r-this.offset&1)==1&&r++,this.ip=r}getChunk(e){let t=this.chunkList[e];return t===void 0?null:t}getNumberOfChunks(){return this.chunkList.length}},n=class{constructor(e,t,n){this.type=e,this.size=t,this.offset=n}},r=class e{static CHUNK_ID_SIZE=4;static EXPECTED_RIFF_CHUNKS=1;static EXPECTED_SFBK_CHUNKS=3;static EXPECTED_PDTA_CHUNKS=9;static EXPECTED_SDTA_CHUNKS=1;static PRESET_HEADER_SIZE=38;static INSTRUMENT_HEADER_SIZE=22;static NAME_SIZE=20;static SAMPLE_HEADER_SIZE=46;static BAG_SIZE=4;static MODULATOR_SIZE=10;static GENERATOR_SIZE=4;constructor(t,n={}){this.input=t,this.parserOption=n.parserOption||{},this.sampleRate=n.sampleRate||22050,this.presetHeader=[],this.presetZone=[],this.presetZoneModulator=[],this.presetZoneGenerator=[],this.instrument=[],this.instrumentZone=[],this.instrumentZoneModulator=[],this.instrumentZoneGenerator=[],this.sampleHeader=[],this.sample=[],this.samplingData=void 0,this.GeneratorEnumeratorTable=Object.keys(e.getGeneratorTable())}static getGeneratorTable(){return Object.freeze({startAddrsOffset:0,endAddrsOffset:0,startloopAddrsOffset:0,endloopAddrsOffset:0,startAddrsCoarseOffset:0,modLfoToPitch:0,vibLfoToPitch:0,modEnvToPitch:0,initialFilterFc:13500,initialFilterQ:0,modLfoToFilterFc:0,modEnvToFilterFc:0,endAddrsCoarseOffset:0,modLfoToVolume:0,unused1:void 0,chorusEffectsSend:0,reverbEffectsSend:0,pan:0,unused2:void 0,unused3:void 0,unused4:void 0,delayModLFO:-12e3,freqModLFO:0,delayVibLFO:-12e3,freqVibLFO:0,delayModEnv:-12e3,attackModEnv:-12e3,holdModEnv:-12e3,decayModEnv:-12e3,sustainModEnv:0,releaseModEnv:-12e3,keynumToModEnvHold:0,keynumToModEnvDecay:0,delayVolEnv:-12e3,attackVolEnv:-12e3,holdVolEnv:-12e3,decayVolEnv:-12e3,sustainVolEnv:0,releaseVolEnv:-12e3,keynumToVolEnvHold:0,keynumToVolEnvDecay:0,instrument:null,reserved1:void 0,keyRange:null,velRange:null,startloopAddrsCoarseOffset:0,keynum:null,velocity:null,initialAttenuation:0,reserved2:void 0,endloopAddrsCoarseOffset:0,coarseTune:0,fineTune:0,sampleID:null,sampleModes:0,reserved3:void 0,scaleTuning:100,exclusiveClass:null,overridingRootKey:null,unuded5:void 0,endOper:void 0})}readSignature(e,t){return String.fromCharCode(e[t],e[t+1],e[t+2],e[t+3])}validateChunkType(e,t){if(e.type!==t)throw Error(`invalid chunk type: expected '${t}', got '${e.type}'`)}validateSignature(e,t){if(e!==t)throw Error(`invalid signature: expected '${t}', got '${e}'`)}parse(){let n=new t(this.input.buffer,this.parserOption);if(n.parse(),n.chunkList.length!==e.EXPECTED_RIFF_CHUNKS)throw Error(`wrong chunk length: expected ${e.EXPECTED_RIFF_CHUNKS}, got ${n.chunkList.length}`);let r=n.getChunk(0);if(r===null)throw Error(`chunk not found`);this.parseRiffChunk(r),this.input=null}parseRiffChunk(n){let r=this.input,i=n.offset;this.validateChunkType(n,`RIFF`);let a=this.readSignature(r,i);i+=e.CHUNK_ID_SIZE,this.validateSignature(a,`sfbk`);let o=new t(r,{index:i,length:n.size-e.CHUNK_ID_SIZE});if(o.parse(),o.getNumberOfChunks()!==e.EXPECTED_SFBK_CHUNKS)throw Error(`invalid sfbk structure: expected ${e.EXPECTED_SFBK_CHUNKS} chunks, got ${o.getNumberOfChunks()}`);this.parseInfoList(o.getChunk(0)),this.parseSdtaList(o.getChunk(1)),this.parsePdtaList(o.getChunk(2))}parseInfoList(n){let r=this.input,i=n.offset;this.validateChunkType(n,`LIST`);let a=this.readSignature(r,i);i+=e.CHUNK_ID_SIZE,this.validateSignature(a,`INFO`),new t(r,{index:i,length:n.size-e.CHUNK_ID_SIZE}).parse()}parseSdtaList(n){let r=this.input,i=n.offset;this.validateChunkType(n,`LIST`);let a=this.readSignature(r,i);i+=e.CHUNK_ID_SIZE,this.validateSignature(a,`sdta`);let o=new t(r,{index:i,length:n.size-e.CHUNK_ID_SIZE});if(o.parse(),o.chunkList.length!==e.EXPECTED_SDTA_CHUNKS)throw Error(`invalid sdta structure: expected ${e.EXPECTED_SDTA_CHUNKS} chunk, got ${o.chunkList.length}`);this.samplingData=o.getChunk(0)}parsePdtaList(n){let r=this.input,i=n.offset;this.validateChunkType(n,`LIST`);let a=this.readSignature(r,i);i+=e.CHUNK_ID_SIZE,this.validateSignature(a,`pdta`);let o=new t(r,{index:i,length:n.size-e.CHUNK_ID_SIZE});if(o.parse(),o.getNumberOfChunks()!==e.EXPECTED_PDTA_CHUNKS)throw Error(`invalid pdta chunk: expected ${e.EXPECTED_PDTA_CHUNKS} chunks, got ${o.getNumberOfChunks()}`);this.parsePhdr(o.getChunk(0)),this.parsePbag(o.getChunk(1)),this.parsePmod(o.getChunk(2)),this.parsePgen(o.getChunk(3)),this.parseInst(o.getChunk(4)),this.parseIbag(o.getChunk(5)),this.parseImod(o.getChunk(6)),this.parseIgen(o.getChunk(7)),this.parseShdr(o.getChunk(8))}parsePhdr(e){this.validateChunkType(e,`phdr`);let t=this.input,n=e.offset,r=this.presetHeader=[],i=e.offset+e.size;for(;n<i;)r.push({presetName:String.fromCharCode(...Array.from(t.subarray(n,n+=20))),preset:t[n++]|t[n++]<<8,bank:t[n++]|t[n++]<<8,presetBagIndex:t[n++]|t[n++]<<8,library:(t[n++]|t[n++]<<8|t[n++]<<16|t[n++]<<24)>>>0,genre:(t[n++]|t[n++]<<8|t[n++]<<16|t[n++]<<24)>>>0,morphology:(t[n++]|t[n++]<<8|t[n++]<<16|t[n++]<<24)>>>0})}parsePbag(e){this.validateChunkType(e,`pbag`);let t=this.input,n=e.offset,r=this.presetZone=[],i=e.offset+e.size;for(;n<i;)r.push({presetGeneratorIndex:t[n++]|t[n++]<<8,presetModulatorIndex:t[n++]|t[n++]<<8})}parsePmod(e){this.validateChunkType(e,`pmod`),this.presetZoneModulator=this.parseModulator(e)}parsePgen(e){this.validateChunkType(e,`pgen`),this.presetZoneGenerator=this.parseGenerator(e)}parseInst(e){this.validateChunkType(e,`inst`);let t=this.input,n=e.offset,r=this.instrument=[],i=e.offset+e.size;for(;n<i;)r.push({instrumentName:String.fromCharCode(...Array.from(t.subarray(n,n+=20))),instrumentBagIndex:t[n++]|t[n++]<<8})}parseIbag(e){this.validateChunkType(e,`ibag`);let t=this.input,n=e.offset,r=this.instrumentZone=[],i=e.offset+e.size;for(;n<i;)r.push({instrumentGeneratorIndex:t[n++]|t[n++]<<8,instrumentModulatorIndex:t[n++]|t[n++]<<8})}parseImod(e){this.validateChunkType(e,`imod`),this.instrumentZoneModulator=this.parseModulator(e)}parseIgen(e){this.validateChunkType(e,`igen`),this.instrumentZoneGenerator=this.parseGenerator(e)}readUInt32LE(e,t){return(e[t]<<0|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24)>>>0}readUInt16LE(e,t){return e[t]|e[t+1]<<8}parseShdr(t){this.validateChunkType(t,`shdr`);let n=this.input,r=t.offset,i=this.sample=[],a=this.sampleHeader=[],o=t.offset+t.size,s=this.samplingData;if(!s)throw Error(`sampling data not found`);for(;r<o;){let t=String.fromCharCode(...Array.from(n.subarray(r,r+e.NAME_SIZE)));r+=e.NAME_SIZE;let o=this.readUInt32LE(n,r);r+=4;let c=this.readUInt32LE(n,r);r+=4;let l=this.readUInt32LE(n,r);r+=4;let u=this.readUInt32LE(n,r);r+=4;let d=this.readUInt32LE(n,r);r+=4;let f=n[r++],p=n[r++]<<24>>24,m=this.readUInt16LE(n,r);r+=2;let h=this.readUInt16LE(n,r);r+=2;let g=new Int16Array(new Uint8Array(n.subarray(s.offset+o*2,s.offset+c*2)).buffer);if(l-=o,u-=o,d>0){let e=this.adjustSampleData(g,d);g=e.sample,d*=e.multiply,l*=e.multiply,u*=e.multiply}i.push(g),a.push({sampleName:t,start:o,end:c,startLoop:l,endLoop:u,sampleRate:d,originalPitch:f,pitchCorrection:p,sampleLink:m,sampleType:h})}}adjustSampleData(e,t){let n,r,i,a,o=1;for(;t<this.sampleRate;){for(n=new Int16Array(e.length*2),r=a=0,i=e.length;r<i;++r)n[a++]=e[r],n[a++]=e[r];e=n,o*=2,t*=2}return{sample:e,multiply:o}}parseModulator(e){let t=this.input,n=e.offset,r=e.offset+e.size,i=[];for(;n<r;){n+=2;let e=t[n++]|t[n++]<<8,r=this.GeneratorEnumeratorTable[e];if(!r)i.push({type:r??`unknown`,value:{code:e,amount:t[n]|t[n+1]<<8<<16>>16,lo:t[n++],hi:t[n++]}});else switch(r){case`keyRange`:case`velRange`:case`keynum`:case`velocity`:i.push({type:r,value:{amount:null,lo:t[n++],hi:t[n++]}});break;default:i.push({type:r,value:{amount:t[n++]|t[n++]<<8<<16>>16}});break}n+=2,n+=2}return i}parseGenerator(e){let t=this.input,n=e.offset,r=e.offset+e.size,i=[];for(;n<r;){let e=t[n++]|t[n++]<<8,r=this.GeneratorEnumeratorTable[e];if(!r){i.push({type:r??`unknown`,value:{code:e,amount:t[n]|t[n+1]<<8<<16>>16,lo:t[n++],hi:t[n++]}});continue}switch(r){case`keynum`:case`keyRange`:case`velRange`:case`velocity`:i.push({type:r,value:{amount:null,lo:t[n++],hi:t[n++]}});break;default:i.push({type:r,value:{amount:t[n++]|t[n++]<<8<<16>>16}});break}}return i}createInstrument(){let e=this.instrument,t=this.instrumentZone,n=[],r,i,a,o,s,c,l,u,d;for(c=0,l=e.length;c<l;++c){for(r=e[c].instrumentBagIndex,i=e[c+1]?e[c+1].instrumentBagIndex:t.length,a=[],u=r,d=i;u<d;++u)o=this.createInstrumentGenerator_(t,u),s=this.createInstrumentModulator_(t,u),a.push({generator:o.generator,generatorSequence:o.generatorInfo,modulator:s.modulator,modulatorSequence:s.modulatorInfo});n.push({name:e[c].instrumentName,info:a})}return n}createPreset(){let e=this.presetHeader,t=this.presetZone,n=[],r,i,a,o=null,s,c,l,u,d,f;for(l=0,u=e.length;l<u;++l){for(r=e[l].presetBagIndex,i=e[l+1]?e[l+1].presetBagIndex:t.length,a=[],d=r,f=i;d<f;++d)s=this.createPresetGenerator_(t,d),c=this.createPresetModulator_(t,d),a.push({generator:s.generator,generatorSequence:s.generatorInfo,modulator:c.modulator,modulatorSequence:c.modulatorInfo}),o=s.generator.instrument===void 0?c.modulator.instrument===void 0?null:c.modulator.instrument.amount:s.generator.instrument.amount;n.push({name:e[l].presetName,info:a,header:e[l],instrument:o})}return n}createInstrumentGenerator_(e,t){let n=this.createBagModGen_(e,e[t].instrumentGeneratorIndex,e[t+1]?e[t+1].instrumentGeneratorIndex:this.instrumentZoneGenerator.length,this.instrumentZoneGenerator);return{generator:n.modgen,generatorInfo:n.modgenInfo}}createInstrumentModulator_(e,t){let n=this.createBagModGen_(e,e[t].instrumentModulatorIndex,e[t+1]?e[t+1].instrumentModulatorIndex:this.instrumentZoneModulator.length,this.instrumentZoneModulator);return{modulator:n.modgen,modulatorInfo:n.modgenInfo}}createPresetGenerator_(e,t){let n=this.createBagModGen_(e,e[t].presetGeneratorIndex,e[t+1]?e[t+1].presetGeneratorIndex:this.presetZoneGenerator.length,this.presetZoneGenerator);return{generator:n.modgen,generatorInfo:n.modgenInfo}}createPresetModulator_(e,t){let n=this.createBagModGen_(e,e[t].presetModulatorIndex,e[t+1]?e[t+1].presetModulatorIndex:this.presetZoneModulator.length,this.presetZoneModulator);return{modulator:n.modgen,modulatorInfo:n.modgenInfo}}createBagModGen_(e,t,n,r){let i=[],a={unknown:[],keyRange:{amount:null,hi:127,lo:0}},o,s,c;for(s=t,c=n;s<c;++s)o=r[s],i.push(o),o.type===`unknown`?a.unknown.push(o.value):a[o.type]=o.value;return{modgen:a,modgenInfo:i}}},i=class e{static CACHE_NAME=`wml`;static FETCH_METHOD=`GET`;static PROGRESS_MAX=100;static CLASS_ALERT_WARNING=`alert alert-warning`;static CLASS_ALERT_INFO=`alert alert-info`;static CLASS_ALERT_DANGER=`alert alert-danger`;static CLASS_PROGRESS=`progress`;static CLASS_PROGRESS_BAR=`progress-bar`;static CLASS_PROGRESS_BAR_ANIMATED=`progress-bar progress-bar-striped progress-bar-animated`;static MSG_LOADING=`Now Loading...`;static MSG_INITIALIZING=`Initializing...`;static MSG_ERROR=`An error occurred while loading SoundFont. See the console log for details. In addition, it may be cured by deleting the cache of the browser.`;constructor(e,t,n,r){this.url=e,this.cache=n,this.callback=r,this.createUIElements(),t.appendChild(this.alert)}createUIElements(){this.alert=this.createElement(`div`,e.CLASS_ALERT_WARNING),this.message=this.createElement(`p`),this.message.innerText=e.MSG_LOADING,this.progressOuter=this.createProgressBar(),this.progress=this.createElement(`div`,e.CLASS_PROGRESS_BAR),this.progressOuter.appendChild(this.progress),this.alert.appendChild(this.message),this.alert.appendChild(this.progressOuter)}createElement(e,t=``){let n=document.createElement(e);return t&&(n.className=t),n}createProgressBar(){let t=this.createElement(`div`,e.CLASS_PROGRESS);return t.role=`progressbar`,t.ariaLabel=`Loading Progress`,t.ariaValueMin=`0`,t.ariaValueNow=`0`,t.ariaValueMax=e.PROGRESS_MAX.toString(),t}onProgress(t,n){let r=Math.floor(t/n*e.PROGRESS_MAX);this.updateProgress(r)}updateProgress(e){this.progress.style.width=`${e}%`,this.progress.innerText=`${e}%`,this.progressOuter.ariaValueNow=e.toString()}onComplete(t){this.alert.className=e.CLASS_ALERT_INFO,this.message.innerText=e.MSG_INITIALIZING,this.progress.className=e.CLASS_PROGRESS_BAR_ANIMATED,this.updateProgress(e.PROGRESS_MAX),this.callback(new Uint8Array(t))}onError(t=void 0){t&&console.error(`[Loader] Error occurred:`,t),requestAnimationFrame(()=>{this.alert.className=e.CLASS_ALERT_DANGER,this.message.innerText=e.MSG_ERROR,this.progressOuter.style.display=`none`})}async fetch(){try{let t=await window.caches.open(e.CACHE_NAME),n=await this.loadFromCache(t);if(n){this.onComplete(n);return}await this.loadFromNetwork(t)}catch(e){this.onError(e)}}async loadFromCache(e){if(!this.cache)return null;let t=await e.match(this.url);return t?new Uint8Array(await t.arrayBuffer()):null}async loadFromNetwork(t){let n=await fetch(this.url,{method:e.FETCH_METHOD}).catch(e=>(this.onError(e),null));if(!n||!n.ok){this.onError(Error(`Failed to fetch: ${n?.status} ${n?.statusText}`));return}let r=n.clone(),i=parseInt(n.headers.get(`Content-Length`)||`0`,10),a=await this.readResponseBody(r,i);await t.put(this.url,n),this.onComplete(a)}async readResponseBody(e,t){let n=e.body.getReader(),r=0,i=[];for(;;){let{done:e,value:a}=await n.read();if(e)break;i.push(a),r+=a.length,this.updateLoadingMessage(r,t),t>0&&this.onProgress(r,t)}return this.mergeChunks(i,r)}updateLoadingMessage(t,n){this.message.innerText=`${e.MSG_LOADING} (${t} of ${n} byte)`}mergeChunks(e,t){let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}},a=1/2**32,o=class{float(e=1){return this.int()*a*e}probability(e){return this.float()<e}norm(e=1){return(this.int()*a-.5)*2*e}normMinMax(e,t){let n=this.minmax(e,t);return this.float()<.5?n:-n}minmax(e,t){return this.float()*(t-e)+e}minmaxInt(e,t){e|=0;let n=(t|0)-e;return n?e+this.int()%n:e}minmaxUint(e,t){e>>>=0;let n=(t>>>0)-e;return n?e+this.int()%n:e}},s=new class extends o{constructor(e){super(),this.rnd=e}rnd;float(e=1){return this.rnd()*e}norm(e=1){return(this.rnd()-.5)*2*e}int(){return this.rnd()*4294967296>>>0}}(Math.random),c={bins:2,scale:1,rnd:s},l=(e,t,n)=>{let r=Array(e);for(let i=0;i<e;i++)r[i]=n.norm(t);return r},u=e=>e.reduce((e,t)=>e+t,0);function*d(e,t){let n=[e[Symbol.iterator](),t[Symbol.iterator]()];for(let e=0;;e^=1){let t=n[e].next();if(t.done)return;yield t.value}}function*f(e){let{bins:t,scale:n,rnd:r}={...c,...e},i=l(t,n,r);i.forEach((e,t)=>i[t]=t&1?e:-e);let a=1/t,o=u(i);for(let e=0,s=-1;;++e>=t&&(e=0))o-=i[e],o+=i[e]=s*r.norm(n),s^=4294967294,yield s*o*a}var p=e=>d(f(e),f(e)),m=e=>{let t=32;return e&=-e,e&&t--,e&65535&&(t-=16),e&16711935&&(t-=8),e&252645135&&(t-=4),e&858993459&&(t-=2),e&1431655765&&--t,t};function*h(e){let{bins:t=8,scale:n,rnd:r}={...c,...e},i=l(t,n,r),a=1/t,o=u(i);for(let e=0;;e=e+1>>>0){let s=m(e)%t;o-=i[s],o+=i[s]=r.norm(n),yield o*a}}function*g(e){let{bins:t,scale:n,rnd:r}={...c,...e},i=l(t,n,r),a=1/t,o=u(i);for(let e=0;;++e>=t&&(e=0))o-=i[e],o+=i[e]=r.norm(n),yield o*a}var _=e=>d(g(e),g(e));function*v(e){let{scale:t,rnd:n}={...c,...e};for(;;)yield n.norm(t)}var y=(e,t)=>typeof e?.[t]==`function`,b=e=>y(e,`xform`)?e.xform():e,x=e=>typeof e?.[Symbol.iterator]==`function`,S=e=>e,C=class{value;constructor(e){this.value=e}deref(){return this.value}},w=e=>new C(e),T=e=>e instanceof C,E=e=>e instanceof C?e:new C(e),D=e=>e instanceof C?e.deref():e,O=(e,t)=>[e,S,t];function k(e){return e?[...e]:O(()=>[],(e,t)=>(e.push(t),e))}function*A(e,t){let n=b(e)(k()),r=n[1],i=n[2];for(let e of t){let t=i([],e);if(T(t)){yield*D(r(t.deref()));return}t.length&&(yield*t)}yield*D(r([]))}var j=(e,t)=>[e[0],e[1],t];function M(e,t){return x(t)?A(M(e),t):t=>{let n=t[2],r=e;return j(t,(e,t)=>--r>0?n(e,t):r===0?E(n(e,t)):w(e))}}var N={version:`1.5.2`,date:`2026-05-17T05:21:07.564Z`},P={blue:f,brown:g,green:p,pink:h,red:g,violet:_,white:v},F={noise:`white`,scale:1,peaks:2,randomAlgorithm:s,decay:2,delay:0,reverse:!1,time:2,filterType:`allpass`,filterFreq:2200,filterQ:1,mix:.5,once:!1},I=class e{static version=N.version;static build=N.date;ctx;wetGainNode;dryGainNode;filterNode;convolverNode;outputNode;options;isConnected;noise=v;noiseMap=P;constructor(e,t={}){this.ctx=e,this.options=Object.assign({},F,t),this.wetGainNode=this.ctx.createGain(),this.dryGainNode=this.ctx.createGain(),this.filterNode=this.ctx.createBiquadFilter(),this.convolverNode=this.ctx.createConvolver(),this.outputNode=this.ctx.createGain(),this.isConnected=!1,this.filterType(this.options.filterType),this.setNoise(this.options.noise),this.buildImpulse(),this.mix(this.options.mix)}connect(e){return this.isConnected&&this.options.once?(this.isConnected=!1,this.outputNode):(this.convolverNode.connect(this.filterNode),this.filterNode.connect(this.wetGainNode),e.connect(this.convolverNode),e.connect(this.dryGainNode),e.connect(this.wetGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.isConnected=!0,this.outputNode)}disconnect(e){return this.isConnected&&(this.convolverNode.disconnect(this.filterNode),this.filterNode.disconnect(this.wetGainNode)),this.isConnected=!1,e}mix(t){if(!e.inRange(t,0,1))throw RangeError(`[Reverb.js] Dry/Wet ratio must be between 0 to 1.`);this.options.mix=t,this.dryGainNode.gain.value=1-t,this.wetGainNode.gain.value=t,console.debug(`[Reverb.js] Set dry/wet ratio to ${t*100}%`)}time(t){if(!e.inRange(t,1,50))throw RangeError(`[Reverb.js] Time length of impulse response must be less than 50sec.`);this.options.time=t,this.buildImpulse(),console.debug(`[Reverb.js] Set impulse response time length to ${t}sec.`)}decay(t){if(!e.inRange(t,0,100))throw RangeError(`[Reverb.js] Impulse Response decay level must be less than 100.`);this.options.decay=t,this.buildImpulse(),console.debug(`[Reverb.js] Set impulse response decay level to ${t}.`)}delay(t){if(!e.inRange(t,0,100))throw RangeError(`[Reverb.js] Impulse Response delay time must be less than 100.`);this.options.delay=t,this.buildImpulse(),console.debug(`[Reverb.js] Set impulse response delay time to ${t}sec.`)}reverse(e){this.options.reverse=e,this.buildImpulse(),console.debug(`[Reverb.js] Inpulse response is ${e?``:`not `}reversed.`)}filterType(e=`allpass`){this.filterNode.type=this.options.filterType=e,console.debug(`[Reverb.js] Set filter type to ${e}`)}filterFreq(t){if(!e.inRange(t,20,2e4))throw RangeError(`[Reverb.js] Filter frequrncy must be between 20 and 20000.`);this.options.filterFreq=t,this.filterNode.frequency.value=this.options.filterFreq,console.debug(`[Reverb.js] Set filter frequency to ${t}Hz.`)}filterQ(t){if(!e.inRange(t,0,10))throw RangeError(`[Reverb.js] Filter Q value must be between 0 and 10.`);this.options.filterQ=t,this.filterNode.Q.value=this.options.filterQ,console.debug(`[Reverb.js] Set filter Q to ${t}.`)}peaks(e){this.options.peaks=e,this.buildImpulse(),console.debug(`[Reverb.js] Set IR source noise peaks to ${e}.`)}scale(e){this.options.scale=e,this.buildImpulse(),console.debug(`[Reverb.js] Set IR source noise scale to ${e}.`)}getNoise(e){return[...M(e,this.noise({bins:this.options.peaks,scale:this.options.scale,rnd:this.options.randomAlgorithm}))]}setNoise(e){switch(this.options.noise=e,e){case`blue`:this.noise=this.noiseMap.blue;break;case`brown`:this.noise=this.noiseMap.brown;break;case`green`:this.noise=this.noiseMap.green;break;case`pink`:this.noise=this.noiseMap.pink;break;case`red`:this.noise=this.noiseMap.red;break;case`violet`:this.noise=this.noiseMap.violet;break;case`white`:this.noise=this.noiseMap.white;break;default:this.noise=v;break}this.buildImpulse(),console.debug(`[Reverb.js] Set IR generator source noise type to ${e}.`)}setRandomAlgorithm(e){this.options.randomAlgorithm=e,this.buildImpulse(),console.debug(`[Reverb.js] Set IR source noise generator.`)}static inRange(e,t,n){return e>=t&&e<=n}buildImpulse(){let e=this.ctx.sampleRate,t=Math.max(e*this.options.time,1),n=e*this.options.delay,r=this.ctx.createBuffer(2,t,e),i=new Float32Array(t),a=new Float32Array(t),o=new Float32Array(1),s=new Float32Array(1),c=this.getNoise(t),l=this.getNoise(t);for(let e=0;e<t;e++){let r;e<n?(o[0]=0,s[0]=0,i.set(o,e),a.set(s,e),r=this.options.reverse??!1?t-(e-n):e-n):r=this.options.reverse??!1?t-e:e,o[0]=(c.at(e)??0)*(1-r/t)**this.options.decay,s[0]=(l.at(e)??0)*(1-r/t)**this.options.decay,i.set(o,e),a.set(s,e)}r.getChannelData(0).set(i),r.getChannelData(1).set(a),this.convolverNode.buffer=r}},L=class e{static SEMITONE_RATIO=1.0594630943592953;static MIDI_CENTER_VALUE=64;static MIDI_MAX_VALUE=127;static CENTS_PER_OCTAVE=1200;static DEFAULT_Q_VALUE=10;static Q_DIVISOR=200;constructor(e,t,n){this.ctx=e,this.destination=t,this.instrument=n;let{channel:r,key:i,velocity:a,sample:o,basePlaybackRate:s,loopStart:c,loopEnd:l,sampleRate:u,volume:d,panpot:f,pitchBend:p,pitchBendSensitivity:m,modEnvToPitch:h,expression:g,modulation:_,cutOffFrequency:v,harmonicContent:y,reverb:b}=n;this.channel=r,this.key=i,this.velocity=a,this.buffer=o,this.playbackRate=s,this.loopStart=c,this.loopEnd=l,this.sampleRate=u,this.volume=d,this.panpot=f,this.pitchBend=p,this.pitchBendSensitivity=m,this.modEnvToPitch=h,this.expression=g,this.modulation=_,this.cutOffFrequency=v,this.harmonicContent=y,this.reverb=b,this.startTime=e.currentTime,this.computedPlaybackRate=this.playbackRate|0,this.noteOffState=!1,this.audioBuffer=null,this.bufferSource=e.createBufferSource(),this.panner=e.createPanner(),this.outputGainNode=e.createGain(),this.expressionGainNode=e.createGain(),this.filter=e.createBiquadFilter(),this.modulator=e.createBiquadFilter(),this.lfo=null,this.lfoDepth=null}ensureFinite(e,t=0){return Number.isFinite(e)?e:t}ensurePositiveFinite(e,t=.001){return Number.isFinite(e)&&e>0?e:t}calculateEnvelopeTiming(){let{instrument:e}=this,t=this.ctx.currentTime||0;return{now:t,volDelay:t+e.volDelay,modDelay:t+e.modDelay,volAttack:t+e.volDelay+e.volAttack,modAttack:t+e.modDelay+e.modAttack,volHold:t+e.volDelay+e.volAttack+e.volHold,modHold:t+e.modDelay+e.modAttack+e.modHold,volDecay:t+e.volDelay+e.volAttack+e.volHold+e.volDecay,modDecay:t+e.modDelay+e.modAttack+e.modHold+e.modDecay}}setupAudioBuffer(){let{instrument:e,sampleRate:t,buffer:n}=this,r=n.subarray(0,n.length+e.end),i=this.ctx.createBuffer(1,r.length,t);return i.getChannelData(0).set(r),i}noteOn(){let{instrument:t}=this,n=this.calculateEnvelopeTiming(),r=t.loopStart/this.sampleRate,i=t.loopEnd/this.sampleRate,a=t.start/this.sampleRate,o=t.pan===0?this.panpot:t.pan;this.audioBuffer=this.setupAudioBuffer();let{bufferSource:s}=this;s.buffer=this.audioBuffer,s.loop=t.sampleModes!==0,s.loopStart=r,s.loopEnd=i,this.updatePitchBend(this.pitchBend),this.expressionGainNode.gain.value=this.expression/e.MIDI_MAX_VALUE,this.setupPanner(o),this.setupVolumeEnvelope(n),this.setupModulationEnvelope(n),this.setupVibrato(),this.connectAudioNodes(),t.mute||this.connect(),this.expressionGainNode.connect(this.outputGainNode),s.start(0,a)}setupPanner(e=0){let{panner:t}=this;t.panningModel=`equalpower`,t.distanceModel=`inverse`,t.positionX.setValueAtTime(Math.sin(e*Math.PI/2),0),t.positionY.setValueAtTime(0,0),t.positionZ.setValueAtTime(Math.cos(e*Math.PI/2),0)}setupVolumeEnvelope(t){let{instrument:n,velocity:r,volume:i}=this,{now:a,volDelay:o,volHold:s,volDecay:c}=t,l=this.ensureFinite(Math.max(0,i*(r/e.MIDI_MAX_VALUE)*(1-n.initialAttenuation/1e3)),0),u=this.ensureFinite(l*(1-n.volSustain),0),d=this.outputGainNode.gain;d.setValueAtTime(0,this.ensureFinite(a,0)),d.setValueAtTime(0,this.ensureFinite(o,0)),d.setTargetAtTime(l,this.ensureFinite(o,0),this.ensureFinite(n.volAttack,.001)),d.setValueAtTime(l,this.ensureFinite(s,0)),d.linearRampToValueAtTime(u,this.ensureFinite(c,0))}setupModulationEnvelope(t){let{instrument:n}=this,{now:r,modDelay:i,modHold:a,modDecay:o}=t,{modulator:s}=this,c=this.ensurePositiveFinite(n.initialFilterFc,350),l=this.ensurePositiveFinite(c+n.modEnvToFilterFc,c),u=this.ensurePositiveFinite(c+(l-c)*(1-n.modSustain),c),d=this.ensurePositiveFinite(e.DEFAULT_Q_VALUE**(n.initialFilterQ/e.Q_DIVISOR),1);s.Q.setValueAtTime(d,this.ensureFinite(r,0)),s.frequency.value=c,s.type=`lowpass`,s.frequency.setTargetAtTime(this.ensurePositiveFinite(c/e.MIDI_MAX_VALUE,.001),this.ensureFinite(this.ctx.currentTime,0),.5),s.frequency.setValueAtTime(c,this.ensureFinite(r,0)),s.frequency.setValueAtTime(c,this.ensureFinite(i,0)),s.frequency.setTargetAtTime(l,this.ensureFinite(i,0),this.ensureFinite(n.modAttack,.001)),s.frequency.setValueAtTime(l,this.ensureFinite(a,0)),s.frequency.exponentialRampToValueAtTime(u,this.ensureFinite(o,0))}setupVibrato(){let{instrument:t,modulation:n}=this;if(!(!n||!t.freqVibLFO))try{this.lfo=this.ctx.createOscillator(),this.lfoDepth=this.ctx.createGain(),this.lfo.type=`sine`,this.lfo.frequency.value=t.freqVibLFO;let r=n/e.MIDI_MAX_VALUE*.01;this.lfoDepth.gain.value=r,this.lfo.connect(this.lfoDepth),this.lfoDepth.connect(this.bufferSource.playbackRate),this.lfo.start(0)}catch(e){if(console.warn(`[SynthesizerNote] Failed to setup vibrato:`,e),this.lfo){try{this.lfo.disconnect()}catch{}this.lfo=null}if(this.lfoDepth){try{this.lfoDepth.disconnect()}catch{}this.lfoDepth=null}}}connectAudioNodes(){let{bufferSource:e,modulator:t,panner:n,expressionGainNode:r,outputGainNode:i,instrument:a}=this;e.connect(t),t.connect(n),n.connect(r),a.mute||this.connect(),r.connect(i)}amountToFreq(t){return 2**((t-6900)/e.CENTS_PER_OCTAVE)*440}noteOff(){this.noteOffState=!0}isNoteOff(){return this.noteOffState}release(){let{instrument:t,outputGainNode:n,ctx:r,modulator:i}=this,a=this.ensureFinite(r.currentTime,0),o=t.releaseTime-e.MIDI_CENTER_VALUE;if(!this.audioBuffer)return;if(this.lfo)try{this.lfo.stop(a)}catch{}let s=this.ensureFinite(t.volRelease*n.gain.value,.1),c=this.ensureFinite(1+o/(o<0?e.MIDI_CENTER_VALUE:63),1),l=this.ensureFinite(a+s*c,a+.1),u=this.ensurePositiveFinite(t.initialFilterFc,350),d=this.ensurePositiveFinite(u+t.modEnvToFilterFc,u),f=u===d?1:(i.frequency.value-u)/(d-u),p=this.ensureFinite(a+t.modRelease*this.ensureFinite(f,1),a+.1);this.applySampleModeRelease(l,p,u)}applySampleModeRelease(e,t,n){let{instrument:r,bufferSource:i,outputGainNode:a,modulator:o,ctx:s}=this,c=this.ensureFinite(s.currentTime,0),l={NO_LOOP:0,CONTINUOUS_LOOP:1,UNUSED:2,LOOP_UNTIL_NOTE_OFF:3},u=this.ensureFinite(e,c+.1),d=this.ensureFinite(t,c+.1),f=this.ensurePositiveFinite(n,350);switch(r.sampleModes){case l.NO_LOOP:i.loop=!1;break;case l.CONTINUOUS_LOOP:case l.LOOP_UNTIL_NOTE_OFF:if(this.scheduleRelease(a,o,i,c,u,d,f),r.sampleModes===l.LOOP_UNTIL_NOTE_OFF)i.loop=!1,i.buffer=null;else try{i.stop(u)}catch(e){console.warn(`[SynthesizerNote] Failed to stop buffer source:`,e);try{i.stop()}catch{}}break;case l.UNUSED:throw Error(`[SynthesizerNote] Detected unused sampleModes`);default:throw Error(`[SynthesizerNote] ${r.sampleModes} is an undefined sampleMode`)}}scheduleRelease(e,t,n,r,i,a,o){e.gain.cancelScheduledValues(0),e.gain.setValueAtTime(this.ensureFinite(e.gain.value,0),this.ensureFinite(r,0)),e.gain.linearRampToValueAtTime(0,this.ensureFinite(i,0)),t.frequency.cancelScheduledValues(0),t.frequency.setValueAtTime(this.ensurePositiveFinite(t.frequency.value,350),this.ensureFinite(r,0)),t.frequency.exponentialRampToValueAtTime(this.ensurePositiveFinite(o,350),this.ensureFinite(a,0)),n.playbackRate.cancelScheduledValues(0),n.playbackRate.setValueAtTime(this.ensurePositiveFinite(n.playbackRate.value,1),this.ensureFinite(r,0)),n.playbackRate.exponentialRampToValueAtTime(this.ensurePositiveFinite(this.computedPlaybackRate,1),this.ensureFinite(a,0))}connect(){this.reverb.connect(this.outputGainNode).connect(this.destination)}disconnect(){if(this.outputGainNode.disconnect(0),this.lfo){try{this.lfo.disconnect()}catch{}this.lfo=null}if(this.lfoDepth){try{this.lfoDepth.disconnect()}catch{}this.lfoDepth=null}}schedulePlaybackRate(){let{bufferSource:t,computedPlaybackRate:n,startTime:r,instrument:i,modEnvToPitch:a}=this,o=t.playbackRate,s=this.ensureFinite(r+i.modAttack,this.ctx.currentTime),c=this.ensureFinite(s+i.modDecay,s),l=this.ensureFinite(n*e.SEMITONE_RATIO**(a*i.scaleTuning),n),u=this.ensureFinite(n+(l-n)*(1-i.modSustain),n);o.cancelScheduledValues(0),o.setValueAtTime(this.ensureFinite(n,1),this.ensureFinite(r,this.ctx.currentTime)),o.linearRampToValueAtTime(l,s),o.linearRampToValueAtTime(u,c)}updateExpression(t){this.expression=t,this.expressionGainNode.gain.value=t/e.MIDI_MAX_VALUE}updatePitchBend(t){let n=t<0?8192:8191;this.computedPlaybackRate=this.playbackRate*e.SEMITONE_RATIO**(t/n*this.pitchBendSensitivity*this.instrument.scaleTuning),this.schedulePlaybackRate()}},R=class e{static MIDI_CHANNELS=16;static MIDI_KEYS=128;static DEFAULT_CHANNEL_VALUE=64;static DEFAULT_EXPRESSION=127;static DEFAULT_VOLUME=100;static DEFAULT_PITCH_BEND_SENSITIVITY=2;static MASTER_VOLUME_DEFAULT=16384;static MASTER_VOLUME_MAX=16383;static MASTER_VOLUME_DIVISOR=16384;static BASE_VOLUME_DIVISOR=65535;static DRUM_CHANNEL=9;static BUFFER_SIZE=2048;static PITCH_BEND_CENTER=8192;static PERCUSSION_BANK_XG=127;static PERCUSSION_BANK_GS=128;static SFX_BANK=64;static SFX_BANK_XG=125;static MIDI_CLOSED_HI_HAT=42;static MIDI_PEDAL_HI_HAT=44;static MIDI_OPEN_HI_HAT=46;static MIDI_MUTE_TRIANGLE=80;static MIDI_OPEN_TRIANGLE=81;constructor(t){let n;for(this.input=t,this.parser=null,this.bank=0,this.bankSet=[],this.bufferSize=e.BUFFER_SIZE,this.ctx=this.getAudioContext(),this.gainMaster=this.ctx.createGain(),this.bufSrc=this.ctx.createBufferSource(),this.channelInstrument=Array(e.MIDI_CHANNELS).fill(0),this.channelBank=Array(e.MIDI_CHANNELS).fill(0),this.channelBank[e.DRUM_CHANNEL]=e.PERCUSSION_BANK_XG,this.channelVolume=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_VOLUME),this.channelPanpot=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.channelPitchBend=Array(e.MIDI_CHANNELS).fill(0),this.channelPitchBendSensitivity=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_PITCH_BEND_SENSITIVITY),this.channelExpression=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_EXPRESSION),this.channelAttack=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.channelDecay=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.channelSustin=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.channelRelease=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.channelHold=Array(e.MIDI_CHANNELS).fill(!1),this.channelHarmonicContent=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.channelCutOffFrequency=Array(e.MIDI_CHANNELS).fill(e.DEFAULT_CHANNEL_VALUE),this.mode=`GM2`,this.programSet=[],this.channelMute=Array(e.MIDI_CHANNELS).fill(!1),this.currentNoteOn=Array.from({length:e.MIDI_CHANNELS},()=>[]),this.baseVolume=1/e.BASE_VOLUME_DIVISOR,this.masterVolume=e.MASTER_VOLUME_DEFAULT,this.percussionPart=Array(e.MIDI_CHANNELS).fill(!1),this.percussionPart[e.DRUM_CHANNEL]=!0,this.percussionVolume=Array(e.MIDI_KEYS).fill(e.DEFAULT_EXPRESSION),this.programSet=[],this.reverb=[],this.modulation=Array(e.MIDI_CHANNELS).fill(0),this.filter=[],n=0;n<e.MIDI_CHANNELS;++n)this.reverb[n]=new I(this.ctx,{noise:`violet`}),this.filter[n]=this.ctx.createBiquadFilter();this.items=[],this.intersection=new IntersectionObserver(e=>e.forEach(e=>{e.target.dataset.isIntersecting=e.isIntersecting}),{}),this.timer=void 0,this.drag=!1}getAudioContext(){let e=new AudioContext,t=()=>{document.removeEventListener(`touchstart`,t);let n=e.createBufferSource();n.start(),n.stop()};return document.addEventListener(`touchstart`,t),e}init(t=`GM`){this.gainMaster.disconnect(),this.refreshInstruments(this.input),this.mode=t;for(let t=0;t<e.MIDI_CHANNELS;++t)this.setPercussionPart(t,t===9),this.programChange(t,0),this.volumeChange(t,100),this.panpotChange(t,64),this.pitchBend(t,0,64),this.pitchBendSensitivity(t,2),this.hold(t,0),this.expression(t,127),this.bankSelectMsb(t,t===9?127:0),this.bankSelectLsb(t,t===9?127:0),this.attackTime(t,64),this.decayTime(t,64),this.sustinTime(t,64),this.releaseTime(t,64),this.harmonicContent(t,64),this.cutOffFrequency(t,64),this.reverbDepth(t,40),this.modulationDepth(t,0),this.updateBankSelect(t),this.updateProgramSelect(t);this.setPercussionPart(e.DRUM_CHANNEL,!0);for(let t=0;t<e.MIDI_KEYS;++t)this.percussionVolume[t]=127;if(this.setMasterVolume(e.MASTER_VOLUME_DEFAULT/2),this.gainMaster.connect(this.ctx.destination),this.element){let e=this.element.querySelector(`.header .keys div`);e&&(e.innerText=t+` Mode`),this.element.querySelectorAll(`.instrument .bank > select`).forEach(e=>e.disabled=t===`GM`),this.element.dataset.mode=t}}async close(){await this.ctx.close()}refreshInstruments(e){this.parser&&=(this.parser.input=new Uint8Array,null),this.bankSet=[],this.input&&=new Uint8Array,this.input=e,this.parser=new r(e,{sampleRate:this.ctx.sampleRate}),this.bankSet=this.createAllInstruments()}createAllInstruments(){let e=this.parser;if(!e)throw Error(`parser is not initialized`);e.parse();let t=e.createPreset(),n=e.createInstrument(),r=[],i,a,o,s,c,l=[];return t.forEach(t=>{if(s=t.header.preset,a=t.header.bank,c=t.name.replace(/\0*$/,``),typeof t.instrument!=`number`||(o=n[t.instrument],o.name.replace(/\0*$/,``)===`EOI`))return;r[a]=r[a]??[],i=r[a]??[],r[a]=i,i[s]={name:c};let u=i[s];u&&(o.info.forEach(t=>this.createNoteInfo(e,t,u)),l[a]||(l[a]=[]),l[a][s]=c)}),this.programSet=l,r}static SEMITONE_RATIO=1.0594630943592953;static CENTS_PER_OCTAVE=1200;static COARSE_OFFSET_MULTIPLIER=32768;static BASE_FREQUENCY_HZ=8.176;static MIDDLE_C_NOTE=60;static ENVELOPE_SUSTAIN_DIVISOR=1e3;static FILTER_Q_DIVISOR=10;static ATTENUATION_DIVISOR=10;static REVERB_SEND_DIVISOR=10;static PAN_DIVISOR=1200;static MODULATION_DIVISOR=100;createNoteInfo(t,n,r){let i=n.generator;if(!i.keyRange||!i.sampleID)return;let a=this.getModGenAmount(i,`delayVolEnv`),o=this.getModGenAmount(i,`attackVolEnv`),s=this.getModGenAmount(i,`holdVolEnv`),c=this.getModGenAmount(i,`decayVolEnv`),l=this.getModGenAmount(i,`sustainVolEnv`),u=this.getModGenAmount(i,`releaseVolEnv`),d=this.getModGenAmount(i,`delayModEnv`),f=this.getModGenAmount(i,`attackModEnv`),p=this.getModGenAmount(i,`holdModEnv`),m=this.getModGenAmount(i,`decayModEnv`),h=this.getModGenAmount(i,`sustainModEnv`),g=this.getModGenAmount(i,`releaseModEnv`),_=this.getModGenAmount(i,`scaleTuning`)/100,v=this.getModGenAmount(i,`coarseTune`)+this.getModGenAmount(i,`fineTune`)/100,y=this.getModGenAmount(i,`sampleModes`);for(let n=i.keyRange.lo,b=i.keyRange.hi;n<=b;++n){if(r[n])continue;let b=this.getModGenAmount(i,`sampleID`),x=t.sampleHeader[b];r[n]={sample:t.sample[b],sampleRate:x.sampleRate,sampleModes:y,basePlaybackRate:e.SEMITONE_RATIO**((n-this.getModGenAmount(i,`overridingRootKey`)+v+x.pitchCorrection/e.MODULATION_DIVISOR)*_),modEnvToPitch:this.getModGenAmount(i,`modEnvToPitch`)/e.MODULATION_DIVISOR,scaleTuning:_,start:this.getModGenAmount(i,`startAddrsCoarseOffset`)*e.COARSE_OFFSET_MULTIPLIER+this.getModGenAmount(i,`startAddrsOffset`),end:this.getModGenAmount(i,`endAddrsCoarseOffset`)*e.COARSE_OFFSET_MULTIPLIER+this.getModGenAmount(i,`endAddrsOffset`),loopStart:x.startLoop+this.getModGenAmount(i,`startloopAddrsCoarseOffset`)*e.COARSE_OFFSET_MULTIPLIER+this.getModGenAmount(i,`startloopAddrsOffset`),loopEnd:x.endLoop+this.getModGenAmount(i,`endloopAddrsCoarseOffset`)*e.COARSE_OFFSET_MULTIPLIER+this.getModGenAmount(i,`endloopAddrsOffset`),volDelay:2**(a/e.CENTS_PER_OCTAVE),volAttack:2**(o/e.CENTS_PER_OCTAVE),volHold:2**(s/e.CENTS_PER_OCTAVE)*2**((e.MIDDLE_C_NOTE-n)*this.getModGenAmount(i,`keynumToVolEnvHold`)/e.CENTS_PER_OCTAVE),volDecay:2**(c/e.CENTS_PER_OCTAVE)*2**((e.MIDDLE_C_NOTE-n)*this.getModGenAmount(i,`keynumToVolEnvDecay`)/e.CENTS_PER_OCTAVE),volSustain:l/e.ENVELOPE_SUSTAIN_DIVISOR,volRelease:2**(u/e.CENTS_PER_OCTAVE),modDelay:2**(d/e.CENTS_PER_OCTAVE),modAttack:2**(f/e.CENTS_PER_OCTAVE),modHold:2**(p/e.CENTS_PER_OCTAVE)*2**((e.MIDDLE_C_NOTE-n)*this.getModGenAmount(i,`keynumToModEnvHold`)/e.CENTS_PER_OCTAVE),modDecay:2**(m/e.CENTS_PER_OCTAVE)*2**((e.MIDDLE_C_NOTE-n)*this.getModGenAmount(i,`keynumToModEnvDecay`)/e.CENTS_PER_OCTAVE),modSustain:h/e.ENVELOPE_SUSTAIN_DIVISOR,modRelease:2**(g/e.CENTS_PER_OCTAVE),initialFilterFc:e.BASE_FREQUENCY_HZ*2**(this.getModGenAmount(i,`initialFilterFc`)/e.CENTS_PER_OCTAVE),modEnvToFilterFc:this.getModGenAmount(i,`modEnvToFilterFc`)/e.MODULATION_DIVISOR,initialFilterQ:this.getModGenAmount(i,`initialFilterQ`)/e.FILTER_Q_DIVISOR,reverbEffectSend:this.getModGenAmount(i,`reverbEffectSend`)/e.REVERB_SEND_DIVISOR,initialAttenuation:this.getModGenAmount(i,`initialAttenuation`)/e.ATTENUATION_DIVISOR,freqVibLFO:e.BASE_FREQUENCY_HZ*2**(this.getModGenAmount(i,`freqVibLFO`)/e.CENTS_PER_OCTAVE),pan:this.getModGenAmount(i,`pan`)/e.PAN_DIVISOR}}}getModGenAmount(e,t){let n=e[t];return n&&typeof n.amount==`number`?n.amount:Number(r.getGeneratorTable()[t]??0)}start(){this.connect(),this.bufSrc.start(0),this.setMasterVolume(e.MASTER_VOLUME_MAX)}setMasterVolume(t){this.masterVolume=t,this.gainMaster.gain.value=this.baseVolume*(t/e.MASTER_VOLUME_DIVISOR)}connect(){this.bufSrc.connect(this.gainMaster)}disconnect(){this.bufSrc.disconnect(this.gainMaster),this.bufSrc.buffer=null}drawSynth(){let t=window.document,n=this.element=t.createElement(`div`);n.className=`synthesizer`;let r=t.createElement(`div`);r.className=`instrument`,this.items=[`mute`,`bank`,`program`,`volume`,`expression`,`panpot`,`pitchBend`,`pitchBendSensitivity`,`reverbDepth`,`keys`];let i=`ontouchstart`in window?`touchstart`:`mousedown`,a=`ontouchend`in window?`touchend`:`mouseup`;for(let n=0;n<e.MIDI_CHANNELS;n++){let e=t.createElement(`div`);e.className=`channel`,e.addEventListener(i,()=>{this.hold(n,0)});for(let r in this.items){if(!Object.hasOwn(this.items,r))continue;let o=t.createElement(`div`);switch(o.className=this.items[r],this.items[r]){case`mute`:{let e=t.createElement(`div`);e.className=`form-check form-check-inline`;let r=t.createElement(`input`);r.ariaLabel=`Ch.${n+1} Mute`,r.setAttribute(`type`,`checkbox`),r.className=`form-check-input`,r.id=`mute`+n+`ch`,r.value=n.toString(),r.addEventListener(`change`,e=>{this.mute(n,e.target.checked)},!1),e.appendChild(r);let i=t.createElement(`label`);i.className=`form-check-label`,i.textContent=(n+1).toString(),i.setAttribute(`for`,`mute`+n+`ch`),e.appendChild(i),o.appendChild(e);break}case`bank`:{let r=t.createElement(`select`);r.ariaLabel=`Ch.${n+1} Bank Select`,r.className=`form-select form-select-sm bank-select`,r.addEventListener(`change`,((t,n)=>r=>{let i=e.querySelector(`.program select`);i&&(t.bankChange(n,r.target.value),t.programChange(n,Number.parseInt(i.value)))})(this,n),!1),o.appendChild(r);break}case`program`:{let e=t.createElement(`select`);e.className=`form-select form-select-sm`,e.ariaLabel=`Ch.${n+1} Program Change`,e.addEventListener(`change`,((e,t)=>n=>{e.programChange(t,n.target.value)})(this,n),!1),o.appendChild(e);break}case`volume`:{let e=document.createElement(`var`);e.ariaLabel=`Ch.${n+1} Volume`,e.innerText=`100`,o.appendChild(e);break}case`expression`:{let e=document.createElement(`var`);e.ariaLabel=`Ch.${n+1} Expression`,e.innerText=`127`,o.appendChild(e);break}case`pitchBendSensitivity`:{let e=document.createElement(`var`);e.ariaLabel=`Ch.${n+1} Pitch Bend Sensitivity`,e.innerText=`2`,o.appendChild(e);break}case`reverbDepth`:{let e=document.createElement(`var`);e.ariaLabel=`Ch.${n+1} Reverb Depth`,e.innerText=`40`,o.appendChild(e);break}case`panpot`:{let e=t.createElement(`div`);e.role=`progressbar`,e.ariaLabel=`Ch.${n+1} Panpod`,e.ariaValueMin=`0`,e.ariaValueNow=`64`,e.ariaValueMax=`127`,e.className=`progress`;let r=t.createElement(`div`);r.className=`progress-bar`,e.appendChild(r),o.appendChild(e);break}case`pitchBend`:{let e=t.createElement(`div`);e.className=`progress`,e.role=`progressbar`,e.ariaLabel=`Ch.${n+1} Pitch Bend`,e.ariaValueMin=`-8192`,e.ariaValueNow=`0`,e.ariaValueMax=`8192`,e.className=`progress`;let r=t.createElement(`div`);r.className=`progress-bar progress-bar-animated`,e.appendChild(r),o.appendChild(e);break}case`keys`:for(let e=0;e<127;e++){let r=t.createElement(`div`),s=e%12;r.className=`key `+([1,3,6,8,10].includes(s)?`semitone`:`tone`),o.appendChild(r),r.addEventListener(i,((e,t,n)=>r=>{r.preventDefault(),e.drag=!0,e.noteOn(t,n,127)})(this,n,e)),r.addEventListener(`mouseover`,((e,t,n)=>r=>{r.preventDefault(),e.drag&&e.noteOn(t,n,127)})(this,n,e)),r.addEventListener(`mouseout`,((e,t,n)=>r=>{r.preventDefault(),e.noteOff(t,n)})(this,n,e)),r.addEventListener(a,((e,t,n)=>r=>{r.preventDefault(),e.drag=!1,e.noteOff(t,n)})(this,n,e))}break}e.appendChild(o)}r.appendChild(e),this.intersection.observe(e)}let o=[`Ch.`,`Bank`,`Program`,`Vol.`,`Exp.`,`Panpot`,`Pitch Bend`,``,`Rev.`,``],s=t.createElement(`div`);s.className=`header`;for(let e in this.items){if(!Object.hasOwn(this.items,e))continue;let n=t.createElement(`div`);n.className=this.items[e],n.textContent=o[e],this.items[e]===`keys`&&(n.appendChild(document.createElement(`code`)),n.appendChild(document.createElement(`div`))),s.appendChild(n)}return r.prepend(s),n.appendChild(r),new ResizeObserver(e=>{this.items.forEach(e=>{let t=n.querySelector(`.header .${e}`),r=n.querySelector(`.channel .${e}`);!t||!r||(t.style.width=r.offsetWidth+`px`)});let t=n.querySelector(`.header .keys`);t&&(t.style.display=document.documentElement.clientWidth<=680?`none`:`flex`)}).observe(n),n}updateSynthElement(e,t,n=100){if(!this.element)return;let r=this.element.querySelectorAll(`.instrument > .channel`);if(r[e].dataset.isIntersecting){let i=r[e].querySelector(`.key:nth-child(${t+1})`);if(!i)return;n?(i.classList.add(`note-on`),i.style.opacity=(n/127).toFixed(2)):(i.classList?.contains(`note-on`)&&i.classList.remove(`note-on`),i.style.opacity=`1`)}}getChannelElement(e){return this.element&&this.element.querySelectorAll(`.instrument > .channel`)[e]||null}getChannelChildElement(e,t){let n=this.getChannelElement(e);return n?n.querySelector(t):null}updateBankSelect(e){let t=this.getChannelChildElement(e,`.bank > select`);if(t){for(;t.firstChild;)t.removeChild(t.firstChild);for(let n in this.programSet){if(!Object.hasOwn(this.programSet,n))continue;let r=document.createElement(`option`);r.value=n,r.textContent=(`000`+Number.parseInt(n)).slice(-3),Number.parseInt(n)===this.channelBank[e]&&(r.selected=!0),t.appendChild(r)}}}updateProgramSelect(e){let t=this.getChannelElement(e);if(!t)return;let n=this.channelBank[e],r=t.querySelector(`.bank > select`),i=t.querySelector(`.program > select`);if(!(!r||!i)){for(r.value=this.channelBank[e].toString();i.firstChild;)i.removeChild(i.firstChild);for(let t in this.programSet[n]){if(!Object.hasOwn(this.programSet[n],t))continue;let r=document.createElement(`option`);r.value=t,r.textContent=`${(`000`+(Number.parseInt(t)+1)).slice(-3)}:${this.programSet[n][t]}`,Number.parseInt(t)===this.channelInstrument[e]&&(r.selected=!0),i.appendChild(r)}}}getInstrumentForChannel(t){let n=this.channelBank[t],r=this.bankSet[n]??this.bankSet[0]??[],i;return i=typeof r[this.channelInstrument[t]]==`object`?r[this.channelInstrument[t]]??null:this.percussionPart[t]?(this.bankSet[this.mode===`XG`?e.PERCUSSION_BANK_XG:e.PERCUSSION_BANK_GS]??[])[0]??null:(this.bankSet[0]??[])[this.channelInstrument[t]]??null,i??null}calculatePanpot(t){let n=this.channelPanpot[t]===0?Math.floor(Math.random()*e.DEFAULT_EXPRESSION):this.channelPanpot[t]-e.DEFAULT_CHANNEL_VALUE;return n/(n<0?e.DEFAULT_CHANNEL_VALUE:63)}handlePercussionExclusiveNotes(t,n,r,i){r<e.PERCUSSION_BANK_XG||((n===e.MIDI_CLOSED_HI_HAT||n===e.MIDI_PEDAL_HI_HAT)&&this.noteOff(t,e.MIDI_OPEN_HI_HAT),n===e.MIDI_MUTE_TRIANGLE&&this.noteOff(t,e.MIDI_OPEN_TRIANGLE),i.volume=(i.volume??1)*this.percussionVolume[n]/e.DEFAULT_EXPRESSION)}noteOn(t,n,r=100){let i=this.channelBank[t],a=this.getInstrumentForChannel(t);if(!a?.[n]){console.warn(`instrument not found: bank=%s instrument=%s channel=%s key=%s`,i,this.channelInstrument[t],t,n);return}let o=a[n],s=this.calculatePanpot(t);Object.assign(o,{channel:t,key:n,velocity:r,panpot:s,volume:this.channelVolume[t]/e.DEFAULT_EXPRESSION,pitchBend:this.channelPitchBend[t]-e.PITCH_BEND_CENTER,expression:this.channelExpression[t],pitchBendSensitivity:Math.round(Number.isFinite(this.channelPitchBendSensitivity[t])?this.channelPitchBendSensitivity[t]:e.DEFAULT_PITCH_BEND_SENSITIVITY),mute:this.channelMute[t],releaseTime:this.channelRelease[t],cutOffFrequency:this.channelCutOffFrequency[t],harmonicContent:this.channelHarmonicContent[t],reverb:this.reverb[t],modulation:this.modulation[t]}),this.handlePercussionExclusiveNotes(t,n,i,a);let c=o,l=new L(this.ctx,this.gainMaster,c);l.noteOn(),this.currentNoteOn[t].push(l),this.updateSynthElement(t,n,r)}noteOff(e,t){let n,r,i=this.currentNoteOn[e],a,o=this.channelHold[e];for(n=0,r=i.length;n<r;++n)a=i[n],a.key===t&&(a.noteOff(),o||(a.release(),i.splice(n,1),--n,--r));this.updateSynthElement(e,t,0)}hold(t,n){let r=this.currentNoteOn[t],i=this.channelHold[t]=n>e.DEFAULT_CHANNEL_VALUE,a,o,s;if(!i)for(o=0,s=r.length;o<s;++o)a=r[o],a.isNoteOff()&&(a.release(),r.splice(o,1),--o,--s);let c=this.getChannelElement(t);c&&(this.channelHold[t]?c.classList.add(`hold`):c.classList.contains(`hold`)&&c.classList.remove(`hold`))}bankSelectMsb(t,n){this.percussionPart[t]=n>=e.SFX_BANK_XG,t===e.DRUM_CHANNEL?this.channelBank[t]=this.mode===`XG`||this.mode===`GM`?e.PERCUSSION_BANK_XG:e.PERCUSSION_BANK_GS:this.channelBank[t]=0,this.mode!==`GM`&&(this.mode===`XG`?n===e.SFX_BANK?this.channelBank[t]=e.SFX_BANK_XG:n===e.PERCUSSION_BANK_XG-1||n===e.PERCUSSION_BANK_XG?this.channelBank[t]=n:n===128&&(this.channelBank[t]=127):(this.channelBank[t]=t===e.DRUM_CHANNEL?e.PERCUSSION_BANK_GS:n,this.percussionPart[t]=n===e.PERCUSSION_BANK_GS),this.updateBankSelect(t))}bankSelectLsb(e,t){this.mode===`XG`&&(this.percussionPart[e]||(this.channelBank[e]=t),this.updateBankSelect(e))}programChange(e,t){this.channelInstrument[e]=t,this.bankChange(e,this.channelBank[e]);let n=this.getChannelChildElement(e,`.program > select`);n&&(n.value=t.toString())}bankChange(t,n){let r=this.mode===`XG`||this.mode===`GM`?e.PERCUSSION_BANK_XG:e.PERCUSSION_BANK_GS;this.mode===`GM`&&(n=0),t===e.DRUM_CHANNEL&&(n=r),this.bankSet[n]?this.channelBank[t]=n:this.channelBank[t]=this.percussionPart[t]?r:0;let i=this.getChannelChildElement(t,`.bank > select`);i&&(i.value=n.toString()),this.updateProgramSelect(t)}volumeChange(e,t=100){let n=this.getChannelChildElement(e,`.volume var`);n&&(n.innerText=t.toString()),this.channelVolume[e]=t}expression(e,t=127){let n,r,i=this.currentNoteOn[e];for(n=0,r=i.length;n<r;++n)i[n].updateExpression(t);let a=this.getChannelChildElement(e,`.expression var`);a&&(a.innerText=t.toString()),this.channelExpression[e]=t}panpotChange(t,n=64){this.channelPanpot[t]=n;let r=this.getChannelChildElement(t,`.panpot`);if(r){r.ariaValueNow=n.toString();let t=r.querySelector(`.progress-bar`);if(!t)return;let i=n/e.DEFAULT_EXPRESSION*100;if(t.style.width=`${i}%`,t.classList.remove(`left`,`right`),t.title=n.toString(),n===e.DEFAULT_CHANNEL_VALUE)return;r.classList.add(n<63?`left`:`right`)}}pitchBend(t,n,r){let i=n&127|(r&127)<<7,a,o,s=this.currentNoteOn[t],c=i-e.PITCH_BEND_CENTER;for(a=0,o=s.length;a<o;++a)s[a].updatePitchBend(c);if(this.channelPitchBend[t]=i,this.element){let e=this.element.querySelectorAll(`.instrument > .channel`)[t].querySelector(`.pitchBend`);if(!e)return;e.ariaValueNow=i.toString();let n=e.querySelector(`.progress-bar`);if(!n||(n.style.width=`${Math.floor(i/16384*100)}%`,n.title=c.toString(),n.classList.remove(`high`,`low`),c===0))return;n.classList.add(c<0?`low`:`high`)}}pitchBendSensitivity(t,n=2){let r=Number.isFinite(n)?n:e.DEFAULT_PITCH_BEND_SENSITIVITY;if(this.element){let e=this.element.querySelectorAll(`.instrument > .channel`)[t].querySelector(`.pitchBendSensitivity > var`);if(!e)return;e.innerText=r.toString()}this.channelPitchBendSensitivity[t]=r}attackTime(e,t){this.channelAttack[e]=t}decayTime(e,t){this.channelDecay[e]=t}sustinTime(e,t){this.channelSustin[e]=t}releaseTime(e,t){this.channelRelease[e]=t}harmonicContent(e,t=64){this.channelHarmonicContent[e]=t}cutOffFrequency(e,t=64){this.channelCutOffFrequency[e]=t}reverbDepth(e,t=40){if(this.reverb[e].mix(t/127),this.element){let n=this.element.querySelectorAll(`.instrument > .channel`)[e].querySelector(`.reverbDepth var`);if(!n)return;n.innerText=t.toString()}}modulationDepth(e,t=0){if(this.element){let n=this.element.querySelectorAll(`.instrument > .channel`)[e].querySelector(`.pitchBend .progress-bar`);if(!n)return;t===0?n.classList.remove(`progress-bar-striped`):n.classList.add(`progress-bar-striped`)}this.modulation[e]=t}getPitchBendSensitivity(e){return this.channelPitchBendSensitivity[e]}drumInstrumentLevel(e,t){this.percussionVolume[e]=t}allNoteOff(e){let t=this.currentNoteOn[e];for(this.hold(e,0);t.length>0;)this.noteOff(e,t[0].key)}allSoundOff(e){let t=this.currentNoteOn[e],n;for(;t.length>0;)n=t.shift(),n&&(this.noteOff(e,n.key),n.release(),n.disconnect());this.hold(e,0)}resetAllControl(e){this.allNoteOff(e),this.expression(e,127),this.pitchB