modern-audio
Version:
A modern web audio lib
412 lines (411 loc) • 11.3 kB
JavaScript
const I = ({ context: e }) => {
const o = e.createAnalyser();
return o.fftSize = 2048, {
name: "analyser",
node: o
};
}, H = ({ context: e }) => ({
name: "destination",
node: e.destination
}), O = ({ source: e }) => ({
name: "loop",
props: {
loop: {
value: !1,
getter() {
return this.value;
},
setter(o) {
this.value = o = Boolean(o), e instanceof MediaElementAudioSourceNode ? e.mediaElement.loop = o : e instanceof AudioBufferSourceNode && (e.loop = o);
}
}
}
}), V = (e) => {
const { context: o } = e;
let t = !1;
const n = o.createBiquadFilter();
n.Q.value = 8.3, n.frequency.value = 355, n.gain.value = 3, n.type = "bandpass";
const r = o.createDynamicsCompressor();
return r.attack.value = 0, r.knee.value = 40, r.ratio.value = 12, r.release.value = 0.25, r.threshold.value = -50, {
name: "noise-reduction",
node: () => t ? n : void 0,
connect: (s) => n.connect(r).connect(s),
disconnect: () => {
n.disconnect(), r.disconnect();
},
props: {
noiseReduction: {
value: t,
getter() {
return this.value;
},
setter(s) {
s === "" && (s = !0), t !== s && (this.value = t = s, e.reconnect());
}
}
}
};
}, F = ({ context: e }) => {
const o = e.createStereoPanner();
return {
name: "panner",
node: o,
props: {
pan: {
value: o.pan.value,
getter() {
return this.value;
},
setter(t) {
this.value = o.pan.value = Number(t);
}
}
}
};
}, W = ({ source: e }) => ({
name: "playbackRate",
props: {
playbackRate: {
value: 1,
getter() {
return this.value;
},
setter(o) {
this.value = o = Number(o), e instanceof MediaElementAudioSourceNode ? e.mediaElement.playbackRate = o : e instanceof AudioBufferSourceNode && (e.playbackRate.value = o);
}
}
}
}), q = ({ source: e }) => ({
name: "source",
node: e
});
function j(e) {
return e instanceof MediaElementAudioSourceNode ? e.mediaElement.playbackRate : e instanceof AudioBufferSourceNode ? e.playbackRate.value : 0;
}
function z(e) {
var o, t;
return e instanceof MediaElementAudioSourceNode ? e.mediaElement.duration : e instanceof AudioBufferSourceNode && (t = (o = e.buffer) == null ? void 0 : o.duration) != null ? t : 0;
}
const $ = ({ context: e, source: o }) => {
const t = e.createGain();
return {
name: "volume",
node: t,
props: {
db: {
value: 1,
getter() {
return this.value;
},
setter(n) {
this.value = n = Number(n), t.gain.value = Math.pow(10, n / 20);
}
},
fadeIn: {
value: 0,
getter() {
return this.value;
},
setter(n) {
this.value = n;
const { at: r, duration: s } = typeof n == "object" ? n : {
at: e.currentTime,
duration: Number(n)
};
t.gain.setValueAtTime(0.01, r), t.gain.exponentialRampToValueAtTime(t.gain.value, r + s);
}
},
fadeOut: {
value: 0,
getter() {
return this.value;
},
setter(n) {
this.value = n;
const { at: r, duration: s } = typeof n == "object" ? n : {
at: e.currentTime + z(o) / j(o) - Number(n),
duration: Number(n)
};
t.gain.setValueAtTime(t.gain.value, r), t.gain.exponentialRampToValueAtTime(0.01, r + s);
}
}
}
};
};
function G(e) {
return e = e || [], [
q,
W,
O,
V,
$,
F,
...e,
I,
H
];
}
function se(e) {
return e;
}
function Q(e, o) {
return G(o).map((t) => t(e));
}
function k({ node: e }) {
return typeof e == "function" ? e() : e;
}
function N(e) {
e.slice(1).reduce(
(o, t) => {
var r;
const n = k(t);
return n ? (o.connect ? o.connect(n) : (r = k(o)) == null || r.connect(n), t) : o;
},
e[0]
);
}
function P(e) {
e.forEach((o) => {
var t;
o.disconnect ? o.disconnect() : (t = k(o)) == null || t.disconnect();
});
}
function J(e) {
const o = /* @__PURE__ */ new Map();
for (const t of e)
if (!!t.props)
for (const n in t.props)
o.set(n, t.props[n]);
return o;
}
function v(e) {
return e.replace(/-(\w)/g, (o, t) => t.toLocaleUpperCase());
}
function C(e, o) {
const t = o in e ? e[o] : void 0;
return t instanceof Function ? t.bind(e) : t;
}
function T(e, o) {
return fetch(e).then((t) => t.arrayBuffer()).then((t) => o.decodeAudioData(t));
}
function K(e, o, t = 0, n) {
t = t || 0, n = n || o - 1;
const r = e.length / o, s = ~~(r / 10) || 1, i = e.numberOfChannels, c = [], a = [];
for (let u = 0; u < i; u++) {
c[u] = c[u] || [];
const l = c[u], d = e.getChannelData(u);
let f;
for (f = t; f <= n; f++) {
const g = ~~(f * r), y = ~~(g + r);
let h = d[g], p = h;
for (let A = g; A < y; A += s) {
const b = d[A];
b > p && (p = b), b < h && (h = b);
}
l[2 * f] = p, l[2 * f + 1] = h, (u === 0 || p > a[2 * f]) && (a[2 * f] = p), (u === 0 || h < a[2 * f + 1]) && (a[2 * f + 1] = h);
}
}
return {
splitPeaks: c,
mergedPeaks: a
};
}
async function X(e, o = "#364356", t, n) {
const { width: r, height: s } = e, i = e.getContext("2d"), c = typeof t == "string" ? await T(t, n) : t.buffer, { mergedPeaks: a } = K(c, r), u = window.devicePixelRatio || 1, l = a.some((m) => m < 0) ? 2 : 1, f = a.length / l / r, g = s / 2, y = 0.5 / u, h = 2, p = 1, b = 1 / 1, B = h * u, D = Math.max(u, ~~(B / 2)), R = B + D;
i.clearRect(0, 0, r, s);
for (let m = 0; m < a.length; m += R) {
let x = 0, S = Math.floor(m * f) * l;
const L = Math.floor((m + R) * f) * l;
do {
const E = Math.abs(a[S]);
E > x && (x = E), S += l;
} while (S < L);
const M = Math.max(Math.round(x / b * g), p);
i.fillStyle = o, i.fillRect(m + y, g - M, B + y, M * 2);
}
}
function Y(e, o = "#364356", t) {
const { width: n, height: r } = e, s = e.getContext("2d"), i = t.frequencyBinCount, c = new Uint8Array(i);
s.clearRect(0, 0, n, r), function a() {
requestAnimationFrame(a), t.getByteTimeDomainData(c);
const u = n / i * 1.5;
s.clearRect(0, 0, n, r);
for (let l = 0, d = 0; l < i; l++) {
const f = c[l];
s.fillStyle = o, s.fillRect(d, r - f, u, f), d += u + 1;
}
}();
}
function Z(e, o) {
const t = o != null ? o : new AudioContext();
if (typeof e == "string") {
const n = t.createBufferSource();
return {
context: t,
source: n,
src: e,
load: async () => {
n.buffer = await T(e, t);
},
clone: () => {
const r = n.buffer, s = t.createBufferSource();
return s.buffer = r, s;
}
};
} else if (e instanceof AudioBuffer) {
const n = t.createBufferSource();
return n.buffer = e, {
context: t,
source: n,
clone: () => {
const r = n.buffer, s = t.createBufferSource();
return s.buffer = r, s;
}
};
} else
return t instanceof AudioContext && e instanceof HTMLMediaElement ? {
context: t,
source: t.createMediaElementSource(e),
src: e.src
} : {
context: t,
source: e
};
}
function _(e, o) {
const { load: t, clone: n, ...r } = Z(e, o), s = {
...r,
processors: [],
props: /* @__PURE__ */ new Map(),
setup() {
P(this.processors), this.processors = Q(this), this.props = J(this.processors), N(this.processors);
},
reconnect() {
P(this.processors), N(this.processors);
},
get(i) {
var c, a;
if (i)
return (a = (c = this.props.get(v(i))) == null ? void 0 : c.getter) == null ? void 0 : a.call(c);
{
const u = {};
return this.props.forEach((l, d) => {
u[d] = this.get(d);
}), u;
}
},
set(i, c) {
var a, u;
if (typeof i == "string")
(u = (a = this.props.get(v(i))) == null ? void 0 : a.setter) == null || u.call(a, c);
else
for (const l in i)
this.set(l, i[l]);
},
renderBarChart(i, c) {
return X(
i,
c,
this.source instanceof MediaElementAudioSourceNode ? this.source.mediaElement.src : this.source,
this.context
);
},
renderTimeDomainBarChart(i, c) {
return Y(
i,
c,
this.processors.find((a) => a.name === "analyser").node
);
}
};
return t && (s.load = async function() {
await t(), this.setup();
}), n && (s.reset = function() {
this.source = n();
const i = this.get();
this.setup(), this.set(i);
}, s.resetAndStart = function(i, c, a) {
var u;
(u = this.reset) == null || u.call(this), this.source.start(i, c, a);
}), s.source instanceof AudioBufferSourceNode || s.setup(), new Proxy(s, {
get(i, c) {
var a;
return (a = C(i, c)) != null ? a : C(i.source, c);
},
set(i, c, a) {
return c in i ? i[c] = a : i.source[c] = a, !0;
}
});
}
class U extends HTMLAudioElement {
constructor() {
super(), this.source = _(this), this.setup();
}
static install() {
customElements.define("modern-audio", U, { extends: "audio" });
}
async setup() {
this.setupListeners();
for (let o = 0; o < this.attributes.length; o++) {
const t = this.attributes.item(o);
t && this.source.set(t.name, t.value);
}
}
setupListeners() {
this.addEventListener("play", () => this.source.context.resume());
}
}
function ee(e) {
return new Blob([
ne(
te(e),
e.sampleRate
)
], { type: "audio/wav" });
}
function te(e) {
const o = e.getChannelData(0), t = o.length * 2, n = new Float32Array(t);
let r = 0, s = 0;
for (; r < t; )
n[r++] = o[s], n[r++] = o[s], s++;
return n;
}
function ne(e, o) {
const t = new ArrayBuffer(44 + e.length * 2), n = new DataView(t);
return w(n, 0, "RIFF"), n.setUint32(4, 32 + e.length * 2, !0), w(n, 8, "WAVE"), w(n, 12, "fmt "), n.setUint32(16, 16, !0), n.setUint16(20, 1, !0), n.setUint16(22, 2, !0), n.setUint32(24, o, !0), n.setUint32(28, o * 4, !0), n.setUint16(32, 4, !0), n.setUint16(34, 16, !0), w(n, 36, "data"), n.setUint32(40, e.length * 2, !0), oe(n, e, 44);
}
function oe(e, o, t) {
for (let n = 0; n < o.length; n++, t += 2) {
const r = Math.max(-1, Math.min(1, o[n]));
e.setInt16(t, r < 0 ? r * 32768 : r * 32767, !0);
}
return e;
}
function w(e, o, t) {
for (let n = 0; n < t.length; n++)
e.setUint8(o + n, t.charCodeAt(n));
}
function ie(e, o = 2, t = 44100) {
return new OfflineAudioContext({
length: t * e,
numberOfChannels: o,
sampleRate: t
});
}
async function re(e) {
return ee(
await e.startRendering()
);
}
async function ae(e, o = "audio") {
const t = await re(e), n = document.createElement("a");
n.style.display = "none", n.href = URL.createObjectURL(t), n.download = `${o}.${t.type.split("/")[1]}`, n.click();
}
export {
U as ModernAudio,
_ as createAudio,
ie as createOfflineAudioContext,
se as defineProcessor,
ae as downloadOfflineAudio,
re as exportOfflineAudio
};