Chapter 7 - August 15th, 2013

Interrupt driven sound

To kickstart the actual game code or what I call the Game Loop, I need to write the core routine which handles the interrupt driven sound.

Normally I would add this routine last after creating the main game and then add the interrupt driven sound routine depending on the amount of CPU time I had left, but here it's a bit different. I need to use the same interrupt to keep track of the video scanline count so as to implement my split screen horizontal scrolling.

Let's get technical !

For this chapter, I will delve into some of the inner workings of the Color Computer 3's custom GIME chip. I will try to keep it at a level for most people to follow although some knowledge of interrupts and how the GIME chip uses them would be beneficial.

My sound routine uses the FIRQ interrupt as the trigger. FIRQ is faster and consumes less CPU cycles than IRQ because it doesn't push all the 6809 registers on and off the System Stack each time it is invoked. With FIRQ, I will push/pull only the registers that I need to save/restore with a seperate command. In this case, I restrict the code to using only the A and X registers and this still keeps the cycle use of FIRQ less than IRQ.

Minimum CPU cycle use is paramount so to leave as many cycles available for the rest of the game code. This routine will be triggered many times each second. The rate at which it is triggered determines the highest possible audible frequency or playback rate it can produce. A tradeoff needs to be set between this playback rate and how much time I leave for the game code. The higher the playback rate, the less available for the game code and vice versa.

I chose to activate the FIRQ interrupt from the GIME chip's TIMER interrupt feature. This works by feeding an interrupt source to the TIMER and the TIMER then generates the FIRQ interrupt based on the TIMER count setting. The TIMER can be set to receive a signal source from either a 14.318Mhz or a 15.734Khz source. The 14Mhz pulse is far higher than what I need although it can be used for sound. I chose the 15Khz source which is derived from the horizontal scanline frequency. The 15Khz rate is adequate for sound timing and ideal for counting the horizontal scanlines.

The TIMER is then set to trigger after a pre-chosen number of these pulses. If we set it to 1, then it will trigger on each horizontal sync pulse. This is essentially a 1:1 trigger setting. Using this setting for sound provides good fidelity but means the main game loop is being interrupted at each horizontal scanline which would hog a lot of the free cycles required by the game itself.

Setting the TIMER count to 2 halves the incoming frequency to 7.159Khz. The fidelity drops with 7.159Khz being the highest possible frequency but this only interrupts the game code after every 2 horizontal scanlines. I tested the fidelity by having the routine generate a constant tone and I was happy with the quality.

I then wondered how bad it would get if I dropped it even further by setting the TIMER trigger to 3 which derives a frequency of 4.77Khz. The tone was clearly lower but I wondered if this still was ok for sound effects such as gunshots and explosions. I knew that for music it would be too low but I wasn't planning on music during the game loop.

I decided to fire up my trusty Amiga 4000 and ran Digital Sound Studio. I opened an audio WAV file of a gunshot sound then resampled it down to 4.77Khz, comparing the sound quality before and after the resample. The sound was clearly not as crisp but it still sounded fine for my purposes so I decided to stick with this setting for now. This will be the setting I will resample all the sound samples which will be created later in the game design cycle.


Digital Sound Studio on the Amiga


 With the sample rate set, it was a matter of adding the code to count scanlines and adjust the horizontal offset accordingly to create the split screen horizontal scrolling.

Firstly, I had to determine at which count I would implement the change to horizontal offset and when it reverts back to zero. A zero setting keeps the page offset to the left most part of a 256 byte (512 pixel) virtual screen width. The GIME chip will only display 160 of these bytes (320 pixels).

I want a zero offset for the 8K MMU block at the top of the screen and also the 8K MMU block at the bottom of the screen. The remaining 5 blocks in between will have the horizontal offset altered. To locate these two points, I chose two values and changed the color of the border frame (blue areas on the diagram below) to indicate when I was altering the horizontal offset and when it was reset back to zero. I also set it to increment the horizontal offset between these two points.

To make it clear where the boundaries of the MMU blocks were, I colored the top and bottom blocks a solid color (white areas on the diagram below) and the blocks in between were striped to make it obvious where the scrolling region was. It was a matter of experimenting with different scanline counts to adjust the scroll regions so it fell within the middle five MMU blocks. 



It worked perfectly except for one minor flaw. The point I switched horizontal offsets varied by one count in alternating frames causing a jittering of color at this point. But I found an easy fix which was to adjust the boundaries so that the jittering points fell just outside the scrolling region. Here, I simply need to ensure there will be no graphics or color changes on the screen. This worked out well because these regions were going to be black (any solid color would work) and you don't see the jitter. The diagram above is an accurate representation of how it comes out on a real Color Computer 3 except that the striped region scrolls rapidly and very smoothly from right to left.

Two channel sound

 At this point, I had single channel sound. This is simply done by reading a byte at a time with each interrupt call from a table in memory that contained a sampled (digitized) sound. The last byte is a zero and when the sound routine reaches this point, it merely doesn't move forward past this point until I reset the table pointer to a new location from within the game code. For example, when I press the fire button, I set the table pointer to the start of the fire sound sample. The interrupt routine playes that till it reaches the zero byte. If I press the fire button again at any time, the table pointer is reset back to the start again and the sound replays.

The exciting part is how to create two channels of sound so that both can be heard simultaneously. Here we just duplicate the single channel logic and add the two data values together before sending them to the Color Computer's sound output (DAC). So, we can be doing rapid fire with the sound of explosions happening simultaneously.

Three and four channels are possible but a compromise needs to be made between the time allocated to sound and the time allocated to the game itself. 

One last detail

It all appears to be working how I wanted and that left only one last thing. Optimization!

This FIRQ routine needs to consume as few CPU cycles as possible so the last stage is to optimise the code as much as I can. What I tend to do for this routine is to place it in the Direct Page region of the 6809. I also place commonly used variable locations in this area. Code allocated in this Direct Page region gets accessed by the 6809 in Direct Page mode and shaves a few CPU cycles off instructions that access memory from here.

That concludes the interrupt driven sound and split screen horizontal scrolling control routine. As I progress into the construction of the game, I may return to apply any other optimization or changes that may be required.



Copyright 2013 by Nickolas Marentes