Morse code
It has been in use for more than 175 years — longer than any other electronic encoding system.



Task

Send a string as audible Morse code to an audio device   (e.g., the PC speaker).



As the standard Morse code does not contain all possible characters,
you may either ignore unknown characters in the file,
or indicate them somehow   (e.g. with a different pitch).




--
-- demo\rosetta\Morse_code.exw
-- ===========================
--
with javascript_semantics
include pGUI.e
include builtins\beep.e

Ihandle input, canvas, output, vbox, dlg
cdCanvas cddbuffer, cdcanvas

constant title = "Morse code",
         help_text = """
Enter a message to convert to morse code.
Press Return to listen to the result.
Characters A-Z are shown with Baden-Powell menemonics.
(Note said are a memory aid, not perfectly readable.)
Obviously deliberately looking away and listening
would be the best way to use this as a learning aid.
"""

function show_help()
    IupMessage(title,help_text,bWrap:=false)
    IupSetFocus(input)
    return IUP_IGNORE -- (don't open the browser help!)
end function

constant morse_data = """
!-.-.--#".-..-.#$...-..-#&.-...#'.----.#(-.--.#)-.--.-#+.-.-.#,--..--#--....-#..-.-.-#/-..-.#=-...-#
0-----#1.----#2..---#3...--#4....-#5.....#6-....#7--...#8---..#9----.#:---...#;-.-.-.#?..--..#@.--.-.#
A.-51517525#B-...7177414144444747#C-.-.7121121277271616#D-..717741414747#E.7474#F..-.7171111174247777#
G--.712177271818#H....2121717127277777#I..51515757#J.---2121222558269668#K-.-742184854428#(-.--.#
L.-..7171727677771717#M--45118155#N-.71362727#O---712182861216#P.--.8181612164247777#)-.--.-#
Q--.-8286121677775818#R.-.717144261717#S...515154545757#T-7121#U..-818111117727#V...-8181111156567727#
W.--818187534317#X-..-8154333365654518#Y-.--8163545433115558#Z--..7121762277771717#_..--.-#     #"""

sequence morse = repeat(``,255), -- the eg "..."'s
        bdnpwl = repeat({},255) -- Baden-Powell mnemonics

procedure setMorse()
    -- I trust the characters and morse codes are all pretty evident in morse_data.
    -- Baden-Powell mnemonics are encoded as 4 points on a 9wx7h grid per dot/dash,
    -- counting (it just turned out that way) right to left and top to bottom, such
    -- that "9711" (=1197) is(/looks like) a forwardslash and "9117" a backslash.
    -- Each quite fiddly to set up - rather relieved there were only 26 of them!
    sequence data = split(substitute(morse_data,"\n",""),'#')
    for di in data do
        integer key = di[1]
        string bpm = trim_head(di[2..$],".- "),
              code = di[2..-length(bpm)-1]
        assert(length(bpm)=0 or length(bpm)=4*length(code))
        morse[key] = code   -- eg morse['S'] = "..."
        bdnpwl[key] = sq_sub('5',bpm)
    end for
    morse['['] = morse['(']
    morse[']'] = morse[')']
end procedure

setMorse()

function redraw_cb(Ihandle /*ih*/)
    string text = upper(IupGetAttribute(input,"VALUE")),
         outstr = ""
    integer {dw,dh} = IupGetIntInt(canvas, "DRAWSIZE"),
            {tw,th} = cdCanvasGetTextSize(cddbuffer,text)
    while tw>dw do
        if length(outstr) then outstr &= " " end if
        outstr &= morse[text[1]]
        text = text[2..$]
        {tw,th} = cdCanvasGetTextSize(cddbuffer,text)
    end while
    atom cw = tw/max(length(text),1),
         cx = dw/2,
         cy = dh/2

    cdCanvasActivate(cddbuffer)
    cdCanvasSetBackground(cddbuffer, CD_LIGHT_PARCHMENT)
    cdCanvasClear(cddbuffer)
    cdCanvasSetForeground(cddbuffer, #BBADA0)
    cdCanvasText(cddbuffer, cx, cy, text)
    cx -= cw*(length(text)/2-0.5)

    cdCanvasSetForeground(cddbuffer, CD_BLUE)
    cdCanvasSetLineWidth(cddbuffer,3)
    {} = cdCanvasMarkSize(cddbuffer,3)

    atom gw = cw*0.75, gh = th*0.285
    for ch in text do
        sequence bpm = bdnpwl[ch]
        if length(bpm) then
            for k=1 to length(bpm) by 4 do
                atom x1 = cx+gw*bpm[k+0]/8-1,
                     y1 = cy+gh*bpm[k+1]/4-3,
                     x2 = cx+gw*bpm[k+2]/8-1,
                     y2 = cy+gh*bpm[k+3]/4-3
                if x1=x2 and y1=y2 then
                    cdCanvasMark(cddbuffer, x1, y1)
                else
                    cdCanvasLine(cddbuffer, x1, y1, x2, y2)
                end if
            end for
        end if
        cx += cw
        if length(outstr) then outstr &= " " end if
        outstr &= morse[ch]
    end for
    cdCanvasFlush(cddbuffer)
    IupSetStrAttribute(output,"TITLE",outstr)
    return IUP_DEFAULT
end function

function map_cb(Ihandle ih)
    cdcanvas = cdCreateCanvas(CD_IUP, ih)
    cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
--  cdCanvasFont(cddbuffer,"Courier",CD_PLAIN,24)
    cdCanvasFont(cddbuffer,"Courier",CD_PLAIN,48)
    cdCanvasSetTextAlignment(cddbuffer, CD_CENTER) 
    return IUP_DEFAULT
end function

constant frequency = 1280,          -- in Hz, 37..32767
         wpm = 15,                  -- words per minute
         dit = 1200/wpm,            -- in milliseconds
         dah = 3*dit,
         lettergap = 3*dit,
         wordgap = 7*dit

function key_cb(Ihandle /*dlg*/, atom c)
    if c=K_ESC then return IUP_CLOSE end if -- (standard practice for me)
    if c=K_F5 then return IUP_DEFAULT end if -- (let browser reload work)
    if c=K_F1 then return show_help() end if
    if c=K_CR then
        string text = trim(upper(IupGetAttribute(input,"VALUE")))
        sequence durations = {}
        for ch in text do
            if ch=' ' then
                durations &= wordgap
            else
                if length(durations) then
                    durations &= lettergap
                end if
                string m = morse[ch]
                for i=1 to length(m) do
                    if i>1 then durations &= dit end if
                    durations &= iff(m[i]='.'?dit:dah)
                end for
            end if
        end for
        beep(frequency,durations,0.5)
        IupSetAttribute(input,"SELECTION","ALL")
    elsif find(c,"#") then
        beep()
        return IUP_IGNORE
    else
        IupUpdate(canvas)
    end if
    return IUP_CONTINUE
end function

IupOpen()
input = IupText("EXPAND=HORIZONTAL")
canvas = IupCanvas("RASTERSIZE=520x40")
output = IupLabel("","EXPAND=HORIZONTAL")
vbox = IupVbox({input,canvas,output}, "MARGIN=10x5, GAP=5")
dlg = IupDialog(vbox,`TITLE="%s",MINSIZE=440x140`,{title})
IupSetCallback(dlg,"KEY_CB",Icallback("key_cb"))
IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"),
                         "ACTION", Icallback("redraw_cb")})
IupShow(dlg)
IupSetAttribute(canvas, "RASTERSIZE", NULL)
IupSetAttributeHandle(NULL,"PARENTDIALOG",dlg)
if platform()!=JS then
    IupMainLoop()
    IupClose()
end if
