Proof-of-concept tools for the UNI FAN SL-INF Wireless kit (SLV3 TX/RX dongles 0416:8040/8041): a Python scan/colour script, tinyuz helpers, a JavaScript tinyuz encoder, the SignalRGB plugin and tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
2.4 KiB
JavaScript
116 lines
2.4 KiB
JavaScript
// Minimal tinyuz encoder (greedy LZ77), stream format of github.com/sisong/tinyuz.
|
|
// Output decodes with the reference tuz_decompress_mem.
|
|
const kMinDictMatchLen = 2;
|
|
const kBigPosForLen = (1 << 11) + (1 << 9) + (1 << 7) - 1;
|
|
const kMaxDist = 4096;
|
|
const kCtrlStreamEnd = 3;
|
|
|
|
function tuzCompress(data) {
|
|
const code = [0, 0, 0, 0]; // dict size, patched at the end
|
|
let typesIndex = 0;
|
|
let typeCount = 0;
|
|
let haveDataBack = false;
|
|
let posBack = 1;
|
|
let dictSizeMax = 1;
|
|
|
|
const outType = (bit) => {
|
|
if (typeCount === 0) {
|
|
typesIndex = code.length;
|
|
code.push(0);
|
|
}
|
|
code[typesIndex] |= bit << typeCount;
|
|
typeCount = (typeCount + 1) & 7;
|
|
};
|
|
|
|
const outLen = (v, packBit) => {
|
|
let count = 1;
|
|
while (v >= 2 ** (count * packBit)) {
|
|
v -= 2 ** (count * packBit);
|
|
count++;
|
|
}
|
|
while (count--) {
|
|
for (let i = 0; i < packBit; i++) {
|
|
outType(Math.floor(v / 2 ** (count * packBit + i)) & 1);
|
|
}
|
|
outType(count > 0 ? 1 : 0);
|
|
}
|
|
};
|
|
|
|
const outDictPos = (pos) => {
|
|
const big = pos >= 128 ? 1 : 0;
|
|
if (big) {
|
|
pos -= 128;
|
|
}
|
|
code.push((pos & 0x7f) | (big << 7));
|
|
if (big) {
|
|
outLen(pos >> 7, 2);
|
|
}
|
|
};
|
|
|
|
const outLiteral = (b) => {
|
|
outType(1);
|
|
code.push(b);
|
|
haveDataBack = true;
|
|
};
|
|
|
|
const outDict = (len, dist) => {
|
|
outType(0);
|
|
const same = haveDataBack && dist === posBack;
|
|
let saved = len - kMinDictMatchLen;
|
|
if (!same && dist > kBigPosForLen) {
|
|
saved--;
|
|
}
|
|
outLen(saved, 1);
|
|
if (haveDataBack) {
|
|
outType(same ? 1 : 0);
|
|
}
|
|
if (!same) {
|
|
outDictPos(dist);
|
|
}
|
|
dictSizeMax = Math.max(dictSizeMax, dist);
|
|
haveDataBack = false;
|
|
posBack = dist;
|
|
};
|
|
|
|
let i = 0;
|
|
while (i < data.length) {
|
|
let bestLen = 0;
|
|
let bestDist = 0;
|
|
for (let dist = 1; dist <= Math.min(i, kMaxDist); dist++) {
|
|
let len = 0;
|
|
while (i + len < data.length && data[i + len] === data[i + len - dist]) {
|
|
len++;
|
|
}
|
|
if (len > bestLen) {
|
|
bestLen = len;
|
|
bestDist = dist;
|
|
}
|
|
}
|
|
const minLen = bestDist > kBigPosForLen ? 3 : kMinDictMatchLen;
|
|
if (bestLen >= minLen && (bestLen > 2 || bestDist < 128)) {
|
|
outDict(bestLen, bestDist);
|
|
i += bestLen;
|
|
} else {
|
|
outLiteral(data[i]);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// stream end control code
|
|
outType(0);
|
|
outLen(kCtrlStreamEnd, 1);
|
|
if (haveDataBack) {
|
|
outType(0);
|
|
}
|
|
outDictPos(0);
|
|
|
|
for (let k = 0; k < 4; k++) {
|
|
code[k] = (dictSizeMax >> (8 * k)) & 0xff;
|
|
}
|
|
return code;
|
|
}
|
|
|
|
if (typeof module !== "undefined") {
|
|
module.exports = { tuzCompress };
|
|
}
|