import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.*; import sun.audio.AudioPlayer; import gnu.regexp.*; /* -------------------------------------------------------------------------------- Morse Code Translator Java Applet Copyright (C) 1999 Stephen C Phillips Copyright (C) 2001 Michael R Ditto This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -------------------------------------------------------------------------------- You can contact Stephen by email at: steve@scphillips.com You can contact Michael by email at: ford@omnicron.com -------------------------------------------------------------------------------- Version MD.0.1 2001-4-21 Add Save button and FileDialog (doesn't work on most browsers) Version MD.0.0 2001-3-24 Update for AWT1.1. Add "Stop" button. Encapsulate the sun.audio dependency so it still works in browsers without sound. Add random quotation option (fetches a quote from a URL). Add random words option (fetches a series of words from a URL). Translate `` and '' into " since they are used in the random quotes. Use element compression for slow speeds (sends individual characters at 12WPM, with the inter-character spacing stretched out to keep the overall WPM as specified). Version 1.0.1 1999-8-23 Minor correction to translate action button Version 1.0.0 1999-8-10 First public release -------------------------------------------------------------------------------- This is my first Java program so if you have any constructive comments then please let me know! I will be tidying it up shortly... -Stephen -------------------------------------------------------------------------------- */ public class NMorse extends Applet implements ActionListener, ItemListener { TextArea input; Label lettersLabel; TextArea letters; TextArea output; TextArea status; Button transButton, saveButton; Button playButton, stopButton; Choice sourceChoice; Choice wpmChoice; Hashtable hMorse; Hashtable hText; byte [] sample = null; int sPos; int timeunit; public void init() { Label l; Panel p; Font butFont = new Font("SansSerif", Font.PLAIN, 10); GridBagLayout gbag = new GridBagLayout(); GridBagConstraints con = new GridBagConstraints(); setBackground(Color.lightGray); setLayout(gbag); l = new Label("Morse Code Translator", Label.CENTER); l.setFont(new Font("SansSerif", Font.BOLD, 16)); con.gridx = 0; con.gridy = 0; con.gridwidth = 6; con.anchor = GridBagConstraints.CENTER; con.weightx = 1.0; gbag.setConstraints(l, con); add(l); l = new Label("Input", Label.LEFT); l.setFont(new Font("SansSerif", Font.BOLD, 12)); con.gridx = 0; con.gridy = 1; con.gridwidth = 1; gbag.setConstraints(l, con); add(l); input = new TextArea("Type your text or dots and dashes here.", 4, 80, TextArea.SCROLLBARS_VERTICAL_ONLY); input.setEditable(true); input.selectAll(); con.gridx = 0; con.gridy = 2; con.gridwidth = 6; con.fill = GridBagConstraints.BOTH; gbag.setConstraints(input, con); add(input); l = new Label("Translation", Label.LEFT); l.setFont(new Font("SansSerif", Font.BOLD, 12)); con.gridx = 0; con.gridy = 3; con.gridwidth = 1; con.weightx = 1; con.fill = GridBagConstraints.NONE; gbag.setConstraints(l, con); add(l); sourceChoice = new Choice(); sourceChoice.setFont(butFont); sourceChoice.addItem("Typed Input"); sourceChoice.addItem("Random Quotation"); sourceChoice.addItem("Random Words"); sourceChoice.select(0); sourceChoice.setForeground(Color.black); sourceChoice.setBackground(Color.lightGray); con.gridx = 2; con.gridwidth = 2; gbag.setConstraints(sourceChoice, con); add(sourceChoice); sourceChoice.addItemListener(this); lettersLabel = new Label("using these letters:"); lettersLabel.setEnabled(false); lettersLabel.setFont(butFont); letters = new TextArea("etianmos", 1, 10, TextArea.SCROLLBARS_NONE); letters.setEditable(true); letters.setEnabled(false); p = new Panel(); p.add(lettersLabel); p.add(letters); con.gridx = 4; con.weightx = 0; con.gridwidth = 2; gbag.setConstraints(p, con); add(p); output = new TextArea("", 2, 80, TextArea.SCROLLBARS_VERTICAL_ONLY); output.setEditable(false); con.gridx = 0; con.gridy = 4; con.gridwidth = 6; con.fill = GridBagConstraints.BOTH; gbag.setConstraints(output, con); add(output); transButton = new Button("Translate"); transButton.setFont(butFont); transButton.setForeground(Color.black); transButton.setBackground(Color.lightGray); con.gridx = 0; con.gridy = 5; con.gridwidth = 2; con.fill = GridBagConstraints.VERTICAL; con.ipadx = 2; con.ipady = 2; con.insets = new Insets(6,0,0,0); gbag.setConstraints(transButton, con); add(transButton); transButton.addActionListener(this); con.gridx = 2; con.gridy = 5; con.gridwidth = 2; con.insets = new Insets(6,0,0,0); if (true) { // add "Save" Button saveButton = new Button("Save..."); saveButton.setFont(butFont); saveButton.setForeground(Color.black); saveButton.setBackground(Color.lightGray); gbag.setConstraints(saveButton, con); add(saveButton); saveButton.addActionListener(this); } else { // disabled since it doesn't work on most browsers saveButton = null; Panel blank = new Panel(); gbag.setConstraints(blank, con); add(blank); } p = new Panel(); GridBagLayout gbag1 = new GridBagLayout(); p.setLayout(gbag1); //sub panel playButton = new Button("Play"); playButton.setFont(butFont); playButton.setForeground(Color.black); playButton.setBackground(Color.lightGray); con.gridx = 0; con.gridy = 0; con.gridwidth = 1; con.ipadx = 2; con.ipady = 2; con.insets = new Insets(0,0,0,0); gbag1.setConstraints(playButton, con); p.add(playButton); playButton.addActionListener(this); l = new Label("at"); l.setFont(butFont); con.gridx = 1; con.gridy = 0; con.ipadx = 0; con.ipady = 0; gbag1.setConstraints(l, con); p.add(l); wpmChoice = new Choice(); wpmChoice.setFont(butFont); wpmChoice.addItem("5"); wpmChoice.addItem("10"); wpmChoice.addItem("13"); wpmChoice.addItem("15"); wpmChoice.addItem("18"); wpmChoice.addItem("20"); wpmChoice.addItem("25"); wpmChoice.addItem("30"); wpmChoice.addItem("35"); wpmChoice.addItem("40"); wpmChoice.select(1); wpmChoice.setForeground(Color.black); wpmChoice.setBackground(Color.lightGray); con.gridx = 2; con.gridy = 0; gbag1.setConstraints(wpmChoice, con); p.add(wpmChoice); l = new Label("WPM"); l.setFont(butFont); con.gridx = 3; con.gridy = 0; gbag1.setConstraints(l, con); p.add(l); stopButton = new Button("Stop"); stopButton.setEnabled(false); stopButton.setFont(butFont); stopButton.setForeground(Color.black); stopButton.setBackground(Color.lightGray); con.gridx = 4; con.gridy = 0; con.gridwidth = 1; con.ipadx = 2; con.ipady = 2; con.insets = new Insets(0,0,0,0); gbag1.setConstraints(stopButton, con); p.add(stopButton); stopButton.addActionListener(this); con.gridx = 4; con.gridy = 5; con.ipadx = 2; con.ipady = 2; con.gridwidth = 2; gbag.setConstraints(p, con); add(p); status = new TextArea("", 1, 80, TextArea.SCROLLBARS_NONE); status.setEditable(false); con.gridx = 0; con.gridy = 6; con.gridwidth = 6; con.fill = GridBagConstraints.BOTH; con.ipadx = 0; con.ipady = 0; gbag.setConstraints(status, con); add(status); hMorse = new Hashtable(46); hText = new Hashtable(46); hMorse.put(new Character('A'), ".-"); hMorse.put(new Character('B'), "-..."); hMorse.put(new Character('C'), "-.-."); hMorse.put(new Character('D'), "-.."); hMorse.put(new Character('E'), "."); hMorse.put(new Character('F'), "..-."); hMorse.put(new Character('G'), "--."); hMorse.put(new Character('H'), "...."); hMorse.put(new Character('I'), ".."); hMorse.put(new Character('J'), ".---"); hMorse.put(new Character('K'), "-.-"); hMorse.put(new Character('L'), ".-.."); hMorse.put(new Character('M'), "--"); hMorse.put(new Character('N'), "-."); hMorse.put(new Character('O'), "---"); hMorse.put(new Character('P'), ".--."); hMorse.put(new Character('Q'), "--.-"); hMorse.put(new Character('R'), ".-."); hMorse.put(new Character('S'), "..."); hMorse.put(new Character('T'), "-"); hMorse.put(new Character('U'), "..-"); hMorse.put(new Character('V'), "...-"); hMorse.put(new Character('W'), ".--"); hMorse.put(new Character('X'), "-..-"); hMorse.put(new Character('Y'), "-.--"); hMorse.put(new Character('Z'), "--.."); hMorse.put(new Character('1'), ".----"); hMorse.put(new Character('2'), "..---"); hMorse.put(new Character('3'), "...--"); hMorse.put(new Character('4'), "....-"); hMorse.put(new Character('5'), "....."); hMorse.put(new Character('6'), "-...."); hMorse.put(new Character('7'), "--..."); hMorse.put(new Character('8'), "---.."); hMorse.put(new Character('9'), "----."); hMorse.put(new Character('0'), "-----"); hMorse.put(new Character('.'), ".-.-.-"); hMorse.put(new Character('!'), "-.-.--"); hMorse.put(new Character(','), "--..--"); hMorse.put(new Character(':'), "---..."); hMorse.put(new Character('?'), "..--.."); hMorse.put(new Character('\''), ".----."); hMorse.put(new Character('`'), ".----."); // treat ` as ' hMorse.put(new Character('-'), "-....-"); hMorse.put(new Character('/'), "-..-."); hMorse.put(new Character('('), "-.--.-"); hMorse.put(new Character(')'), "-.--.-"); hMorse.put(new Character('"'), ".-..-."); hMorse.put(new Character('='), "-...-"); hMorse.put(new Character('+'), ".-.-."); hMorse.put(new Character(';'), "-.-.-."); hMorse.put(new Character('_'), "..--.-"); hMorse.put(new Character('$'), "...-..-"); hMorse.put(new Character('@'), ".--.-."); Enumeration list = hMorse.keys(); while (list.hasMoreElements()) { Character c = (Character)list.nextElement(); String s = (String)hMorse.get(c); hText.put(s, c); } } public void start() { if (sourceChoice.getSelectedIndex() == 0) input.requestFocus(); } public void itemStateChanged(ItemEvent event) { if (event.getSource() == sourceChoice && event.getStateChange() == ItemEvent.SELECTED) { boolean enaInput = sourceChoice.getSelectedIndex() == 0; boolean enaLetters = sourceChoice.getSelectedIndex() == 2; input.setEditable(enaInput); if (enaInput) { input.selectAll(); input.requestFocus(); } letters.setEnabled(enaLetters); lettersLabel.setEnabled(enaLetters); } } private Frame getFrame() { Container c; for (c=this; !(c instanceof Frame); c = c.getParent()) ; return (Frame)c; } public void actionPerformed(ActionEvent event) { String morse = null; quiet(); stopButton.setEnabled(false); if (event.getSource() == transButton || event.getSource() == playButton || event.getSource() == saveButton) { switch (sourceChoice.getSelectedIndex()) { case 0: status.setText("translating..."); if (isText(input.getText())) { morse = toMorse(input.getText()); output.setText(morse); } else { morse = input.getText(); output.setText(toText(morse)); } break; case 1: status.setText("fetching random text..."); input.setText(randomText(null)); status.setText("translating..."); morse = toMorse(input.getText()); output.setText(morse); break; case 2: status.setText("fetching random text..."); input.setText(randomText(letters.getText())); status.setText("translating..."); morse = toMorse(input.getText()); output.setText(morse); break; } status.setText("translated"); if (event.getSource() == playButton) { byte[] sample = makeSample(morse); status.setText("playing..."); try { playButton.setEnabled(false); stopButton.setEnabled(true); (new Sound(sample)).play(); playButton.setEnabled(true); } catch (Exception ex) { status.setText(ex.toString()); playButton.setEnabled(true); } } if (event.getSource() == saveButton) { byte[] sample = makeSample(morse); final byte[] header = { (byte)0x2E, (byte)0x73, (byte)0x6E, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x20, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x1F, (byte)0x40, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, }; try { FileDialog fd = new FileDialog(getFrame(), "File name for audio sample", FileDialog.SAVE); fd.setFile("morse.au"); fd.setVisible(true); String f = fd.getFile(); if (f != null) { File file = new File(fd.getDirectory(), f); FileOutputStream toFile = new FileOutputStream(file); toFile.write(header); toFile.write(sample); toFile.close(); } } catch (Exception ex) { status.setText("Browser error: " + ex.toString()); } } } } /**************************************************************/ /* Random Text Acquisition */ /**************************************************************/ private String randomText(String letters) { try { URL textURL = new URL(getCodeBase(), ("/cgi-bin/randomtext" + (letters == null ? "" : "?" + letters))); InputStreamReader sr = new InputStreamReader((InputStream)textURL.getContent(), "UTF8"); StringBuffer b = new StringBuffer(); char[] buf = new char[100]; int n; while ((n = sr.read(buf)) > 0) { b.append(buf, 0, n); } return b.toString(); } catch (Exception ex) { ex.printStackTrace(); status.setText(ex.toString()); return "error"; } } /**************************************************************/ /* Text Processing */ /**************************************************************/ boolean isText(String sText) { boolean matches = false; try { RE morseRE = new RE("^[._/ \\|-]*$"); matches = morseRE.isMatch(sText); } catch (REException arg) { } return !matches; } String toMorse(String sText) { /* tr/a-z/A-Z/; #lowercase tr/ / /s; #sqeeze spaces s/^ *(.*?) *$/$1/; #chop start and end of ' ' s# #~ #g; #mark word boundaries (with non-Morse character) s#([A-Z0-9.!,:?'`/()\"=+;_$@ -])#$toMorse{$1}.' '#eg; #put Morse in s#~#/#g; #re-mark word boundaries */ StringBuffer sbMorse = new StringBuffer(); sText = sText.toUpperCase(); try { RE sub1 = new RE("\\s+"); RE sub2 = new RE("^\\s+"); RE sub3 = new RE("\\s+$"); RE sub5 = new RE("[^A-Z0-9.!,:?'`/()\"=+;_$@ -]"); RE sub4 = new RE("[\'`]{2}"); sText = sub1.substituteAll(sText, " "); sText = sub2.substitute(sText, ""); sText = sub3.substitute(sText, ""); sText = sub4.substituteAll(sText, "\""); sText = sub5.substituteAll(sText, ""); input.setText(sText); for (int i = 0; i < sText.length(); i++) { char c = sText.charAt(i); Character ch = new Character(c); if (Character.isWhitespace(c)) { sbMorse.append("/ "); } else { sbMorse.append(hMorse.get(ch)); sbMorse.append(' '); } } } catch (REException arg) { //input.setText(arg.getMessage()); input.setText("error " + arg); } return sbMorse.toString(); } public String toText(String sMorse) { /* tr/ / /s; #sqeeze space s#( ?/ ?)+# /#g; #deal with multiple and space-padded '/' s#^[ /]*##; #chop start of '/' and ' ' s#[ /]*$# #; #chop end of same, leaving one ' ' s#/#~#g; #mark word boundaries (with non-Morse character) s/([-.]+) /$fromMorse{"$1"}/eg; #put in text s/~/ /g; #replace word boundaries s#\(([^(]*)\(#\($1\)#g; #match brackets */ StringBuffer sbText = new StringBuffer(); try { RE sub1 = new RE("\\|"); // for '/' RE sub2 = new RE("_"); // for '-' RE sub3 = new RE("\\s+"); // for ' ' RE sub4 = new RE("( ?/ ?)+"); // for ' / ' RE sub5 = new RE("^[ /]*"); // for '' RE sub6 = new RE("[ /]*$"); // for '' sMorse = sub1.substituteAll(sMorse, "/"); sMorse = sub2.substituteAll(sMorse, "-"); sMorse = sub3.substituteAll(sMorse, " "); sMorse = sub4.substituteAll(sMorse, " / "); sMorse = sub5.substitute(sMorse, ""); sMorse = sub6.substitute(sMorse, ""); input.setText(sMorse); sMorse = sMorse + " "; RE sub7 = new RE("^.*? "); // for '' (rest of string) RE sub8 = new RE(" .*$"); // for '' (first word) while (sMorse.length() != 0) { String letter = sub8.substitute(sMorse, ""); sMorse = sub7.substitute(sMorse, ""); if (letter.equals("/")) { sbText.append(" "); } else if (hText.get(letter) == null) { sbText.append("*"); } else { sbText.append(hText.get(letter)); } } // need to do parentheses matching! } catch (REException arg) { input.setText(arg.toString()); } return sbText.toString(); } /**************************************************************/ /* Sound Processing */ /**************************************************************/ Sound playing = null; class Sound { private InputStream stream = null; byte[] sample; Sound(byte[] sample) { this.sample = sample; } void play() { quiet(); stream = new ByteArrayInputStream(sample); AudioPlayer.player.start(stream); playing = this; } void stop() { if (stream != null) { AudioPlayer.player.stop(stream); stream = null; } if (playing == this) playing = null; } } void quiet() { if (playing != null) { status.setText(""); playing.stop(); } } byte [] makeSample(String morse) { int wpm = Integer.parseInt(wpmChoice.getSelectedItem()); final int ditLen = 1; final int dahLen = 3; final int wordLen = 50; final int charPause = 3; final int wordPause = 7; char c; float compression; int extra; // milliseconds per dit timeunit = 1000*60/(wpm*wordLen); if (timeunit > 100) { // If the characters are to be sent slowly (less than 12wpm) // compress the elements, and stretch the gap between characters // to keep the wpm the same. compression = (float)timeunit / (float)100.0 - 1; timeunit = 100; } else { compression = 0; } int length = 0; extra = 0; for (int i = 0; i < morse.length(); ++i) { c = morse.charAt(i); switch (c) { case '.': extra += ditLen + ditLen; length += ditLen + ditLen; break; case '-': extra += dahLen + ditLen; length += dahLen + ditLen; break; case ' ': extra += charPause - ditLen; length += charPause - ditLen + (int)(extra * compression); extra = 0; break; case '/': extra += wordPause - ditLen; length += wordPause - ditLen + (int)(extra * compression); extra = 0; break; } } length *= 8 * timeunit; sample = new byte[length]; sPos = 0; extra = 0; for (int i = 0; i < morse.length(); ++i) { c = morse.charAt(i); switch (c) { case '.': extra += ditLen + ditLen; addSound(ditLen); addSilence(ditLen); break; case '-': extra += dahLen + ditLen; addSound(dahLen); addSilence(ditLen); break; case ' ': extra += charPause - ditLen; addSilence(charPause - ditLen + (int)(extra * compression)); extra = 0; break; case '/': extra += wordPause - ditLen; addSilence(wordPause - ditLen + (int)(extra * compression)); extra = 0; break; } } return sample; } private void addSound(int units) { for (int i = 0; i < units*timeunit; ++i) { sample[sPos++] = (byte)0xA7; sample[sPos++] = (byte)0x81; sample[sPos++] = (byte)0xA7; sample[sPos++] = (byte)0; sample[sPos++] = (byte)0x59; sample[sPos++] = (byte)0x7F; sample[sPos++] = (byte)0x59; sample[sPos++] = (byte)0; } } private void addSilence(int units) { int oPos = sPos; sPos += 8*units*timeunit; fill(sample, oPos, sPos, (byte)0); } private static void fill(byte[] a, int s, int e, byte v) { while (s