UNPKG

sfumato

Version:

This library is able to play soundfonts (sf2 files) with the Web Audio API.

230 lines (229 loc) 7.35 kB
import { DEFAULT_GENERATOR_VALUES as w, SoundFont2 as q } from "soundfont2"; const _ = (e) => { var r; if (typeof e != "string") return []; const [t, n = "", o] = ((r = e.match(/^([a-gA-G])([#bs]*)([0-9])?$/)) == null ? void 0 : r.slice(1)) || []; return t ? [t, n, o ? Number(o) : void 0] : []; }, $ = { "#": 1, b: -1, s: 1 }, ne = (e) => { if (typeof e == "number") return e; const [t, n, o] = _(e); if (!t) throw new Error('not a note: "' + e + '"'); const r = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[t.toLowerCase()], s = (n == null ? void 0 : n.split("").reduce((a, c) => a + $[c], 0)) || 0; return (Number(o) + 1) * 12 + r + s; }, m = (e) => Math.pow(2, e / 1200), oe = (e) => Math.round(Math.log2(e) * 1200), Q = (e) => e / 1e3, G = (e, t) => { const n = Math.pow(10, t); return Math.round(e * n) / n; }; typeof AudioParam < "u" && (AudioParam.prototype.dahdsr = function(e, t, n, o, r, s, a, c, i) { r = Math.max(G(r, 4), 1e-3), a = Math.max(G(a, 4), 1e-3), i = G(i, 4), t = Math.max(t, 1e-3); let l = e; return this.setValueAtTime(t, l), this.setValueAtTime(t, l += o), this.exponentialRampToValueAtTime(n, l += r), this.setValueAtTime(n, l += s), this.exponentialRampToValueAtTime(Math.max(c * n, 1e-3), l += a), (d, u) => { this.cancelAndHoldAtTime(d); const f = Math.max(u != null ? u : t, 1e-3); this.exponentialRampToValueAtTime(f, d + i); }; }); const T = { 0: "startAddrOffset", 1: "endAddrOffset", 2: "startloopAddrsOffset", 3: "endloopAddrsOffset", 4: "startAddrsCoarseOffset", 5: "modLfoToPitch", 6: "vibLfoToPitch", 7: "modEnvToPitch", 8: "initialFilterFc", 9: "initialFilterQ", 10: "modLfoToFilterFc", 11: "modEnvToFilterFc", 12: "endAddrsCoarseOffset", 13: "modLfoToVolume", 14: "unused1", 15: "chorusEffectsSend", 16: "reverbEffectsSend", 17: "pan", 18: "unused2", 19: "unused3", 20: "unused4", 21: "delayModLFO", 22: "freqModLFO", 23: "delayVibLFO", 24: "freqVibLFO", 25: "delayModEnv", 26: "attackModEnv", 27: "holdModEnv", 28: "decayModEnv", 29: "sustainModEnv", 30: "releaseModEnv", 31: "keyNumToModEnvHold", 32: "keyNumToModEnvDecay", 33: "delayVolEnv", 34: "attackVolEnv", 35: "holdVolEnv", 36: "decayVolEnv", 37: "sustainVolEnv", 38: "releaseVolEnv", 39: "keyNumToVolEnvHold", 40: "keyNumToVolEnvDecay", 41: "instrument", 42: "reserved1", 43: "keyRange", 44: "velRange", 45: "startloopAddrsCoarseOffset", 46: "keyNum", 47: "velocity", 48: "initialAttenuation", 49: "reserved2", 50: "endloopAddrsCoarseOffset", 51: "coarseTune", 52: "fineTune", 53: "sampleID", 54: "sampleModes", 55: "reserved3", 56: "scaleTuning", 57: "exclusiveClass", 58: "overridingRootKey", 59: "unused5", 60: "endOper" }, re = Object.fromEntries( Object.entries(w).map(([e, t]) => [T[e], t]) ), D = (e, t, n, o, r) => { var h, g, y, E, b, A, O; const s = w[e]; if (typeof s != "number") throw new Error(`no default value found for generator with index ${e}`); const a = t.generators[e], c = (g = (h = n.globalZone) == null ? void 0 : h.generators) == null ? void 0 : g[e], i = (y = o == null ? void 0 : o.generators) == null ? void 0 : y[e], l = (b = (E = r.globalZone) == null ? void 0 : E.generators) == null ? void 0 : b[e], d = a && "value" in a ? a.value : void 0, u = c && "value" in c ? c.value : void 0, f = i && "value" in i ? i.value : void 0, v = l && "value" in l ? l.value : void 0, p = (A = d != null ? d : u) != null ? A : s, M = (O = f != null ? f : v) != null ? O : 0; return p + M; }, J = (e) => w[e] !== void 0, W = (e, t, n) => { var o, r, s, a; return Object.fromEntries( Array.from( new Set( [ Object.keys((r = (o = n.globalZone) == null ? void 0 : o.generators) != null ? r : {}), Object.keys(t.generators), Object.keys((a = (s = t.instrument.globalZone) == null ? void 0 : s.generators) != null ? a : {}), Object.keys(e.generators) ].flat() ) ).filter(J).map((c) => [T[c], D(parseInt(c), e, t.instrument, t, n)]) ); }; async function ae(e) { const t = await fetch(e).then((o) => o.arrayBuffer()), n = new Uint8Array(t); return new q(n); } function X(e, t, n) { let { time: o = e.currentTime } = n; const { midi: r, start: s, velocity: a = 0.3, startLoop: c, endLoop: i, sampleRate: l, originalPitch: d, pitchCorrection: u, type: f, sampleModes: v = 0, overridingRootKey: p, fineTune: M = 0, startloopAddrsOffset: h = 0, startloopAddrsCoarseOffset: g = 0, endloopAddrsOffset: y = 0, endloopAddrsCoarseOffset: E = 0, delayVolEnv: b = -12e3, attackVolEnv: A = -12e3, holdVolEnv: O = -12e3, decayVolEnv: N = -12e3, sustainVolEnv: F = 0, releaseVolEnv: L = -12e3, pan: P = 0, ...Z } = n, B = 100 * (p !== void 0 && p !== -1 ? p : d) + u - M, I = r * 100 - B, K = 1 * Math.pow(2, I / 1200); t.playbackRate.value = K; const j = c + h + g * 32768, S = i + y + E * 32768; S > j && v === 1 ? (t.loopStart = j / l, t.loopEnd = S / l, t.loop = !0) : v === 3 && console.warn("unimplemented sampleMode 3 (play till end on note off)"), Object.keys(Z).filter( (V) => !["name", "instrument", "keyRange", "sampleID", "end"].includes(V) ).length; const k = e.createGain(), H = [ o, 0, a, m(b), m(A), m(O), m(N), F >= 960 ? 0 : 1 - Q(F), m(L) ], U = k.gain.dahdsr(...H), R = e.createStereoPanner(); return R.pan.value = P / 1e3, k.connect(R), t.connect(k), R.connect(e.destination), t.start(o), (V = e.currentTime) => { t.stop(V + m(L)), U(V); }; } function Y(e, t, n = {}) { const { header: o, data: r } = t, s = new Float32Array(r.length); for (let l = 0; l < r.length; l++) s[l] = r[l] / 32768; const a = e.createBuffer(1, s.length, o.sampleRate); a.getChannelData(0).set(s); const i = e.createBufferSource(); return i.buffer = a, n = { ...o, ...n }, X(e, i, n); } function se(e) { return Object.fromEntries( Object.entries(e).map(([t, n]) => { const o = T[t]; return ["keyRange", "velRange"].includes(o) ? [o, n.range] : [o, n.value]; }) ); } const C = (e, t) => !e.keyRange || e.keyRange.lo <= t && t <= e.keyRange.hi, le = (e, t, n, o) => { Object.fromEntries( Object.entries(T).map(([r, s]) => [ s, D(parseInt(r), n, t, o, e) ]) ); }, x = (e, t) => e.zones.filter((o) => C(o, t) && o.instrument).map((o) => o.instrument.zones.filter((r) => C(r, t)).map((r) => { const s = W(r, o, e); return { ...r, mergedGenerators: s }; })).flat(), ce = (e, t, n, o = e.currentTime) => { const s = x(t, n).map( (a) => Y(e, a.sample, { ...a.mergedGenerators, midi: n, time: o }) ); return (a = e.currentTime) => { s.forEach((c) => c(a)); }; }; export { X as applyOptions, re as defaultGeneratorValues, T as generators, x as getActiveZones, Y as getBufferSourceFromSample, D as getGeneratorValue, W as getGeneratorValues, J as hasDefaultValue, C as isActiveZone, ae as loadSoundfont, le as mergeGenerators, Q as normalizePermille, G as precision, se as readableGenerators, oe as s2tc, ce as startPresetNote, m as tc2s, ne as toMidi, _ as tokenizeNote };