UNPKG

@logue/sf2synth

Version:
1,195 lines (1,194 loc) 71.5 kB
/** * @logue/sf2synth * * @description SoundFont2 Synthesizer * @author iyama, Logue * @license MIT * @version 0.9.0 * @see {@link https://github.com/logue/sf2synth.js} */ //#region src/meta.js 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(/* @__PURE__ */ 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.preve