{"_id":"@dobroslavlabs/morse","name":"@dobroslavlabs/morse","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@dobroslavlabs/morse","version":"0.0.1","description":"Morse / CW toolkit: encode, decode, timing, WAV, Web Audio play, tap/mic decode, keyers. Zero runtime dependencies. Browser + Node. ITU-R M.1677-1.","keywords":["cw","decode","encode","farnsworth","itu","morse","morse-code","prosign","telegraphy","typescript","wav","web-audio"],"homepage":"https://github.com/dobroslavlabs/morse#readme","bugs":{"url":"https://github.com/dobroslavlabs/morse/issues"},"license":"MIT","author":{"name":"Dobroslav Labs"},"repository":{"type":"git","url":"git+https://github.com/dobroslavlabs/morse.git"},"bin":{"dobroslavlabs-morse":"dist/cli.mjs","morse":"dist/cli.mjs"},"type":"module","sideEffects":false,"main":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","require":null,"default":"./dist/index.mjs"},"./edge":{"types":"./dist/edge.d.mts","import":"./dist/edge.mjs","require":null,"default":"./dist/edge.mjs"},"./signal":{"types":"./dist/signal.d.mts","import":"./dist/signal.mjs","require":null,"default":"./dist/signal.mjs"},"./browser":{"types":"./dist/browser.d.mts","import":"./dist/browser.mjs","require":null,"default":"./dist/browser.mjs"},"./package.json":"./package.json"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build":"tsdown","dev":"tsdown --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"bun ultracite check","format":"bun ultracite fix","typecheck":"tsc --noEmit","check:pack":"npm pack --dry-run","check:all":"bun run lint && bun run typecheck && bun run test && bun run build && bun run check:pack","npm:login":"npm login --auth-type=web","npm:logout":"npm logout","npm:whoami":"npm whoami","release:dry":"npm publish --access public --dry-run","release":"npm publish --access public","prepublishOnly":"bun run check:all","cli":"bun run build && node dist/cli.mjs"},"devDependencies":{"@arethetypeswrong/core":"^0.18.5","@effect/platform-node":"4.0.0-beta.99","@types/bun":"1.3.14","@vitest/coverage-v8":"^4.1.10","effect":"4.0.0-beta.99","oxfmt":"^0.59.0","oxlint":"^1.74.0","publint":"^0.3.21","tsdown":"^0.22.13","typescript":"^7.0.2","ultracite":"7.9.4","unplugin-unused":"^0.5.7","vitest":"^4.1.10"},"engines":{"node":">=20"},"packageManager":"bun@1.3.14","_id":"@dobroslavlabs/morse@0.0.1","gitHead":"7cf0d6de9fc1e4c52d81c67c25d80a808ab75f5f","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-KxJoANR9oOh0kXz9Jve8QjOJt0+xozYoP6MgXlNbjDs/F281k4bwAo0qACxZ0ohL1wPpdnv4CuHm8t2VGaPMaQ==","shasum":"1e45273bd2b308ceb49ed7144bac92534d95f44a","tarball":"https://registry.npmjs.org/@dobroslavlabs/morse/-/morse-0.0.1.tgz","fileCount":12,"unpackedSize":1576527,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCNPKpDaPfULol6jZ+DNgULJsaRdTW9BuOg2SCDFrCfuQIhAJCcNf6OXFf68fkotph9HT6ciS+APHzUDiNThw4lYeml"}]},"_npmUser":{"name":"dobroslavr","email":"dobroslav.radosavljevic@gmail.com"},"directories":{},"maintainers":[{"name":"dobroslavr","email":"dobroslav.radosavljevic@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/morse_0.0.1_1785144719746_0.8155444239497611"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-27T09:31:59.547Z","0.0.1":"2026-07-27T09:31:59.887Z","modified":"2026-07-27T09:32:00.148Z"},"maintainers":[{"name":"dobroslavr","email":"dobroslav.radosavljevic@gmail.com"}],"description":"Morse / CW toolkit: encode, decode, timing, WAV, Web Audio play, tap/mic decode, keyers. Zero runtime dependencies. Browser + Node. ITU-R M.1677-1.","homepage":"https://github.com/dobroslavlabs/morse#readme","keywords":["cw","decode","encode","farnsworth","itu","morse","morse-code","prosign","telegraphy","typescript","wav","web-audio"],"repository":{"type":"git","url":"git+https://github.com/dobroslavlabs/morse.git"},"author":{"name":"Dobroslav Labs"},"bugs":{"url":"https://github.com/dobroslavlabs/morse/issues"},"license":"MIT","readme":"# @dobroslavlabs/morse\n\nProduction Morse / CW toolkit: **encode**, **decode**, **validate**, **timing**, **WAV**, **Web Audio play**, **tap/mic decode**, and **keyers**.\n\nZero runtime dependencies. Fully typed. ESM-only. Browser + Node / Bun / Deno / Workers. Alphabet and spacing anchored to **[ITU-R M.1677-1](https://www.itu.int/rec/R-REC-M.1677-1-200910-I/en)** with PARIS / Farnsworth timing.\n\n## Install\n\n```bash\nbun add @dobroslavlabs/morse\n# or\nnpm install @dobroslavlabs/morse\n```\n\nRequires **Node.js ≥ 20** (or equivalent Bun / Deno / modern browser).\n\n## Quick start\n\n```ts\nimport {\n  encodeMorse,\n  decodeMorse,\n  encodeProsign,\n  getCharacter,\n  listAlphabet,\n  toSchedule,\n  parseMorse,\n  validateMorse,\n  createDrill,\n} from \"@dobroslavlabs/morse\";\nimport { toWavBytes, decodeAudio } from \"@dobroslavlabs/morse/signal\";\nimport { MorsePlayer, createTapDecoder } from \"@dobroslavlabs/morse/browser\";\n\nencodeMorse(\"HELLO WORLD\");\n// \".... . .-.. .-.. --- / .-- --- .-. .-.. -..\"\n\ndecodeMorse(\"... --- ...\"); // \"SOS\"\nencodeProsign(\"SOS\"); // \"...---...\" (unbroken distress)\n\ngetCharacter(\"A\");\n// { char: \"A\", morse: \".-\", category: \"letter\", charset: \"itu\" }\n\nlistAlphabet({ includeDigits: true });\n\nconst events = toSchedule(\"CQ\", { wpm: 20 });\nconst wav = toWavBytes(\"CQ DX\", { wpm: 20, frequency: 600 });\n\nconst player = new MorsePlayer({ wpm: 20 });\nawait player.play(\"HELLO\");\n\nconst drill = createDrill({ kind: \"koch\", lesson: 5, seed: 1 });\n```\n\n## Entry points\n\n| Import | Runtime | Use |\n| --- | --- | --- |\n| `@dobroslavlabs/morse` | any | Text, tables, timing, drills, SSR |\n| `@dobroslavlabs/morse/edge` | Workers / edge | Same surface as core (no Node/DOM APIs) |\n| `@dobroslavlabs/morse/signal` | any | PCM / WAV synth + Goertzel audio decode |\n| `@dobroslavlabs/morse/browser` | browser | Player, mic, tap decoder, keyers |\n\nLibrary entries never import Effect. The CLI bundles Effect only into `dist/cli.mjs`.\n\n## Features\n\n- **Encode / decode** — ITU text ↔ Morse, custom glyphs, unknown policies (`error` / `skip` / `placeholder`)\n- **Charsets** — ITU + American, Latin Ext, Cyrillic, Greek, Hebrew, Arabic, Persian, Japanese (Wabun), Korean (SKATS), Thai\n- **Prosigns** — `SOS`, `AR`, `SK`, `BT`, `CT`/`KA`, … unbroken runs; optional inline `<SOS>` when encoding\n- **Documents** — `parseMorse` / `parseFromText` / `parseFromMorse` → `MorseDocument` with tokens, errors, stats\n- **Validation** — `validateMorse`, `validateText`, `validateMorseCode`, `assertValid*`\n- **Timing** — PARIS WPM, Farnsworth gap stretch, `toSchedule` / `iterateSchedule` for flashlight / UI sync\n- **Timings decode** — keyer / tap on–off durations → Morse / text (adaptive or fixed)\n- **Signal** — PCM synth (sine/square/triangle/saw), WAV read/write, Goertzel tone gate, adaptive frequency\n- **Browser** — `MorsePlayer`, `TapDecoder`, `MicListener`, straight / iambic A–B keyers\n- **Drills** — Koch lessons, random characters, word drills, `scoreDrill`\n- **CLI** — `morse encode|decode|validate|parse|wav`\n\n## Encode & decode\n\n```ts\nimport {\n  encodeMorse,\n  decodeMorse,\n  encodeMorseDetailed,\n} from \"@dobroslavlabs/morse\";\n\nencodeMorse(\"paris\"); // case-folds letters → \"PARIS\" Morse\ndecodeMorse(\".- -...\"); // \"AB\"\n\n// Unknown characters\nencodeMorse(\"A☺B\", { unknownPolicy: \"skip\" }); // \".- -...\"\nencodeMorseDetailed(\"A☺B\", { unknownPolicy: \"placeholder\" });\n// { morse: \".- ? -...\", warnings: [...], errors: [] }\n\n// Prefer prosign vs punctuation for colliding patterns (BT/`=`, AR/`+`)\ndecodeMorse(\"-...-\", { prefer: \"charset\" }); // \"=\"\ndecodeMorse(\"-...-\", { prefer: \"prosign\" }); // \"BT\"\n```\n\nSpaces become word separators (`/`). Unicode dots/dashes (`·`, `–`, …) are accepted on decode. Pipe `|` is treated as a word separator.\n\n## Prosigns\n\n```ts\nimport { encodeProsign, decodeProsign, PROSIGNS } from \"@dobroslavlabs/morse\";\n\nencodeProsign(\"SOS\"); // \"...---...\"\nencodeMorse(\"SOS\"); // \"... --- ...\"  (letters with gaps)\n\nencodeMorse(\"HI <SOS>\", { prosigns: true }); // unbroken SOS inline\n```\n\nITU starting signal is `-.-.-` (`CT` / `KA`). Shared reverse map prefers `CT`.\n\n## Timing & schedule\n\n```ts\nimport {\n  computeTiming,\n  farnsworthTiming,\n  toSchedule,\n  scheduleDurationMs,\n  timingsToText,\n} from \"@dobroslavlabs/morse\";\n\ncomputeTiming({ wpm: 20 }).dot; // 60 ms (PARIS: unit = 1200/wpm)\nfarnsworthTiming(10, 20); // character speed 20, stretched gaps → ~10 WPM\n\nconst events = toSchedule(\"PARIS\", { wpm: 20, trailingWordGap: true });\nscheduleDurationMs(events) / 60; // ≈ 50 units (full PARIS word-minute)\n\ntimingsToText(\n  events.map((e) => ({ on: e.kind === \"on\", ms: e.ms })),\n  { wpm: 20 }\n); // \"PARIS\"\n```\n\n## Signal (WAV / PCM)\n\n```ts\nimport {\n  toWavBytes,\n  decodeAudio,\n  synthesizePcm,\n} from \"@dobroslavlabs/morse/signal\";\n\nconst wav = toWavBytes(\"CQ\", {\n  wpm: 18,\n  frequency: 600,\n  sampleRate: 16_000,\n  padStartMs: 40,\n  padEndMs: 40,\n});\n\ndecodeAudio(wav, { targetHz: 600, wpm: 18 }).text; // \"CQ\"\n\nconst { samples, sampleRate } = synthesizePcm(\"E\", {\n  waveform: \"sine\",\n  volume: 80,\n});\n```\n\n## Browser\n\n```ts\nimport {\n  MorsePlayer,\n  PLAYER_PRESETS,\n  createTapDecoder,\n  createIambicKeyer,\n} from \"@dobroslavlabs/morse/browser\";\n\nconst player = new MorsePlayer({\n  ...PLAYER_PRESETS.beginner,\n  onCharacter: ({ char }) => console.log(char),\n});\nawait player.play(\"SOS\");\n\nconst tap = createTapDecoder({ wpm: 20 });\n// wire pointerdown → tap.down(); pointerup → tap.up();\ntap.toText();\n\nconst keyer = createIambicKeyer({ wpm: 20, mode: \"iambic-a\" });\n```\n\n`MicListener` and `decodeAudioBuffer` need `getUserMedia` / Web Audio in the browser.\n\n## CLI\n\nAfter install, binaries `morse` and `dobroslavlabs-morse` point at `dist/cli.mjs`.\n\n```bash\n# from this repo\nbun run cli -- encode --text \"SOS\"\nbun run cli -- decode - <<< \"... --- ...\"\nbun run cli -- validate - <<< \"HELLO\"\nbun run cli -- parse - <<< \"CQ DX\"\nbun run cli -- wav --text \"CQ\" -o cq.wav --wpm 20 --frequency 600\n\n# published package\nnpx @dobroslavlabs/morse encode --text \"HI\"\n```\n\n| Command    | Role                                                   |\n| ---------- | ------------------------------------------------------ |\n| `encode`   | Text → Morse (`--text` / file / stdin)                 |\n| `decode`   | Morse → text (prefer stdin when Morse starts with `-`) |\n| `validate` | JSON `{ ok, errors, warnings }`                        |\n| `parse`    | Full `MorseDocument` JSON                              |\n| `wav`      | Write RIFF WAV (`-o`, `--wpm`, `-f`)                   |\n\n## Spec notes\n\n- Alphabet / punctuation: **ITU-R M.1677-1** (including accented **É** and multiplication **×**, same pattern as **X**)\n- Timing: PARIS standard (50 units per word-minute including trailing word space); Farnsworth stretches inter-character / inter-word gaps only\n- A few amateur extensions (`!`, `;`, `_`, `$`, `&`) ship in the ITU charset table and are documented as non-Recommendation\n\n## Development\n\n```bash\nbun install\nbun run test\nbun run build\nbun run check:all   # lint + typecheck + test + build + pack dry-run\n```\n\n```bash\nbun run release:dry # npm publish --dry-run\nbun run release     # npm publish (public)\n```\n\n`prepublishOnly` runs `check:all`.\n\n## License\n\nMIT © Dobroslav Radosavljevic / Dobroslav Labs\n","readmeFilename":"README.md","_rev":"1-25fb2af930c7576c6c8c9ce56da4b2fb"}