superdough
Version:
simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.
610 lines • 139 kB
JavaScript
import { map as rn } from "nanostores";
if (typeof DelayNode < "u") {
class e extends DelayNode {
constructor(n, o, a, c) {
return super(n), o = Math.abs(o), this.delayTime.value = a, this.feedbackGain = n.createGain(), this.feedbackGain.gain.value = Math.min(Math.abs(c), 0.995), this.feedback = this.feedbackGain.gain, this.delayGain = n.createGain(), this.delayGain.gain.value = o, this.connect(this.feedbackGain), this.connect(this.delayGain), this.feedbackGain.connect(this), this.connect = (s) => this.delayGain.connect(s), this;
}
start(n) {
this.delayGain.gain.setValueAtTime(this.delayGain.gain.value, n + this.delayTime.value);
}
}
BaseAudioContext.prototype.createFeedbackDelay = function(t, n, o) {
return new e(this, t, n, o);
};
}
let ze;
const pn = () => (ze = new AudioContext(), ze), Uo = (e) => (ze = e, ze), z = () => ze || pn();
function jo() {
return z().currentTime;
}
let wt = (e) => console.log(e);
function ct(e, t = "superdough") {
process.env.NODE_ENV === "development" && console.error(e), j(`[${t}] error: ${e.message}`);
}
const j = (...e) => wt(...e), Bo = (e) => {
wt = e;
};
let $e = {};
function Ft(e, t) {
const n = z();
if ($e[e])
return $e[e];
const o = 2 * n.sampleRate, a = n.createBuffer(1, o, n.sampleRate), c = a.getChannelData(0);
let s = 0, d, l, i, p, r, h, u;
d = l = i = p = r = h = u = 0;
for (let m = 0; m < o; m++)
if (e === "white")
c[m] = Math.random() * 2 - 1;
else if (e === "brown") {
let G = Math.random() * 2 - 1;
c[m] = (s + 0.02 * G) / 1.02, s = c[m];
} else if (e === "pink") {
let G = Math.random() * 2 - 1;
d = 0.99886 * d + G * 0.0555179, l = 0.99332 * l + G * 0.0750759, i = 0.969 * i + G * 0.153852, p = 0.8665 * p + G * 0.3104856, r = 0.55 * r + G * 0.5329522, h = -0.7616 * h - G * 0.016898, c[m] = d + l + i + p + r + h + u + G * 0.5362, c[m] *= 0.11, u = G * 0.115926;
} else if (e === "crackle") {
const G = t * 0.01;
Math.random() < G ? c[m] = Math.random() * 2 - 1 : c[m] = 0;
}
return e !== "crackle" && ($e[e] = a), a;
}
function at(e = "white", t, n = 0.02) {
const a = z().createBufferSource();
return a.buffer = Ft(e, n), a.loop = !0, a.start(t), {
node: a,
stop: (c) => a.stop(c)
};
}
function un(e, t, n) {
const o = at("pink", n), a = zn(e, o.node, t);
return ue(o.node, () => {
Y(o.node);
}), {
node: a.node,
stop: (c) => o?.stop(c),
teardown: a.teardown
};
}
const st = /* @__PURE__ */ new Map(), ut = Symbol("nodePoolKey"), hn = (e) => !!e[ut], Ct = (e) => e.context?.currentTime ?? 0, mn = (e) => {
const t = /* @__PURE__ */ new Set();
e.parameters?.forEach((a) => t.add(a));
const n = /* @__PURE__ */ new Set();
let o = e;
for (; o !== Object.prototype; ) {
for (const a of Object.getOwnPropertyNames(o)) {
if (n.has(a)) continue;
n.add(a);
const c = e[a];
c instanceof AudioParam && t.add(c);
}
o = Object.getPrototypeOf(o);
}
return t;
}, ht = (e) => {
if (e.disconnect(), e instanceof AudioScheduledSourceNode)
return;
const t = e[ut];
if (t == null) return;
const n = Ct(e);
mn(e).forEach((a) => a.cancelScheduledValues(n));
const o = st.get(t) ?? [];
o.push(new WeakRef(e)), st.set(t, o);
}, Gn = (e) => {
if (!(e instanceof AudioWorkletNode)) return !0;
const t = Ct(e), n = e?.parameters?.get("end").value ?? 0;
return t < n + 0.45;
}, we = (e, t) => {
const n = st.get(e) ?? [];
let o, a = !1;
for (; n.length; )
if (o = n.pop()?.deref(), o != null && Gn(o)) {
a = !0;
break;
}
return a || (o = t()), o[ut] = e, o;
}, Xn = (e) => {
if (typeof e != "string")
return [];
const [t, n = "", o] = e.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || [];
return t ? [t, n, o ? Number(o) : void 0] : [];
}, yn = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }, bn = { "#": 1, b: -1, s: 1, f: -1 }, Zn = (e) => e?.split("").reduce((t, n) => t + bn[n], 0) || 0, Be = (e, t = 3) => {
const [n, o, a = t] = Xn(e);
if (!n)
throw new Error('not a note: "' + e + '"');
const c = yn[n.toLowerCase()], s = Zn(o);
return (Number(a) + 1) * 12 + c + s;
}, kt = (e) => Math.pow(2, (e - 69) / 12) * 440, se = (e, t, n) => Math.min(Math.max(e, t), n), Wn = (e) => 12 * Math.log(e / 440) / Math.LN2 + 69, fn = (e, t) => {
if (typeof e != "object")
throw new Error("valueToMidi: expected object value");
let { freq: n, note: o } = e;
return typeof n == "number" ? Wn(n) : typeof o == "string" ? Be(o) : typeof o == "number" ? o : t;
};
function re(e, t = 0, n) {
return isNaN(Number(e)) ? (!n && j(`"${e}" is not a number, falling back to ${t}`, "warning"), t) : e;
}
const Pt = (e, t) => (e % t + t) % t, Vt = (e, t) => Pt(Math.round(re(e, 0)), t);
function Lt(e, t) {
return e / t;
}
function It(e, t) {
const { s: n, n: o = 0 } = e;
let a = fn(e, 36), c = a - 36, s, d = 0;
if (Array.isArray(t))
d = Vt(o, t.length), s = t[d];
else {
const i = (r) => Be(r) - a, p = Object.keys(t).filter((r) => !r.startsWith("_")).reduce(
(r, h, u) => !r || Math.abs(i(h)) < Math.abs(i(r)) ? h : r,
null
);
c = -i(p), d = Vt(o, t[p].length), s = t[p][d];
}
const l = `${n}:${d}`;
return { transpose: c, url: s, index: d, midi: a, label: l };
}
const et = (e, t) => Object.fromEntries(Object.entries(t).map(([n, o]) => [n, e[o]])), Jt = (e) => {
try {
return new URL(".", new URL(e)).href.replace(/\/$/, "");
} catch {
return e.split("/").slice(0, -1).join("/");
}
}, Ut = ["pink", "white", "brown", "crackle"];
function T(e) {
const t = z().createGain();
return t.gain.value = e, t;
}
function Ue(e, t, n) {
const o = T(n);
return e.connect(o), o.connect(t), o;
}
const xt = (e, t, n, o) => o - n === 0 ? 0 : (t - e) / (o - n);
function q(e, t, n, o) {
const a = new AudioWorkletNode(e, t, o);
return Object.entries(n).forEach(([c, s]) => {
s !== void 0 && (a.parameters.get(c).value = s);
}), a;
}
const _ = (e, t, n, o, a, c, s, d, l, i = "exponential") => {
t = re(t), n = re(n), o = re(o), a = re(a);
const p = i === "exponential" ? "exponentialRampToValueAtTime" : "linearRampToValueAtTime";
i === "exponential" && (c = c === 0 ? 1e-3 : c, s = s === 0 ? 1e-3 : s);
const r = s - c, h = c + o * r, u = l - d, m = (G) => {
let b;
return t > G ? b = G * xt(c, s, 0, t) + c : b = (G - t) * xt(s, h, 0, n) + s, i === "exponential" && (b = b || 1e-3), b;
};
e.setValueAtTime(c, d), t > u ? e[p](m(u), l) : t + n > u ? (e[p](m(t), d + t), e[p](m(u), l)) : (e[p](m(t), d + t), e[p](m(t + n), d + t + n), e.setValueAtTime(h, l)), e[p](c, l + a);
};
function Mn(e) {
return typeof e == "number" ? e % 5 : { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[e] ?? 0;
}
function Vn(e, t = {}) {
return q(e, "envelope-processor", t);
}
function Fe(e, t = {}) {
const {
shape: n = 0,
begin: o = 0,
end: a = 0,
time: c,
depth: s = 1,
dcoffset: d = -0.5,
frequency: l = 1,
skew: i = 0.5,
phaseoffset: p = 0,
curve: r = 1,
min: h,
max: u,
...m
} = t, G = {
begin: o,
end: a,
time: c ?? o,
depth: s,
dcoffset: d,
frequency: l,
skew: i,
phaseoffset: p,
curve: r,
shape: Mn(n),
min: h ?? d * s,
max: u ?? d * s + s,
...m
};
return q(e, "lfo-processor", G);
}
function Ln(e, t, n, o, a, c) {
const s = we("compressor", () => new DynamicsCompressorNode(e, {}));
return Object.entries({
threshold: t ?? -3,
ratio: n ?? 10,
knee: o ?? 10,
attack: a ?? 5e-3,
release: c ?? 0.05
}).forEach(([l, i]) => {
s[l].value = i;
}), s;
}
const $ = (e, t = "linear", n) => {
const [s, d, l, i] = e;
if (s == null && d == null && l == null && i == null)
return n ?? [1e-3, 1e-3, 1, 0.01];
const p = l ?? (s != null && d == null || s == null && d == null ? 1 : 1e-3);
return [Math.max(s ?? 0, 1e-3), Math.max(d ?? 0, 1e-3), Math.min(p, 1), Math.max(i ?? 0, 0.01)];
};
function jt(e, t, n, o, a) {
let { defaultDepth: c = 1, depth: s, dcoffset: d, ...l } = a;
s == null && (s = Object.values(l).some((r) => r != null) ? c : 0);
let i;
return s && (i = Fe(e, {
begin: n,
end: o,
depth: s,
dcoffset: d,
...l
}), i.connect(t)), i;
}
function zt(e, t, n, o, a, c) {
let { amount: s, offset: d, defaultAmount: l = 1, curve: i = "linear", values: p, holdEnd: r, defaultValues: h } = a;
s == null && (s = p.some((y) => y != null) ? l : 0);
const u = d ?? 0, m = s + u;
if (Math.abs(m - u)) {
const [f, y, M, Z] = $(p, i, h);
_(t, f, y, M, Z, u, m, n, r, i);
}
return jt(e, t, n, o, c);
}
function xn(e, t, n, o, a, c) {
let {
frequency: s,
anchor: d,
env: l,
type: i,
model: p,
q: r = 1,
drive: h = 0.69,
depth: u,
depthfrequency: m,
dcoffset: G = -0.5,
skew: b,
shape: f,
rate: y,
sync: M
} = o, Z, W;
p === "ladder" ? (W = q(e, "ladder-processor", { frequency: s, q: r, drive: h }), Z = W.parameters.get("frequency")) : (W = we("filter", () => e.createBiquadFilter()), W.type = i, Object.entries({ Q: r, frequency: s }).forEach(([O, w]) => {
W[O].value = w;
}), Z = W.frequency);
const S = [o.attack, o.decay, o.sustain, o.release], [Q, F, N, g] = $(S, "exponential", [5e-3, 0.14, 0, 0.1]);
if ([...S, l].some((k) => k !== void 0)) {
l = re(l, 1, !0), d = re(d, 0, !0);
const k = Math.abs(l), O = k * d;
let w = se(2 ** -O * s, 0, 2e4), E = se(2 ** (k - O) * s, 0, 2e4);
l < 0 && ([w, E] = [E, w]), _(Z, Q, F, N, g, w, E, t, n, "exponential");
}
M != null && (y = a * M);
const K = [u, m, b, f, y].some((k) => k !== void 0);
let I;
if (K) {
u = u ?? 1;
const k = c / a, w = {
depth: m ?? (u ?? 1) * s,
dcoffset: G,
skew: b,
shape: f,
frequency: y ?? a,
min: -s + 30,
max: 2e4 - s,
time: k,
curve: 1
};
I = jt(e, Z, t, n, w);
}
return { filter: W, lfo: I };
}
let Rt = (e) => e < 0.5 ? 1 : 1 - (e - 0.5) / 0.5;
function zn(e, t, n = 0) {
const o = z();
if (!n)
return e;
let a = o.createGain(), c = o.createGain();
e.connect(a), t.connect(c), a.gain.value = Rt(n), c.gain.value = Rt(1 - n);
let s = o.createGain();
return a.connect(s), c.connect(s), {
node: s,
teardown: () => {
Y(a), Y(c), e.disconnect(a), t.disconnect(c);
}
};
}
let Rn = ["linear", "exponential"];
function He(e, t, n, o) {
if ((t.pattack ?? t.pdecay ?? t.psustain ?? t.prelease ?? t.penv) === void 0)
return;
const c = re(t.penv, 1, !0), s = Rn[t.pcurve ?? 0];
let [d, l, i, p] = $(
[t.pattack, t.pdecay, t.psustain, t.prelease],
s,
[0.2, 1e-3, 1, 1e-3]
), r = t.panchor ?? i;
const h = c * 100, u = 0 - h * r, m = h - h * r;
_(e, d, l, i, p, u, m, n, o, s);
}
function Te(e, t, n) {
const { vibmod: o = 0.5, vib: a } = t;
let c;
if (a > 0) {
c = z().createOscillator(), c.frequency.value = a;
const s = z().createGain();
return s.gain.value = o * 100, c.connect(s), s.connect(e), ue(c, () => {
Y(s), Y(c);
}), c.start(n), { stop: (d) => c.stop(d), nodes: { vib: [c], vib_gain: [s] } };
}
}
function Qo(e, t, n = z()) {
const o = n.currentTime;
pe(n, e, o, t);
}
function pe(e, t, n, o) {
const a = new ConstantSourceNode(e), c = T(0);
return c.connect(e.destination), a.connect(c), ue(a, () => {
Y(c), Y(a), t();
}), a.start(n), a.stop(o), a;
}
const Yn = (e, t = "sine") => {
const n = z();
let o;
return Ut.includes(t) ? (o = n.createBufferSource(), o.buffer = Ft(t, 2), o.loop = !0) : (o = n.createOscillator(), o.type = t, o.frequency.value = e), o.start(), o;
}, Sn = (e, t, n = "sine") => {
const a = e.value * t;
return { osc: Yn(a, n), freq: a };
};
function je(e, t, n) {
const o = z(), a = [], c = {}, s = {};
for (let d = 1; d <= 8; d++)
for (let l = 0; l <= 8; l++) {
let i;
d === l + 1 ? i = `fmi${d === 1 ? "" : d}` : i = `fmi${d}${l}`;
const p = t[i];
if (!p) continue;
let r = [];
for (let [h, u] of [
[!0, d],
// source
[!1, l]
// target
]) {
if (u === 0) {
r.push(e);
continue;
}
if (!c[u]) {
const W = u === 1 ? "" : u, { osc: S, freq: Q } = Sn(e, t[`fmh${W}`] ?? 1, t[`fmwave${W}`] ?? "sine");
a.push(S);
const F = [S], N = ["attack", "decay", "sustain", "release"].map((C) => t[`fm${C}${W}`]);
let g = S;
if (N.some((C) => C !== void 0)) {
const C = o.createGain(), [K, I, k, O] = $(N), w = n + t.duration, E = t[`fmenv${W}`] ?? "exp";
_(
C.gain,
K,
I,
k,
O,
0,
1,
n,
w,
E === "exp" ? "exponential" : "linear"
), F.push(C), g = S.connect(C);
}
c[u] = { input: S.frequency, output: g, freq: Q, osc: S, toCleanup: F }, s[`fm_${u}`] = [S];
}
const { input: m, output: G, freq: b, osc: f, toCleanup: y } = c[u], M = T(p), Z = T(b);
r.push(h ? G.connect(M).connect(Z) : m), kn(f, [...y, M, Z]), s[`fm_${u}_gain`] = [M];
}
if (!r[1]) {
j(
`[superdough] control ${i} failed to connect FM ${d} to target ${l} due to missing frequency parameter (likely because fm${l} is noise)`,
"warning"
);
continue;
}
r[0].connect(r[1]);
}
return {
nodes: s,
stop: (d) => a.forEach((l) => l?.stop(d))
};
}
const Bt = (e) => e / (1 + e), Kn = (e, t) => (e % t + t) % t, gn = (e, t) => (1 + t) * e / (1 + t * Math.abs(e)), Ve = (e, t) => Math.tanh(e * (1 + t)), Nn = (e, t) => se((1 + t) * e, -1, 1), Qt = (e, t) => {
let n = (1 + 0.5 * t) * e;
const o = Kn(n + 1, 4);
return 1 - Math.abs(o - 2);
}, Hn = (e, t) => Math.sin(Math.PI / 2 * Qt(e, t)), Tn = (e, t) => {
const n = Bt(Math.log1p(t)), o = (e - n / 3 * e * e * e) / (1 - n / 3);
return Ve(o, t);
}, vt = (e, t, n = !1) => {
const o = 1 + 2 * t, c = 0.07 * Bt(Math.log1p(t)), s = Ve(e + c, 2 * t), d = Ve(n ? c : -e + c, 2 * t), l = s - d, i = 1 / Math.cosh(o * c), p = i * i, r = Math.max(1e-8, (n ? 1 : 2) * o * p);
return Ve(l / r, t);
}, wn = (e, t) => vt(e, t, !0), Fn = (e, t) => {
const n = 10 * Math.log1p(t);
let o = 1, a = e, c, s = 0;
for (let d = 1; d < 64; d++) {
if (d < 2) {
s += d == 0 ? o : a;
continue;
}
c = 2 * e * o - a, a = o, o = c, d % 2 === 0 && (s += Math.min(1.3 * n / d, 2) * c);
}
return Ve(s, n / 20);
}, Et = {
scurve: gn,
soft: Ve,
hard: Nn,
cubic: Tn,
diode: vt,
asym: wn,
fold: Qt,
sinefold: Hn,
chebyshev: Fn
}, ge = Object.freeze(Object.keys(Et)), vo = (e) => {
let t = e;
typeof e == "string" && (t = ge.indexOf(e), t === -1 && (j(`[superdough] Could not find waveshaping algorithm ${e}.
Available options are ${ge.join(", ")}.
Defaulting to ${ge[0]}.`), t = 0));
const n = ge[t % ge.length];
return Et[n];
}, Cn = (e, t, n) => q(z(), "distort-processor", { distort: e, postgain: t }, { processorOptions: { algorithm: n } }), Xe = (e, t = 36) => {
let { note: n, freq: o, octave: a = 0 } = e;
return n = n || t, typeof n == "string" && (n = Be(n)), !o && typeof n == "number" && (o = kt(n)), o *= Math.pow(2, a), Number(o);
}, ue = (e, t) => {
const n = t;
e.onended = function() {
n && n(), this.onended = null;
};
}, Y = (e) => {
if (e != null) {
if (!(e instanceof AudioNode))
throw new Error("releaseAudioNode can only release an AudioNode");
if (e.disconnect(), e instanceof AudioScheduledSourceNode) {
process.env.NODE_ENV === "development" && e.onended && e.onended.name !== "cleanup" && j(
"[superdough] Deprecation warning: it seems your code path is setting 'node.onended = callback' instead of using the onceEnded helper"
);
try {
e.stop();
} catch {
e.start(e.context.currentTime + 5), e.stop();
}
}
e instanceof AudioWorkletNode && e.parameters.get("end")?.setValueAtTime(0, 0);
}
}, kn = (e, t) => {
ue(e, () => t.forEach((n) => Y(n)));
};
var mt = {};
mt.generateReverb = function(e, t) {
for (var n = e.audioContext || new AudioContext(), o = n.sampleRate, a = e.numChannels || 2, c = e.decayTime * 1.5, s = Math.round(e.decayTime * o), d = Math.round(c * o), l = Math.round((e.fadeInTime || 0) * o), i = Math.pow(1 / 1e3, 1 / s), p = n.createBuffer(a, d, o), r = 0; r < a; r++) {
for (var h = p.getChannelData(r), u = 0; u < d; u++)
h[u] = Jn() * Math.pow(i, u);
for (var u = 0; u < l; u++)
h[u] *= u / l;
}
Pn(p, e.lpFreqStart || 0, e.lpFreqEnd || 0, e.decayTime, t);
};
mt.generateGraph = function(e, t, n, o, a) {
var c = document.createElement("canvas");
c.width = t, c.height = n;
var s = c.getContext("2d");
s.fillStyle = "#000", s.fillRect(0, 0, c.width, c.height), s.fillStyle = "#fff";
for (var d = t / e.length, l = n / (a - o), i = 0; i < e.length; i++)
s.fillRect(i * d, n - (e[i] - o) * l, 1, 1);
return c;
};
var Pn = function(e, t, n, o, a) {
if (t == 0) {
a(e);
return;
}
var c = In(e), s = new OfflineAudioContext(e.numberOfChannels, c[0].length, e.sampleRate), d = s.createBufferSource();
d.buffer = e;
var l = s.createBiquadFilter();
t = Math.min(t, e.sampleRate / 2), n = Math.min(n, e.sampleRate / 2), l.type = "lowpass", l.Q.value = 1e-4, l.frequency.setValueAtTime(t, 0), l.frequency.linearRampToValueAtTime(n, o), d.connect(l), l.connect(s.destination), d.start(), s.oncomplete = function(i) {
a(i.renderedBuffer), Y(l), Y(d);
}, s.startRendering(), window.filterNode = l;
}, In = function(e) {
for (var t = [], n = 0; n < e.numberOfChannels; n++)
t[n] = e.getChannelData(n);
return t;
}, Jn = function() {
return Math.random() * 2 - 1;
};
typeof AudioContext < "u" && (BaseAudioContext.prototype.adjustLength = function(e, t, n = 1, o = 0) {
const a = Math.floor(se(o, 0, 1) * t.length), c = t.sampleRate * e, s = this.createBuffer(t.numberOfChannels, t.length, t.sampleRate);
for (let d = 0; d < t.numberOfChannels; d++) {
let l = t.getChannelData(d), i = s.getChannelData(d);
for (let p = 0; p < c; p++) {
let r = (a + p * Math.abs(n)) % l.length;
n < 1 && (r = r * -1), i[p] = l.at(r) || 0;
}
}
return s;
}, BaseAudioContext.prototype.createReverb = function(e, t, n, o, a, c, s) {
const d = this.createConvolver();
return d.generate = (l = 2, i = 0.1, p = 15e3, r = 1e3, h, u, m) => {
d.duration = l, d.fade = i, d.lp = p, d.dim = r, d.ir = h, d.irspeed = u, d.irbegin = m, h ? d.buffer = this.adjustLength(l, h, u, m) : mt.generateReverb(
{
audioContext: this,
numChannels: 2,
decayTime: l,
fadeInTime: i,
lpFreqStart: p,
lpFreqEnd: r
},
(G) => {
d.buffer = G;
}
);
}, d.generate(e, t, n, o, a, c, s), d;
});
var Yt = {
a: { freqs: [660, 1120, 2750, 3e3, 3350], gains: [1, 0.5012, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
e: { freqs: [440, 1800, 2700, 3e3, 3300], gains: [1, 0.1995, 0.1259, 0.1, 0.1], qs: [70, 80, 100, 120, 120] },
i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] },
o: { freqs: [430, 820, 2700, 3e3, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] },
u: { freqs: [370, 630, 2750, 3e3, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ae: { freqs: [650, 1515, 2400, 3e3, 3350], gains: [1, 0.5, 0.1008, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
aa: { freqs: [560, 900, 2570, 3e3, 3300], gains: [1, 0.5, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
oe: { freqs: [500, 1430, 2300, 3e3, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ue: { freqs: [250, 1750, 2150, 3200, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
y: { freqs: [400, 1460, 2400, 3e3, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
uh: { freqs: [600, 1250, 2100, 3100, 3500], gains: [1, 0.3, 0.0608, 0.0316, 0.01995], qs: [40, 70, 100, 120, 130] },
un: { freqs: [500, 1240, 2280, 3e3, 3500], gains: [1, 0.1, 0.1708, 0.0216, 0.02995], qs: [40, 60, 100, 120, 120] },
en: { freqs: [600, 1480, 2450, 3200, 3300], gains: [1, 0.15, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
an: { freqs: [700, 1050, 2500, 3e3, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
on: { freqs: [500, 1080, 2350, 3e3, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
get æ() {
return this.ae;
},
get ø() {
return this.oe;
},
get ɑ() {
return this.aa;
},
get å() {
return this.aa;
},
get ö() {
return this.oe;
},
get ü() {
return this.ue;
},
get ı() {
return this.y;
}
};
if (typeof GainNode < "u") {
class e extends GainNode {
constructor(n, o) {
if (super(n), !Yt[o])
throw new Error("vowel: unknown vowel " + o);
const { gains: a, qs: c, freqs: s } = Yt[o];
this.makeupGain = n.createGain(), this.filters = [], this.gains = [];
for (let d = 0; d < 5; d++) {
const l = n.createGain();
l.gain.value = a[d];
const i = n.createBiquadFilter();
i.type = "bandpass", i.Q.value = c[d], i.frequency.value = s[d], super.connect(i), i.connect(l), this.filters.push(i), l.connect(this.makeupGain), this.gains.push(l);
}
return this.makeupGain.gain.value = 8, this;
}
connect(n) {
this.makeupGain.connect(n);
}
disconnect() {
Y(this.makeupGain), this.filters.forEach(Y), this.gains.forEach(Y), super.disconnect(), this.makeupGain = null, this.filters = null, this.gains = null;
}
}
BaseAudioContext.prototype.createVowelFilter = function(t) {
return new e(this, t);
};
}
const Un = "data:text/javascript;base64,dmFyIF89ZnVuY3Rpb24oUil7InVzZSBzdHJpY3QiO3ZhciBXZT1PYmplY3QuZGVmaW5lUHJvcGVydHk7dmFyIFllPShSLFcsWCk9PlcgaW4gUj9XZShSLFcse2VudW1lcmFibGU6ITAsY29uZmlndXJhYmxlOiEwLHdyaXRhYmxlOiEwLHZhbHVlOlh9KTpSW1ddPVg7dmFyIER0PShSLFcsWCk9PlllKFIsdHlwZW9mIFchPSJzeW1ib2wiP1crIiI6VyxYKTtjbGFzcyBYIGV4dGVuZHMgQXVkaW9Xb3JrbGV0UHJvY2Vzc29ye2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpLHRoaXMuc3RhcnRlZD0hMSx0aGlzLm5iSW5wdXRzPXQubnVtYmVyT2ZJbnB1dHMsdGhpcy5uYk91dHB1dHM9dC5udW1iZXJPZk91dHB1dHMsdGhpcy5ibG9ja1NpemU9dC5wcm9jZXNzb3JPcHRpb25zLmJsb2NrU2l6ZSx0aGlzLmhvcFNpemU9MTI4LHRoaXMubmJPdmVybGFwcz10aGlzLmJsb2NrU2l6ZS90aGlzLmhvcFNpemUsdGhpcy5pbnB1dEJ1ZmZlcnM9bmV3IEFycmF5KHRoaXMubmJJbnB1dHMpLHRoaXMuaW5wdXRCdWZmZXJzSGVhZD1uZXcgQXJyYXkodGhpcy5uYklucHV0cyksdGhpcy5pbnB1dEJ1ZmZlcnNUb1NlbmQ9bmV3IEFycmF5KHRoaXMubmJJbnB1dHMpO2ZvcihsZXQgcz0wO3M8dGhpcy5uYklucHV0cztzKyspdGhpcy5hbGxvY2F0ZUlucHV0Q2hhbm5lbHMocywxKTt0aGlzLm91dHB1dEJ1ZmZlcnM9bmV3IEFycmF5KHRoaXMubmJPdXRwdXRzKSx0aGlzLm91dHB1dEJ1ZmZlcnNUb1JldHJpZXZlPW5ldyBBcnJheSh0aGlzLm5iT3V0cHV0cyk7Zm9yKGxldCBzPTA7czx0aGlzLm5iT3V0cHV0cztzKyspdGhpcy5hbGxvY2F0ZU91dHB1dENoYW5uZWxzKHMsMSl9cmVhbGxvY2F0ZUNoYW5uZWxzSWZOZWVkZWQodCxzKXtmb3IobGV0IGU9MDtlPHRoaXMubmJJbnB1dHM7ZSsrKXtsZXQgaT10W2VdLmxlbmd0aDtpIT10aGlzLmlucHV0QnVmZmVyc1tlXS5sZW5ndGgmJnRoaXMuYWxsb2NhdGVJbnB1dENoYW5uZWxzKGUsaSl9Zm9yKGxldCBlPTA7ZTx0aGlzLm5iT3V0cHV0cztlKyspe2xldCBpPXNbZV0ubGVuZ3RoO2khPXRoaXMub3V0cHV0QnVmZmVyc1tlXS5sZW5ndGgmJnRoaXMuYWxsb2NhdGVPdXRwdXRDaGFubmVscyhlLGkpfX1hbGxvY2F0ZUlucHV0Q2hhbm5lbHModCxzKXt0aGlzLmlucHV0QnVmZmVyc1t0XT1uZXcgQXJyYXkocyk7Zm9yKGxldCBlPTA7ZTxzO2UrKyl0aGlzLmlucHV0QnVmZmVyc1t0XVtlXT1uZXcgRmxvYXQzMkFycmF5KHRoaXMuYmxvY2tTaXplKzEyOCksdGhpcy5pbnB1dEJ1ZmZlcnNbdF1bZV0uZmlsbCgwKTt0aGlzLmlucHV0QnVmZmVyc0hlYWRbdF09bmV3IEFycmF5KHMpLHRoaXMuaW5wdXRCdWZmZXJzVG9TZW5kW3RdPW5ldyBBcnJheShzKTtmb3IobGV0IGU9MDtlPHM7ZSsrKXRoaXMuaW5wdXRCdWZmZXJzSGVhZFt0XVtlXT10aGlzLmlucHV0QnVmZmVyc1t0XVtlXS5zdWJhcnJheSgwLHRoaXMuYmxvY2tTaXplKSx0aGlzLmlucHV0QnVmZmVyc1RvU2VuZFt0XVtlXT1uZXcgRmxvYXQzMkFycmF5KHRoaXMuYmxvY2tTaXplKX1hbGxvY2F0ZU91dHB1dENoYW5uZWxzKHQscyl7dGhpcy5vdXRwdXRCdWZmZXJzW3RdPW5ldyBBcnJheShzKTtmb3IobGV0IGU9MDtlPHM7ZSsrKXRoaXMub3V0cHV0QnVmZmVyc1t0XVtlXT1uZXcgRmxvYXQzMkFycmF5KHRoaXMuYmxvY2tTaXplKSx0aGlzLm91dHB1dEJ1ZmZlcnNbdF1bZV0uZmlsbCgwKTt0aGlzLm91dHB1dEJ1ZmZlcnNUb1JldHJpZXZlW3RdPW5ldyBBcnJheShzKTtmb3IobGV0IGU9MDtlPHM7ZSsrKXRoaXMub3V0cHV0QnVmZmVyc1RvUmV0cmlldmVbdF1bZV09bmV3IEZsb2F0MzJBcnJheSh0aGlzLmJsb2NrU2l6ZSksdGhpcy5vdXRwdXRCdWZmZXJzVG9SZXRyaWV2ZVt0XVtlXS5maWxsKDApfXJlYWRJbnB1dHModCl7aWYodFswXS5sZW5ndGgmJnRbMF1bMF0ubGVuZ3RoPT0wKXtmb3IobGV0IHM9MDtzPHRoaXMubmJJbnB1dHM7cysrKWZvcihsZXQgZT0wO2U8dGhpcy5pbnB1dEJ1ZmZlcnNbc10ubGVuZ3RoO2UrKyl0aGlzLmlucHV0QnVmZmVyc1tzXVtlXS5maWxsKDAsdGhpcy5ibG9ja1NpemUpO3JldHVybn1mb3IobGV0IHM9MDtzPHRoaXMubmJJbnB1dHM7cysrKWZvcihsZXQgZT0wO2U8dGhpcy5pbnB1dEJ1ZmZlcnNbc10ubGVuZ3RoO2UrKyl7bGV0IGk9dFtzXVtlXTt0aGlzLmlucHV0QnVmZmVyc1tzXVtlXS5zZXQoaSx0aGlzLmJsb2NrU2l6ZSl9fXdyaXRlT3V0cHV0cyh0KXtmb3IobGV0IHM9MDtzPHRoaXMubmJJbnB1dHM7cysrKWZvcihsZXQgZT0wO2U8dGhpcy5pbnB1dEJ1ZmZlcnNbc10ubGVuZ3RoO2UrKyl7bGV0IGk9dGhpcy5vdXRwdXRCdWZmZXJzW3NdW2VdLnN1YmFycmF5KDAsMTI4KTt0W3NdW2VdLnNldChpKX19c2hpZnRJbnB1dEJ1ZmZlcnMoKXtmb3IobGV0IHQ9MDt0PHRoaXMubmJJbnB1dHM7dCsrKWZvcihsZXQgcz0wO3M8dGhpcy5pbnB1dEJ1ZmZlcnNbdF0ubGVuZ3RoO3MrKyl0aGlzLmlucHV0QnVmZmVyc1t0XVtzXS5jb3B5V2l0aGluKDAsMTI4KX1zaGlmdE91dHB1dEJ1ZmZlcnMoKXtmb3IobGV0IHQ9MDt0PHRoaXMubmJPdXRwdXRzO3QrKylmb3IobGV0IHM9MDtzPHRoaXMub3V0cHV0QnVmZmVyc1t0XS5sZW5ndGg7cysrKXRoaXMub3V0cHV0QnVmZmVyc1t0XVtzXS5jb3B5V2l0aGluKDAsMTI4KSx0aGlzLm91dHB1dEJ1ZmZlcnNbdF1bc10uc3ViYXJyYXkodGhpcy5ibG9ja1NpemUtMTI4KS5maWxsKDApfXByZXBhcmVJbnB1dEJ1ZmZlcnNUb1NlbmQoKXtmb3IobGV0IHQ9MDt0PHRoaXMubmJJbnB1dHM7dCsrKWZvcihsZXQgcz0wO3M8dGhpcy5pbnB1dEJ1ZmZlcnNbdF0ubGVuZ3RoO3MrKyl0aGlzLmlucHV0QnVmZmVyc1RvU2VuZFt0XVtzXS5zZXQodGhpcy5pbnB1dEJ1ZmZlcnNIZWFkW3RdW3NdKX1oYW5kbGVPdXRwdXRCdWZmZXJzVG9SZXRyaWV2ZSgpe2ZvcihsZXQgdD0wO3Q8dGhpcy5uYk91dHB1dHM7dCsrKWZvcihsZXQgcz0wO3M8dGhpcy5vdXRwdXRCdWZmZXJzW3RdLmxlbmd0aDtzKyspZm9yKGxldCBlPTA7ZTx0aGlzLmJsb2NrU2l6ZTtlKyspdGhpcy5vdXRwdXRCdWZmZXJzW3RdW3NdW2VdKz10aGlzLm91dHB1dEJ1ZmZlcnNUb1JldHJpZXZlW3RdW3NdW2VdL3RoaXMubmJPdmVybGFwc31wcm9jZXNzKHQscyxlKXtjb25zdCBuPXRbMF1bMF0hPT12b2lkIDA7cmV0dXJuIHRoaXMuc3RhcnRlZCYmIW4/ITE6KHRoaXMuc3RhcnRlZD1uLHRoaXMucmVhbGxvY2F0ZUNoYW5uZWxzSWZOZWVkZWQodCxzKSx0aGlzLnJlYWRJbnB1dHModCksdGhpcy5zaGlmdElucHV0QnVmZmVycygpLHRoaXMucHJlcGFyZUlucHV0QnVmZmVyc1RvU2VuZCgpLHRoaXMucHJvY2Vzc09MQSh0aGlzLmlucHV0QnVmZmVyc1RvU2VuZCx0aGlzLm91dHB1dEJ1ZmZlcnNUb1JldHJpZXZlLGUpLHRoaXMuaGFuZGxlT3V0cHV0QnVmZmVyc1RvUmV0cmlldmUoKSx0aGlzLndyaXRlT3V0cHV0cyhzKSx0aGlzLnNoaWZ0T3V0cHV0QnVmZmVycygpLCEwKX1wcm9jZXNzT0xBKHQscyxlKXtjb25zb2xlLmFzc2VydCghMSwiTm90IG92ZXJyaWRlbiIpfX1jbGFzcyBRdHtjb25zdHJ1Y3Rvcih0KXtpZih0aGlzLnNpemU9dHwwLHRoaXMuc2l6ZTw9MXx8dGhpcy5zaXplJnRoaXMuc2l6ZS0xKXRocm93IG5ldyBFcnJvcigiRkZUIHNpemUgbXVzdCBiZSBhIHBvd2VyIG9mIHR3byBhbmQgYmlnZ2VyIHRoYW4gMSIpO3RoaXMuX2NzaXplPXQ8PDE7Zm9yKHZhciBzPW5ldyBBcnJheSh0aGlzLnNpemUqMiksZT0wO2U8cy5sZW5ndGg7ZSs9Mil7Y29uc3QgaD1NYXRoLlBJKmUvdGhpcy5zaXplO3NbZV09TWF0aC5jb3MoaCksc1tlKzFdPS1NYXRoLnNpbihoKX10aGlzLnRhYmxlPXM7Zm9yKHZhciBpPTAsbj0xO3RoaXMuc2l6ZT5uO248PD0xKWkrKzt0aGlzLl93aWR0aD1pJTI9PT0wP2ktMTppLHRoaXMuX2JpdHJldj1uZXcgQXJyYXkoMTw8dGhpcy5fd2lkdGgpO2Zvcih2YXIgYT0wO2E8dGhpcy5fYml0cmV2Lmxlbmd0aDthKyspe3RoaXMuX2JpdHJldlthXT0wO2Zvcih2YXIgbz0wO288dGhpcy5fd2lkdGg7bys9Mil7dmFyIGM9dGhpcy5fd2lkdGgtby0yO3RoaXMuX2JpdHJldlthXXw9KGE+Pj5vJjMpPDxjfX10aGlzLl9vdXQ9bnVsbCx0aGlzLl9kYXRhPW51bGwsdGhpcy5faW52PTB9ZnJvbUNvbXBsZXhBcnJheSh0LHMpe2Zvcih2YXIgZT1zfHxuZXcgQXJyYXkodC5sZW5ndGg+Pj4xKSxpPTA7aTx0Lmxlbmd0aDtpKz0yKWVbaT4+PjFdPXRbaV07cmV0dXJuIGV9Y3JlYXRlQ29tcGxleEFycmF5KCl7Y29uc3QgdD1uZXcgQXJyYXkodGhpcy5fY3NpemUpO2Zvcih2YXIgcz0wO3M8dC5sZW5ndGg7cysrKXRbc109MDtyZXR1cm4gdH10b0NvbXBsZXhBcnJheSh0LHMpe2Zvcih2YXIgZT1zfHx0aGlzLmNyZWF0ZUNvbXBsZXhBcnJheSgpLGk9MDtpPGUubGVuZ3RoO2krPTIpZVtpXT10W2k+Pj4xXSxlW2krMV09MDtyZXR1cm4gZX1jb21wbGV0ZVNwZWN0cnVtKHQpe2Zvcih2YXIgcz10aGlzLl9jc2l6ZSxlPXM+Pj4xLGk9MjtpPGU7aSs9Mil0W3MtaV09dFtpXSx0W3MtaSsxXT0tdFtpKzFdfXRyYW5zZm9ybSh0LHMpe2lmKHQ9PT1zKXRocm93IG5ldyBFcnJvcigiSW5wdXQgYW5kIG91dHB1dCBidWZmZXJzIG11c3QgYmUgZGlmZmVyZW50Iik7dGhpcy5fb3V0PXQsdGhpcy5fZGF0YT1zLHRoaXMuX2ludj0wLHRoaXMuX3RyYW5zZm9ybTQoKSx0aGlzLl9vdXQ9bnVsbCx0aGlzLl9kYXRhPW51bGx9cmVhbFRyYW5zZm9ybSh0LHMpe2lmKHQ9PT1zKXRocm93IG5ldyBFcnJvcigiSW5wdXQgYW5kIG91dHB1dCBidWZmZXJzIG11c3QgYmUgZGlmZmVyZW50Iik7dGhpcy5fb3V0PXQsdGhpcy5fZGF0YT1zLHRoaXMuX2ludj0wLHRoaXMuX3JlYWxUcmFuc2Zvcm00KCksdGhpcy5fb3V0PW51bGwsdGhpcy5fZGF0YT1udWxsfWludmVyc2VUcmFuc2Zvcm0odCxzKXtpZih0PT09cyl0aHJvdyBuZXcgRXJyb3IoIklucHV0IGFuZCBvdXRwdXQgYnVmZmVycyBtdXN0IGJlIGRpZmZlcmVudCIpO3RoaXMuX291dD10LHRoaXMuX2RhdGE9cyx0aGlzLl9pbnY9MSx0aGlzLl90cmFuc2Zvcm00KCk7Zm9yKHZhciBlPTA7ZTx0Lmxlbmd0aDtlKyspdFtlXS89dGhpcy5zaXplO3RoaXMuX291dD1udWxsLHRoaXMuX2RhdGE9bnVsbH1fdHJhbnNmb3JtNCgpe3ZhciB0PXRoaXMuX291dCxzPXRoaXMuX2NzaXplLGU9dGhpcy5fd2lkdGgsaT0xPDxlLG49cy9pPDwxLGEsbyxjPXRoaXMuX2JpdHJldjtpZihuPT09NClmb3IoYT0wLG89MDthPHM7YSs9bixvKyspe2NvbnN0IG09Y1tvXTt0aGlzLl9zaW5nbGVUcmFuc2Zvcm0yKGEsbSxpKX1lbHNlIGZvcihhPTAsbz0wO2E8czthKz1uLG8rKyl7Y29uc3QgbT1jW29dO3RoaXMuX3NpbmdsZVRyYW5zZm9ybTQoYSxtLGkpfXZhciBoPXRoaXMuX2ludj8tMToxLGY9dGhpcy50YWJsZTtmb3IoaT4+PTI7aT49MjtpPj49Mil7bj1zL2k8PDE7dmFyIHU9bj4+PjI7Zm9yKGE9MDthPHM7YSs9bilmb3IodmFyIGQ9YSt1LGw9YSxwPTA7bDxkO2wrPTIscCs9aSl7Y29uc3QgbT1sLEk9bSt1LHc9SSt1LHY9dyt1LE09dFttXSxQPXRbbSsxXSxnPXRbSV0sVD10W0krMV0sQT10W3ddLFY9dFt3KzFdLE89dFt2XSxOPXRbdisxXSxDPU0sRj1QLHE9ZltwXSxCPWgqZltwKzFdLEw9ZypxLVQqQixFPWcqQitUKnEsSD1mWzIqcF0saXQ9aCpmWzIqcCsxXSwkPUEqSC1WKml0LFU9QSppdCtWKkgsSz1mWzMqcF0sWj1oKmZbMypwKzFdLGs9TypLLU4qWixmdD1PKlorTipLLGR0PUMrJCxydD1GK1UsbnQ9Qy0kLHB0PUYtVSxtdD1MK2ssYXQ9RStmdCxvdD1oKihMLWspLGd0PWgqKEUtZnQpLHZ0PWR0K210LEF0PXJ0K2F0LE90PWR0LW10LE50PXJ0LWF0LEN0PW50K2d0LEZ0PXB0LW90LEV0PW50LWd0LGt0PXB0K290O3RbbV09dnQsdFttKzFdPUF0LHRbSV09Q3QsdFtJKzFdPUZ0LHRbd109T3QsdFt3KzFdPU50LHRbdl09RXQsdFt2KzFdPWt0fX19X3NpbmdsZVRyYW5zZm9ybTIodCxzLGUpe2NvbnN0IGk9dGhpcy5fb3V0LG49dGhpcy5fZGF0YSxhPW5bc10sbz1uW3MrMV0sYz1uW3MrZV0saD1uW3MrZSsxXSxmPWErYyx1PW8raCxkPWEtYyxsPW8taDtpW3RdPWYsaVt0KzFdPXUsaVt0KzJdPWQsaVt0KzNdPWx9X3NpbmdsZVRyYW5zZm9ybTQodCxzLGUpe2NvbnN0IGk9dGhpcy5fb3V0LG49dGhpcy5fZGF0YSxhPXRoaXMuX2ludj8tMToxLG89ZSoyLGM9ZSozLGg9bltzXSxmPW5bcysxXSx1PW5bcytlXSxkPW5bcytlKzFdLGw9bltzK29dLHA9bltzK28rMV0sbT1uW3MrY10sST1uW3MrYysxXSx3PWgrbCx2PWYrcCxNPWgtbCxQPWYtcCxnPXUrbSxUPWQrSSxBPWEqKHUtbSksVj1hKihkLUkpLE89dytnLE49ditULEM9TStWLEY9UC1BLHE9dy1nLEI9di1ULEw9TS1WLEU9UCtBO2lbdF09TyxpW3QrMV09TixpW3QrMl09QyxpW3QrM109RixpW3QrNF09cSxpW3QrNV09QixpW3QrNl09TCxpW3QrN109RX1fcmVhbFRyYW5zZm9ybTQoKXt2YXIgdD10aGlzLl9vdXQscz10aGlzLl9jc2l6ZSxlPXRoaXMuX3dpZHRoLGk9MTw8ZSxuPXMvaTw8MSxhLG8sYz10aGlzLl9iaXRyZXY7aWYobj09PTQpZm9yKGE9MCxvPTA7YTxzO2ErPW4sbysrKXtjb25zdCBSdD1jW29dO3RoaXMuX3NpbmdsZVJlYWxUcmFuc2Zvcm0yKGEsUnQ+Pj4xLGk+Pj4xKX1lbHNlIGZvcihhPTAsbz0wO2E8czthKz1uLG8rKyl7Y29uc3QgUnQ9Y1tvXTt0aGlzLl9zaW5nbGVSZWFsVHJhbnNmb3JtNChhLFJ0Pj4+MSxpPj4+MSl9dmFyIGg9dGhpcy5faW52Py0xOjEsZj10aGlzLnRhYmxlO2ZvcihpPj49MjtpPj0yO2k+Pj0yKXtuPXMvaTw8MTt2YXIgdT1uPj4+MSxkPXU+Pj4xLGw9ZD4+PjE7Zm9yKGE9MDthPHM7YSs9bilmb3IodmFyIHA9MCxtPTA7cDw9bDtwKz0yLG0rPWkpe3ZhciBJPWErcCx3PUkrZCx2PXcrZCxNPXYrZCxQPXRbSV0sZz10W0krMV0sVD10W3ddLEE9dFt3KzFdLFY9dFt2XSxPPXRbdisxXSxOPXRbTV0sQz10W00rMV0sRj1QLHE9ZyxCPWZbbV0sTD1oKmZbbSsxXSxFPVQqQi1BKkwsSD1UKkwrQSpCLGl0PWZbMiptXSwkPWgqZlsyKm0rMV0sVT1WKml0LU8qJCxLPVYqJCtPKml0LFo9ZlszKm1dLGs9aCpmWzMqbSsxXSxmdD1OKlotQyprLGR0PU4qaytDKloscnQ9RitVLG50PXErSyxwdD1GLVUsbXQ9cS1LLGF0PUUrZnQsb3Q9SCtkdCxndD1oKihFLWZ0KSx2dD1oKihILWR0KSxBdD1ydCthdCxPdD1udCtvdCxOdD1wdCt2dCxDdD1tdC1ndDtpZih0W0ldPUF0LHRbSSsxXT1PdCx0W3ddPU50LHRbdysxXT1DdCxwPT09MCl7dmFyIEZ0PXJ0LWF0LEV0PW50LW90O3Rbdl09RnQsdFt2KzFdPUV0O2NvbnRpbnVlfWlmKHAhPT1sKXt2YXIga3Q9cHQsT2U9LW10LE5lPXJ0LENlPS1udCxGZT0taCp2dCxFZT0taCpndCxrZT0taCpvdCxSZT0taCphdCxEZT1rdCtGZSx6ZT1PZStFZSxxZT1OZStSZSxMZT1DZS1rZSxadD1hK2QtcCxYdD1hK3UtcDt0W1p0XT1EZSx0W1p0KzFdPXplLHRbWHRdPXFlLHRbWHQrMV09TGV9fX19X3NpbmdsZVJlYWxUcmFuc2Zvcm0yKHQscyxlKXtjb25zdCBpPXRoaXMuX291dCxuPXRoaXMuX2RhdGEsYT1uW3NdLG89bltzK2VdLGM9YStvLGg9YS1vO2lbdF09YyxpW3QrMV09MCxpW3QrMl09aCxpW3QrM109MH1fc2luZ2xlUmVhbFRyYW5zZm9ybTQodCxzLGUpe2NvbnN0IGk9dGhpcy5fb3V0LG49dGhpcy5fZGF0YSxhPXRoaXMuX2ludj8tMToxLG89ZSoyLGM9ZSozLGg9bltzXSxmPW5bcytlXSx1PW5bcytvXSxkPW5bcytjXSxsPWgrdSxwPWgtdSxtPWYrZCxJPWEqKGYtZCksdz1sK20sdj1wLE09LUksUD1sLW0sZz1wLFQ9STtpW3RdPXcsaVt0KzFdPTAsaVt0KzJdPXYsaVt0KzNdPU0saVt0KzRdPVAsaVt0KzVdPTAsaVt0KzZdPWcsaVt0KzddPVR9fWxldCB0cz1yPT5jb25zb2xlLmxvZyhyKTtjb25zdCBzcz0oLi4ucik9PnRzKC4uLnIpLGVzPShyLHQscyk9Pk1hdGgubWluKE1hdGgubWF4KHIsdCkscyksenQ9cj0+ci8oMStyKSxpcz0ocix0KT0+KHIldCt0KSV0LHJzPShyLHQpPT4oMSt0KSpyLygxK3QqTWF0aC5hYnMocikpLEo9KHIsdCk9Pk1hdGgudGFuaChyKigxK3QpKSxucz0ocix0KT0+ZXMoKDErdCkqciwtMSwxKSxxdD0ocix0KT0+e2xldCBzPSgxKy41KnQpKnI7Y29uc3QgZT1pcyhzKzEsNCk7cmV0dXJuIDEtTWF0aC5hYnMoZS0yKX0sYXM9KHIsdCk9Pk1hdGguc2luKE1hdGguUEkvMipxdChyLHQpKSxvcz0ocix0KT0+e2NvbnN0IHM9enQoTWF0aC5sb2cxcCh0KSksZT0oci1zLzMqcipyKnIpLygxLXMvMyk7cmV0dXJuIEooZSx0KX0sTHQ9KHIsdCxzPSExKT0+e2NvbnN0IGU9MSsyKnQsbj0uMDcqenQoTWF0aC5sb2cxcCh0KSksYT1KKHIrbiwyKnQpLG89SihzP246LXIrbiwyKnQpLGM9YS1vLGg9MS9NYXRoLmNvc2goZSpuKSxmPWgqaCx1PU1hdGgubWF4KDFlLTgsKHM/MToyKSplKmYpO3JldHVybiBKKGMvdSx0KX0sV3Q9e3NjdXJ2ZTpycyxzb2Z0OkosaGFyZDpucyxjdWJpYzpvcyxkaW9kZTpMdCxhc3ltOihyLHQpPT5MdChyLHQsITApLGZvbGQ6cXQsc2luZWZvbGQ6YXMsY2hlYnlzaGV2OihyLHQpPT57Y29uc3Qgcz0xMCpNYXRoLmxvZzFwKHQpO2xldCBlPTEsaT1yLG4sYT0wO2ZvcihsZXQgbz0xO288NjQ7bysrKXtpZihvPDIpe2ErPW89PTA/ZTppO2NvbnRpbnVlfW49MipyKmUtaSxpPWUsZT1uLG8lMj09PTAmJihhKz1NYXRoLm1pbigxLjMqcy9vLDIpKm4pfXJldHVybiBKKGEscy8yMCl9fSxodD1PYmplY3QuZnJlZXplKE9iamVjdC5rZXlzKFd0KSksaHM9cj0+e2xldCB0PXI7dHlwZW9mIHI9PSJzdHJpbmciJiYodD1odC5pbmRleE9mKHIpLHQ9PT0tMSYmKHNzKGBbc3VwZXJkb3VnaF0gQ291bGQgbm90IGZpbmQgd2F2ZXNoYXBpbmcgYWxnb3JpdGhtICR7cn0uCiAgICAgICAgQXZhaWxhYmxlIG9wdGlvbnMgYXJlICR7aHQuam9pbigiLCAiKX0uCiAgICAgICAgRGVmYXVsdGluZyB0byAke2h0WzBdfS5gKSx0PTApKTtjb25zdCBzPWh0W3QlaHQubGVuZ3RoXTtyZXR1cm4gV3Rbc119O2Z1bmN0aW9uIGN0KHIsdCl7aWYodHx8KHQ9ImFzc2VydGlvbiBmYWlsZWQiKSwhcil0aHJvdyBuZXcgRXJyb3IodCl9ZnVuY3Rpb24gY3Mocix0LHMpe3JldHVybiByPD0wP3Q6cj49MT9zOnQrcioocy10KX1mdW5jdGlvbiB1cyhyLHQscyl7cmV0dXJuIHI8PXQ/MDpyPj1zPzE6cz09PXQ/MDooci10KS8ocy10KX1mdW5jdGlvbiBscyhyLHQpe3JldHVybiByPHQ/KHIvPXQscityLXIqci0xKTpyPjEtdD8ocj0oci0xKS90LHIqcityK3IrMSk6MH1mdW5jdGlvbiBmcyhyKXtyZXR1cm4gTWF0aC5mbG9vcihyKT09PXJ9ZnVuY3Rpb24gZHMocil7cmV0dXJuIGZzKHIpJiZyPjB9ZnVuY3Rpb24gcHMocix0KXt0PU1hdGgubWluKE1hdGgubWF4KHQsMCksMSksdC09LjAxO3ZhciBzPTIqdC8oMS10KSxlPSgxK3MpKnIvKDErcypNYXRoLmFicyhyKSk7cmV0dXJuIGV9ZnVuY3Rpb24gSXQocix0LHMpe3JldHVybiByPj0xP3M6dCtyKihzLXQpfWZ1bmN0aW9uIFl0KCl7dGhpcy5zdGF0ZT0ib2ZmIix0aGlzLnN0YXJ0VGltZT0wLHRoaXMuc3RhcnRWYWw9MH1ZdC5wcm90b3R5cGUuZXZhbD1mdW5jdGlvbihyLHQscyxlLGksbil7c3dpdGNoKHRoaXMuc3RhdGUpe2Nhc2Uib2ZmIjpyZXR1cm4gdD4wJiYodGhpcy5zdGF0ZT0iYXR0YWNrIix0aGlzLnN0YXJ0VGltZT1yLHRoaXMuc3RhcnRWYWw9MCksMDtjYXNlImF0dGFjayI6e2xldCBhPXItdGhpcy5zdGFydFRpbWU7cmV0dXJuIGE+cz8odGhpcy5zdGF0ZT0iZGVjYXkiLHRoaXMuc3RhcnRUaW1lPXIsMSk6SXQoYS9zLHRoaXMuc3RhcnRWYWwsMSl9Y2FzZSJkZWNheSI6e2xldCBhPXItdGhpcy5zdGFydFRpbWUsbz1JdChhL2UsMSxpKTtyZXR1cm4gdDw9MD8odGhpcy5zdGF0ZT0icmVsZWFzZSIsdGhpcy5zdGFydFRpbWU9cix0aGlzLnN0YXJ0VmFsPW8sbyk6YT5lPyh0aGlzLnN0YXRlPSJzdXN0YWluIix0aGlzLnN0YXJ0VGltZT1yLGkpOm99Y2FzZSJzdXN0YWluIjpyZXR1cm4gdDw9MCYmKHRoaXMuc3RhdGU9InJlbGVhc2UiLHRoaXMuc3RhcnRUaW1lPXIsdGhpcy5zdGFydFZhbD1pKSxpO2Nhc2UicmVsZWFzZSI6e2xldCBhPXItdGhpcy5zdGFydFRpbWU7aWYoYT5uKXJldHVybiB0aGlzLnN0YXRlPSJvZmYiLDA7bGV0IG89SXQoYS9uLHRoaXMuc3RhcnRWYWwsMCk7cmV0dXJuIHQ+MCYmKHRoaXMuc3RhdGU9ImF0dGFjayIsdGhpcy5zdGFydFRpbWU9cix0aGlzLnN0YXJ0VmFsPW8pLG99fXRocm93ImludmFsaWQgZW52ZWxvcGUgc3RhdGUifTtmdW5jdGlvbiB3dCgpe3RoaXMuczA9MCx0aGlzLnMxPTB9d3QucHJvdG90eXBlLmFwcGx5PWZ1bmN0aW9uKHIsdCxzKXtjdCghaXNOYU4ociksIk5hTiB2YWx1ZSBmZWQgaW4gVHdvUG9sZUZpbHRlciIpLHQ9TWF0aC5taW4odCwxKSxzPU1hdGgubWF4KHMsMCk7dmFyIGU9TWF0aC5wb3coLjUsKDEtdCkvLjEyNSksaT1NYXRoLnBvdyguNSwocysuMTI1KS8uMTI1KSxuPTEtaSplLGE9dGhpcy5zMCxvPXRoaXMuczE7cmV0dXJuIGE9biphLWUqbytlKnIsbz1uKm8rZSphLHI9byx0aGlzLnMwPWEsdGhpcy5zMT1vLHJ9O2xldCBtcz1jbGFzcyBKdHtjb25zdHJ1Y3Rvcih0LHMpe3RoaXMuc2FtcGxlUmF0ZT10LHM/dGhpcy5idWZmZXI9cy5zbGljZSgwKToodGhpcy5idWZmZXI9bmV3IEZsb2F0MzJBcnJheSgxMCp0KSx0aGlzLmJ1ZmZlci5maWxsKDApKSx0aGlzLndyaXRlSWR4PTAsdGhpcy5yZWFkSWR4PTB9cmVzZXQoKXt0aGlzLmJ1ZmZlci5maWxsKDApLHRoaXMud3JpdGVJZHg9MCx0aGlzLnJlYWRJZHg9MH1jbG9uZSgpe2NvbnN0IHQ9bmV3IEp0KHRoaXMuc2FtcGxlUmF0ZSx0aGlzLmJ1ZmZlcik7cmV0dXJuIHQud3JpdGVJZHg9dGhpcy53cml0ZUlkeCx0LnJlYWRJZHg9dGhpcy5yZWFkSWR4LHR9d3JpdGUodCxzKXt0aGlzLndyaXRlSWR4PSh0aGlzLndyaXRlSWR4KzEpJXRoaXMuYnVmZmVyLmxlbmd0aCx0aGlzLmJ1ZmZlclt0aGlzLndyaXRlSWR4XT10O2xldCBlPU1hdGgubWluKE1hdGguZmxvb3IodGhpcy5zYW1wbGVSYXRlKnMpLHRoaXMuYnVmZmVyLmxlbmd0aC0xKTt0aGlzLnJlYWRJZHg9dGhpcy53cml0ZUlkeC1lLHRoaXMucmVhZElkeDwwJiYodGhpcy5yZWFkSWR4Kz10aGlzLmJ1ZmZlci5sZW5ndGgpfXJlYWQoKXtyZXR1cm4gdGhpcy5idWZmZXJbdGhpcy5yZWFkSWR4XX19O2NvbnN0IFN0PTEvNDhlMyxfdD0yNCxncz1fdC80O2NsYXNzIHl7Y29uc3RydWN0b3IodCxzLGUsaSl7dGhpcy5ub2RlSWQ9dCx0aGlzLnN0YXRlPXMsdGhpcy5zYW1wbGVSYXRlPWUsdGhpcy5zYW1wbGVUaW1lPTEvZSx0aGlzLnNlbmQ9aX19Y2xhc3MgYnMgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMuZW52PW5ldyBZdH11cGRhdGUodCxzLGUsaSxuLGEpe3JldHVybiB0aGlzLmVudi5ldmFsKHQscyxlLGksbixhKX19Y2xhc3MgdnMgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMucGhhc2U9MH11cGRhdGUodCl7bGV0IHM9X3QqdC82MCxlPS41O3JldHVybiB0aGlzLnBoYXNlKz10aGlzLnNhbXBsZVRpbWUqcyx0aGlzLnBoYXNlJTE8ZT8xOi0xfX1jbGFzcyBJcyBleHRlbmRzIHl7Y29uc3RydWN0b3IodCxzLGUsaSl7c3VwZXIodCxzLGUsaSksdGhpcy5pblNnbj0hMCx0aGlzLm91dFNnbj0hMCx0aGlzLmNsb2NrQ250PTB9dXBkYXRlKHQscyl7bGV0IGU9dD4wO3JldHVybiB0aGlzLmluU2duIT1lJiYodGhpcy5jbG9ja0NudCsrLHRoaXMuY2xvY2tDbnQ+PXMmJih0aGlzLmNsb2NrQ250PTAsdGhpcy5vdXRTZ249IXRoaXMub3V0U2duKSksdGhpcy5pblNnbj1lLHRoaXMub3V0U2duPzE6LTF9fWNsYXNzIHdzIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKSx0aGlzLmluU2duPSExfXVwZGF0ZSh0LHMpe2xldCBlPXM+MDtyZXR1cm4gZSYmdGhpcy5pblNnbiE9ZSYmdGhpcy5zZW5kKHt0eXBlOiJDTE9DS19QVUxTRSIsbm9kZUlkOnRoaXMubm9kZUlkLHRpbWU6dH0pLHRoaXMuaW5TZ249ZSwwfX1jb25zdCBNdD1uZXcgTWFwO2NsYXNzIFNzIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKTtjb25zdCBuPXMuaW5wdXRzWzJdO24mJk10LmhhcyhuKT90aGlzLmRlbGF5PU10LmdldChuKS5jbG9uZSgpOnRoaXMuZGVsYXk9bmV3IG1zKGUpLG4mJk10LnNldChuLHRoaXMuZGVsYXkpfXVwZGF0ZSh0LHMpe3JldHVybiB0aGlzLmRlbGF5LndyaXRlKHQscyksdGhpcy5kZWxheS5yZWFkKCl9fWNsYXNzIF9zIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKX11cGRhdGUodCxzKXtyZXR1cm4gcHModCxzKX19Y2xhc3MgTXMgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMudmFsdWU9MCx0aGlzLnRyaWdTZ249ITF9d3JpdGUodCxzKXshdGhpcy50cmlnU2duJiZzPjAmJih0aGlzLnZhbHVlPXQpLHRoaXMudHJpZ1Nnbj1zPjB9cmVhZCgpe3JldHVybiB0aGlzLnZhbHVlfXVwZGF0ZSh0LHMpe3JldHVybiB0aGlzLndyaXRlKHQscyksdGhpcy5yZWFkKCl9fWNsYXNzIHhze2NvbnN0cnVjdG9yKCl7dGhpcy52YWx1ZT0wfXVwZGF0ZSh0KXtyZXR1cm4gdGhpcy52YWx1ZT10LHRoaXMudmFsdWV9fWNvbnN0IHlzPTM0MDtsZXQgR3Q9MDtjbGFzcyBQc3tjb25zdHJ1Y3Rvcigpe3RoaXMuY2g9R3QsdGhpcy5zdGFydF9zZWVkPXlzKih0aGlzLmNoKzEpPj4+MCx0aGlzLnN0YXRlPXRoaXMuc3RhcnRfc2VlZCx0aGlzLnZhbHVlPTAsdGhpcy5hPTE2NjQ1MjUsdGhpcy5jPTEwMTM5MDQyMjMsdGhpcy5tYXNrPTE2Nzc3MjE1LHRoaXMuc2NhbGU9NTk2MDQ2NDQ3NzUzOTA2M2UtMjMsR3QrK311cGRhdGUodCxzKXtpZighdClyZXR1cm4gdGhpcy52YWx1ZTtzJiYodGhpcy5zdGF0ZT10aGlzLnN0YXJ0X3NlZWQpLHRoaXMuc3RhdGU9dGhpcy5zdGF0ZSp0aGlzLmErdGhpcy5jPj4+MDtjb25zdCBlPSh0aGlzLnN0YXRlJnRoaXMubWFzaykqdGhpcy5zY2FsZTtyZXR1cm4gdGhpcy52YWx1ZT1lKjItMSx0aGlzLnZhbHVlfX1jbGFzcyBUc3tjb25zdHJ1Y3Rvcigpe3RoaXMudmFsdWU9TWF0aC5yYW5kb20oKSoyLTF9dXBkYXRlKHQpe3JldHVybiB0Pyh0aGlzLnZhbHVlPU1hdGgucmFuZG9tKCkqMi0xLHRoaXMudmFsdWUpOnRoaXMudmFsdWV9fWNsYXNzIEJze3VwZGF0ZSh0KXtyZXR1cm4gTWF0aC5yYW5kb20oKTx0KlN0P01hdGgucmFuZG9tKCk6MH19Y2xhc3MgVnN7Y29uc3RydWN0b3IoKXt0aGlzLm91dD0wfXVwZGF0ZSgpe2xldCB0PU1hdGgucmFuZG9tKCkqMi0xO3JldHVybiB0aGlzLm91dD0odGhpcy5vdXQrLjAyKnQpLzEuMDIsdGhpcy5vdXR9fWNsYXNzIEFze2NvbnN0cnVjdG9yKCl7dGhpcy5iMD0wLHRoaXMuYjE9MCx0aGlzLmIyPTAsdGhpcy5iMz0wLHRoaXMuYjQ9MCx0aGlzLmI1PTAsdGhpcy5iNj0wfXVwZGF0ZSgpe2NvbnN0IHQ9TWF0aC5yYW5kb20oKSoyLTE7dGhpcy5iMD0uOTk4ODYqdGhpcy5iMCt0Ki4wNTU1MTc5LHRoaXMuYjE9Ljk5MzMyKnRoaXMuYjErdCouMDc1MDc1OSx0aGlzLmIyPS45NjkqdGhpcy5iMit0Ki4xNTM4NTIsdGhpcy5iMz0uODY2NSp0aGlzLmIzK3QqLjMxMDQ4NTYsdGhpcy5iND0uNTUqdGhpcy5iNCt0Ki41MzI5NTIyLHRoaXMuYjU9LS43NjE2KnRoaXMuYjUtdCouMDE2ODk4O2NvbnN0IHM9dGhpcy5iMCt0aGlzLmIxK3RoaXMuYjIrdGhpcy5iMyt0aGlzLmI0K3RoaXMuYjUrdGhpcy5iNit0Ki41MzYyO3JldHVybiB0aGlzLmI2PXQqLjExNTkyNixzKi4xMX19Y2xhc3MgT3MgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMucGhhc2U9MX11cGRhdGUodCl7dGhpcy5waGFzZSs9dGhpcy5zYW1wbGVUaW1lKnQ7bGV0IHM9dGhpcy5waGFzZT49MT8xOjA7cmV0dXJuIHRoaXMucGhhc2U9dGhpcy5waGFzZSUxLHN9fWNsYXNzIE5zIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKSx0aGlzLnBoYXNlPTB9dXBkYXRlKHQscyl7cmV0dXJuIHRoaXMucGhhc2UrPXRoaXMuc2FtcGxlVGltZSp0LHRoaXMucGhhc2UlMTxzPzE6LTF9fWNsYXNzIENzIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKSx0aGlzLnBoYXNlPTB9dXBkYXRlKHQpe3JldHVybiB0aGlzLnBoYXNlKz10aGlzLnNhbXBsZVRpbWUqdCx0aGlzLnBoYXNlJTEqMi0xfX1jbGFzcyBGc3tjb25zdHJ1Y3Rvcigpe3RoaXMucGhhc2U9TWF0aC5yYW5kb20oKX11cGRhdGUodCl7Y29uc3Qgcz10L3NhbXBsZVJhdGU7bGV0IGU9bHModGhpcy5waGFzZSxzKSxpPTIqdGhpcy5waGFzZS0xLWU7cmV0dXJuIHRoaXMucGhhc2UrPXMsdGhpcy5waGFzZT4xJiYodGhpcy5waGFzZS09MSksaX19Y2xhc3MgRXMgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMucGhhc2U9MCx0aGlzLnN5bmNTZ249ITF9dXBkYXRlKHQscyxlKXshdGhpcy5zeW5jU2duJiZzPjAmJih0aGlzLnBoYXNlPTApLHRoaXMuc3luY1Nnbj1zPjA7bGV0IGk9KHRoaXMucGhhc2UrZSklMTtyZXR1cm4gdGhpcy5waGFzZSs9dGhpcy5zYW1wbGVUaW1lKnQsTWF0aC5zaW4oaSoyKk1hdGguUEkpfX1jbGFzcyBrc3tkQlRvTGluZWFyKHQpe3JldHVybiBNYXRoLnBvdygxMCx0LzIwKX1saW5lYXJUb0RCKHQpe3JldHVybiAyMCpNYXRoLmxvZzEwKHQpfXVwZGF0ZSh0LHMsZSl7bGV0IGk9dGhpcy5saW5lYXJUb0RCKE1hdGguYWJzKHQpKSxuPTA7cmV0dXJuIGk+cyYmKG49KGktcykqKDEtMS9lKSksdGhpcy5kQlRvTGluZWFyKC1uKX19Y2xhc3MgUnMgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMucGhhc2U9MH11cGRhdGUodCl7dGhpcy5waGFzZSs9dGhpcy5zYW1wbGVUaW1lKnQ7bGV0IHM9dGhpcy5waGFzZSUxO3JldHVybihzPC41PzIqczoxLTIqKHMtLjUpKSoyLTF9fWNsYXNzIERzIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKTtjb25zdCBuPWUvMzA7Y3QoZHMobikpLHRoaXMuYnVmZmVyPW5ldyBGbG9hdDMyQXJyYXkobiksdGhpcy53cml0ZVBvcz0wfXVwZGF0ZSh0LHMsZSxpKXtyZXR1cm4gdGhpcy5idWZmZXJbdGhpcy53cml0ZVBvc109dCx0aGlzLndyaXRlUG9zKyssdGhpcy53cml0ZVBvcyV0aGlzLmJ1ZmZlci5sZW5ndGg9PTAmJih0aGlzLndyaXRlUG9zPTAsdGhpcy5zZW5kKHt0eXBlOiJTRU5EX1NBTVBMRVMiLGlkOnMsc2FtcGxlczp0aGlzLmJ1ZmZlcixjaGFubmVsczplLGNoYW5uZWw6aX0pKSx0fX1jbGFzcyB6c3tjb25zdHJ1Y3Rvcigpe3RoaXMubGFnVW5pdD00NDEwLHRoaXMucz0wfXVwZGF0ZSh0LHMpe3JldHVybiBzPXMqdGhpcy5sYWdVbml0LHM8MSYmKHM9MSksdGhpcy5zKz0xL3MqKHQtdGhpcy5zKSx0aGlzLnN9fWNsYXNzIHFze2NvbnN0cnVjdG9yKCl7dGhpcy5sYXN0PTB9dXBkYXRlKHQscyxlKXtjb25zdCBpPXMqU3Qsbj1lKlN0O2xldCBhPXQtdGhpcy5sYXN0O3JldHVybiBhPmk/YT1pOmE8LW4mJihhPS1uKSx0aGlzLmxhc3QrPWEsdGhpcy5sYXN0fX1jbGFzcyBMcyBleHRlbmRzIHl7Y29uc3RydWN0b3IodCxzLGUsaSl7c3VwZXIodCxzLGUsaSksdGhpcy5zPTB9dXBkYXRlKHQscyl7cmV0dXJuIHM9cyoxZTMsczwxJiYocz0xKSx0aGlzLnMrPTEvcyoodC10aGlzLnMpLHRoaXMuc319Y2xhc3MgV3MgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMuZmlsdGVyPW5ldyB3dH11cGRhdGUodCxzLGUpe3JldHVybiB0aGlzLmZpbHRlci5hcHBseSh0LHMsZSksdGhpcy5maWx0ZXIuczF9fWNsYXNzIFlzIGV4dGVuZHMgeXtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKSx0aGlzLmZpbHRlcj1uZXcgd3R9dXBkYXRlKHQscyxlKXtyZXR1cm4gdGhpcy5maWx0ZXIuYXBwbHkodCxzLGUpLHRoaXMuZmlsdGVyLnMwfX1jbGFzcyBHcyBleHRlbmRzIHl7Y29uc3RydWN0b3IodCxzLGUsaSl7c3VwZXIodCxzLGUsaSl9dXBkYXRlKHQscyl7cmV0dXJuIHM8MCYmKHM9MCkscz1zKzEsdD10KnMsNCooTWF0aC5hYnMoLjI1KnQrLjI1LU1hdGgucm91bmQoLjI1KnQrLjI1KSktLjI1KX19Y2xhc3MganMgZXh0ZW5kcyB5e3VwZGF0ZSh0KXtyZXR1cm4gdH19Y2xhc3MgYnQgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMubm90ZT0wLHRoaXMuZnJlcT0wLHRoaXMudmVsb2NpdHk9MCx0aGlzLmdhdGVTdGF0ZT0ib2ZmIix0aGlzLnR5cGU9Im1pZGlpbiIsdGhpcy5jaGFubmVsPS0xfWlzRnJlZSgpe3JldHVybiB0aGlzLmdhdGVTdGF0ZT09PSJvZmYifW5vdGVPbih0LHMpe3M+MD8odGhpcy5ub3RlPXQsdGhpcy52ZWxvY2l0eT1zLHRoaXMuZnJlcT0yKiooKHQtNjkpLzEyKSo0NDAsdGhpcy5nYXRlU3RhdGU9InByZXRyaWciKTp0aGlzLm5vdGVPZmYoKX1ub3RlT2ZmKCl7dGhpcy5ub3RlPTAsdGhpcy5nYXRlU3RhdGU9Im9mZiJ9Z2V0R2F0ZSgpe3N3aXRjaCh0aGlzLmdhdGVTdGF0ZSl7Y2FzZSJwcmV0cmlnIjpyZXR1cm4gdGhpcy5nYXRlU3RhdGU9Im9uIiwwO2Nhc2Uib24iOnJldHVybiAxO2Nhc2Uib2ZmIjpyZXR1cm4gMDtkZWZhdWx0OmN0KCExKX19Z2V0RnJlcSgpe3N3aXRjaCh0aGlzLmdhdGVTdGF0ZSl7Y2FzZSJwcmV0cmlnIjpyZXR1cm4gdGhpcy5nYXRlU3RhdGU9Im9uIiwwO2Nhc2Uib24iOnJldHVybiB0aGlzLmZyZXE7Y2FzZSJvZmYiOnJldHVybiB0aGlzLmZyZXE7ZGVmYXVsdDpjdCghMSl9fWdldFZlbG9jaXR5KCl7c3dpdGNoKHRoaXMuZ2F0ZVN0YXRlKXtjYXNlInByZXRyaWciOnJldHVybiB0aGlzLmdhdGVTdGF0ZT0ib24iLDA7Y2FzZSJvbiI6cmV0dXJuIHRoaXMudmVsb2NpdHk7Y2FzZSJvZmYiOnJldHVybiB0aGlzLnZlbG9jaXR5O2RlZmF1bHQ6Y3QoITEpfX19Y2xhc3MgSHMgZXh0ZW5kcyBidHtjb25zdHJ1Y3Rvcih0LHMsZSxpKXtzdXBlcih0LHMsZSxpKSx0aGlzLnR5cGU9Im1pZGlnYXRlIn11cGRhdGUodCl7cmV0dXJuIHRoaXMuY2hhbm5lbD10LHRoaXMuZ2V0R2F0ZSgpfX1jbGFzcyBVcyBleHRlbmRzIGJ0e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMudHlwZT0ibWlkaWZyZXEifXVwZGF0ZSh0KXtyZXR1cm4gdGhpcy5jaGFubmVsPXQsdGhpcy5nZXRGcmVxKCl9fWNsYXNzICRzIGV4dGVuZHMgYnR7Y29uc3RydWN0b3IodCxzLGUsaSl7c3VwZXIodCxzLGUsaSksdGhpcy50eXBlPSJtaWRpdmVsIn11cGRhdGUodCl7cmV0dXJuIHRoaXMuY2hhbm5lbD10LHRoaXMuZ2V0VmVsb2NpdHkoKX19Y2xhc3MgS3N7Y29uc3RydWN0b3IodCxzLGUsaSl7dGhpcy51cD0hMSx0aGlzLnNlbmQ9aSx0aGlzLnZhbHVlPTAsdGhpcy50eXBlPSJjYyJ9c2V0VmFsdWUodCl7dGhpcy52YWx1ZT10fXVwZGF0ZSh0LHMsZSl7cmV0dXJuIHRoaXMuaWQ9cywhdGhpcy51cCYmdD4wPyh0aGlzLnVwPSEwLHRoaXMuc2VuZCh7dHlwZToiU0lHTkFMX1RSSUdHRVIiLGlkOnMsdGltZTplfSksdGhpcy52YWx1ZSk6KHRoaXMudXA9dD4wLHRoaXMudmFsdWUpfX1jbGFzcyBacyBleHRlbmRzIHl7Y29uc3RydWN0b3IodCxzLGUsaSl7c3VwZXIodCxzLGUsaSksdGhpcy50eXBlPSJjYyIsdGhpcy52YWx1ZT1zLmlucHV0c1sxXT8/MH1zZXRWYWx1ZSh0KXt0aGlzLnZhbHVlPXR9dXBkYXRlKHQpe3JldHVybiB0aGlzLmlkPXQsdGhpcy52YWx1ZX19Y2xhc3MgWHMgZXh0ZW5kcyB5e2NvbnN0cnVjdG9yKHQscyxlLGkpe3N1cGVyKHQscyxlLGkpLHRoaXMudHlwZT0ibWlkaWNjIix0aGlzLnZhbHVlPXMuaW5wdXRzWzJdPz8tMSx0aGlzLmNoYW5uZWw9LTEsdGhpcy5jY251bWJlcj0tMX1zZXRWYWx1ZSh0KXt0aGlzLnZhbHVlPXR9dXBkYXRlKHQscyl7cmV0dXJuIHRoaXMuY2NudW1iZXI9dCx0aGlzLmNoYW5uZWw9cyx0aGlzLnZhbHVlfX1jbGFzcyBKcyBleHRlbmRzIHl7Y29uc3RydWN0b3IodCxzLGUsaSl7c3VwZXIodCxzLGUsaSksdGhpcy5jbG9ja1Nnbj0hMCx0aGlzLnN0ZXA9MCx0aGlzLmZpcnN0PSEwfXVwZGF0ZSh0LC4uLnMpe3JldHVybiF0aGlzLmNsb2NrU2duJiZ0PjA/KHRoaXMuc3RlcD0odGhpcy5zdGVwKzEpJXMubGVuZ3RoLHRoaXMuY2xvY2tTZ249dD4wLDApOih0aGlzLmNsb2NrU2duPXQ+MCxzW3RoaXMuc3RlcF0pfX1jbGFzcyBRcyBleHRlbmRzIHl7dXBkYXRlKHQsLi4ucyl7Y29uc3QgZT10JXMubGVuZ3RoK3MubGVuZ3RoO3JldHVybiBzW01hdGguZmxvb3IoZSklcy5sZW5ndGhdfX1jbGFzcyB0ZXt1cGRhdGUodCxzLGUsaSxuKXtsZXQgYT11cyh0LHMsZSk7cmV0dXJuIGNzKGEsaSxuKX19Y2xhc3Mgc2V7dXBkYXRlKHQscyxlKXtyZXR1cm4gTWF0aC5taW4oTWF0aC5tYXgodCxzKSxlKX19Y2xhc3MgZWV7Y29uc3RydWN0b3IoKXt0aGlzLmhpPSExfXVwZGF0ZSh0KXtyZXR1cm4hdGhpcy5oaSYmdD4wPyh0aGlzLmhpPSEwLDEpOih0aGlzLmhpJiZ0PD0wJiYodGhpcy5oaT0hMSksMCl9fWNsYXNzIGlle2NvbnN0cnVjdG9yKCl7dGhpcy54MT0wLHRoaXMueDI9MCx0aGlzLnkxPTAsdGhpcy55Mj0wLHRoaXMuYTA9MSx0aGlzLmExPTAsdGhpcy5hMj0wLHRoaXMuYjA9MSx0aGlzLmIxPTAsdGhpcy5iMj0wfXVwZGF0ZSh0PTAscz0wLGU9NTAwLGk9MSxuPTEpe2NvbnN0IGE9MipNYXRoLlBJKmUvc2FtcGxlUmF0ZSxvPU1hdGguc2luKGEpO2k9TWF0aC5wb3coMTAsaS8yMCk7Y29uc3QgYz1vLygyKmkpLGg9TWF0aC5jb3MoYSk7aWYocz09PTApdGhpcy5iMT0xLWgsdGhpcy5iMD10aGlzLmIxLzIsdGhpcy5iMj10aGlzLmIwLHRoaXMuYTA9MStjLHRoaXMuYTE9LTIqaCx0aGlzLmEyPTEtYztlbHNlIGlmKHM9PT0xKXRoaXMuYjA9KDEraCkvMix0aGlzLmIxPS0oMStoKSx0aGlzLmIyPXRoaXMuYjAsdGhpcy5hMD0xK2MsdGhpcy5hMT0tMipoLHRoaXMuYTI9MS1jO2Vsc2UgaWYocz09PTIpdGhpcy5iMD1vLzIsdGhpcy5iMT0wLHRoaXMuYjI9LXRoaXMuYjAsdGhpcy5hMD0xK2MsdGhpcy5hMT0tMipoLHRoaXMuYTI9MS1jO2Vsc2UgaWYocz09PTMpdGhpcy5iMD0xLHRoaXMuYjE9LTIqaCx0aGlzLmIyPTEsdGhpcy5hMD0xK2MsdGhpcy5hMT0tMipoLHRoaXMuYTI9MS1jO2Vsc2UgaWYocz09PTQpdGhpcy5iMD0xLWMsdGhpcy5iMT0tMipoLHRoaXMuYjI9MStjLHRoaXMuYTA9MStjLHRoaXMuYTE9LTIqaCx0aGlzLmEyPTEtYztlbHNlIGlmKHM9PT01KXtjb25zdCB1PU1hdGgucG93KDEwLG4vNDApO3RoaXMuYjA9MStjKnUsdGhpcy5iMT0tMipoLHRoaXMuYjI9MS1jKnUsdGhpcy5hMD0xK2MvdSx0aGlzLmExPS0yKmgsdGhpcy5hMj0xLWMvdX1lbHNlIGlmKHM9PT02KXtjb25zdCB1PU1hdGgucG93KDEwLG4vNDApLGQ9MipNYXRoLnNxcnQodSkqYyxsPSh1LTEpKmgscD0odSsxKSpoO3RoaXMuYjA9dSoodSsxLWwrZCksdGhpcy5iMT0yKnUqKHUtMS1wKSx0aGlzLmIyPXUqKHUrMS1sLWQpLHRoaXMuYTA9dSsxK2wrZCx0aGlzLmExPS0yKih1LTErcCksdGhpcy5hMj11KzErbC1kfWVsc2UgaWYocz09PTcpe2NvbnN0IHU9TWF0aC5wb3coMTAsbi80MCksZD0yKk1hdGguc3FydCh1KSpjLGw9KHUtMSkqaCxwPSh1KzEpKmg7dGhpcy5iMD11Kih1KzErbCtkKSx0aGlzLmIxPS0yKnUqKHUtMStwKSx0aGlzLmIyPXUqKHUrMStsLWQpLHRoaXMuYTA9dSsxLWwrZCx0aGlzLmExPTIqKHUtMS1wKSx0aGlzLmEyPXUrMS1sLWR9dGhpcy5iMC89dGhpcy5hMCx0aGlzLmIxLz10aGlzLmEwLHRoaXMuYjIvPXRoaXMuYTAsdGhpcy5hMS89dGhpcy5hMCx0aGlzLmEyLz10aGlzLmEwLHRoaXMuYTA9MTtjb25zdCBmPXRoaXMuYjAqdCt0aGlzLmIxKnRoaXMueDErdGhpcy5iMip0aGlzLngyLXRoaXMuYTEqdGhpcy55MS10aGlzLmEyKnRoaXMueTI7cmV0dXJuIHRoaXMueDI9dGhpcy54MSx0aGlzLngxPXQsdGhpcy55Mj10aGlzLnkxLHRoaXMueTE9ZixmfX1jb25zdCByZT1PYmplY3QuZnJlZXplKE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh7X19wcm90b19fOm51bGwsQURTUk5vZGU6YnMsQXVkaW9JbjpqcyxBdWRpb05vZGU6eSxCUEY6WXMsQmlxdWFkRmlsdGVyOmllLEJyb3duTm9pc2VPc2M6VnMsQ0M6WnMsQ0xPQ0tfUFBROl90LENMT0NLX1BQUzpncyxDbGlwOnNlLENsb2NrOnZzLENsb2NrRGl2OklzLENsb2NrT3V0OndzLERlbGF5OlNzLERpc3RvcnQ6X3MsRHVzdE9zYzpCcyxGaWx0ZXI6V3MsRm9sZDpHcyxIb2xkOk1zLEltcHVsc2VPc2M6T3MsTGFnOnpzLExjZ05vaXNlOlBzLE1pZGlDQzpYcyxNaWRpRnJlcTpVcyxNaWRpR2F0ZTpIcyxNaWRpSW46YnQsTWlkaVZlbDokcyxOb2lzZU9zYzpUcyxPdXRwdXQ6eHMsUGljazpRcyxQaW5rTm9pc2U6QXMsUHVsc2VPc2M6TnMsUmVtYXA6dGUsU2F3T3NjOkZzLFNjb3BlOkRzLFNlcXVlbmNlOkpzLFNpZGVjaGFpbkNvbXByZXNzb3I6a3MsU2lnbmFsOktzLFNpbmVPc2M6RXMsU2xldzpxcyxTbGlkZTpMcyxUcmlPc2M6UnMsVHJpZzplZSxaYXdPc2M6Q3N9LFN5bWJvbC50b1N0cmluZ1RhZyx7dmFsdWU6Ik1vZHVsZSJ9KSksbmU9bmV3IE1hcChPYmplY3QuZW50cmllcyhyZSkpLFk9MTI4LEQ9TWF0aC5QSSx6PTIqRCxHPTEvc2FtcGxlUmF0ZSx4dD1yPT4xLU1hdGguZXhwKC1HL3IpLGp0PXI9Pk1hdGgucG93KDEwLHIvMjApLFM9KHIsdCxzKT0+TWF0aC5taW4oTWF0aC5tYXgocix0KSxzKSx1dD0ocix0LHMpPT5zKih0LXIpK3IsYj0ocix0KT0+clt0XT8/clswXSxRPXI9PnItTWF0aC5mbG9vcihyKSx0dD1yPT5yfDAsc3Q9cj0+dHQocisuNSksYWU9cj0+dHQocisxKSxIdD1yPT5yLXR0KHIpLGo9cj0+e2NvbnN0IHQ9cioqMjtyZXR1cm4gciooMjcrdCkvKDI3KzkqdCl9LFV0PShyLHQpPT57aWYocjwyKXJldHVybiBpPT4wO2NvbnN0IHM9dC8oci0xKSxlPXQqLjU7cmV0dXJuIGk9Pmkqcy1lfSxldD0ocix0KT0+cipNYXRoLnBvdygyLHQvMTIpO2Z1bmN0aW9uIG9lKHIsdCl7dD1NYXRoLm1pbih0LDEtdCk7Y29uc3Qgcz0xL3Q7cmV0dXJuIHI8dD8ocio9cywyKnItcioqMi0xKTpyP