<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Waleed</title>
    <description>The latest articles on DEV Community by Waleed (@wali0007).</description>
    <link>https://dev.to/wali0007</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4069711%2F6a1e70b6-8be5-4cf8-a9e7-fd6f1e102213.png</url>
      <title>DEV Community: Waleed</title>
      <link>https://dev.to/wali0007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wali0007"/>
    <language>en</language>
    <item>
      <title>Designing an Interface Around a Single Binary Input (What Morse Code Taught Me About Constraints)</title>
      <dc:creator>Waleed</dc:creator>
      <pubDate>Tue, 11 Aug 2026 07:42:05 +0000</pubDate>
      <link>https://dev.to/wali0007/designing-an-interface-around-a-single-binary-input-what-morse-code-taught-me-about-constraints-3d4h</link>
      <guid>https://dev.to/wali0007/designing-an-interface-around-a-single-binary-input-what-morse-code-taught-me-about-constraints-3d4h</guid>
      <description>&lt;p&gt;Most UI decisions assume the user has a full keyboard, a mouse, and a touchscreen to work with. I didn't really think about what happens when none of that is true until I started getting messages about my Morse code translator from a different kind of user than I'd designed for — people using assistive switches, where the entire available input is a single short-press or long-press.&lt;/p&gt;

&lt;p&gt;That constraint turned out to be a genuinely interesting design problem, and it changed how I think about input handling generally, not just for this project.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The constraint: one bit of information, repeated&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
For someone operating a sip-and-puff switch, a cheek sensor, or a single-finger tap pad, the entire input vocabulary is binary: short signal, long signal. That's it. No modifier keys, no click-and-drag, no multi-touch gestures.&lt;/p&gt;

&lt;p&gt;What's interesting is that this is exactly the same constraint Morse code was designed around in the first place — it's a two-symbol system by definition. So in a strange way, the "legacy" 1830s encoding turns out to be closer to an ideal fit for extremely constrained modern input hardware than most contemporary UI patterns are.&lt;/p&gt;

&lt;p&gt;This reframed how I thought about my &lt;a href="https://easymorsecodetranslator.com/morse-code-keyboard" rel="noopener noreferrer"&gt;interactive Morse keyboard tool&lt;/a&gt;. I'd originally built it as a novelty — tap keys, see them convert to text. But once I understood the accessibility use case, I realized the actual design requirement wasn't "make tapping feel fun," it was "make the short/long distinction as forgiving and unambiguous as possible," which is a very different UX problem.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Timing thresholds are the entire interface&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
With a full keyboard, a keypress is a keypress — there's no ambiguity about what the user meant. With binary tap input, the only signal is duration, which means your threshold for "this was a dot" vs. "this was a dash" is doing all the interpretive work a normal UI would split across dozens of distinct controls.&lt;/p&gt;

&lt;p&gt;Get that threshold wrong and the interface becomes unusable, not just annoying. Too strict, and a user with slightly inconsistent motor control can't reliably produce a "short" input at all. Too loose, and dots and dashes blur into each other and nothing decodes correctly.&lt;/p&gt;

&lt;p&gt;I ended up making the threshold adaptive rather than fixed — calibrating against the user's own first few inputs rather than assuming a universal duration cutoff, similar to the relative-classification approach I used for &lt;a href="https://easymorsecodetranslator.com/morse-code-audio-translator" rel="noopener noreferrer"&gt;decoding recorded audio&lt;/a&gt;. The lesson generalized surprisingly well: anywhere you're classifying a single continuous signal into discrete categories, calibrating against the user's own behavior beats a fixed global threshold, whether that's tap duration, audio pulse length, or honestly any other analog-to-discrete UI decision.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Why full-speed pattern recognition matters more than lookup tables&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
The other thing that changed my thinking: for someone using Morse as their primary communication method rather than a hobby, the difference between "slowly counting dots and dashes" and "recognizing a whole letter's rhythm instantly" isn't a nice-to-have — it's the difference between usable and unusably slow.&lt;/p&gt;

&lt;p&gt;This is basically the same principle behind the Koch method in Morse code education (learn characters at full target speed from day one, and shrink the gaps between them over time, rather than starting slow and speeding up) — I built a &lt;a href="https://easymorsecodetranslator.com/morse-code-game" rel="noopener noreferrer"&gt;practice tool around this&lt;/a&gt; for general learners, but the underlying idea applies directly to accessibility interfaces too: optimizing for eventual fluency matters more than optimizing for an easy first impression, if the tool is going to be used daily rather than once.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The general lesson&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
None of this is really specific to Morse code. It's a broader point about designing for constrained input: the fewer degrees of freedom your user has, the more interpretive weight falls on your timing/classification logic, and the more that logic needs to adapt to the individual rather than assuming a fixed global standard. A keyboard shortcut system can afford to be rigid because keys are unambiguous. A single-switch binary system can't — the "interface" is really just your classifier, and it has to bend toward the user instead of the other way around.&lt;/p&gt;

&lt;p&gt;It's a good reminder that accessibility constraints often aren't edge cases to bolt on afterward — they're frequently just the general design problem, minus the padding that makes it easy to ignore.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>ux</category>
      <category>programming</category>
    </item>
    <item>
      <title>Generating Morse Code Is Easy. Decoding It Back Is a Completely Different Problem.</title>
      <dc:creator>Waleed</dc:creator>
      <pubDate>Mon, 10 Aug 2026 08:44:39 +0000</pubDate>
      <link>https://dev.to/wali0007/generating-morse-code-is-easy-decoding-it-back-is-a-completely-different-problem-5dik</link>
      <guid>https://dev.to/wali0007/generating-morse-code-is-easy-decoding-it-back-is-a-completely-different-problem-5dik</guid>
      <description>&lt;p&gt;After building a text-to-Morse generator, I assumed decoding would basically be the same problem run in reverse. It's not. Generating a signal means you control every timing boundary exactly. Decoding means figuring out where those boundaries actually are in someone else's imperfect signal — and "imperfect" turns out to be the normal case, not the exception.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Why decoding is a genuinely different problem&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
When you generate Morse code, every dot, dash, and gap comes from the same clean base unit. There's no ambiguity — a dash is exactly 3 units, always, because you defined it that way.&lt;/p&gt;

&lt;p&gt;When you're decoding, you're working backward from raw signal data (audio amplitude over time, or pixel positions in an image) and trying to answer two separate questions for every pulse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was this a dot or a dash? — depends on comparing its duration to the other durations in the same message, not to some fixed threshold, because different senders transmit at wildly different speeds.&lt;/li&gt;
&lt;li&gt;Was this gap a letter-break or a word-break? — same problem. A 3-unit gap and a 7-unit gap look totally different at 20 WPM, but if you hardcode those exact durations, decoding fails the instant someone sends at 15 WPM instead.
The fix that actually worked: instead of hardcoding absolute durations, I classify relative to the shortest pulse detected in the message so far, treating that as an approximation of one dot-unit, then bucket every other pulse and gap against that reference — real Morse timing is imprecise, so it's not exact 1:3:3:7 in captured audio, but even a slightly wobbly human hand still clusters recognizably around those ratios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;`js&lt;br&gt;
function classifyDurations(pulses) {&lt;br&gt;
  // pulses: array of raw durations in ms&lt;br&gt;
  const shortest = Math.min(...pulses);&lt;br&gt;
  const unit = shortest; // approximate dot-unit&lt;/p&gt;

&lt;p&gt;return pulses.map((d) =&amp;gt; {&lt;br&gt;
    const ratio = d / unit;&lt;br&gt;
    if (ratio &amp;lt; 2) return 'dot';&lt;br&gt;
    if (ratio &amp;lt; 5) return 'dash';&lt;br&gt;
    return 'dash'; // fallback for noisy input&lt;br&gt;
  });&lt;br&gt;
}`&lt;/p&gt;

&lt;p&gt;This is a simplified version — the real implementation also has to filter out background noise, handle slight timing drift over a long message, and decide what to do when a signal genuinely doesn't cluster cleanly (which happens more than you'd expect with a beginner tapping by hand).&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Two different decoding problems, two different tools&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
I ended up building this as two separate tools rather than one, because the input data is fundamentally different in each case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://easymorsecodetranslator.com/morse-code-audio-translator" rel="noopener noreferrer"&gt;Audio decoding&lt;/a&gt;  has to deal with continuous amplitude over time — detecting where a tone starts and stops against a noise floor, which is its own small signal-processing problem before you even get to timing classification.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://easymorsecodetranslator.com/morse-code-image-translator" rel="noopener noreferrer"&gt;Image decoding&lt;/a&gt; is spatial instead of temporal — dots and dashes as marks on a page, where the "duration" you're measuring is actually a pixel-width, and gaps are measured in pixel-distance rather than milliseconds. Same underlying ratio logic, completely different input pipeline to get there.&lt;/p&gt;

&lt;p&gt;Trying to force both into one shared codepath early on made the code harder to reason about, not easier — splitting them was the right call even though the core classification logic (dot vs. dash vs. gap-type) is conceptually identical between the two.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The part that actually humbled me&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
I tested the audio decoder against my own generated WAV files first, and it worked immediately — because my own generator produces mathematically perfect timing. Then I tried it against a real amateur radio recording, and accuracy dropped noticeably. Real human-sent Morse has speed drift over a message, inconsistent dash lengths, and background static — none of which exists in a signal I synthesized myself.&lt;/p&gt;

&lt;p&gt;That gap between "works on my own clean test data" and "works on real messy input" ended up being the actual project, more than the classification algorithm itself. It's a pretty good reminder that testing against your own generator is really just testing against your own assumptions.&lt;/p&gt;

&lt;p&gt;If you're working on anything with pulse-timing decoding — Morse, or honestly any binary-rhythm signal — happy to talk through what worked and what didn't. And if you want to see the full thing in action, both decoders are live and free to try at the links above.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>I Built a Morse Code Translator — and the Timing Math Turned Out to Be the Hard Part</title>
      <dc:creator>Waleed</dc:creator>
      <pubDate>Sun, 09 Aug 2026 10:20:01 +0000</pubDate>
      <link>https://dev.to/wali0007/i-built-a-morse-code-translator-and-the-timing-math-turned-out-to-be-the-hard-part-4c63</link>
      <guid>https://dev.to/wali0007/i-built-a-morse-code-translator-and-the-timing-math-turned-out-to-be-the-hard-part-4c63</guid>
      <description>&lt;p&gt;When I started building an online Morse code translator, I assumed the hard part would be the alphabet. Map letters to dots and dashes, done. I was wrong. The alphabet took an afternoon. The timing took weeks.&lt;/p&gt;

&lt;p&gt;Here's the thing nobody tells you about Morse code until you actually try to generate it: it's not really an alphabet. It's a rhythm. And rhythm is brutally unforgiving to get wrong in code.&lt;/p&gt;

&lt;p&gt;The bug that wasn't a bug&lt;/p&gt;

&lt;p&gt;Early on, my translator worked fine for something short like SOS. But longer sentences sounded... off. Not broken exactly, just slightly wrong in a way that was hard to describe. I compared it against real recordings of amateur radio operators sending CW, and the difference was obvious once I heard it side by side: my dashes were too short relative to my dots, and my word gaps were inconsistent.&lt;/p&gt;

&lt;p&gt;It turned out I'd been treating "dot" and "dash" as separate arbitrary durations I could tune by ear, instead of treating them as ratios of a single base unit. That's the actual rule, standardized internationally as &lt;strong&gt;ITU-R M.1677-1&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dot is 1 unit&lt;/li&gt;
&lt;li&gt;A dash is exactly 3 units — not "about 3," exactly 3&lt;/li&gt;
&lt;li&gt;The gap between dots/dashes inside a letter is 1 unit&lt;/li&gt;
&lt;li&gt;The gap between letters is 3 units&lt;/li&gt;
&lt;li&gt;The gap between words is 7 units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once everything is derived from a single base unit (itself calculated from target WPM using the classic "PARIS" reference word — 1 WPM = 1.2 seconds per dot-unit), the whole system snaps into place and starts sounding like an actual telegraph key instead of a beeping approximation.&lt;/p&gt;

&lt;p&gt;Here's roughly what the core timing logic ended up looking like, stripped down to the essentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;js
function dotUnitSeconds(wpm) {
  return 1.2 / wpm; // PARIS reference word standard
}

function buildTimingSequence(morse, wpm, farnsworthWpm) {
  const unit = dotUnitSeconds(wpm);
  const effectiveUnit = farnsworthWpm ? dotUnitSeconds(farnsworthWpm) : unit;
  const events = [];

  morse.split(' / ').forEach((word, wIndex, words) =&amp;gt; {
    const letters = word.split(' ').filter(Boolean);
    letters.forEach((letter, lIndex) =&amp;gt; {
      [...letter].forEach((symbol, sIndex) =&amp;gt; {
        events.push({
          type: symbol === '.' ? 'dot' : 'dash',
          duration: symbol === '.' ? unit : unit * 3,
        });
        if (sIndex &amp;lt; letter.length - 1) events.push({ type: 'gap', duration: unit });
      });
      if (lIndex &amp;lt; letters.length - 1) events.push({ type: 'gap', duration: effectiveUnit * 3 });
    });
    if (wIndex &amp;lt; words.length - 1) events.push({ type: 'gap', duration: effectiveUnit * 7 });
  });

  return events;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every duration falls out of that one dotUnitSeconds value — change the WPM, and dots, dashes, and every gap scale together automatically instead of drifting out of ratio the way my first hardcoded version did.&lt;/p&gt;

&lt;p&gt;I ended up documenting the full ratio breakdown, including the PARIS reference word math, on an &lt;a href="https://easymorsecodetranslator.com/accuracy" rel="noopener noreferrer"&gt;accuracy and methodology page&lt;/a&gt; on the site — partly so I'd stop re-deriving it from memory every time I touched the code.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Sound was the easy half. Light was harder.&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Once audio synthesis was solid, I wanted the same timing engine to drive other outputs — because historically, that's exactly how Morse code has always worked. The rhythm doesn't care what's carrying it. A ship-to-ship signal lamp uses the identical timing ratios as a radio tone; it's just light instead of sound.&lt;/p&gt;

&lt;p&gt;So I reused the same core engine to build a &lt;a href="https://easymorsecodetranslator.com/morse-code-flasher" rel="noopener noreferrer"&gt;flashlight and screen-strobe generator&lt;/a&gt;, and later a &lt;a href="https://easymorsecodetranslator.com/morse-code-vibrator" rel="noopener noreferrer"&gt;vibration output for mobile devices&lt;/a&gt;, aimed partly at accessibility use cases — some assistive typing systems for people with limited motor control are built around exactly this kind of binary tap-length input. Reusing one timing core across sound, light, and vibration was the first moment the project actually felt like a coherent tool instead of three separate scripts glued together.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Then people started asking for weirder things&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Once the core translator worked, requests started rolling in that I hadn't planned for at all — someone wanted a Morse code tattoo design, someone else wanted Morse spelled out as a printable image for a wedding invitation. That's what led to building an &lt;a href="https://easymorsecodetranslator.com/morse-code-image-generator" rel="noopener noreferrer"&gt;image generator&lt;/a&gt; that exports clean PNG, SVG, and JPG versions of any message, and eventually a downloadable &lt;a href="https://easymorsecodetranslator.com/morse-code-audio-generator" rel="noopener noreferrer"&gt;audio generator&lt;/a&gt; so people could save a WAV file instead of just playing it live in the browser.&lt;/p&gt;

&lt;p&gt;None of these were things I set out to build. They came from watching what people actually typed into the translator and noticing the same requests showing up again and again.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What surprised me most&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
I expected this project to be a straightforward encode/decode exercise. What it actually became was a small lesson in how much of "old" technology is really just very precise engineering that happened to predate computers. Morse code has survived almost two centuries not because it's simple, but because the ratios underneath it are exact enough to stay intelligible through static, low bandwidth, and — as I learned firsthand — through a developer who didn't read the spec closely enough on the first try.&lt;/p&gt;

&lt;p&gt;If you want to hear the difference correct timing makes, the &lt;a href="https://easymorsecodetranslator.com/" rel="noopener noreferrer"&gt;live translator&lt;/a&gt; is free to use — type anything and listen.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
