Saturday, June 25, 2016

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

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.

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 MorseKeyer.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. Yea! 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, 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, in the <head> and </head> lines, add this little bit:

<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, like:

<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>
</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 header, using the built-in definition for the first-level of headers. An <h2> would use the second-level of built-in header 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.

In the <body> section, just above the closing </body> tag, add this section:
<script>
document.addEventListener("keydown", dealWithKeyboard, false);

function dealWithKeyboard(event) {
    alert("Hey! A key has been pressed");
}
</script>
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" (people with some experience programming with functions should notice there is no "()" pair at the end of the function name). In the next thee 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:
function dealWithKeyboard(event) {
    alert(event.keyCode + " has been pressed");
}
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 all the information that is contained in the variable "event", which includes quite a bit of stuff, but of most interest to us, 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. If you wanted to, you could use a different name for the "event" parameter, such as "e" or "theKeyThatWasPressed", but "theKeyThatWasPressed.keyCode" is more tedious 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.

Checkpoint

At this point, your entire "MorseKeyer.html" document should look something like this:

<!DOCtype html>
<html>
<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>

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

function dealWithKeyboard(event) {
    alert(event.keyCode + " has been pressed");
}
</script>

</body>
</html>

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, or just comment it out. Let's comment it out, so that it won't be treated like a computer programming instruction anymore, but just a comment. This way we can always uncomment it later if we need to test other keys.
Just add a couple of forward slashes somewhere at the front of the line, like so:

//     alert(event.keyCode + " has been pressed");
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.

Just after the newly-commented alert line, add these lines:
switch(event.keyCode) {
  case 39:
    alert("You pressed the Right Arrow");
    break;
  case 37:
    alert("You pressed the Left Arrow");
    break;
}
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 "40" and "39", I'm going to use variables with meaningful names here, so that the lines become

case _Dit:
and

case _Dah:
Then earlier in the script, just below the opening <script> tag, I'll define these two variables. The entire <script> section now looks like this:

<script>   
        var _Dah = 40; // 40 = down arrow
        var _Dit = 39; // 39 = right arrow

        document.addEventListener("keydown", dealWithKeyboard, true);

        function dealWithKeyboard(event) {
//          alert(event.keyCode + " has been pressed");
            switch(event.keyCode) {
                case _Dit:
                   alert("You pressed the Dit key");
                   break;
                case _Dah:
                   alert("You pressed the Dah key");
                   break;
             }
          }
</script>
Now if you want to change your keys for Dit and Dah, just change those variables.

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;
}

Replace Message With Tones

We're nearly there. All that is left is to generate the tones.

For the last and final section of our program, we'll just replace the pop-up alert boxes with sound-generation code.

So comment out the Dit alert lines, and add the following code:

//                    alert("You pressed the Dit key");

                    // Create the audio context
                    var context = new AudioContext();
                    var oscillator = context.createOscillator();
                    oscillator.frequency.value = 220;
 
                    // Connect the oscillator to our speakers
                    oscillator.connect(context.destination);

                    // Start the oscillator now
                    oscillator.start(context.currentTime);

                    // Stop the oscillator after "DitLength" seconds from now
                    oscillator.stop(context.currentTime + 1);
Now if you save the code and reload your web page, and then press the Dit key, you should hear a tone of 220 hertz. (The Dah key does not yet work.)

Again, there are a couple of "magic numbers"in this code, so let's replace them with variables.

Replace the "220" with "_Freq", and the "1" with "_DitLength", and then declare these two (and a Dah) variables:

        var _Dah = 37; // 37 = left arrow
        var _Dit = 39; // 39 = right arrow
        var _Freq = 220; // Tone frequency
        var _DitLength = 1; // Length of the Dit
        var _DahLength = _DitLength * 3; // A Dah is usually three times a Dit
and replace those magic numbers with their variable names.

Now add the Dah key handler: replace the alert-Dah line with:

//                    alert("You pressed the Dah key");

                    // Create the audio context
                    var context = new AudioContext();
                    var oscillator = context.createOscillator();
                    oscillator.frequency.value = _Freq;
 
                    // Connect the oscillator to our speakers
                    oscillator.connect(context.destination);

                    // Start the oscillator now
                    oscillator.start(context.currentTime);

                    // Stop the oscillator after "DahLength" seconds from now
                    oscillator.stop(context.currentTime + _DahLength);
If you run your code now, you should have a working keyer.

I find that a _DitLength of 0.6 works better than 1. You can also tinker with the frequency.

With a little more programming, you could even have these values input by the user. And you may hear a bit of click in your tones; I do on my MacBook. But this is a simple program; I'll leave it to you to work out all the kinks.

Just in case I've left something out, or left something a little vague, here's the entire .html document for reference (I rearranged the order of the variables a bit):

<!doctype html>
<!-- From http://code.tutsplus.com/tutorials/the-web-audio-api-what-is-it--cms-23735 -->
<html>
    <head>
        <title>My Morse Code Keyer</title>
    </head>
    <body>
        <h1>Welcome to the Web Audio API</h1>
    <p>Press Down Arrow for Dit, Right Arrow for Dah.</p>

    <script>   
        // Duration of the dits and dahs
        var _DitLength = 0.06;
        var _DahLength = _DitLength * 3;
        var _Freq = 440;
        var _Dah = 37; // 37 = down arrow
        var _Dit = 39; // 39 = right arrow

        addEventListener("keydown", dealWithKeyboard, true);

        function dealWithKeyboard(event) {
//            alert(event.keyCode + " has been pressed");
            switch(event.keyCode) {
                    case _Dit:
//                    alert("down");

                    // Create the audio context
                    var context = new AudioContext();
                    var oscillator = context.createOscillator();
                    oscillator.frequency.value = _Freq;
 
                    // Connect the oscillator to our speakers
                    oscillator.connect(context.destination);

                    // Start the oscillator now
                    oscillator.start(context.currentTime);

                    // Stop the oscillator after "DitLength" seconds from now
                    oscillator.stop(context.currentTime + _DitLength);

                    break;

                case _Dah:
//                    alert("down");

                    // Create the audio context
                    var context = new AudioContext();
                    var oscillator = context.createOscillator();
                    oscillator.frequency.value = _Freq;
 
                    // Connect the oscillator to our speakers
                    oscillator.connect(context.destination);

                    // Start the oscillator now
                    oscillator.start(context.currentTime);

                    // Stop the oscillator after "DahLength" seconds from now
                    oscillator.stop(context.currentTime + _DahLength);

                    break;
            }
        }
    </script>
    </body>
</html>

Have fun playing with your new web-based Morse code keyer!

UPDATE: The full listing below, created 19-Jan-2023, and published at https://kentwest.neocities.org/coding/web/morse-keyer, is a better version.

<!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>
    
        <script>
            document.addEventListener("keydown", dealWithKeyboard, false);


            function playTone(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";                    // sine, square, sawtooth, triangle
                
                const soundBooth = new AudioContext();      // Creates a virtual recording studio, sort of.
                oscillator = soundBooth.createOscillator(); // Creates an oscillator.
                oscillator.type = waveType;                 // That generates this type of wave.
                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()


            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>



Originally published at https://kentwest.blogspot.com/2016/06/a-simple-web-based-morse-code-keyer.html

3 comments:

Nathaniel said...

Kent,
What a fun simple project and tutorial!
First, I must confess that I've not yet had time to build it myself using your instructions. I have been able to run some real-world browser compatibility tests and I've compiled the results below.

It works perfectly so far in both Firefox for Windows as well as Firefox on OS X.

On my Windows 10 Chrome and Edge (the IE replacement), after I play a total of 6 tones (dit and/or dah), it stops working but no errors or anything. A reload of the page and it works again for 6 more tones. I've gotten the same result in Chrome on Mac.

On my Safari for Mac 9.0.1 it simply doesn't work at all and the same for IE 11 on Windows 10.



Kent West said...

Thanks for the comment. And for the usage testing.

I find that it doesn't work on Konqueror on Debian GNU/Linux, or on the "Web Browser" (a very generic name, seems to me, I believe from the Gnome Project) on the same platform, and on Chromium (parent/cousin of Chrome), it also only plays six beeps.

Firefox works fine. Interesting....

mm said...

Great code, what I was looking for!
One good modification would be to get the 'var context = new AudioContext();'
out of the function and put it in the initial script code (at the top) as it only needs to be created once,
and it tended to stop completely after a little while.
Also you may want to prevent multiple keypresses before the tone is finished, using:
oscillator.onended = function() {
ended=true; // global var, use this to allow/prevent a new keypress
}