Main menu

How Drum8 Works: Multichannel Sampled Audio On A 6502

Here I describe how I managed to get a 1MHz 6502 sequencing, mixing and playing 8 samples in real time, in mono or stereo. For demos and downloads, check out the Drum8 Page.

No Hardware Sound Support

On the Apple II there is no sample buffer or audio clock, not even a DAC, hence the add-on card in Slot 5. The 6502 has to process each sample and clock it out at a consistent rate regardless of what is playing.

I used a layered approach to achieve this. From the top down:

A sequencer selects a pattern every musical bar, the page of squares you see in the Live Beat Editor,

A bar is split up into 32nd notes. Any of the 16 sounds can be triggered at these 32 boundaries. The presence of a square in the pattern causes a sound to be allocated to a “voice channel”. The allocation is pre-determined in the Configuration Editor and any playing sound for that voice is interrupted, useful in cases like the open and closed high hat.

Within a 32nd note, a tight loop executes which:
1) Reads samples from memory,
2) Adds them together (mixing) and outputs them to a DAC
(the specifics depend on the DAC/Voice configuration),
3) Increments the memory pointers for the voices
(and checks when the end of a given audio sound is reached),
4) Also checks a counter which determines when the 32nd note is complete.

Working Around No Audio Clock

To keep the timing (and hence pitch) of the 32nd notes as consistent as possible, Drum8 uses the trick of always playing samples, even when a given voice is quiet/inactive. One page of memory (at $1F00) is used to store “silence”. So silencing a finished voice is just a case of pointing it to the silent page.

This also means that the mixer is always adding up the same number of samples for every output DAC. The Loader uses this fact to prescale and offset all the sounds (including the silent page) so they will add up to a sample centred around the audio zero, 128, which is half the full scale range of an 8 bit DAC.

Finding The Cycles To Mix 8 Voices

In Drum8 V1, using the 6502’s indirect addressing mode to read the samples worked well for 4 voices, at 8kHz sample rate with a few cycles to spare.

The indirect references each take 5 cycles compared to 4 for absolute reads/adds, and also requires the Y register to be zero, wasting it in the loop. The solution was to move the sample mixing to Page Zero (addresses $0000-$00FF in the 64k address space). The same mixing code becomes:

For the price of an extra JMP back from page zero (the jump to the mixer is needed in any case), 8-3 = 5 cycles are saved when mixing 8 voices.

The now free Y register is used to store the silence end page, cleaning up the mixer pointer handling, in itself important to keep it all in one page as mentioned below.

Give Me A Few Cycles More

It was close but still too low in pitch for my liking. It got there by rearranging the pointer increment tests so the most often case of only a single byte increment for each voice pointer avoided branches. This came at the cost of a “branch back” after having to increment the high byte, but it sounded OK. The increment code looks like:

I had to take care that the entire sample pointer incrementing code was assembled into the same page to avoid extra cycles.

Take A Look Around

With Drum8 loaded you can reset into the monitor and look around. You’ll find the sample mixer at $90 and the pointer incrementor near $1B00, part of the mixer plugin that is selected in the Configuration Editor.

The 16 sound sample voice channel, sample start, and end pages are at $60,$70 and $80 respectively.