Using the HTML5 Audio API has a number of advantages - far less bandwidth used, you can change the speed/pitch as it's playing, and so long as the page is still hosted it will always work, since no processing is done at the server. The disadvantage is you need a fairly recent browser to use it, as the Audio API was only introduced a few years ago.
Receiving (copying) Morse Code is generally much harder than sending it. I had previously made a few quickfire Morse tools, but they were very limited. I've also built both software and hardware Morse Code USB keyboards.
In the Koch method, you should start at full speed but with a limited character set. When you can copy just two characters with 90% accuracy, add one more. When you can copy those three with 90% accuracy, add another, and so on. There is a recommended learning order which I've put into the dropdown box. These are chosen at random but with weighted probabilities so the most recent two characters are most likely to appear.
Using the custom character set, characters are chosen with equal probabilities.
When you feel ready to move on to real blocks of text, be prepared to slow down again. Morse Code is designed around the letter frequencies in the English language, with E and T (the most frequent letters) having the shortest codes. So copying real text will be faster than random characters at the same character speed.
The speeds are determined by the word PARIS being 50 dot-lengths long. See the wikipedia page on Morse Code for more info about the timing.
Despite my ambition to have this entirely Javascript-based, the same-origin policy means some server involvement is unavoidable. The RSS feeds are bounced off a PHP file on my server so that an AJAX request can be made without breaking this policy. This is possibly open to abuse so I've only given it a fixed set of available feeds, but if there is an RSS feed you want added to the list feel free to message me.
Wikipedia actually has an entire API for fetching its content, and it supports JSONP which would avoid the need to bounce it off my server. However, it isn't really intended for this sort of thing, and grabbing the featured article's description is rather complicated through this method. Easier just to use the RSS feeds in the same way. The feeds are located here and here.
The simplest method is to find the Levenshtein distance between the two strings. This gives you the number of single-character edits required to get from one string to the other. Divide that by the length of the first string to get a percentage error, and subtract that from 100% to get the accuracy.
In order to do this I had to implement the Levenshtein distance calculation in javascript. This has probably already been done but I like doing this sort of thing.
/* Find the Levenshtein distance between two strings * implemented by mitxela as described on wikipedia */ function Levenshtein(s, t){ var m=s.length, n=t.length, d=[], i,j, cost=0; function min(a,b,c){ if (a<=b&&a<=c) return a; if (b<=a&&b<=c) return b; return c; } for (i=0;i<=m;i++) d[i] = [i]; for(j=1;j<=n;j++) { d[0][j] = j; for (i=1;i<=m;i++){ cost = (s.charAt(i-1) == t.charAt(j-1))?0:1; d[i][j] = min(d[i-1][j ] + 1, // deletion d[i ][j-1] + 1, // insertion d[i-1][j-1] + cost) // substitution } } return d[m][n] }Note that you can actually use
s[i]
instead of s.charAt(i)
with strings in Javascript now (it was added fairly recently). However charAt makes it more explicit to a human reader that it's a string. Since some of these strings could be quite long, I tried to optimize it for speed. I made a couple of jsperf test cases and found that defining our own min
function gave a 74% speedup in Chrome but had no effect on Firefox. I originally had a funky bit of implicit type conversion for the cost calculation, but again jsperf showed that the ternary or a conditional would be faster in Chrome, while in Firefox it had no effect. Probably to do with just-in-time compiling. That was all on Firefox 45 and Chrome 49. The function calculates a matrix of the Levenshtein distances between all the prefixes of the first string and all the prefixes of the second. The last value in this array is the shortest distance between the strings. Instead of returning this value, we can plot the entire matrix.
Enter two strings and hit the button
In this table, I've traced back one possible path in yellow whose distance is equal to the levenshtein distance. Moving horizontally or vertically corresponds to adding or subtracting a character. Moving diagonally represents either no change or replacing a character (you can tell by if the distance value changes).
If the difference between strings is ambiguous, especially if mistakes are adjacent to each other, there can be multiple possible paths through the table which have the same distance. There is no way of knowing which way is the 'correct' path when it comes to what you typed in the morse code box. However, if you are practising near your comfort zone most of the mistakes should be in isolation and unambiguous.
Understanding this, I decided to offer individual scores on a per-character basis so you know which characters are your weakest, and having done that, I added highlighting to the output box to show you exactly where they took place. This is possibly over the top but I really like over-analysing my performance.