UNPKG

superdough

Version:

simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.

610 lines 139 kB
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