A Simple Web-Based Morse Code Keyer Using HTML and Javascript

Try the Keyer here.

Introduction

For some years I've wanted to write a little app that allowed me to use a couple of keys on my computer keyboard (or the mouse buttons on my mouse) as a dit/dah keyer, but to make it available for other hams, I wanted it in a universal language.

Whereas Windows users have VisualBASIC, Linux and Mac users do not, and whereas Linux/Mac users have shell scripting, such is pretty limited in the Windows world.

But everybody has a web browser.

It's only recently that the HTML5 web standard has made my goal fairly trivial. So if you'd like to code a bit of web-page to play some computer-keyboard Morse, come along....

Of course, you'd probably like to know what the result will be, before you follow these instructions, so click here to give the keyer a tryout. (If the audio doesn't work, try leaving the page alone for 30 seconds or so; it seems that the audio features of HTML5 may not be entirely stable/reliable on all browsers at all times, and sometimes waiting half a minute or so allows the system to "wake up".)

The HTML Skeleton Document

If you've never written a web page, let's start out with that. I won't deal with all the exacting web standards, but I will do some of the basics. Open a text editor of some sort that produces plain ASCII text (like Notepad on Windows, or TextEdit on Mac, or Gedit on Linux - be aware that the Mac TextEdit has a few gotchas; you have to remember to convert to Plain Text before you save your document, and to manually add the .html extension, and to turn off Smart Quotes and Smart Dashes in the Edit / Substitutions menu), and type in the following:

Hello, World!

Save the document as plain text, giving it a name like "keyer.html". If you have a web presence, you can place this file on your web server, but if you don't, just save it to your hard drive, perhaps on your Desktop. Then open a web-browser, and either browse to your web server, or do a File / Open and browse to the document. When you open it, you should see the words "Hello, World!" in your web-browser. Yay! You have a web page. But it's really not up to web standards, so let's add the basic minimum it should have. Edit your document so that it now looks like the following:

<html>

  <head>
  </head>

  <body>
    "Hello, World!"
  </body>

</html>

The blank lines are not necessary, nor are the indentations, but they help to break the code up into visual blocks that make the code more readable.

This code doesn't exactly meet proper web standards, but it's good enough for our purposes. Save the document (as plain text, remember, with an .html extension), and then refresh (or reload) your web document/page. You shouldn't see any visible change.

Developing Our Page

Now, between the <head> and </head> lines, add this little bit (that is in highlight):

  <head>
    <title>My Morse Code Keyer</title>
  </head>

Save the document and reload it in your web-browser, and you should see the Window or Tab title change to say "My Morse Code Keyer".

The <header> section is for web-page housekeeping stuff; the <body> section is where most of your web-page code goes.

In the <body> section, replace the "Hello, World!" with something more meaningful (deleting what is in reddish strikeout, and adding what is in yellowish highlight), like:

  <body>
    Hello, World!
    <h1>Welcome to Kent's Web-based Morse Code Keyer!</h1>
    <p>Press the left arrow key for a dah, and the right arrow key for a dit.</p>
  </body>

(Later, you can choose to use different keys than what's convenient on this MacBook keyboard on which I'm currently typing, and change the message accordingly.)

The <h1> tells your web-browser to display the enclosed text as a heading, using the built-in definition for the first-level of headings. An <h2> would use the second-level of built-in heading definitions, etc.

The <p> defines its enclosed text as being a paragraph. Although not strictly required for your web page to work, it's good to get into the habit of using this tag around your paragraphs.

Save your document, reload it in your web-browser, and you should see the appropriate changes.

Using Javascript to Watch for a Keypress

Now we need to do a bit of Javascript programming, as we need a little more programming power than HTML by itself can handle. Since Javascript is built into modern web browsers, we won't have to download/install anything extra. And despite the similarity in names, Javascript has no relation to Java; they are two separate programming tools.

Now we need to watch for a keypress. Later we'll generate the code to generate the dit and dah tones, based on which key is pressed. For now, we'll just display a message that we've pressed a key.

In the <body> section, just above the closing </body> tag, add this section that is in highlight:

  <body>
    <h1>Welcome to Kent's Web-based Morse Code Keyer!</h1>
    <p>Press the left arrow key for a dah, and the right arrow key for a dit.</p>
    
    <script>
    
      document.addEventListener("keydown", dealWithKeyboard, false);
    
      function dealWithKeyboard(event) {
        alert("Hey! A key has been pressed");
      } // end of dealWithKeyboard()
      
    </script>
  </body>

This code tells the web-browser that this is a Javascript script (more properly the <script> line should be <script type="text/javascript">).

The script command, addEventListener, tells the browser (or more accurately, your document in the browser) to listen for an event, in this case the event of a key being pressed down (you could also listen for a keyup, or a mouse button, or several other options), and when it hears one, to run another piece of code called a "function", which is named dealWithKeyboard. In the next three lines we build that function ourselves, which simply displays a message via the "built-in" function of "alert".

We could call the function pretty much anything we wanted, as long as it matches both in the addEventListener command and in the function definition. For example, we could call it "playMorseTones" if we wanted. The "false" operator at the end of the addEventListener command just tells the command to not "capture" further input.

Save your file and reload the page in your browser. Now when you press a key, you should see that message.

Which Key Was Pressed?

Of course, we need to know which key was pressed, so change the function to this (again deleting the reddish highlight/strikethrough, and adding the yellowish highlight):

...
      function dealWithKeyboard(event) {
          alert("Hey! A key has been pressed");
          alert(event.keyCode + " has been pressed");
      } // end of dealWithKeyboard()
...

The "event" parameter holds the keycode of the key that is pressed, and passes it to our function. The line function dealWithKeyboard(event) tells the system that "dealWithKeyboard" is a function, and it's being given a "package" of stuff that gets generated by an "event", and this "package" is named "event". This "package" (or object, or variable) named "event" includes quite a bit of stuff within it, although the only thing we're really interested in is the ".keyCode" value in that package/object, which contains the keycode of the key that was pressed.

The ".keyCode" is a built-in function that returns the keycode's value from that "event" parameter/object/package/variable. If you wanted to, you could use a different name for the "event" parameter, such as "e" or "packageOfStuffWeGotWhenTheKeyPressEventHappened", but "packageOfStuffWeGotWhenTheKeyPressEventHappened.keyCode" is a bit more tedious and error-prone to type than "e.keyCode", which itself is not quite as self-explanatory as "event.keyCode".

Save your .html file, and refresh your browser page, and press some keys to see what keycodes they generate.

Now would be a good time to try pressing a few keys to figure out what you want to use for your Dit and your Dah, and make a note of the keycodes generated by those keys. I'm going to use 39 for the right arrow (dit), and 37 for the left (dah), on my keyboard.

Responding to the Appropriate Keypress

Now that we've captured the keypresses, we have to do different things based on which key is pressed. We'll handle this with a switch statement, which is a form of an If-Then statement you may remember from your high school programming class.

We no longer need the pop-up box telling us what key we've pressed, so we can delete it:

...
      function dealWithKeyboard(event) {
         alert(event.keyCode + " has been pressed");
      } // end of dealWithKeyboard()
...

Now if you reload your page and press a key, you won't see anything happen, because our function no longer does anything.

So let's give it something to do.

In place of the line you just deleted, add these lines:

...
      function dealWithKeyboard(event) {
        switch(event.keyCode) {
          case 39:
            alert("You pressed the Right Arrow");
            break;
          case 37:
            alert("You pressed the Left Arrow");
            break;
        }
      } // end of dealWithKeyboard()
...

Now if you reload your web page, and press one of your two chosen keys, you should see a pop-up window telling you which key you pressed. All other keys are ignored.

I don't like "magic numbers" in my program, so rather than test the case of "39" and "37", I'm going to use variables (constants, actually, which are kind of like unchangeable variables) with meaningful names here.

...
      function dealWithKeyboard(event) {
        const dah = 37;             // 37 = left arrow
        const dit = 39;             // 39 = right arrow
  
        switch(event.keyCode) {
          case 39dit:
            alert("You pressed the Right ArrowDit key.");
            break;
          case 37dah:
            alert("You pressed the Left ArrowDah key.");
            break;
        }
      } // end of dealWithKeyboard()
...

Now if you want to change your keys for Dit and Dah, just change those variables/constants.

Notice that all other keypresses are ignored. We could put a default case for those, like so:

...
switch(event.keyCode) {
    case _Dit:
        alert("You pressed the Dit key");
        break;
    case _Dah:
        alert("You pressed the Dah key");
    break;
    default:
        alert("You pressed an invalid key");
        break;
}
...

... or we could just ignore them and not put in the default case. Your choice. I wouldn't bother; the user will figure out pretty quickly that other keys don't do anything, without any explicit messages saying so.

Replace Messages With Calls to Tone-playing Function

Of course, we don't want messages on the screen; we want dit and dah tones to play. Let's replace the messages with calls to a function to play a dit or a dah.

...
      function dealWithKeyboard(event) {
        const dah = 37;             // 37 = left arrow
        const dit = 39;             // 39 = right arrow
        
        switch(event.keyCode) {
          case dit:
            alert("You pressed the Dit key.");
            playTone("dit");
            break;
          case dah:
            alert("You pressed the Dah key.");
            playTone("dah");
            break;
        }
      } // end of dealWithKeyboard()
...

Create the Tone-playing Function

This new function will need to go above the function that calls it, between it and the addEventListener above it.

...
    <script>
      document.addEventListener("keydown", dealWithKeyboard, false);

      function playTone(DitOrDah) {
        alert("You pressed " + DitOrDah + "!");
      } // end of playTone()
  
      function dealWithKeyboard(event) {
...
  </body>

The minimal function code above should allow us to test the basic functionality of pressing a key and getting to the function. Now we just need to generate the tones.

We will use a feature of HTML5 to create an oscillator, and we'll use that oscillator to generate a short tone for a dit, and a 3-times longer tone for a dah.

      function playTone(DitOrDah) {
        alert("You pressed " + DitOrDah + "!");

                const ditLength = 0.06;                       // Length of the Dit tone in seconds.
                const dahMultiplier = 3;                      // A dah is this many times as long as a dit.
                const freq = 440;                             // Frequency of tone, in Hertz.
                const waveType = "sine";                      // Can be sine, square, sawtooth, or triangle.
                
                const soundBooth = new AudioContext();        // Creates a virtual recording studio, sort of.
                oscillator = soundBooth.createOscillator();   // Creates an oscillator in that studio.
                oscillator.type = waveType;                   // That generates this type of wave (defined above).
                oscillator.frequency.value = freq;            // At this frequency.
                oscillator.connect(soundBooth.destination);   // Connects the output to the system's default speaker system.
                
                switch(DitOrDah) {
                    case "dit":       // If this function gets a dit, play oscillator for dit's length of time.
                        oscillator.start(soundBooth.currentTime);
                        oscillator.stop(soundBooth.currentTime + ditLength);
                        break;
                    case "dah":       // If a dah, then longer.
                        oscillator.start(soundBooth.currentTime);
                        oscillator.stop(soundBooth.currentTime + (ditLength * dahMultiplier));
                        break;
                }
            } // end of playTone()

That should have a working keyer.

Add Some Visual Feedback

Before we quit, let's add some visual feedback to the screen. Let's start by adding couple of spaces in the center-ish of the screen that we can light up with color. (Let's also add a note about the keyer maybe not working immediately.)

...
    <body>
        <h1>Welcome to Kent's Web-based Morse Code Keyer!</h1>
        <p>Press the left arrow key for a dah, and the right arrow key for a dit.</p>
        <p>(If the audio doesn't work, try leaving the page alone for 30 seconds or so; it seems that the audio features of HTML5 may not be entirely stable/reliable on all browsers at all times, and sometimes waiting half a minute or so allows the system to "wake up".)</p>
        <center><span id="dah_spot" style="border:solid 1px black;">Dah</span> - <span id="dit_spot" style="border:solid 1px black;">Dit</span></center>
    
        <script>
            document.addEventListener("keydown", dealWithKeyboard, false);
...

This change by itself should just put the words "Dit" and "Dah", with black borders around the words, centered in the screen.

Now in our function, when we play a tone, let's also flash the background colors of these two <span> blocks.

...
                switch(DitOrDah) {                          // Did we get a "dit" or a "dah"?
                    case "dit":
                        // If this function gets a dit, play oscillator for dit's length of time.
                        oscillator.start(soundBooth.currentTime);
                        oscillator.stop(soundBooth.currentTime + ditLength);
                        // And give some visual feedback.
                        document.getElementById("dit_spot").style.backgroundColor="orange";
                        setTimeout(() => {  document.getElementById("dit_spot").style.backgroundColor=""; }, ditLength * 1000);
                        break;
                    case "dah":       // If a dah, then longer.
                        // Play a dah.
                        oscillator.start(soundBooth.currentTime);
                        oscillator.stop(soundBooth.currentTime + (ditLength * dahMultiplier));
                        // And give some visual feedback.
                        document.getElementById("dah_spot").style.backgroundColor="yellow";
                        setTimeout(() => {  document.getElementById("dah_spot").style.backgroundColor=""; }, ditLength * (dahMultiplier - 1) * 1000);
                        break;
                }
...

Now give that a try.

Full Listing

Below is the complete "keyer.html" file, in case anything was missed. Have fun!

Complete "keyer.html" Listing:

<!doctype html>
<html lang="en">

    <head>
        <title>My Morse Code Keyer</title>
    </head>

    <body>
        <h1>Welcome to Kent's Web-based Morse Code Keyer!</h1>
        <p>Press the left arrow key for a dah, and the right arrow key for a dit.</p>
        <p>(If the audio doesn't work, try leaving the page alone for 30 seconds or so; it seems that the audio features of HTML5 may not be entirely stable/reliable on all browsers at all times, and sometimes waiting half a minute or so allows the system to "wake up".)</p>
        <center><span id="dah_spot" style="border:solid 1px black;">Dah</span> - <span id="dit_spot" style="border:solid 1px black;">Dit</span></center>
        
        <script>
            document.addEventListener("keydown", dealWithKeyboard, false);


            function playTone(DitOrDah) {                   // We're gonna get either a "dit" or a "dah" to play.
                const ditLength = 0.06;                     // Length of the Dit tone in seconds.
                const dahMultiplier = 3;                    // A dah is this many times as long as a dit.
                const freq = 440;                           // Frequency of tone, in Hertz.
                const waveType = "sine";                    // Can be sine, square, sawtooth, or triangle.
                
                const soundBooth = new AudioContext();      // Creates a virtual recording studio, sort of.
                oscillator = soundBooth.createOscillator(); // Creates an oscillator in that studio.
                oscillator.type = waveType;                 // That generates this type of wave (defined above).
                oscillator.frequency.value = freq;          // At this frequency.
                oscillator.connect(soundBooth.destination); // Connects the output to the system's speaker system.
                
                switch(DitOrDah) {                          // Did we get a "dit" or a "dah"?
                    case "dit":     
                        // If this function gets a dit, play oscillator for dit's length of time.
                        oscillator.start(soundBooth.currentTime);
                        oscillator.stop(soundBooth.currentTime + ditLength);
                        // And give some visual feedback.
                        document.getElementById("dit_spot").style.backgroundColor="orange";
                        setTimeout(() => {  document.getElementById("dit_spot").style.backgroundColor=""; }, ditLength * 1000);
                        break;
                    case "dah":       // If a dah, then longer.
                        // Play a dah.
                        oscillator.start(soundBooth.currentTime);
                        oscillator.stop(soundBooth.currentTime + (ditLength * dahMultiplier));
                        // And give some visual feedback.
                        document.getElementById("dah_spot").style.backgroundColor="yellow";
                        setTimeout(() => {  document.getElementById("dah_spot").style.backgroundColor=""; }, ditLength * (dahMultiplier - 1) * 1000);
                        break;
                }
            } // end of playTone()


            function dealWithKeyboard(event) {
                const dah = 37;             // 37 = left arrow
                const dit = 39;             // 39 = right arrow
                switch(event.keyCode) {
                    case dit:
                        playTone("dit");
                        break;
                    case dah:
                        playTone("dah");
                        break;
                }
            } // end of dealWithKeyboard()
        </script>
    </body>
</html>