Monthly Archives: September 2018

Sound works!

The past few weeks turned out rather interesting. After getting the basic Device Manager API written, we needed to have some simple test case to try it out – and as mentioned in the previous post, Sound Driver was an interesting potential use case. So that’s what we did…

The “Classic Mac” sound hardware

To get sound output abstracted in the emulator, we needed a bunch of stuff to support it, which includes:

  • Host platform abstraction for sound device: This allows us to create a SDL output buffer, and handle transfer to sound data from emulator to the host.
  • Hardware abstraction for sound: This separates “Classic Mac” type sound from future “Sound Manager” type generic audio output, and also ties the “Classic Mac” VIA to the audio output to control square wave generator, volume, alternative sound buffer, etc.
  • Vertical retrace simulation: More about this below
  • And also the Sound Driver DRVR

Vertical retrace interrupt simulation

One challenge in the old “Classic Mac” type computers is, that the sound output is deeply tied into the video signal generation, and especially the timings depend a lot on the frequency this generator works. In attempt to match this hardware as closely as possible, we added a secondary thread to simulate vertical retrace interrupts. As some might know, simulating hardware interrupts using threads is very tricky: On real hardware, interrupts are non-preemptive (except when allowed by the status register, as VBL interrupt handler does to keep Ticks low-mem variable in sync), but on multithreading system main thread could at any time pre-empt the interrupt thread.

To prevent this, we devised a locking scheme, which attempts to ensure that the interrupt service never gets cut off by main thread. At this moment it’s not a problem, as there is not yet actual 68K code running on main thread, but that we need to prepare for that in future, especially when VBL service routines written in 68K code might interrupt 68K code running on main thread!

In any way, the interrupt simulator currently reads the full sound buffer on each simulated VBL interrupt, and outputs the result through the hardware abstraction layer. The biggest challenge here at moment is keeping the SDL’s audio thread and VBL thread in sync, as the frequency of SDL sound output needs to match *exactly* the rate at which VBL thread is providing the sound data. It appears to be quite OK at the moment though, with occasional minor ticks here and there – currently the biggest source of breaks in audio appears to actually be the Xcode debug console, which seems to not be very thread-friendly…

Square-wave generator test

One neat feature of “Classic Mac” sound hardware is the built-in square-wave generator. Absent from later macs, this feature was very practical way of generating sound output without using almost any CPU resources. Controlled by the VIA, it was the first sound feature we implemented:

A simple square-wave sound test (sorry for low volume)

The Sound Driver

With sound hardware emulation in place, we could finally implement the Sound Driver. It took a day to decipher the old “.Sound” DRVR code, but after understanding how it works, we were able to whip up a C implementation which has equivalent functionality. In the process, we finalized an initial implementation for Vertical Retrace Manager.

Examining the original .Sound driver

After adding four-tone support, we needed a way to test this, so we wrote a test song by hard-coding it in C-language using set of frequencies as described in Inside Macintosh II-237 “Sound Driver” chapter, and used FTSynthRec to feed them to the virtual “.Sound” DRVR combined with a custom VBL task:

MacEmu four-tone synthesizer test

Device Manager

On the road to file system

The major motivation for working on Device Manager at this point was the goal of getting file system eventually up and running. As planned earlier, we will implement file system driver as a “virtual” DRVR resource, which we will mount the file system with, and using it requires naturally the Device Manager.

Interestingly, the Device Manager API overlaps with File Manager’s calls, which means that certain traps (Open/Write/Read/Close) can be called for both files using the FCB IDs (positive ioRefNums), or device drivers using the driver reference numbers (negative ioRefNum). Besides those four traps, Device Manager has a set of calls which only apply to device drivers, and File Manager has a lot of calls which are only valid for FCBs. At this point the goal is to implement the core I/O functions.

Other benefits

Besides the (planned future) loading of file system DRVR and file system support, there are a few other benefits of implementing the device Manager:

  • We can also implement the famous Sound Driver, which a lot of early Mac games used (in combination with direct sound hardware access)
  • At some point, implement the standard mac Printer/Modem serial ports (which can be mapped with the Platform abstraction to any character-based serial physical or virtual input/output devices)

68K Emulator integration

The CPU update

At this point, Pukka had been working hard to update the 68K emulator to work with the new environment. With 64-bit support, cross-platform standard C and POSIX compatibility as requirements, we had already discussed how to interface the emulator with the virtual memory system.

First integration test

To make a simple test case for the 68K emulator, we created a short 68K assembly program which we wrote manually to the memory using the C code below, and ran the CPU:

First successful 68K emulator integration test

Quite simple test, but it not only displayed that the 68K emulator worked, but also that our trap dispatcher was also working as intended! Which meant that the calls from 68K to native C code worked, arguments got translated correctly, return value got transferred to 68K stack and it just…worked!

…Which was a bit ironic, as soon after this we noticed that we forgot to allocate space on stack for the return value of Random trap (0x558F = SUBQ.L #2,A7). The MOVEA._L -1,A0 and MOVE.L A0,-(A7) was supposed to put return address for RTS but somehow it worked! I guess we got lucky this time…