morse-code / views /learn.js
RemiFabre
ui: single scrolling page, always-on Listen, leaner Learn
d96c9a7
Raw
History Blame Contribute Delete
2.28 kB
/**
* Learn view: the letter ↔ rhythm chart. Tap any entry to hear (or have the
* robot tap) its rhythm — the whole point is that a letter *is* a rhythm.
*/
import { el, clear, morsePattern } from "./dom.js";
import { CHAR_TO_MORSE, morseToTokens } from "../lib/morse.js";
import { tokensToOnsets } from "../lib/wire.js";
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
const DIGITS = "0123456789".split("");
export function createLearn(ctx) {
const root = el("section.view#view-learn");
function playChar(ch) {
const pattern = CHAR_TO_MORSE[ch];
if (!pattern) return;
const onsets = tokensToOnsets(morseToTokens(pattern), ctx.timing());
if (ctx.state.emitter === "robot" && ctx.reachy) {
ctx.tapper().tap(onsets);
} else {
ctx.synth().play(onsets, { clickMs: ctx.timing().clickMs });
}
}
function grid(title, chars) {
const g = el("div.chart-grid");
chars.forEach((ch) => {
const cell = el("button.chart-cell", { onclick: () => {
playChar(ch);
cell.classList.remove("ping"); void cell.offsetWidth; cell.classList.add("ping");
} }, [
el("span.cell-char", {}, ch),
morsePattern(CHAR_TO_MORSE[ch]),
]);
g.append(cell);
});
return el("div.chart-section", {}, [el("h3", {}, title), g]);
}
const emitterNote = el("div.status muted");
function refresh() {
emitterNote.textContent = ctx.state.emitter === "robot" && ctx.reachy
? "Tapping plays on the robot's antennas."
: "Tapping plays a beep on this device. (Switch sender in Compose.)";
}
root.append(
el("h2", {}, "Learn Morse"),
el("p.muted", {}, "A letter is just a rhythm of short and long beats. Tap any tile to feel it."),
emitterNote,
grid("Letters", LETTERS),
grid("Numbers", DIGITS),
el("div.legend", {}, [
el("span.legend-item", {}, [morsePattern("."), " dot: one tap"]),
el("span.legend-item", {}, [morsePattern("-"), " dash: quick double tap"]),
]),
);
refresh();
return { node: root, onShow: refresh };
}
void clear;