diff --git a/src/lib/assets/keymaps/chara-chorder.yml b/src/lib/assets/keymaps/chara-chorder.yml
index ab5688cb..225304db 100644
--- a/src/lib/assets/keymaps/chara-chorder.yml
+++ b/src/lib/assets/keymaps/chara-chorder.yml
@@ -104,6 +104,26 @@ actions:
<<: *tertiary_keymap
id: "KM_3_R"
variant: right
+ 558:
+ id: HOLD_COMPOUND
+ title: Activate Chord Library
+ icon: layers
+ description: |
+ When used in a chord includes that chord as a base
+ compound chord for all subsequent chords.
+ This is effectively a library switch.
+ Since library activations can be nested, you
+ usually add a "Reset Chord Library" before this action.
+ 559:
+ id: RELEASE_COMPOUND
+ title: Reset Chord Library
+ icon: layers_clear
+ description: |
+ Releases the active compound state, returning
+ to the default library.
+ While "Activate Chord Library" can only be used
+ as an output of a chord, this action can be assigned
+ to switches directly.
576:
id: ACTION_DELAY_1000
icon: clock_loader_90
diff --git a/src/lib/serial/chord.ts b/src/lib/serial/chord.ts
index 959be4e3..4d52aad1 100644
--- a/src/lib/serial/chord.ts
+++ b/src/lib/serial/chord.ts
@@ -55,3 +55,19 @@ export function deserializeActions(native: bigint): number[] {
return actions;
}
+
+/**
+ * Hashes a chord input the same way as CCOS
+ */
+export function hashChord(actions: number[]) {
+ const chord = new Uint8Array(16);
+ const view = new DataView(chord.buffer);
+ const serialized = serializeActions(actions);
+ view.setBigUint64(0, serialized & 0xffff_ffff_ffff_ffffn, true);
+ view.setBigUint64(8, serialized >> 64n, true);
+ let hash = 2166136261;
+ for (let i = 0; i < 16; i++) {
+ hash = Math.imul(hash ^ view.getUint8(i), 16777619);
+ }
+ return hash & 0x3fff_ffff;
+}
diff --git a/src/lib/undo-redo.ts b/src/lib/undo-redo.ts
index 413131df..f0a51f48 100644
--- a/src/lib/undo-redo.ts
+++ b/src/lib/undo-redo.ts
@@ -1,6 +1,6 @@
import { persistentWritable } from "$lib/storage";
import { derived } from "svelte/store";
-import type { Chord } from "$lib/serial/chord";
+import { hashChord, type Chord } from "$lib/serial/chord";
import {
deviceChords,
deviceLayout,
@@ -158,3 +158,9 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
a.localeCompare(b),
);
});
+
+export const chordHashes = derived(
+ chords,
+ (chords) =>
+ new Map(chords.map((chord) => [hashChord(chord.actions), chord] as const)),
+);
diff --git a/src/routes/(app)/config/chords/+page.svelte b/src/routes/(app)/config/chords/+page.svelte
index ee5be783..dbef7749 100644
--- a/src/routes/(app)/config/chords/+page.svelte
+++ b/src/routes/(app)/config/chords/+page.svelte
@@ -144,7 +144,6 @@
progress = i;
if ("phrase" in chord) {
- console.log(encodeChord(chord, osLayout));
await index.addAsync(i, encodeChord(chord, osLayout));
}
}
diff --git a/src/routes/(app)/config/chords/ChordActionEdit.svelte b/src/routes/(app)/config/chords/ChordActionEdit.svelte
index beffaaca..052271c9 100644
--- a/src/routes/(app)/config/chords/ChordActionEdit.svelte
+++ b/src/routes/(app)/config/chords/ChordActionEdit.svelte
@@ -1,6 +1,6 @@