From c709878d6a3a89dd3af0b46b55686929a6c0bd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Fri, 28 Jul 2023 17:04:43 +0200 Subject: [PATCH] fix: use proper phrase decompress algorithm [deploy] --- src/lib/assets/keymaps/action-codes.yml | 2 +- src/lib/serial/chord.spec.ts | 4 +-- src/lib/serial/chord.ts | 10 ++++--- src/lib/serialization/actions.spec.ts | 18 +------------ src/lib/serialization/actions.ts | 35 +++++++++++-------------- src/routes/config/+layout.svelte | 2 +- 6 files changed, 27 insertions(+), 44 deletions(-) diff --git a/src/lib/assets/keymaps/action-codes.yml b/src/lib/assets/keymaps/action-codes.yml index cb3156b3..265ff1ea 100644 --- a/src/lib/assets/keymaps/action-codes.yml +++ b/src/lib/assets/keymaps/action-codes.yml @@ -1,5 +1,5 @@ name: Action Codes -description: 10-bit action codes 0x00-0x1F +description: Invalid action codes actions: 0x00: id: "0x00" diff --git a/src/lib/serial/chord.spec.ts b/src/lib/serial/chord.spec.ts index 2f3b7ba3..020fd191 100644 --- a/src/lib/serial/chord.spec.ts +++ b/src/lib/serial/chord.spec.ts @@ -28,11 +28,11 @@ describe("chords", function () { describe("phrase", function () { it("should stringify", function () { - expect(stringifyPhrase([0x01, 0x68, 0x72, 0xd4, 0x65])).toEqual("016872D465") + expect(stringifyPhrase([0x20, 0x68, 0x72, 0xd4, 0x65, 0x1fff])).toEqual("206872D4651FFF") }) it("should parse", function () { - expect(parsePhrase("016872D465")).toEqual([0x01, 0x68, 0x72, 0xd4, 0x65]) + expect(parsePhrase("206872D4651FFF")).toEqual([0x20, 0x68, 0x72, 0xd4, 0x65, 0x1fff]) }) }) diff --git a/src/lib/serial/chord.ts b/src/lib/serial/chord.ts index b3241950..7fd06cd7 100644 --- a/src/lib/serial/chord.ts +++ b/src/lib/serial/chord.ts @@ -1,16 +1,20 @@ +import {compressActions, decompressActions} from "../serialization/actions" + export interface Chord { actions: number[] phrase: number[] } export function parsePhrase(phrase: string): number[] { - return Array.from({length: phrase.length / 2}).map((_, i) => - Number.parseInt(phrase.slice(i * 2, i * 2 + 2), 16), + return decompressActions( + Uint8Array.from({length: phrase.length / 2}).map((_, i) => + Number.parseInt(phrase.slice(i * 2, i * 2 + 2), 16), + ), ) } export function stringifyPhrase(phrase: number[]): string { - return phrase + return [...compressActions(phrase)] .map(it => it.toString(16).padStart(2, "0")) .join("") .toUpperCase() diff --git a/src/lib/serialization/actions.spec.ts b/src/lib/serialization/actions.spec.ts index d4d166fb..72743be8 100644 --- a/src/lib/serialization/actions.spec.ts +++ b/src/lib/serialization/actions.spec.ts @@ -2,27 +2,11 @@ import {describe, it, expect} from "vitest" import {compressActions, decompressActions} from "./actions" describe("layout", function () { - const actions = [1, 5, 2, 1023, 42, 2, 4, 78] + const actions = [1023, 255, 256, 42, 32, 532, 8000] describe("compression", function () { it("should compress back and forth arrays divisible by 4", function () { expect(decompressActions(compressActions(actions))).toEqual(actions) }) - - it("should compress back and forth arrays divisible not divisible by 4", function () { - expect(decompressActions(compressActions([...actions, 1023, 512, 123]))).toEqual([ - ...actions, - 1023, - 512, - 123, - ]) - expect(decompressActions(compressActions([...actions, 1023, 512]))).toEqual([...actions, 1023, 512]) - expect(decompressActions(compressActions([...actions, 1023]))).toEqual([...actions, 1023]) - }) - - it("should compress alternating 0/1023", function () { - const array = Array.from({length: 128}).map((_, i) => (i % 2 === 0 ? 0 : 1023)) - expect(decompressActions(compressActions(array))).toEqual(array) - }) }) }) diff --git a/src/lib/serialization/actions.ts b/src/lib/serialization/actions.ts index 2dea113d..10f3384d 100644 --- a/src/lib/serialization/actions.ts +++ b/src/lib/serialization/actions.ts @@ -1,23 +1,18 @@ /** - * Compresses an action list into a Uint8Array of 10-bit integers, supporting values of up to 1023 + * Compresses an action list into a Uint8Array of variable-length 8/13-bit integers. + * + * Action codes <32 are invalid. */ export function compressActions(actions: number[]): Uint8Array { - const overflow = actions.length % 4 - const array = new Uint8Array( - Math.ceil((actions.length - overflow) * 1.25 + (overflow === 0 ? 0 : overflow + 1)), - ) - let arrayOffset = 0 - for (let i = 0; i < actions.length; i += 4) { - let final = 0 - for (let j = 0; j < 4 && i + j < actions.length; j++) { - const action = actions[i + j] - array[arrayOffset++] = (action >>> 2) & 0xff - final |= (action & 0x03) << (j * 2) + const buffer = new Uint8Array(actions.length * 2) + let i = 0 + for (const action of actions) { + if (action > 0xff) { + buffer[i++] = action >>> 8 } - array[arrayOffset++] = final + buffer[i++] = action & 0xff } - console.assert(arrayOffset === array.length) - return array + return buffer.slice(0, i) } /** @@ -27,12 +22,12 @@ export function compressActions(actions: number[]): Uint8Array { */ export function decompressActions(raw: Uint8Array): number[] { const actions: number[] = [] - for (let i = 0; i < raw.length + 4; i += 5) { - const overflow = raw[Math.min(i + 4, raw.length - 1)] - - for (let j = 0; j < 4 && i + j < raw.length - 1; j++) { - actions.push((raw[i + j] << 2) | ((overflow >>> (j * 2)) & 0x3)) + for (let i = 0; i < raw.length; i++) { + let action = raw[i] + if (action < 32) { + action = (action << 8) | raw[++i] } + actions.push(action) } return actions } diff --git a/src/routes/config/+layout.svelte b/src/routes/config/+layout.svelte index 826fa190..6b8d14d9 100644 --- a/src/routes/config/+layout.svelte +++ b/src/routes/config/+layout.svelte @@ -2,7 +2,7 @@ import {page} from "$app/stores" import LL from "../../i18n/i18n-svelte" - const paths = [ + $: paths = [ {href: "/config/chords/", title: $LL.configure.chords.TITLE(), icon: "piano"}, {href: "/config/layout/", title: $LL.configure.layout.TITLE(), icon: "keyboard"}, {href: "/config/settings/", title: $LL.configure.settings.TITLE(), icon: "settings"},