Snial
Well-known member
Earlier this year I became interested in learning the basics of Morse code. I had a very simple concept for my Morse code tutor, it would simply convert a letter I typed in into Morse or convert dots and dashes typed in, into a single letter. And initially that was because I wanted to fit it on a 1K ZX81 in BASIC, but then decided to write a ZX Spectrum version and also, then thought that would be a good, little project to write in Think C (and Think Pascal) for the Mac.
The program itself is remarkably effective at teaching morse code. All you basically need to do is start typing some real text, and it'll display the morse for it. Then as soon as you're familiar enough with the Morse for a given letter, type the Morse and see the letter instead. It took me about 15 minutes to learn pretty much all the alphabet + digits and another 10 minutes to become familiar with the unusual letters (because if you're typing normal text, they don't occur often enough to learn them).
And because the program is so simple, I thought I'd write a Mac version in assembler, using the very first Mac assembler: MacASM. I think I downloaded it from MacGUI (thank you) but it might have been Macintosh Garden.
MacASM is amazingly primitive, and consequently, very tiny, at 23916 Bytes, but it can handle 68000 assembler; macros for making traps calls easier; linking; editing and includes a resource compiler. It comes with the trap library as .inc files, Frankly I think that's amazing! MacASM really does make it possible to develop applications on an original 128kB Mac, though I can't say it's particularly suited to complex apps, for that I guess you'd need MDS.
Nevertheless, it works! Well, here's the code itself. I did all the development on a System 3 disk (which I think is an HFS disk; and is about 3MB in size. Here though I've converted it to run from a 400kB System 1.x disk image. After the listing I'll show you how to compile it and in future posts on this I'll bore you to tears with some of the details on MacASM development !
You'll the code in the main disk's window. The Libraries for the include files are in another Folder, but since this is a System 1.x disk image the folders are a fiction, it's really a flat disk format and the inc files can be included directly. Here's how to make it: Double-click on MiniMorse.asm and it'll open MacASM to edit it. Type: asm<cr> and MacASM will start to assemble the code. It would normally display the text it's assembling as it goes on, but LIST OFF turns that off to speed things up. Even so, it's pretty slow if you run miniVMac at 1x. Don't press any keys while it's assembling as it'll pause and won't tell you it's paused. Eventually it'll say:
0000 Errors In Assembly
Ok.
Type quit to exit MacASM. You'll see there's now a file called MakeApp.sh in the MinMorseFull window. Strangely, MacASM didn't link the file in the first pass, but double-clicking on MakeApp.sh will run MacASM again and automatically link the file producing the App: MiniMorse. Double-click that and you get the app:
Which greets you with "Hello World!" thanks to me leaving the even more humble origins of MiniMorse in the code. Typing in a capital letter or digit generates the appropriate Morse and quickly typing in a sequence of dots '.' and dashes '-' converts it to a letter.
MiniMorse has enough of a normal Macintosh application implemented to make it seem as least a little bit conventional. Considering that MiniMorse is only 1473 bytes long I'm pleased with that. You can move the window; it handles an About... box and desk accessories. The single File:Quit menu option is supported, but closing the box also does the same thing, returning to the desktop without crashing!. If something obscures the window and it has to redraw, it doesn't do it properly though. I've assembled and run MiniMorse on System 7.1 and System 1.x, 2.0, 2.1 (on an emulated Mac 128kB), and 3.0.
In future posts on this topic, I'll outline a bit about what it's like to work with MacASM and how I did some debugging with it. I might even get around to converting the manual from the massive 22.3MB of scanned images into the real, typewritten thing that looks like it was written on an Apple ][ !
If you get around to playing with it, send me a simple Morse code message in a reply and I'll see if I can decode it !
The program itself is remarkably effective at teaching morse code. All you basically need to do is start typing some real text, and it'll display the morse for it. Then as soon as you're familiar enough with the Morse for a given letter, type the Morse and see the letter instead. It took me about 15 minutes to learn pretty much all the alphabet + digits and another 10 minutes to become familiar with the unusual letters (because if you're typing normal text, they don't occur often enough to learn them).
And because the program is so simple, I thought I'd write a Mac version in assembler, using the very first Mac assembler: MacASM. I think I downloaded it from MacGUI (thank you) but it might have been Macintosh Garden.
MacASM is amazingly primitive, and consequently, very tiny, at 23916 Bytes, but it can handle 68000 assembler; macros for making traps calls easier; linking; editing and includes a resource compiler. It comes with the trap library as .inc files, Frankly I think that's amazing! MacASM really does make it possible to develop applications on an original 128kB Mac, though I can't say it's particularly suited to complex apps, for that I guess you'd need MDS.
Nevertheless, it works! Well, here's the code itself. I did all the development on a System 3 disk (which I think is an HFS disk; and is about 3MB in size. Here though I've converted it to run from a 400kB System 1.x disk image. After the listing I'll show you how to compile it and in future posts on this I'll bore you to tears with some of the details on MacASM development !
Code:
00010 ;SAVE "MiniMorse.asm"
00020 optDebugOn equ 0
00030 BrkPtTrap MACRO
00040 DO optDebugOn
00050 trap #1
00060 ENDM
00070 LIST OFF
00080 INCLUDE "ToolBox.inc"
00090 INCLUDE "Windows.inc"
00100 INCLUDE "Event.inc"
00110 LIST OFF
00120 GLOBAL 32,$200
00130 DEFV L,gWin
00140 DEFV Event.Sizeof,gMyEvent
00150 ENDG
00160 TFILE "MakeApp.sh"
00170 RFILE "MiniMorse",APPL
00180 SEG 1,4
00190 Start
00200 DO optDebugOn
00210 bsr DbgInit
00220 FIN
00230 bsr Init
00240 moveq #0,d7 ;gDone in bit 0 (global flag).
00250 moveq #1,d5 ;morseTap=1.
00260 bra.s Start10
00270 Start05
00280 bsr DoEvent
00290 Start10
00300 btst #0,d7
00310 beq.s Start05
00320 DoQuit
00330 TBX ExitToShell
00340 kTrap1 equ $4e41
00350 DO optDebugOn
00360 DbgInit
00370 lea DbgWait(pc),a0
00380 move.l a0,$84 ;TRAP#1 vector.
00390 rts
00400 DbgWait
00410 * Stack: D0.l:SR:RetPC
00420 move.l d0,-(sp) ;we use d0.
00430 move.w #-1,d0
00440 DbgWait10
00450 tst.w d0
00460 bmi.s DbgWait10
00470 beq.s DbgWait99 ;just skip
00480 cmp.w #$100,d0
00490 bne.s DbgWait15
00500 lea DoQuit(pc),a0
00510 move.l a0,6(sp) ;return to ExitToShell
00520 bra.s DbgWait99
00530 DbgWait15
00540 sub.l #2,6(sp) ;make pc return to same place.
00550 move.l a0,-(sp) ;save a0, used for
00560 move.l 10(sp),a0 ;src/dst
00570 DbgWait20
00580 move.w 2(a0),(a0)
00590 addq.l #2,a0 ;next word.
00600 dbra d0,DbgWait20
00610 move.w #kTrap1,(a0)
00620 move.l (sp)+,a0 ;restore a0
00630 DbgWait99
00640 move.l (sp)+,d0
00650 rte ;done
00660 FIN
00670 DoMenuBarInit
00680 link a6,#-4 ;menu bar.
00690 clr.l -4(a6) ; set to nil.
00700 clr.l -(sp)
00710 move.w #kMenuIdApple,-(sp)
00720 TBX GetRMenu
00730 move.l (sp)+,-4(a6)
00740 move.l -4(a6),-(sp) ;menu.
00750 move.l #$44525652,-(sp) ;'DRVR'
00760 TBX AddResMenu
00770 move.l -4(a6),-(sp) ;menu.
00780 clr.w -(sp)
00790 TBX InsertMenu
00800 clr.l -(sp)
00810 move.w #kMenuIdFile,-(sp)
00820 TBX GetRMenu
00830 clr.w -(sp) ;menu^^ is on stack.
00840 TBX InsertMenu
00850 TBX DrawMenuBar
00860 unlk a6
00870 rts
00880 Init
00890 link a6,#-2 ;reserve space for FontNum
00900 pea -4(a5) ;&thePort
00910 TBX InitGraf
00920 TBX InitFonts
00930 TBX InitWindows
00940 TBX InitMenus
00950 TBX TEInit
00960 clr.l -(sp) ;InitDialogs(nil)
00970 TBX InitDialogs
00980 TBX InitCursor
00990 move.l #everyEvent,d0
01000 OST FlushEvents
01010 SUBQ #4,SP
01020 MOVE #400,-(SP)
01030 CLR.L -(SP)
01040 MOVE.L #-1,-(SP)
01050 TBX GetNewWindow
01060 MOVE.L (SP)+,gWin(A5)
01070 MOVE.L gWin(A5),-(SP)
01080 TBX SetPort
01090 pea kMonaco(PC)
01100 pea -2(a6) ;&FontNum
01110 TBX GetFNum ;result in (a6).w.
01120 move.w -2(a6),-(sp) ;*&FontNum.
01130 TBX TextFont
01140 move.w #9,-(sp) ;size 9
01150 TBX TextSize
01160 move.l gWin(a5),-(sp)
01170 TBX ShowWindow
01180 bsr DoMenuBarInit
01190 bsr MoveOxOy
01200 LEA kHello(PC),A0
01210 MOVE.L A0,-(SP)
01220 TBX DrawString
01230 unlk a6
01240 rts
01250 MoveOxOy
01260 MOVE.W #10,-(SP) ;OriginX
01270 MOVE.W #10,-(SP) ;OriginY
01280 TBX MoveTo
01290 rts
01300 kMenuIdApple equ 128
01310 kMenuAbout equ 1
01320 kMenuIdFile equ 129
01330 kMenuQuit equ 1
01340 DoMenuApple
01350 kMenuAppleAccName equ -34
01360 kMenuAppleAccId equ kMenuAppleAccName-2
01370 kMenuAppleItemNum equ kMenuAppleAccId-2
01380 kMenuAppleHandle equ kMenuAppleItemNum-4
01390 kMenuAppleLocals equ kMenuAppleHandle
01400 link a6,#kMenuAppleLocals
01410 * d0.w=item.
01420 cmp.w #kMenuAbout,d0
01430 bne.s DoMenuApple10
01440 clr.w -(sp) ;dummy result.
01450 move.w #128,-(sp) ;kAboutAlertId.
01460 clr.l -(sp) ;push nil.
01470 TBX NoteAlert
01480 addq.l #2,sp ;discard result.
01490 bra.s DoMenuApple99
01500 DoMenuApple10
01510 move.w d0,d4 ;temp
01520 clr.l -(sp) ;Result (appleMenu)
01530 move.w #kMenuIdApple,-(sp)
01540 TBX GetMHandle
01550 move.w d4,-(sp) ;aItem.
01560 pea kMenuAppleAccName(a6)
01570 TBX GetItem
01580 clr.w -(sp)
01590 pea kMenuAppleAccName(a6)
01600 TBX OpenDeskAcc
01610 addq.l #2,sp ;discard result.
01620 DoMenuApple99
01630 unlk a6
01640 rts
01650 DoMenuChoice
01660 tst.l d0
01670 beq.s DoMenuChoice99
01680 swap d0 ;menu
01690 cmp.w #kMenuIdApple,d0
01700 bne.s DoMenuChoice10
01710 * Handle Apple Menu.. tbd.
01720 swap d0
01730 bsr DoMenuApple
01740 bra.s DoMenuChoice98
01750 DoMenuChoice10
01760 cmp.w #kMenuIdFile,d0
01770 bne.s DoMenuChoice99 ;no other menus.
01780 swap d0
01790 cmp.w #kMenuQuit,d0
01800 bne.s DoMenuChoice99 ;no other items.
01810 bset #0,d7;gDone=true
01820 DoMenuChoice98
01830 clr.w -(sp)
01840 TBX HiliteMenu
01850 DoMenuChoice99
01860 rts
01870 UiMouseDown
01880 kWhichWindow equ -4
01890 link a6,#kWhichWindow
01900 subq.l #2,sp ;push short
01910 move.l gMyEvent+Event.where(a5),-(sp)
01920 pea kWhichWindow(a6)
01930 TBX FindWindow
01940 move.w (sp)+,d0 ;
01950 cmp.w #inSysWindow,d0
01960 bne.s UiMouseDown10
01970 pea gMyEvent(a5),-(sp)
01980 move.l kWhichWindow(a6),-(sp)
01990 TBX SystemClick
02000 bra.s UiMouseDown99
02010 UiMouseDown10
02020 cmp.w #inDrag,d0
02030 bne.s UiMouseDown20
02040 move.l kWhichWindow(a6),-(sp)
02050 move.l gMyEvent+Event.where(a5),-(sp)
02060 move.l (a5),a0 ;a0^qd globals
02070 pea -116(a0) ;should be screenbits.bounds.
02080 TBX DragWindow
02090 bra.s UiMouseDown99
02100 UiMouseDown20
02110 UiMouseDown80
02120 cmp.w #inGoAway,d0
02130 bne.s UiMouseDown30
02140 bset #0,d7;gDone=true
02150 bra.s UiMouseDown99
02160 UiMouseDown30
02170 clr.l -(sp) ;reserve 4 bytes for where.
02180 move.l gMyEvent+Event.where(a5),-(sp)
02190 TBX MenuSelect
02200 move.l (sp)+,d0
02210 bsr DoMenuChoice
02220 UiMouseDown99
02230 unlk a6
02240 rts
02250 MorsePut
02260 * Disp each bit in turn as ./- until d6=1.
02270 move.b d0,d6 ;save in d6.
02280 MorsePut10
02290 lsr.b #1,d6 ;div 2.
02300 moveq #0,d0
02310 roxl.b #1,d0 ;
02320 move.b kMorseSyms(pc,d0.w),d0
02330 move.w d0,-(sp); push char.
02340 TBX DrawChar ;display the char.
02350 cmp.b #1,d6
02360 bgt.s MorsePut10
02370 rts
02380 kMorseSyms
02390 DATA #'.', #'-'
02400 Repaint
02410 * d0.b contains the ascii char, d5=morsecode.
02420 link a6,#-4
02430 and.w #$ff,d0
02440 move.w d0,-(sp) ;save char on stack.
02450 pea -4(a6) ;push VAR oldPort.
02460 TBX GetPort
02470 move.l gWin(a5),-(sp) ;push gWin
02480 TBX SetPort
02490 move.l gWin(a5),a0 ;gWin^
02500 ;offset to portRect is 2+14 = 16.
02510 pea 16(a0) ;portRect address
02520 TBX EraseRect
02530 bsr MoveOxOy
02540 TBX DrawChar ;it was on the stack.
02550 move.w #' ',-(sp)
02560 TBX DrawChar
02570 move.b d5,d0 ;MorseCode
02580 bsr MorsePut
02590 move.l -4(a6),-(sp) ; push oldPort
02600 TBX SetPort
02610 unlk a6
02620 rts
02630 DoUpdate
02640 * Event.message is the window pointer to update.
02650 move.l gMyEvent+Event.message(a5),d4 ;the window.
02660 move.l d4,-(sp) ;push the window.
02670 TBX BeginUpdate
02680 cmp.w gWin(a5),d4
02690 bne.s DoUpdate10
02700 bsr Repaint
02710 DoUpdate10
02720 move.l d4,-(sp)
02730 TBX EndUpdate
02740 rts
02750 kTapMax equ 15
02760 DoKey
02770 move.b gMyEvent+Event.message+3(a5),d0
02780 and.w #$ff,d0
02790 cmp.b #'-',d0
02800 beq.s DoKey03
02810 cmp.b #'.',d0
02820 bne.s DoKey08
02830 DoKey03
02840 * Morse Tap Support
02850 swap d5 ;timeout
02860 move.w gMyEvent+Event.when+2(a5),d5
02870 add.w #kTapMax,d5
02880 swap d5
02890 add.w d5,d5
02900 cmp.b #'-',d0
02910 bne.s DoKey99 ;done
02920 addq.w #1,d5
02930 bra.s DoKey99 ;done.
02940 DoKey08
02950 DO optDebugOn
02960 cmp.b #',',d0
02970 bne.s DoKey09
02980 move.w gMyEvent+Event.when+2(a5),d1
02990 trap #1
03000 FIN
03010 DoKey09
03020 DoChToMorse
03030 cmp.b #65,d0
03040 blt.s DoKey10 ;could be digit though
03050 cmp.b #65+26,d0
03060 bge.s DoKey99 ;out of range
03070 move.w d0,d5
03080 and.w #$1f,d5
03090 move.b kAlphaToMorse-1(pc,d5.w),d5
03100 bra.s DoKey20 ;done.
03110 DoKey10
03120 cmp.b #48,d0
03130 blt.s DoKey99
03140 cmp.b #48+10,d0
03150 bge.s DoKey99 ;also out of range
03160 ext.w d0 ;bit 7 of d0==0, so this clears upper byte.
03170 move.b kDigitToMorse-48(pc,d0.w),d5
03180 DoKey20
03190 bsr Repaint
03200 move.w #1,d5 ;reset morse code.
03210 DoKey99
03220 rts
03230 kAlphaToMorse
03240 DATA #6, #17, #21, #9, #2, #20, #11, #16
03250 DATA #4, #30, #13, #18, #7, #5, #15, #22
03260 DATA #27, #10, #8, #3, #12, #24, #14, #25
03270 DATA #29, #19
03280 kDigitToMorse
03290 DATA #63, #62, #60, #56, #48
03300 DATA #32, #33, #35, #39, #47
03310 DoNull
03320 cmp.b #1,d5 ;morseTap
03330 ble.s DoNull99 ;not started a MorseTap.
03340 swap d5 ;upper word =timeout.
03350 cmp.w gMyEvent+Event.when+2(a5),d5
03360 bmi.s DoNull10 ;d5-event.wh n=to
03370 swap d5
03380 bra.s DoNull99
03390 DoNull10
03400 swap d5
03410 clr.l -(sp) ;
03420 TBX FrontWindow
03430 move.l (sp)+,d0
03440 cmp.l gWin(a5),d0
03450 bne.s DoNull99 ;wrong window.
03460 and.w #63,d5 ;actual code.
03470 move.b kFromMorse(pc,d5.w),d0
03480 bsr DoChToMorse
03490 moveq #1,d5
03500 DoNull99
03510 rts
03520 kFromMorse
03530 ASC "0.ETIANMSURWDKGOHVF.L.PJBXCYZQ3."
03540 ASC "54.3...24....:.16.5....:7...8.90"
03550 DoEvent
03560 TBX SystemTask
03570 clr.w -(sp) ;push dummy result
03580 move.w #everyEvent,-(sp) ;we want every event.
03590 pea gMyEvent(a5)
03600 TBX GetNextEvent ;result in (sp)+.
03610 move.w gMyEvent+Event.what(a5),d1 ;temp var.
03620 tst.w (sp)+
03630 beq.s DoEvent01 ;no posted event, means NULL
03640 tst.w d1 ;nullEvent==0.
03650 bne.s DoEvent02 ;try next type.
03660 DoEvent01
03670 bsr DoNull
03680 bra.s DoEvent99
03690 DoEvent02
03700 cmp.w #mouseDown,d1 ;mouse down?
03710 bne.s DoEvent10
03720 bsr UiMouseDown
03730 DoEvent10
03740 cmp.w #keyDown,d1 ;key?
03750 bne.s DoEvent20
03760 bsr DoKey
03770 bra.s DoEvent99
03780 DoEvent20
03790 cmp.w #updateEvt,d1 ;update?
03800 bne.s DoEvent99
03810 bsr DoUpdate
03820 DoEvent99
03830 rts
03840 kHello
03850 STR "Hello World!"
03860 kMonaco
03870 STR "monaco"
03880 ENDR
03890 SEG 0,32,VAR.LEN,$20
03900 SEG0
03910 SEG_1 JP Start,1
03920 END_1
03930 END0
03940 ENDR
03950 LIST OFF
03960 RSRC WIND,400,4
03970 DATA /40,/40,/94,/160 ;Bounds
03980 DATA /0 ;Wind Def
03990 DATA #1,#0 ;Visible
04000 DATA #1,#0 ;GoAway
04010 DATA 0 ;refCon
04020 STR "MiniMorse" ;Title
04030 ENDR
04040 *--------------------------------
04050 RSRC MENU,kMenuIdApple,4
04060 DATA /kMenuIdApple ; Menu ID
04070 DATA /0,/0
04080 DATA /0 ; Menu Def.
04090 DATA /0
04100 DATA $FFFFFFFB ; Enable Flags
04110 HEX 0114 ; Title (Apple logo)
04120 STR "About..."
04130 DATA #0,#0,#0,#0 ; Icon,Keyboard,Mark,Style
04140 STR "-"
04150 DATA #0,#0,#0,#0
04160 STR ""
04170 ENDR
04180 *--------------------------------
04190 RSRC MENU,kMenuIdFile,4
04200 DATA /kMenuIdFile
04210 DATA /0,/0
04220 DATA /0
04230 DATA /0
04240 DATA $FFFFFFFF
04250 STR "File"
04260 STR "Quit"
04270 DATA #0,#'Q,#0,#0
04280 STR ""
04290 ENDR
04300 *--------------------------------
04310 RSRC ALRT,128,4
04320 DATA /100,/100,/188,/400 ;Bounds
04330 DATA /128 ;DITL ID
04340 DATA /$5555
04350 ENDR
04360 *--------------------------------
04370 RSRC DITL,128,4
04380 DATA /2-1 ;Number of Items -1.
04390 DATA 0
04400 DATA /10,/64,/42,/290
04410 DATA #8 ;Static text.
04420 DATA #AboutMsgEnd-AboutMsgStart
04430 AboutMsgStart
04440 ASC "MiniMorse "
04450 DATA #$A9 ;Copyright
04460 ASC "Julz 2023"
04470 DATA #13 ;CR
04480 ASC "Type A-Z,0-9 <=> .-.. (Morse)"
04490 AboutMsgEnd
04500 DATA 0 ;Handle or proc^ placeholder
04510 DATA /60,/230,/80,/290 ;Disp Rect
04520 DATA #4 ;Type=Button
04530 STR "OK" ;Content
04540 ENDR
04550 end
You'll the code in the main disk's window. The Libraries for the include files are in another Folder, but since this is a System 1.x disk image the folders are a fiction, it's really a flat disk format and the inc files can be included directly. Here's how to make it: Double-click on MiniMorse.asm and it'll open MacASM to edit it. Type: asm<cr> and MacASM will start to assemble the code. It would normally display the text it's assembling as it goes on, but LIST OFF turns that off to speed things up. Even so, it's pretty slow if you run miniVMac at 1x. Don't press any keys while it's assembling as it'll pause and won't tell you it's paused. Eventually it'll say:
0000 Errors In Assembly
Ok.
Type quit to exit MacASM. You'll see there's now a file called MakeApp.sh in the MinMorseFull window. Strangely, MacASM didn't link the file in the first pass, but double-clicking on MakeApp.sh will run MacASM again and automatically link the file producing the App: MiniMorse. Double-click that and you get the app:
Which greets you with "Hello World!" thanks to me leaving the even more humble origins of MiniMorse in the code. Typing in a capital letter or digit generates the appropriate Morse and quickly typing in a sequence of dots '.' and dashes '-' converts it to a letter.
MiniMorse has enough of a normal Macintosh application implemented to make it seem as least a little bit conventional. Considering that MiniMorse is only 1473 bytes long I'm pleased with that. You can move the window; it handles an About... box and desk accessories. The single File:Quit menu option is supported, but closing the box also does the same thing, returning to the desktop without crashing!. If something obscures the window and it has to redraw, it doesn't do it properly though. I've assembled and run MiniMorse on System 7.1 and System 1.x, 2.0, 2.1 (on an emulated Mac 128kB), and 3.0.
In future posts on this topic, I'll outline a bit about what it's like to work with MacASM and how I did some debugging with it. I might even get around to converting the manual from the massive 22.3MB of scanned images into the real, typewritten thing that looks like it was written on an Apple ][ !
If you get around to playing with it, send me a simple Morse code message in a reply and I'll see if I can decode it !