@logue/sf2synth
Version:
SoundFont2 Synthesizer
1,195 lines (1,194 loc) • 71.5 kB
JavaScript
/**
* @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