• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Making a Game in Assembly with MDS 2.0

jkheiser

Well-known member
I started tinkering on this a month ago and thought I would start sharing my progress about it here on 68kMLA.

Why assembly? Good question. The short answer: it’s just a self-imposed challenge. The long answer: I grew up using a Mac Plus, which gave me the skills that led to an enjoyable career in computers. Despite this, I was never more than a power user. Other than World Builder and BASIC, I avoided programming because it intimidated me. I later overcame this aversion and now enjoy writing JavaScript for a living. Even so, part of me is still afraid of “real programming,” the kind where you’re actually writing instructions for the processor to push bytes around. It’s always looked like an impenetrable soup of hexadecimal gibberish I could never understand, but to hell with that: I’m going to learn 68k assembly and make a fun game before I turn 50 next year.

Other goals for this project:
  • Good-quality sampled sound effects (11KHz) using the original sound driver
  • Good-quality animation (at least 20fps) using CopyBits
  • Compatible with early Macs, even the original 128k
For now, below is a picture of what my R&D efforts have produced so far. It took a while to get here, but it’s been a very rewarding process so far. Can anyone guess what game I’m making?

work-in-progress.gif
 

cheesestraws

Well-known member
I'm looking forward to following along with this :). I've never written a whole application in 68k asm, just isolated bits that either need to go fast or talk to hardware, so I expect to learn quite a bit vicariously. I've found it quite a nice assembly language though...

Also little blue box inconspicuously in the corner there, nice ;-).
 

jkheiser

Well-known member
One of the first things I did was figure out how to enable the alternate video buffer. This was an early feature Apple provided so developers could instantly flip the Mac’s video circuitry to show bitmaps from a different memory address. Inside Macintosh Vol. 1 suggests its use to obtain smoother animation, but it also cautions that its support would likely be removed on later Macintosh hardware! Well, I don’t really care about that in 2023.

I consulted Yves Lempereur’s code for the Demo3D program he wrote with MacASM. It was very insightful. Basically, you have to re-launch your program. The Finder will initially open it with a normal buffer configuration, then you have to call the _Launch trap with a special configuration. Here is how my program is doing it with MDS:

Code:
; -----------------------------------------------------------------------------
; Initialize video (alternate buffer)

INIT_VIDEO

        TST.W       CurPageOption           ; Test for alt. buffer
        BMI.S       @SKIP_RELAUNCH          ; Skip relaunch if already enabled
        MOVE.L      #ApplScratch,A0         ; Use ApplScratch for launch config
        MOVE.L      #CurApName,(A0)         ; Load PTR to app name
        MOVE.W      #-1,4(A0)               ; -1 = enable alt. buffer
        _Launch                             ; Relaunch with new settings

@SKIP_RELAUNCH

        MOVE.L      ScrnBase,A0             ; A0 = Main buffer address
        MOVE.L      A0,CurrScrn(A5)         ; Save current buffer in globals
        MOVE.L      A0,MainScrn(A5)         ; Save main buffer in globals
        SUB.L       #$8000,A0               ; Subtract $8000 to get alt. address
        MOVE.L      A0,AltScrn(A5)          ; Save alt. buffer in globals
        MOVE.L      VIA,A0                  ; A0 = VIA address
        ADD.L       #$1E00,A0               ; A0 = VIA address + vBufA offset
        MOVE.L      A0,vBufA(A5)            ; Save VIA + vBufA in globals

        RTS
 

jkheiser

Well-known member
Switching to the alternate video buffer (and back to the main one) is just a matter of flipping a bit.

Code:
; -----------------------------------------------------------------------------
; Toggle between the main and alternate video buffers

FLIP_SCREEN

        MOVE.L      CurrScrn(A5),A0             ; A0 = current buffer address
        MOVE.L      ScrnBase,A1                 ; A1 = main buffer address
        CMP.L       A0,A1                       ; Is it the main buffer?
        BEQ         ALT_SCREEN                  ; Yes, then go to ALT_SCREEN
        BRA         MAIN_SCREEN                 ; No, then go to MAIN_SCREEN

; -----------------------------------------------------------------------------
; Switch to the main video buffer

MAIN_SCREEN

        MOVE.L      vBufA(A5),A0                ; A0 = VIA + vBufA
        BSET        #vPage2,(A0)                ; Switch to main buffer
        MOVE.L      MainScrn(A5),CurrScrn(A5)   ; Save address in globals
        RTS

; -----------------------------------------------------------------------------
; Switch to the alternate video buffer

ALT_SCREEN

        MOVE.L      vBufA(A5),A0                ; A0 = VIA + vBufA
        BCLR        #vPage2,(A0)                ; Switch to alternate buffer
        MOVE.L      AltScrn(A5),CurrScrn(A5)    ; Save address in globals
        RTS
 
Last edited:

Snial

Well-known member
Gosh, so it looks like you have an original Mac 128 (one that doesn't even say '128' on it) and you're writing Mac applications on it using MacAsm? I've just downloaded MacAsm - it's super simple! The 3D demo doesn't work on miniVMac though. I couldn't get the initial demo 1 to work either at first and I had to change Screen EQU to: ScrnBase EQU $824
then replace the LEA #Screen,a0 with MOVE.L ScrnBase,A0

I'm really impressed with MacAsm though - especially the fact that it crams an editor, assembler and resource compiler into 22kB! Also, the manual is really short! Cool!
 

jkheiser

Well-known member
Gosh, so it looks like you have an original Mac 128 (one that doesn't even say '128' on it) and you're writing Mac applications on it using MacAsm?

It’s actually a 4MB Mac Plus in disguise, but it was an original 128k when I first got it in 1985!

MacASM is pretty nifty for what it manages to do with such a small footprint. I contemplated using it for my project, but adopted MDS because all of the assembly books I’ve been reading use it in their examples. The MDS user interface is much more Mac-like, and its well-integrated suite of build tools make development rather pleasant.

The killer feature in MDS is the Exec app, which lets you automate a bunch of intermediate build steps, which it calls a job. It drives:
  1. ASM to assemble your code into relocatable segments.
  2. RMaker to compile your resources.
  3. Link to piece everything together into an executable application.
  4. Then it opens your fully assembled program!*
*unless you have an error, in which case Exec plops you back into your editor.

On an M1 MacBook Pro running Mini vMac, all of this work is done in a split-second. The results are nearly instantaneous, which is helpful because my results are usually bad. The automative power of Exec and the speed of modern computers let me rapidly screw up over and over and over until I get it right.

As you can see in this short clip, you can barely even see Exec doing anything. It feels like it just launches your program. Blink and you miss it.
 

Snial

Well-known member
It’s actually a 4MB Mac Plus in disguise, but it was an original 128k when I first got it in 1985!
Ah, Mac Plus in disguise*! I've never used an actual 128K Mac, but my first University, the University of East Anglia (UK) had a lab full of Fat Macs, which may have been upgraded Mac 128Ks.
MacASM is pretty nifty for what it manages to do with such a small footprint. I contemplated using it for my project, but adopted MDS because all of the assembly books I’ve been reading use it in their examples. The MDS user interface is much more Mac-like, and its well-integrated suite of build tools make development rather pleasant.

The killer feature in MDS is the Exec app, <snip>
OK, looks pretty smart! I keep coming across MDS in MacGui.com posts.

I think I'll probably continue with MacAsm at least for a while, simply because it's sooooo tiny! I plan to convert my rudimentary Morse Code tutor from Think Pascal to MacAsm. One thing I need to do is get my head around text-based resources as I've always been lazy and used ResEdit. I don't yet get how the MacAsm resource compiler knows what size a given field is supposed to be :) .

-cheers from Julz

*
 

jkheiser

Well-known member
This is really going to hurt sales.

Code:
; -----------------------------------------------------------------------------
; Check that screen resolution is 512×342

RESOLUTION_CHECK

        MOVE.L          (A5),A0                     ; A0 = QuickDraw vars
        LEA             ScreenBits+Bounds(A0),A0    ; A0 = Screen bounds address
        ADD.L           #Bottom,A0                  ; A0 = height and width
        CMP.L           #$01560200,(A0)             ; Is the screen 512×342?
        BNE.S           @CLASSIC_MACS_ONLY          ; No? Sorry, Charlie.
        RTS

@CLASSIC_MACS_ONLY

        PEA             'Sorry, classic Macs only!' ; Push String 1
        CLR.L           -(SP)                       ; Push String 2 (NIL)
        CLR.L           -(SP)                       ; Push String 3 (NIL)
        CLR.L           -(SP)                       ; Push String 4 (NIL)
        _ParamText                                  ; Replace text with params
        MOVE.W          #ErrorDialog,-(SP)          ; Push dialog ID
        CLR.L           -(SP)                       ; Push filter (NIL)
        _Alert
        _ExitToShell

sorry.gif
 
Last edited:

olePigeon

Well-known member
Would be funny if you could somehow tell if it's running in a VM. Then put another dialog, "I said classic Macs only! OK, fine, just this once."
 

jkheiser

Well-known member
Games on the Mac need their sounds prepared properly so they are ready to play and padded to the correct byte length, otherwise they might play during floppy access (which garbles them) or make an awful popping sound because their length is not an even multiple of the sound buffer’s size.

Since “good-quality sound” is one of the goals for this project, this weekend was spent on a sound framework. Here are its routines:
  • BUFFER_SOUND
    This takes a SND resource by name, reads it with the Resource Manager, and coaxes its WaveBytes into a compound record I call the SndBuffRec; it’s essentially a Parameter Block record stacked on top of a Free-Form Synthesizer record. The Memory Manager stores it in a handle so it can be relocated, though I doubt much heap compaction will happen with such a simple program.
  • PLAY_SOUND_ASYNC
    Once a sound has been buffered, it can be played hundreds of times with this subroutine. It dereferences the handle to a SndBuffRec (which reveals a pointer to the Parameter Block) and the Sound Driver takes it from there with the _Write,ASYNC trap.
  • PLAY_SOUND_SYNC
    Same as the other routine, except the audio playback is synchronous.
  • STOP_SOUND
    This sets up a temporary Parameter Block record on the stack so the _KillIO trap can be called.
  • DESTROY_SOUND
    This tears down a SndBuffRec so its memory can be reclaimed.
  • BUFFER_PLAY_DESTROY
    Some sounds are only played once, so why keep them around? This routine is just a wrapper around BUFFER_SOUND, PLAY_SOUND_SYNC, and DESTROY_SOUND.
  • FINISH_SOUND
    This is the completion routine for all sounds when they are finished playing. Right now it just posts an application event that unlocks the SndBuffRec handle, but it will probably have to do more work in the future.
I mapped some numeric keys (1-5) to a handful of different SndBuffRec buffers so PLAY_SOUND_ASYNC could be stress-tested by mashing on the keyboard. It worked great! Other than when the program first opens, there is no dreaded popping.

That first pop does not recur if you close the program and re-open it. Maybe the hardware buffer needs to be filled with silent bytes ($7F) before the first sound plays? I’ll give it a try. I hate that popping sound!

If you’re curious to try this sound framework yourself, check out the attached disk image.
 

Attachments

  • mac-kaboom-021923.zip
    245.7 KB · Views: 3
Last edited:

jkheiser

Well-known member
There appear to be two culprits behind that first popping sound:
  • When your Mac starts, the sound buffer is entirely full with one of the loudest bytes you can put into it: $FF
  • The first time the Mac makes a sound, it turns on the amplifier, which makes it pop a little. Just changing the volume can also make it pop.
The first problem can be ameliorated by writing $7F into the sound bytes. The second, however, I can’t think of anything to help, other than some mitigation. So that’s what I did.

Code:
; -----------------------------------------------------------------------------
; Initialize sound

INIT_SOUND

; Lessen the popping before the first sound

        BSR             BE_QUIET                ; Hush any noisy bytes
        BSR             AMP_ON                  ; Turn on amplifier
        MOVE.L          #30,A0                  ; One-half of a second
        _Delay                                  ; Get some distance on pop
        RTS                                     ; Return from subroutine

; -----------------------------------------------------------------------------
; Make sure the sound buffer is clean

BE_QUIET

    MOVE.L          SoundBase,A0            ; Get PTR to sound buffer
    MOVE.W          #(BufferChunk/2)-1,D0   ; Set loop counter
@Q  MOVE.B          #QuietByte,(A0)+        ; Put silence into sound bytes
    ADD.L           #1,A0                   ; Leave floppy byte alone
    DBRA            D0,@Q                   ; Loop until full
    RTS                                     ; Return from subroutine

; -----------------------------------------------------------------------------
; Turn on the amplifier

AMP_ON

    MOVE.L          VIA,A0                  ; A0 = VIA + vBufB (zero-byte offset)
    BCLR            #AmpBit,(A0)            ; Clear amplifier bit
    RTS                                     ; Return from subroutine

So now the pop happens while the floppy drive is working to buffer the first sounds into RAM. For really fast machines running the game from a hard drive, there is also a half-second delay so the pop doesn’t spoil the first sound the game makes.

Not a perfect solution, but I’m satisfied. Attached is an updated disk image with these changes, along with some different sound effects. Check it out! When the game loads, press keys 1-5 to try the sounds.
 

Attachments

  • mac-kaboom-022123.zip
    261.4 KB · Views: 2

jkheiser

Well-known member
Tonight I spent some time with MAME’s debugging tools to figure out why I was getting an address error on 64k ROM machines like the 128k and Fat Mac. Turns out it was happening in the MaxApplZone trap. It might be incompatible with the alternate video buffer on the 64k ROM. The 128k ROM has no issue with it.

Skipping the trap so the game could launch, I then tried the sound framework on a Fat Mac. It was able to buffer all the demo sounds without running out of memory, but they all sounded terrible! There is a nasty pop at the beginning of every sound. Apple must have revamped the Sound Driver in the 128k ROM. Silicon Beach Software wrote their own driver (RealSound™) for their games. This is probably one of the reasons why.

The sound framework on the 128k is able to buffer one sound before running out of memory. Get this game playable on the original Macintosh might not be worth the struggle. What a straitjacket.
 

Snial

Well-known member
<snip>I then tried the sound framework on a Fat Mac. It was able to buffer all the demo sounds without running out of memory, but they all sounded terrible! There is a nasty pop at the beginning of every sound. Apple must have revamped the Sound Driver in the 128k ROM. Silicon Beach Software wrote their own driver (RealSound™) for their games. This is probably one of the reasons why.

The sound framework on the 128k is able to buffer one sound before running out of memory. Get this game playable on the original Macintosh might not be worth the struggle. What a straitjacket.
Interesting. I had come across this tech article just a few days ago, which basically says you shouldn't use the early sound drivers:

"Apple currently discourages use of the Sound Driver due to compatibility issues. The hardware support for sound designed into the early Macintosh architecture was minimal. (Many things have changed since 1983–1984.) The new Macintosh computers contain a custom chip to provide better support for sound, namely the Apple Sound Chip (ASC). The ASC is present in the complete Macintosh II family as well as the Macintosh SE/30 and later machines. When the older hardware of the Macintosh Plus and SE are accessed, it is likely to cause a click. This click is a hardware problem. The software solution to this problem was to continuously play silence. This is not a real solution to the problem and is not advisable for the following reasons:"

https://spinsidemacintosh.neocities.org/tn405#tn019

Since it looks like you have quite a bit more experience in 68K Mac development than I, apologies if you already knew this.

-cheers from Julz
 

Phipli

Well-known member
Does the buffer return to a specific state when at idle? Can you ramp from that state to your sample start value?

Do all your samples start at the centre zero point in the wave? Not likely to be an issue given you say there is a known issue with the sound driver.

Or is the issue that Apple fundamentally wiggles the speaker when they start playing a sound :/
 

jkheiser

Well-known member
Thanks for the link, @Snial. Funny to see Apple’s own PR describing it as an “embarrassing click.” Poor Andy Hertzfeld, who was forced to write the Sound Driver in one weekend, getting thrown under the bus like this! I would love to use the Sound Manager, but it is not supported on the 128k and 512k Macs.

This Technical Note also describes the click as a hardware problem that plagued only the Plus and SE, but that isn’t completely accurate. Other than one tiny pop when turning on the amplifier, MacKaboom’s sound framework never pops again on a real Mac Plus. Other than padding out all of its buffers with silent bytes, it’s not continuously calling the Sound Driver to play silence.

Does the buffer return to a specific state when at idle?

Yes. After a sound is finished playing, the hardware buffer’s sound bytes are all left at $80, which is the center point of the wave.

Or is the issue that Apple fundamentally wiggles the speaker when they start playing a sound :/

Something like that is a factor. The turning on/off of the amplifier or changing the volume induces a soft clicking/popping. That seems to be the hardware problem referred to in the Technical Note that Snial excerpted above.

Tonight I did some more debugging with MAME and learned the culprit of the big nasty pop that plagues all the sounds on 128k and 512k Macs: it’s KillIO, which MacKaboom’s sound framework calls before every sound. It fills the buffer with $FF, the loudest byte there is.

kill-io.gif
 

Attachments

  • kill-io.gif
    kill-io.gif
    1.4 MB · Views: 4
Last edited:
Top