Spaces:
Running
Running
| import { describe, it, expect } from "vitest"; | |
| import { | |
| tokensToOnsets, | |
| textToSchedule, | |
| onsetsToMorse, | |
| decodeOnsets, | |
| } from "../../lib/wire.js"; | |
| import { morseToTokens } from "../../lib/morse.js"; | |
| import { makeTiming } from "../../lib/timing.js"; | |
| const T = makeTiming(120); // dah=240 elem=480 letter=960 word=1680 | |
| describe("tokensToOnsets", () => { | |
| it("dot = 1 impulse", () => { | |
| expect(tokensToOnsets(["dot"], T)).toEqual([0]); | |
| }); | |
| it("dash = 2 impulses spaced by dahGap", () => { | |
| expect(tokensToOnsets(["dah"], T)).toEqual([0, T.dahGapMs]); | |
| }); | |
| it("A (.-) spaces the dash element after elemGap from the dot", () => { | |
| // dot@0, elemGap, dah@(elem), dah2@(elem+dah) | |
| expect(tokensToOnsets(morseToTokens(".-"), T)).toEqual([ | |
| 0, | |
| T.elemGapMs, | |
| T.elemGapMs + T.dahGapMs, | |
| ]); | |
| }); | |
| }); | |
| describe("onsetsToMorse round-trip (ideal timing)", () => { | |
| const samples = ["SOS", "HELLO WORLD", "HI YOU", "CQ CQ", "E T 1 9", "ABCDE"]; | |
| for (const text of samples) { | |
| it(`recovers "${text}"`, () => { | |
| const { morse, onsets } = textToSchedule(text, T); | |
| expect(onsetsToMorse(onsets, T)).toBe(morse); | |
| expect(decodeOnsets(onsets, T).text).toBe(text); | |
| }); | |
| } | |
| }); | |
| describe("onsetsToMorse with timing jitter", () => { | |
| // Deterministic pseudo-jitter (no Math.random): perturb each onset by a | |
| // bounded sawtooth. Should still decode because gap classes are well | |
| // separated (geometric midpoints). | |
| function jitter(onsets, maxMs) { | |
| return onsets.map((t, i) => t + (((i * 37) % 11) - 5) / 5 * maxMs); | |
| } | |
| it("decodes SOS with +-30ms jitter", () => { | |
| const { onsets, text } = { ...textToSchedule("SOS", T) }; | |
| const jittered = jitter(onsets, 30); | |
| expect(decodeOnsets(jittered, T).text).toBe("SOS"); | |
| expect(text).toBeUndefined; // sanity: textToSchedule has no .text | |
| }); | |
| it("decodes a sentence with +-40ms jitter", () => { | |
| const { onsets } = textToSchedule("HELLO WORLD", T); | |
| expect(decodeOnsets(jitter(onsets, 40), T).text).toBe("HELLO WORLD"); | |
| }); | |
| }); | |
| describe("edge cases", () => { | |
| it("empty onsets -> empty", () => { | |
| expect(onsetsToMorse([], T)).toBe(""); | |
| expect(decodeOnsets([], T)).toEqual({ morse: "", text: "" }); | |
| }); | |
| it("single impulse -> E", () => { | |
| expect(decodeOnsets([0], T).text).toBe("E"); | |
| }); | |
| }); | |