pulseplot
Version:
Pulse data viewer JS library
1,294 lines (1,281 loc) • 69.3 kB
JavaScript
/**
Determine divisor and SI prefix.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2019
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
const E = [
{ name: "yotta", scale: 1e24, prefix: "Y" },
{ name: "zetta", scale: 1e21, prefix: "Z" },
{ name: "exa", scale: 1e18, prefix: "E" },
{ name: "peta", scale: 1e15, prefix: "P" },
{ name: "tera", scale: 1e12, prefix: "T" },
{ name: "giga", scale: 1e9, prefix: "G" },
{ name: "mega", scale: 1e6, prefix: "M" },
{ name: "kilo", scale: 1e3, prefix: "k" },
{ name: "", scale: 1, prefix: "" },
{ name: "milli", scale: 1e-3, prefix: "m" },
{ name: "micro", scale: 1e-6, prefix: "µ" },
{ name: "nano", scale: 1e-9, prefix: "n" },
{ name: "pico", scale: 1e-12, prefix: "p" },
{ name: "femto", scale: 1e-15, prefix: "f" },
{ name: "atto", scale: 1e-18, prefix: "a" },
{ name: "zepto", scale: 1e-21, prefix: "z" },
{ name: "yocto", scale: 1e-24, prefix: "y" }
];
function Z(r, t = 10) {
if (r == 0)
return E[8];
r = r / t;
for (let s = 0; s < E.length; ++s)
if (r >= E[s].scale)
return E[s];
return E[E.length - 1];
}
const T = [
{ name: "year", scale: 31557513, prefix: "Y" },
{ name: "month", scale: 2635200, prefix: "M" },
{ name: "day", scale: 86400, prefix: "D" },
{ name: "hour", scale: 3600, prefix: "h" },
{ name: "minute", scale: 60, prefix: "m" },
{ name: "second", scale: 1, prefix: "s" },
{ name: "milli", scale: 1e-3, prefix: "ms" },
{ name: "micro", scale: 1e-6, prefix: "µs" },
{ name: "nano", scale: 1e-9, prefix: "ns" },
{ name: "pico", scale: 1e-12, prefix: "ps" },
{ name: "femto", scale: 1e-15, prefix: "fs" },
{ name: "atto", scale: 1e-18, prefix: "as" },
{ name: "zepto", scale: 1e-21, prefix: "zs" },
{ name: "yocto", scale: 1e-24, prefix: "ys" }
];
function et(r, t = 10) {
if (r == 0)
return T[8];
r = r / t;
for (let s = 0; s < T.length; ++s)
if (r >= T[s].scale)
return T[s];
return T[T.length - 1];
}
/**
@file Various utils.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2019
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
function A(r) {
return r && (typeof r == "string" ? document.querySelector(r) : r);
}
function G(r, t) {
if (!t || typeof t != "string")
return t;
if (r[t])
return r[t];
const s = t.toLowerCase();
for (let e in r)
if (e.toLowerCase() == s)
return r[e];
for (let e in r)
if (e.toLowerCase().startsWith(s))
return r[e];
return null;
}
function st(r) {
var t = new DOMParser().parseFromString(r, "text/html");
return t.body.textContent || "";
}
/**
@file DropZone JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2019
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
class it {
constructor(t, s) {
t = t || "#dropZone", t = A(t), this.dropZone = t, this.fileLoader = s, this.inputEls = [], this.dragEnteredEls = [], window.addEventListener("dragenter", this), window.addEventListener("dragleave", this), window.addEventListener("dropleave", this), ["dragenter", "dragover", "dragleave", "drop"].forEach((i) => this.dropZone.addEventListener(i, this, !1));
}
addInput(t) {
t = t || "#inputfile", t = A(t), t.addEventListener("change", this, !1), this.inputEls.push(t);
}
destroy() {
window.removeEventListener("dragenter", this, !1), window.removeEventListener("dragleave", this, !1), window.removeEventListener("dropleave", this, !1), ["dragenter", "dragover", "dragleave", "drop"].forEach((s) => this.dropZone.removeEventListener(s, this, !1));
for (let s of this.inputEls)
s.removeEventListener("change", this, !1);
}
allowDrag(t) {
t.dataTransfer.dropEffect = "copy", t.preventDefault();
}
handleDrop(t) {
t.preventDefault(), this.handleFileSelect(t);
}
// https://www.html5rocks.com/en/tutorials/file/dndfiles/
handleFileSelect(t) {
t.stopPropagation(), t.preventDefault();
const s = "dataTransfer" in t ? t.dataTransfer.files : t.target.files;
for (let e = 0, i; i = s[e]; e++) {
const n = new FileReader();
n.onload = (l) => {
i.fileBuffer = l.target.result, this.fileLoader(i);
}, n.readAsArrayBuffer(i);
}
}
// events
handleEvent(t) {
const s = t.type;
if (typeof this[s] == "function")
return t.preventDefault(), this[s](t);
}
dragenter(t) {
this.dragEnteredEls.push(t.target), document.documentElement.classList.add("dragdrop"), this.dropZone.classList.add("active");
}
dragover(t) {
this.dropZone.classList.add("hover"), this.allowDrag(t);
}
dragleave(t) {
const s = this.dragEnteredEls.indexOf(t.target);
s > -1 && this.dragEnteredEls.splice(s, 1), this.dragEnteredEls.indexOf(this.dropZone) < 0 && this.dropZone.classList.remove("hover"), this.dragEnteredEls.length === 0 && (document.documentElement.classList.remove("dragdrop"), this.dropZone.classList.remove("active"));
}
dropleave() {
this.dragEnteredEls.splice(0), this.dropZone.classList.remove("hover"), this.dropZone.classList.remove("active"), document.documentElement.classList.remove("dragdrop");
}
drop(t) {
const s = new Event("dropleave");
window.dispatchEvent(s), this.handleDrop(t);
}
change(t) {
this.handleFileSelect(t);
}
}
/**
@file Hexbuffer JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
function j(r, t = 2) {
return r = Math.round(r), (r + 65536).toString(16).substr(-t).toUpperCase();
}
class R {
constructor(t = "") {
this.fromString(t);
}
fromString(t) {
this.line = t.replace(/\s/g, ""), this.index = 0;
}
hasNibble() {
return this.index + 1 <= this.line.length;
}
hasByte() {
return this.index + 2 <= this.line.length;
}
hasWord() {
return this.index + 4 <= this.line.length;
}
peekNibble() {
return parseInt(this.line.substr(this.index, 1), 16);
}
peekByte() {
return parseInt(this.line.substr(this.index, 2), 16);
}
peekWord() {
return parseInt(this.line.substr(this.index, 4), 16);
}
getNibble() {
const t = parseInt(this.line.substr(this.index, 1), 16);
return this.index += 1, t;
}
getByte() {
const t = parseInt(this.line.substr(this.index, 2), 16);
return this.index += 2, t;
}
getWord() {
const t = parseInt(this.line.substr(this.index, 4), 16);
return this.index += 4, t;
}
pushNibble(t) {
this.line += j(t, 1);
}
pushByte(t) {
this.line += j(t, 2);
}
pushWord(t) {
this.line += j(t, 4);
}
}
/**
@file Histogram JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
const nt = 16;
class lt {
constructor(t) {
typeof t < "u" ? (this.count = 1, this.sum = t, this.mean = t, this.devi = 0, this.min = t, this.max = t) : (this.count = 0, this.sum = 0, this.mean = null, this.devi = 0, this.min = null, this.max = null);
}
add(t) {
this.count++, this.sum += t, this.mean = this.sum / this.count, this.min = this.min === null ? t : Math.min(t, this.min), this.max = this.max === null ? t : Math.max(t, this.max), this.devi = (this.max - this.min) / 2;
}
fuse(t) {
this.count += t.count, this.sum += t.sum, this.mean = this.sum / this.count, this.min = Math.min(this.min, t.min), this.max = Math.max(this.max, t.max), this.devi = (this.max - this.min) / 2;
}
contains(t) {
return t >= this.min && t <= this.max;
}
}
class z {
constructor(t, s = 0.2) {
this.bins = [], this.histogram_sum(t, s);
}
get length() {
return this.bins.length;
}
/// Generate a histogram (unsorted)
histogram_sum(t, s = 0.2) {
const e = t.length;
for (let i = 0; i < e; ++i) {
let n;
for (n = 0; n < this.bins.length; ++n) {
const l = t[i], o = this.bins[n].mean;
if (Math.abs(l - o) < s * Math.max(l, o)) {
this.bins[n].add(t[i]);
break;
}
}
n == this.bins.length && n < nt && this.bins.push(new lt(t[i]));
}
}
/// Delete bin from histogram
delete_bin(t) {
this.bins.splice(t, 1);
}
/// Swap two bins in histogram
swap_bins(t, s) {
if (t < this.bins.length && s < this.bins.length) {
const e = this.bins[t];
this.bins[t] = this.bins[s], this.bins[s] = e;
}
}
/// Sort histogram with mean value (order lowest to highest)
sort_mean() {
if (!(this.bins.length < 2))
for (let t = 0; t < this.bins.length - 1; ++t)
for (let s = t + 1; s < this.bins.length; ++s)
this.bins[s].mean < this.bins[t].mean && this.swap_bins(s, t);
}
/// Sort histogram with count value (order lowest to highest)
sort_count() {
if (!(this.bins.length < 2))
for (let t = 0; t < this.bins.length - 1; ++t)
for (let s = t + 1; s < this.bins.length; ++s)
this.bins[s].count < this.bins[t].count && this.swap_bins(s, t);
}
/// Fuse histogram bins with means within tolerance
fuse_bins(t = 0.2) {
if (!(this.bins.length < 2))
for (let s = 0; s < this.bins.length - 1; ++s)
for (let e = s + 1; e < this.bins.length; ++e) {
const i = this.bins[s].mean, n = this.bins[e].mean;
Math.abs(i - n) < t * Math.max(i, n) && (this.bins[s].fuse(this.bins[e]), this.delete_bin(e), e--);
}
}
/// Trim zero-width bins
trim_bins(t = 0) {
for (let s = 0; s < this.bins.length; ++s)
this.bins[s].mean <= t && this.delete_bin(s);
}
/// Find bin index
find_bin_index(t) {
for (let s = 0; s < this.bins.length; ++s)
if (this.bins[s].contains(t))
return s;
return -1;
}
/// Print a histogram
console_print() {
for (let t = 0; t < this.bins.length; ++t) {
const s = this.bins[t];
console.log(`[${t}] ${s.count} × ${s.mean.toFixed(1)} ±${s.devi.toFixed(1)} µs [${s.min};${s.max}]`);
}
}
string_print(t = ", ") {
let s = [];
for (let e = 0; e < this.bins.length; ++e) {
const i = this.bins[e];
s.push(`${i.count}× ${i.mean.toFixed(1)} <small>±${i.devi.toFixed(1)}</small> µs`);
}
return s.join(t);
}
}
class ot {
constructor(t, s = 0.2) {
this.analyse_pulses(t, s), this.create_rfraw(t);
}
/// Create histograms from pulse data
analyse_pulses(t, s, e = 0.2) {
this.pulses = [], this.gaps = [], this.periods = [], this.pulse_sum = 0, this.gap_sum = 0;
for (let n = 0; n < t.length - 2; n += 2) {
const l = t[n], o = t[n + 1];
this.pulses.push(l), this.gaps.push(o), this.periods.push(l + o), this.pulse_sum += l, this.gap_sum += o;
}
const i = t[t.length - 2];
this.pulses.push(i), this.pulse_sum += i, this.pulse_gap_ratio = this.pulse_sum / this.gap_sum, this.pulse_gap_skew = this.pulse_gap_ratio - 1, this.hist_pulses = new z(this.pulses, e), this.hist_gaps = new z(this.gaps, e), this.hist_periods = new z(this.periods, e), this.hist_timings = new z(t, e), this.hist_pulses.trim_bins(e), this.hist_gaps.trim_bins(e), this.hist_periods.trim_bins(e), this.hist_timings.trim_bins(e), this.hist_pulses.fuse_bins(e), this.hist_gaps.fuse_bins(e), this.hist_periods.fuse_bins(e), this.hist_timings.fuse_bins(e);
}
guess() {
const t = this.hist_pulses, s = this.hist_gaps, e = this.hist_periods;
if (t.sort_mean(), s.sort_mean(), t.bins.length > 0 && t.bins[0].mean == 0 && t.delete_bin(0), this.pulses.length == 1)
return {
name: "Single pulse detected. Probably Frequency Shift Keying or just noise..."
};
if (t.length == 1 && s.length == 1)
return {
name: "Un-modulated signal. Maybe a preamble..."
};
if (t.length == 1 && s.length > 1)
return {
name: "Pulse Position Modulation with fixed pulse width",
modulation: "PPM",
short: s.bins[0].mean,
long: s.bins[1].mean,
gap: s.bins[1].max * 1.2,
// Set limit above next lower gap
reset: s.bins[s.length - 1].max * 1.2
// Set limit above biggest gap
};
if (t.length == 2 && s.length == 1) {
const i = t.bins[0].mean, n = t.bins[1].mean;
return {
name: "Pulse Width Modulation with fixed gap",
modulation: "PWM",
short: i,
long: n,
tolerance: (n - i) * 0.4,
reset: s.bins[s.length - 1].max * 1.2
// Set limit above biggest gap
};
} else if (t.length == 2 && s.length == 2 && e.length == 1) {
const i = t.bins[0].mean, n = t.bins[1].mean;
return {
name: "Pulse Width Modulation with fixed period",
modulation: "PWM",
short: i,
long: n,
tolerance: (n - i) * 0.4,
reset: s.bins[s.length - 1].max * 1.2
// Set limit above biggest gap
};
} else if (t.length == 2 && s.length == 2 && e.length == 3) {
const i = t.bins[0].mean;
return {
name: "Manchester coding (PCM)",
modulation: "MC",
short: i,
// Assume shortest pulse is half period
long: i,
// Not used
reset: s.bins[s.length - 1].max * 1.2
// Set limit above biggest gap
};
} else if (t.length == 2 && s.length >= 3) {
const i = t.bins[0].mean, n = t.bins[1].mean;
return {
name: "Pulse Width Modulation with multiple packets",
modulation: "PWM",
short: i,
long: n,
gap: s.bins[1].max * 1.2,
// Set limit above second gap
tolerance: (n - i) * 0.4,
reset: s.bins[s.length - 1].max * 1.2
// Set limit above biggest gap
};
} else {
if (t.length >= 3 && s.length >= 3 && Math.abs(t.bins[1].mean - 2 * t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(t.bins[2].mean - 3 * t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(s.bins[0].mean - t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(s.bins[1].mean - 2 * t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(s.bins[2].mean - 3 * t.bins[0].mean) <= t.bins[0].mean / 8)
return {
name: "Pulse Code Modulation (Not Return to Zero)",
modulation: "PCM",
short: t.bins[0].mean,
// Shortest pulse is bit width
long: t.bins[0].mean,
// Bit period equal to pulse length (NRZ)
reset: t.bins[0].mean * 1024
// No limit to run of zeros...
};
if (t.length == 3) {
t.sort_count();
const i = t.bins[1].mean, n = t.bins[2].mean, l = i < n ? i : n, o = i < n ? n : i;
return {
name: "Pulse Width Modulation with sync/delimiter",
modulation: "PWM",
short: l,
long: o,
sync: t.bins[0].mean,
// Set to lowest count pulse width
reset: s.bins[s.length - 1].max * 1.2
// Set limit above biggest gap
};
} else
return {
name: "No clue..."
};
}
}
create_rfraw(t) {
const s = this.hist_timings;
if (s.bins.length < 1 || s.bins.length > 8 || t.length > 494)
return "";
let e = new R();
for (let l of s.bins)
e.pushWord(l.mean);
for (let l = 0; l < t.length - 1; l += 2) {
const o = t[l], a = t[l + 1], h = s.find_bin_index(o);
h >= 0 ? e.pushNibble(h | 8) : o == 0 || console.error("RfRaw encoding mark bucket not found:", o, h);
const c = s.find_bin_index(a);
c >= 0 ? e.pushNibble(c) : a == 0 || console.error("RfRaw encoding space bucket not found:", a, c);
}
e.line.length % 2 && e.pushNibble(0), e.pushByte(85);
let i = new R();
i.pushByte(170), i.pushByte(176), i.pushByte(2 + e.line.length / 2 - 1), i.pushByte(s.bins.length), i.pushByte(1);
let n = new R();
n.pushByte(170), n.pushByte(177), n.pushByte(s.bins.length), this.rfrawB0 = i.line + e.line, this.rfrawB1 = n.line + e.line;
}
console_log() {
const t = this.guess();
console.log("Analyzing pulses..."), console.log(`Total count: ${this.pulses.length}`), console.log("Pulse width distribution:"), this.hist_pulses.console_print(), console.log("Gap width distribution:"), this.hist_gaps.console_print(), console.log("Pulse period distribution:"), this.hist_periods.console_print(), console.log("Pulse timing distribution:"), this.hist_timings.console_print(), console.log(`DC bias (Pulse/Gap skew): ${(this.pulse_gap_skew * 100).toFixed(1)}`), console.log("Guessing modulation:"), console.log(t);
}
print_plain(t) {
const s = this.guess();
t.innerHTML = `
<div>Pulses: ${this.hist_pulses.string_print()}</div>
<div>Gaps: ${this.hist_gaps.string_print()}</div>
<div>Periods: ${this.hist_periods.string_print()}</div>
<div>Timings: ${this.hist_timings.string_print()}</div>
<div>${s.name}</div>
`;
}
/*
const locale = new Intl.NumberFormat().resolvedOptions().locale
const formatter = new Intl.NumberFormat(locale, {
style: 'percent',
signDisplay: 'exceptZero',
maximumFractionDigits: 1,
})
formatter.format(0.5)
*/
print(t, s) {
const e = this.guess();
t && (t.innerHTML = `<table>
<tr><th>Pulses</th><td>${this.hist_pulses.string_print("</td><td>")}</td></tr>
<tr><th>Gaps</th><td>${this.hist_gaps.string_print("</td><td>")}</td></tr>
<tr><th>Periods</th><td>${this.hist_periods.string_print("</td><td>")}</td></tr>
<tr><th>Timings</th><td>${this.hist_timings.string_print("</td><td>")}</td></tr>
</table>
`), s && (s.innerHTML = `
<div><small>DC bias (Pulse/Gap skew): ${(this.pulse_gap_skew * 100).toFixed(1)}%</small><br>
Guessing modulation: <strong>${e.name}</strong><br>
modulation: <strong>${e.modulation}</strong>
short: <strong>${e.short ? e.short.toFixed(1) : "-"}</strong>
long: <strong>${e.long ? e.long.toFixed(1) : "-"}</strong>
sync: <strong>${e.sync ? e.sync.toFixed(1) : "-"}</strong>
gap: <strong>${e.gap ? e.gap.toFixed(1) : "-"}</strong>
reset: <strong>${e.reset ? e.reset.toFixed(1) : "-"}</strong><br>
<small>RfRaw (rx): <strong>${this.rfrawB1 ? this.rfrawB1 : "-"}</strong></small><br>
<small>RfRaw (tx): <strong>${this.rfrawB0 ? this.rfrawB0 : "-"}</strong></small>
</div>
`);
}
}
/**
@file Bitbuffer JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
class M {
constructor(t = [], s = 0) {
Array.isArray(t) ? (this.bytes = t, this.len = s || t.length * 8) : this.fromString(t);
}
fromString(t) {
this.bytes = [], this.len = 0;
let s = -1;
if (t = t.trim(), t.startsWith("{")) {
const e = t.indexOf("}");
if (e < 0) return;
s = parseInt(t.slice(1), 10), t = t.slice(e + 1);
}
t.startsWith("0x") && (t = t.slice(2));
for (let e of t) {
const i = parseInt(e, 16);
this.pushNibble(i);
}
s >= 0 && (this.len = s);
}
pushZero() {
this.push(0);
}
pushOne() {
this.push(1);
}
pushSymbol(t) {
t == "0" ? this.push(0) : t == "1" && this.push(1);
}
push(t) {
t = t ? 128 : 0, this.bytes[~~(this.len / 8)] |= t >> this.len % 8, this.len += 1;
}
pushNibble(t) {
for (let s = 3; s >= 0; --s)
this.push(t >> s & 1);
}
pushByte(t) {
for (let s = 7; s >= 0; --s)
this.push(t >> s & 1);
}
pushBreak() {
const t = ~~((this.len + 7) / 8);
this.bytes[t] = -1, this.len = (t + 1) * 8;
}
toBitArray() {
let t = [];
for (let s = 0; s < this.len; ++s) {
const i = (this.bytes[~~(s / 8)] || 0) >> 7 - s % 8 & 1;
t.push(i);
}
return t;
}
toHexString() {
let t = `{${this.len}}`;
for (let s = 0; s < this.len; s += 8) {
const e = this.bytes[~~(s / 8)] || 0;
e < 0 ? t += " / " : (t += " ", t += (e >> 4).toString(16).toUpperCase(), s + 4 < this.len && (t += (e & 15).toString(16).toUpperCase()));
}
return t;
}
}
/**
@file Pulse Slicer JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
function U(r, t) {
return t.modulation == "PCM" ? at(r, t) : t.modulation == "MC" ? ut(r, t) : t.modulation == "PPM" ? ft(r, t) : t.modulation == "PWM" ? ct(r, t) : t.modulation == "DM" ? pt(r, t) : t.modulation == "NRZI" ? dt(r, t) : t.modulation == "CMI" ? mt(r, t) : t.modulation == "PIWM" ? gt(r, t) : [];
}
function at(r, t) {
return !t.long || t.long == t.short ? rt(r, t) : ht(r, t);
}
function rt(r, t) {
const s = t.short, e = t.gap, i = new M();
let n = [], l = 0;
for (let o = 0; o < r.length; o += 1) {
const a = 1 - o % 2, h = r[o];
if (e && h > e)
i.pushBreak();
else {
const c = ~~(h / s + 0.5);
for (let f = 0; f < c; ++f)
n.push([l + h / c * f, l + h / c * (f + 1), a]), i.push(a);
}
l += h;
}
return { hints: n, bits: i };
}
function ht(r, t) {
const s = t.short, e = t.long, i = t.gap, n = s * 0.5, l = s * 1.5, o = new M();
let a = [], h = 0;
for (let c = 0; c < r.length; c += 2) {
const f = r[c], d = r[c + 1];
if (f < n || f > l) {
o.pushBreak(), h += f + d;
continue;
}
let g = f * e / s, b = d + f - g;
if (b < e / 2 && (g = f + d, b = 0), a.push([h, h + g, "1"]), o.pushOne(), h += g, i && d > i) {
o.pushBreak(), h += b;
continue;
}
const x = ~~(b / e + 0.5);
for (let y = 0; y < x; ++y)
a.push([h + b * y / x, h + b * (y + 1) / x, "0"]), o.pushZero();
h += b;
}
return { hints: a, bits: o };
}
function ft(r, t) {
const s = t.short, e = t.long, i = t.sync, n = t.gap, l = s * 0.5, o = s * 1.5, a = e * 0.5, h = e * 1.5, c = i * 0.5, f = i * 1.5, d = new M();
let g = [], b = 0;
for (let x = 0; x < r.length; x += 2) {
const y = r[x], _ = r[x + 1], k = b;
b += y + _, _ > l && _ < o ? (g.push([k, b, "1"]), d.pushOne()) : _ > a && _ < h ? (g.push([k, b, "0"]), d.pushZero()) : _ > c && _ < f ? (g.push([k, b, "X"]), d.pushBreak()) : n && _ > n && d.pushBreak();
}
return { hints: g, bits: d };
}
function ct(r, t) {
const s = t.short, e = t.long, i = t.sync, n = t.gap, l = s * 0.5, o = s * 1.5, a = e * 0.5, h = e * 1.5, c = i * 0.5, f = i * 1.5, d = new M();
let g = [], b = 0;
for (let x = 0; x < r.length; x += 2) {
const y = r[x], _ = r[x + 1], k = b;
let P = b + y + _;
_ > n && (P = b + y + n), b += y + _, y > l && y < o ? (g.push([k, P, "1"]), d.pushOne()) : y > a && y < h ? (g.push([k, P, "0"]), d.pushZero()) : y > c && y < f && (g.push([k, P, "X"]), d.pushBreak()), n && _ > n && d.pushBreak();
}
return { hints: g, bits: d };
}
function N(r, t, s) {
for (let e = t; e < r.length; e += 2) {
if (~~(r[e] / s + 0.5) > 1) return 0;
if (~~(r[e + 1] / s + 0.5) > 1) return 1;
}
return 0;
}
function ut(r, t) {
const s = t.short, e = new M();
let i = [], n = N(r, 0, s), l = 0, o = 0;
for (let a = 0; a < r.length; a += 2) {
const h = r[a], c = ~~(h / s + 0.5), f = r[a + 1], d = ~~(f / s + 0.5);
c == 1 ? (n ? o = l : (i.push([o, l + h, "0"]), e.pushZero(), o = l + h), n = !n) : c == 2 ? (n ? (e.pushBreak(), o = l + h / 2) : (i.push([o, l + h / 2, "0"]), e.pushZero(), o = l + h / 2), n = !1) : c > 2 && (n || (i.push([o, l + h / c, "0"]), e.pushZero()), o = l + h - h / c, e.pushBreak(), n = N(r, a + 1, s)), d == 1 ? (n ? o = l + h : (i.push([o, l + h + f, "1"]), e.pushOne(), o = l + h + f), n = !n) : d == 2 ? (n ? (e.pushBreak(), o = l + h + f / 2) : (i.push([o, l + h + f / 2, "1"]), e.pushOne(), o = l + h + f / 2), n = !1) : d > 2 && (n || (i.push([o, l + h + f / d, "1"]), e.pushOne()), o = l + h + f - f / d, e.pushBreak(), n = N(r, a + 1, s)), l += h + f;
}
return { hints: i, bits: e };
}
function pt(r, t) {
const s = t.short, e = new M();
let i = [], n = 0, l = null;
for (let o = 0; o < r.length; o += 2) {
const a = r[o], h = ~~(a / s + 0.5), c = r[o + 1], f = ~~(c / s + 0.5);
!l && h == 1 && f == 1 ? (i.push([n, n + a + c, "0"]), e.pushZero()) : h == 1 && f == 1 ? (i.push([l, n + a, "0"]), e.pushZero(), l = n + a) : l && h == 1 && f == 2 ? (i.push([l, n + a, "0"]), e.pushZero(), i.push([n + a, n + a + c, "1"]), e.pushOne(), l = null) : h == 2 && f == 1 ? (i.push([n, n + a, "1"]), e.pushOne(), l = n + a) : h == 2 && f == 2 ? (i.push([n, n + a, "1"]), e.pushOne(), i.push([n + a, n + a + c, "1"]), e.pushOne()) : !l && h == 1 ? (i.push([n, n + a + s, "0"]), e.pushZero(), e.pushBreak()) : !l && h == 2 ? (i.push([n, n + a, "1"]), e.pushOne(), e.pushBreak()) : (l && (i.push([l, l + s * 2, "0"]), e.pushZero()), l = null, e.pushBreak()), n += a + c;
}
return { hints: i, bits: e };
}
function dt(r, t) {
const s = t.short, e = new M();
let i = [], n = 0, l = 0;
for (let o = 0; o < r.length; o += 1) {
const a = r[o], h = ~~(a / s + 0.5);
l && (i.push([l, n + s / 2, "1"]), e.pushOne()), l = n + s / 2;
for (let c = 1; c < h; ++c)
i.push([l, l + a / h, "0"]), e.pushZero(), l += a / h;
n += a;
}
return { hints: i, bits: e };
}
function mt(r, t) {
const s = t.short, e = new M();
let i = [], n = 0, l = null;
for (let o = 0; o < r.length; o += 2) {
const a = r[o], h = ~~(a / s + 0.5), c = r[o + 1], f = ~~(c / s + 0.5);
h == 1 && f == 1 ? (l || (l = n - a), i.push([l, n + a, "0"]), e.pushZero(), l = n + a) : h == 1 && f == 2 ? (l || (l = n - a), i.push([l, n + a, "0"]), e.pushZero(), l = n + a + c, i.push([n + a, l, "1"]), e.pushOne()) : h == 1 && f == 3 ? (l || (l = n - a), i.push([l, n + a, "0"]), e.pushZero(), l = n + a + c * 2 / 3, i.push([n + a, l, "1"]), e.pushOne()) : h == 2 && f == 1 ? (i.push([l, n + a, "1"]), e.pushOne(), l = n + a) : h == 2 && f == 2 ? (i.push([l, n + a, "1"]), e.pushOne(), l = n + a + c, i.push([n + a, l, "1"]), e.pushOne()) : h == 2 && f == 3 ? (i.push([l, n + a, "1"]), e.pushOne(), l = n + a + c * 2 / 3, i.push([n + a, l, "1"]), e.pushOne()) : h == 3 && f == 1 ? (i.push([l, n + a / 3, "0"]), e.pushZero(), i.push([n + a / 3, n + a, "1"]), e.pushOne(), l = n + a) : h == 3 && f == 2 ? (i.push([l, n + a / 3, "0"]), e.pushZero(), i.push([n + a / 3, n + a, "1"]), e.pushOne(), l = n + a + c, i.push([n + a, l, "1"]), e.pushOne()) : h == 3 && f == 3 ? (i.push([l, n + a / 3, "0"]), e.pushZero(), i.push([n, n + a / 3, "1"]), e.pushOne(), i.push([n + a / 3, n + a, "1"]), e.pushOne(), i.push([n + a, n + a + c * 3 / 2, "1"]), e.pushOne(), l = n + a + c * 3 / 2) : h == 1 ? (i.push([l, n + a, "0"]), e.pushZero(), e.pushBreak(), l = n + a) : h == 2 ? (i.push([l, n + a, "1"]), e.pushOne(), e.pushBreak(), l = n + a) : e.pushBreak(), n += a + c;
}
return { hints: i, bits: e };
}
function gt(r, t) {
const s = t.short, e = new M();
let i = [], n = 0;
for (let l = 0; l < r.length; l += 1) {
const o = r[l], a = ~~(o / s + 0.5);
a == 1 ? (i.push([n, n + o, "1"]), e.pushOne()) : a == 2 ? (i.push([n, n + o, "0"]), e.pushZero()) : e.pushBreak(), n += o;
}
return { hints: i, bits: e };
}
/**
@file Pulse Builder JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
function bt(r) {
return Array.isArray(r) ? r : r.split(/\D/).map((s) => parseInt(s, 10));
}
class wt {
constructor(t) {
Object.assign(this, t), this.build();
}
build() {
this.pulses = [], this.warmup_raw && this.addRaw(this, this.warmup_raw);
const t = this.repeat || 1;
for (let s = 0; s < t; ++s) {
if (this.gap && s > 0 && (this.pulses.push(0), this.pulses.push(this.gap)), this.preamble) {
const i = new M(this.preamble);
this.addCode(this, i);
}
if (this.preamble_raw && this.addRaw(this, this.preamble_raw), this.sync_raw && this.addRaw(this, this.sync_raw), this.syncword) {
const i = new M(this.syncword);
this.addCode(this, i);
}
const e = new M(this.payload);
if (this.addCode(this, e), this.postamble) {
const i = new M(this.postamble);
this.addCode(this, i);
}
this.postamble_raw && this.addRaw(this, this.postamble_raw);
}
return this.coalesce(), this.jitter && this.addJitter(this), this;
}
addCode(t, s) {
return t.modulation == "PCM" ? this.buildPCM(t, s) : t.modulation == "MC" ? this.buildMC(t, s) : t.modulation == "PPM" ? this.buildPPM(t, s) : t.modulation == "PWM" ? this.buildPWM(t, s) : t.modulation == "DM" ? this.buildDM(t, s) : t.modulation == "NRZI" ? this.buildNRZI(t, s) : t.modulation == "CMI" ? this.buildCMI(t, s) : t.modulation == "PIWM" ? this.buildPIWM(t, s) : console.log("Unknown modulation!"), t;
}
addRaw(t, s) {
return s = bt(s), s.length % 2 && s.push(0), t.pulses = t.pulses.concat(s), t;
}
coalesce() {
let t = [], s = 0;
for (let e = 0; e < this.pulses.length; e += 2) {
const i = this.pulses[e] || 0, n = this.pulses[e + 1] || 0;
!i && !n || (!s && !i && t.length ? t[t.length - 1] += ~~n : n ? (t.push(~~(i + s)), t.push(~~n), s = 0) : s += i);
}
return s && (t.push(~~s), t.push(0)), this.pulses = t, this;
}
addJitter(t) {
const s = t.jitter;
let e = [];
for (let i = 0; i < t.pulses.length; ++i) {
const n = t.pulses[i];
if (n == 0)
e.push(n);
else if (s < 1) {
const l = Math.random() * 2 * s + 1 - s;
e.push(~~(n * l));
} else {
const l = Math.random() * 2 * s - s;
e.push(~~(n + l));
}
}
return t.pulses = e, t;
}
// returned hints array contains triples of start,end,symbol
/// Pulse-code modulation (PCM)
/// https://en.wikipedia.org/wiki/Pulse-code_modulation
/// either NRZ or RZ
buildPCM(t, s) {
return !t.long || t.long == t.short ? this.buildNRZ(t, s) : this.buildRZ(t, s);
}
/// NRZ(L) NRZL Non-return-to-zero level
/// https://en.wikipedia.org/wiki/Non-return-to-zero
buildNRZ(t, s) {
const e = t.short;
let i = t.pulses;
const n = s.toBitArray();
for (let l = 0; l < n.length; ++l)
n[l] ? (i.push(e), i.push(0)) : (i.push(0), i.push(e));
return t;
}
/// Return-to-zero level
/// https://en.wikipedia.org/wiki/Return-to-zero
buildRZ(t, s) {
const e = t.short, i = t.long, n = i - e;
let l = t.pulses;
const o = s.toBitArray();
for (let a = 0; a < o.length; ++a)
o[a] ? (l.push(e), l.push(n)) : (l.push(0), l.push(i));
return t;
}
/// Pulse-position modulation (PPM)
/// https://en.wikipedia.org/wiki/Pulse-position_modulation
buildPPM(t, s) {
const e = t.pulse || ~~(t.short / 3), i = t.short, n = t.long;
let l = t.pulses;
const o = s.toBitArray();
for (let a = 0; a < o.length; ++a)
l.push(e), o[a] ? l.push(i) : l.push(n);
return t;
}
/// Pulse-width modulation (PWM)
/// https://en.wikipedia.org/wiki/Pulse-width_modulation
buildPWM(t, s) {
const e = t.short, i = t.long, n = t.short_gap || t.long, l = t.long_gap || t.short;
let o = t.pulses;
const a = s.toBitArray();
for (let h = 0; h < a.length; ++h)
a[h] ? (o.push(e), o.push(n)) : (o.push(i), o.push(l));
return t;
}
/// Manchester code (MC)
/// https://en.wikipedia.org/wiki/Manchester_code
buildMC(t, s) {
const e = t.short;
let i = t.pulses;
const n = s.toBitArray();
for (let l = 0; l < n.length; ++l)
n[l] ? (i.push(e), i.push(e)) : (i.push(0), i.push(e), i.push(e), i.push(0));
return t;
}
/// Differential Manchester Encoding (DM) aka Biphase Mark Code (CC)
/// https://en.wikipedia.org/wiki/Differential_Manchester_encoding
buildDM(t, s) {
const e = t.short;
let i = t.pulses;
const n = s.toBitArray();
let l = !1;
for (let o = 0; o < n.length; ++o) {
const a = n[o];
!a && !l ? (i.push(e), i.push(e)) : !a && l ? (i.push(0), i.push(e), i.push(e), i.push(0)) : a && !l ? (i.push(e * 2), i.push(0), l = !l) : a && l && (i.push(0), i.push(e * 2), l = !l);
}
return t;
}
/// Non-return-to-zero, inverted (NRZI) https://en.wikipedia.org/wiki/Non-return-to-zero#NRZI
/// NRZ(I) NRZI Non-return-to-zero inverted Refers to either an NRZ(M) or NRZ(S) code.
/// NRZ(M) NRZM Non-return-to-zero mark Serializer mapping {0: constant, 1: toggle}.
/// NRZ(S) NRZS Non-return-to-zero space Serializer mapping {0: toggle, 1: constant}.
/// A 1 is transmitted as a transition, and a 0 is transmitted as no transition.
buildNRZI(t, s) {
const e = t.short;
let i = t.pulses;
const n = s.toBitArray();
let l = !n[0];
for (let o = 0; o < n.length; ++o)
n[o] && (l = !l), l ? (i.push(e), i.push(0)) : (i.push(0), i.push(e));
return t;
}
/// Coded Mark Inversion (CMI) https://en.wikipedia.org/wiki/Coded_mark_inversion
/// encodes zero bits as a half bit time of zero followed by a half bit time of one,
/// and one bits are encoded as a full bit time of a constant level,
/// the level used for one bits alternates each time one is coded.
buildCMI(t, s) {
const e = t.short;
let i = t.pulses;
const n = s.toBitArray();
let l = !1;
for (let o = 0; o < n.length; ++o)
n[o] ? l ? (i.push(0), i.push(e * 2), l = !l) : (i.push(e * 2), i.push(0), l = !l) : (i.push(0), i.push(e), i.push(e), i.push(0));
return t;
}
/// Pulse-Interval-Width Modulation (PIWM)
/// Exotic differential coding
buildPIWM(t, s) {
const e = t.short;
let i = t.pulses;
const n = s.toBitArray();
for (let l = 0; l < n.length; ++l)
n[l] ? i.push(e) : i.push(e * 2);
return n.length % 2 && i.push(e * 3), t;
}
}
/**
@file RfRaw JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
class S {
static isRfRaw(t) {
return t.trim().startsWith("AA");
}
static getPulses(t) {
let s = [];
t = t.replace(/5\s*5[+\s]+A\s*A\s*B/g, "55;AAB");
const e = t.split(";");
for (let i of e) {
const n = S.getCodePulses(i);
s = s.concat(n);
}
return s;
}
static getCodePulses(t) {
console.log("parsing rfraw data: ", t);
let s = [];
const e = new R(t);
if (e.getByte() != 170)
return s;
const n = e.getByte();
let l, o, a;
if (n == 177)
o = e.getByte();
else if (n == 176)
l = e.getByte(), o = e.getByte(), a = e.getByte();
else
return s;
let h = [];
for (let g = 0; g < o; ++g)
h[g] = e.getWord();
console.log(`buckets: ${o} (len ${l} repeats ${a}): `, h);
let c = !1;
const f = e.index;
for (; e.hasNibble(); )
if (e.getNibble() > 7) {
c = !0;
break;
}
if (e.index = f, console.log("RfRaw v3 format: ", c), !c) {
for (s.push(0); e.hasNibble() && e.peekByte() != 85; ) {
const g = e.getNibble();
if (g > o) break;
s.push(h[g]);
}
return s.push(0), a ? new Array(a).fill(s).flat() : s;
}
let d = !0;
for (; e.hasNibble() && e.peekByte() != 85; ) {
const g = e.getNibble(), b = g & 7;
if (b > o) break;
g & 8 ? (d || s.push(0), s.push(h[b]), d = !1) : (d && s.push(0), s.push(h[b]), d = !0);
}
return d || s.push(0), a ? new Array(a).fill(s).flat() : s;
}
}
/**
@file BroadlinkRM JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2025
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
class K {
static isBroadlinkRM(t) {
try {
bin = atob(t);
} catch {
return !1;
}
if (bin.length < 4)
return !1;
const s = bin.charCodeAt(0);
return s == 178 || s == 177 || s == 215 ? !0 : (s == 38, !1);
}
static decodeData(t) {
let s = {
pulses: [],
frequency: 0,
repeats: 0,
errors: ""
}, e = "";
try {
e = atob(t);
} catch {
return s.errors = "Invalid Base64", console.warn(s.errors), s;
}
if (e.length < 4)
return s.errors = "Invalid data header", console.warn(s.errors), s;
const i = e.charCodeAt(0);
if (i == 178 || i == 177)
s.frequency = 43392e4;
else if (i == 215)
s.frequency = 315e6;
else return i == 38 ? (s.errors = "Unsupported code type", console.warn(s.errors, i), s) : (s.errors = `Invalid code type (${i.toString(16)})`, console.warn(s.errors, i), s);
s.repeats = e.charCodeAt(1), (e.charCodeAt(2) | e.charCodeAt(3) << 8) + 2 != e.length && (s.errors = "Code length mismatch", console.warn(s.errors));
for (let l = 4; l < e.length; l++) {
let o = e.charCodeAt(l);
o == 0 && (o = e.charCodeAt(++l) | e.charCodeAt(++l) << 8), s.pulses.push(o);
}
return s;
}
}
/**
@file Pulse Data Viewer JS.
@author Christian W. Zuckschwerdt <zany@triq.net>
@copyright Christian W. Zuckschwerdt, 2020
@license
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
*/
function xt(r) {
return new Promise((t, s) => {
var e = new FileReader();
e.onerror = () => {
s(e.error);
}, e.onload = () => {
t(e.result);
}, e.readAsText(r);
});
}
window.File && window.FileReader && window.FileList && window.Blob || alert("The File APIs are not fully supported in this browser.");
let J;
window.addEventListener("resize", function() {
clearTimeout(J), J = setTimeout(function() {
const r = new Event("debouncedResize");
window.dispatchEvent(r);
}, 250);
}, !1);
const yt = {
PCM: "PCM",
PWM: "PWM",
PPM: "PPM",
MC: "MC",
DM: "DM",
NRZI: "NRZI",
CMI: "CMI",
PIWM: "PIWM"
}, _t = {
ManchesterGET: "Manchester-GET",
ManchesterIEEE: "Manchester-IEEE",
DifferentialManchester: "Differential-Manchester"
}, vt = [
{ text: "off", value: "" },
{ text: "PCM", value: "PCM" },
{ text: "PWM", value: "PWM" },
{ text: "PPM", value: "PPM" },
{ text: "MC", value: "MC" },
{ text: "DM", value: "DM" },
{ text: "NRZI", value: "NRZI" },
{ text: "CMI", value: "CMI" },
{ text: "PIWM", value: "PIWM" }
], Mt = [
{ text: "off", value: "" },
{ text: "Manchester G.E.Thomas", value: "ManchesterGET" },
{ text: "Manchester IEEE", value: "ManchesterIEEE" },
{ text: "Differential Manchester", value: "DifferentialManchester" }
];
class kt {
/**
Get all possible slicer options.
*/
static get slicerOptions() {
return vt;
}
/**
Get all possible coding options.
*/
static get codingOptions() {
return Mt;
}
/**
Initialize a new Pulseplot.
@param {Object} options
@param {number} [options.width=0] - canvas width in px, 0 = auto
@param {number} [options.height=150] - canvas height in px
@param {number} [options.yHi=50] - Marks y-height
@param {number} [options.yLo=100] - Spaces y-height
@param {number} [options.yHintLo=115] - Hints y-bottom
@param {number} [options.yHintHi=35] - Hints y-top
@param {number} [options.yHintText=125] - Hints text y-offset
@param {number} [options.scroll=0] - Initial scroll offset
@param {number} [options.zoom=1] - Initial zoom factor
@param {boolean} [options.unlockable=false] - Enable unlockable
@param {boolean} [options.scrollzoom=false] - Enable scrollZoom
@param {Slicers} [options.slicer] - Slicer to apply, unset = auto
@param {string|Object} options.parent - Container element or selector
@param {string|Object} [options.renderInfo] - Info element or selector
@param {string|Object} [options.theme] - Theme name or options, see {@link setTheme}
*/
constructor(t = {}) {
this.constrain = {
histogram: (i) => parseInt(i, 10) || 0,
slicer: (i) => G(yt, i) || "",
coding: (i) => G(_t, i) || ""
};
const s = {
width: 0,
// 0 = auto
scroll: 0,
zoom: 1
};
this.setTheme(t.theme);
const e = this.heightDefaults(t.height);
t = Object.assign({}, s, e, t), this.tag = Math.random().toString().substring(2), this.parent = A(t.parent), this.data = t.data || {}, this.width = t.width || this.parent.clientWidth, this.height = t.height, this.yHi = t.yHi, this.yLo = t.yLo, this.yHintLo = t.yHintLo, this.yHintHi = t.yHintHi, this.yHintText = t.yHintText, this.scroll = t.scroll, this.zoom = t.zoom, this.slicer = t.slicer, this.renderInfo = t.renderInfo, this.opts = t, t.data && this.setData(t.data, t.fileinfo), t.unlockable && this.enableUnlockable(), t.scrollzoom && this.enableScrollZoom(), window.ResizeObserver ? (this.resizeObserver = new ResizeObserver(() => {
this.redrawCanvas();
}), this.resizeObserver.observe(this.parent)) : window.addEventListener("debouncedResize", this, !1);
}
/**
Set a color theme.
@param {string|Object} options - Theme name or theme options
@param {string} [options.hiStroke='#3c3'] - Mark stroke color
@param {string} [options.hiFill='rgba(51,204,51,0.1)'] - Mark fill color
@param {number} [options.hiLine=3] - Mark line width
@param {number[]} [options.hiDash=[]] - Mark dash
@param {string} [options.loStroke='#c33'] - Space stroke color
@param {string} [options.loFill='rgba(204,204,204,0.1)'] - Space fill color
@param {number} [options.loLine=3] - Space line width
@param {number[]} [options.loDash=[]] - Space dash
@param {string} [options.edgeStroke='#ccc'] - Edges stroke color
@param {number} [options.edgeLine=1] - Edges line width
@param {number[]} [options.edgeDash=[]] - Edge dash
@param {string} [options.textFill='#666'] - Texts color
@param {string} [options.dotFill='#999'] - Dots color
@param {string} [options.hintStroke='#aaf'] - Hints stroke color
@param {number[]} [options.hintDash=[3, 2]] - Hints dash
@param {number} [options.hintLine=1] - Hints line width
@param {string} [options.hintAltStroke='#f99'] - Error hints stroke color
@param {number[]} [options.hintAltDash=[3, 2]] - Error hints dash
@param {number} [options.hintAltLine=3] - Error hints line width
@param {string} [options.hintFill='#448'] - Hints text color
@param {string} [options.timeLabelFill='#333'] - Time label text color
@param {string} [options.timeMinorFill='#CCC'] - Time minor tick color
@param {string} [options.timeMajorFill='#666'] - Time major tick color
@param {string} [options.font='10px sans-serif'] - Text font
*/
setTheme(t) {
let s = {
hiStroke: "#3c3",
hiFill: "rgba(51,204,51,0.1)",
hiLine: 3,
hiDash: [],
loStroke: "#c33",
loFill: "rgba(204,204,204,0.1)",
loLine: 3,
loDash: [],
edgeStroke: "#ccc",
edgeLine: 1,
edgeDash: [],
textFill: "#666",
dotFill: "#999",
hintStroke: "#aaf",
hintDash: [3, 2],
hintLine: 1,
hintAltStroke: "#f99",
hintAltDash: [3, 2],
hintAltLine: 3,
hintFill: "#448",
timeLabelFill: "#333",
timeMinorFill: "#CCC",
timeMajorFill: "#666",
font: "10px sans-serif"
};
const e = {};
return e.dark = {
hiStroke: "#b0b",
hiFill: "rgba(191,0,191,0.2)",
hiLine: 3,
hiDash: [],
loStroke: "#27c",
loFill: "rgba(102,102,102,0.2)",
loLine: 3,
loDash: [],
edgeStroke: "#555",
edgeLine: 1,
edgeDash: [],
textFill: "#ccc",
dotFill: "#555",
hintStroke: "#555",
hintDash: [3, 2],
hintLine: 1,
hintAltStroke: "#c55",
hintAltDash: [3, 2],
hintAltLine: 3,
hintFill: "#ccc",
timeLabelFill: "#DDD",
timeMinorFill: "#666",
timeMajorFill: "#999",
font: "10px sans-serif"
}, typeof t == "string" && e[t] && (s = e[t]), this.theme = Object.assign({}, s, t), this.redrawCanvas();
}
/**
Get all height options based on total height value.
@param {number} [height=120] - Total height
@returns {Object} - All height option defaults
*/
heightDefaults(t = 120) {
const s = parseInt(this.theme.font, 10) || 10, e = 22 + ~~(t / 10), i = 1 + ~~(t / 10);
return {
height: t,
yHi: e + i,
yLo: t - s - i,
yHintLo: t - s,
yHintHi: e,
yHintText: t
};
}
/**
Enables mouse and touch scroll zoom on double click.
*/
enableUnlockable() {
if (this.unlockable)
return;
this.unlockable = !0;
const t = this.canvasNode;
["dblclick"].forEach((e) => t.addEventListener(e, this, !1));
}
/**
Disables mouse and touch scroll zoom on double click.
*/
disableUnlockable() {
if (!this.unlockable)
return;
this.unlockable = !1;
const t = this.canvasNode;
["dblclick"].forEach((e) => t.removeEventListener(e, this, !1));
}
/**
Enables mouse and touch scroll zoom.
*/
enableScrollZoom() {
if (this.unlocked)
return;
this.unlocked = !0;
const t = this.canvasNode;
let s = ["mousedown", "mousemove", "mouseup", "wheel"];
this.unlockable || s.push("dblclick"), "ontouchstart" in window && (s = s.concat("touchstart", "touchmove", "touchend")), s.forEach((e) => t.addEventListener(e, this, !1));
}
/**
Disables mouse and touch scroll zoom.
*/
disableScrollZoom() {
if (!this.unlocked)
return;
this.unlocked = !1;
const t = this.canvasNode;
let s = ["mousedown", "mousemove", "mouseup", "wheel"];
this.unlockable || s.push("dblclick"), "ontouchstart" in window && (s = s.concat("touchstart", "touchmove", "touchend")), s.forEach((e) => t.removeEventListener(e, this, !1));
}
/**
Release all event handlers and resources.
*/
destroy() {
this.dropZone && this.dropZone.destroy(), this.resizeObserver && this.resizeObserver.disconnect(), window.removeEventListener("debouncedResize", this, !1), this.disableScrollZoom();
}
createDropZone(t) {
t = t || this.parent.getElementsByClassName("dropzone")[0];
const s = this.setData.bind(this);
this.dropZone = new it(t, s);
}
setSlicer(t) {
if (t && t.modulation ? this.slicer = t : this.data && this.data.modulation ? this.slicer = this.data : this.slicer = this.guess, !this.data.pulses || !this.data.pulses.length) return;
const s = U(this.data.pulses, this.slicer);
if (this.data.hints = s.hints, this.data.bits = s.bits, s.bits) {
const e = this.timingsNode, i = this.messagesNode;
this.analyzer.print(e, i), console.log(s.bits.toHexString()), i && (i.innerHTML += `<div>Bits: <strong>${s.bits.toHexString()}</strong></div>`);
}
this.redrawCanvas();
}
/**
Set an option on the Pulseplot to some value.
@param {string} opt - Option name, see {@link #new_Pulseplot_new new}
@param {Object} value - The new value for the option
*/
setOption(t, s) {
return this[t] = this.constrain[t](s), this.processData();
}
/**
Set a number of options on the Pulseplot to some values.
@param {Object} opts - A key/value object of options to set, see {@link #new_Pulseplot_new new}
*/
setOptions(t) {
for (let s in t)
this[s] = this.constrain[s](t[s]);
return t