We have reached Letter VIII of the Amiga Machine Code course. As always, make sure to read the letter, since I won’t go through all the details.
In this post, we are going to look at audio on the Amiga 500, and go through a little bit of machine code, that shows how to deliver a nice little opening and closing effect to a looped sample.
The Amiga 500, delivers sound through four 8-bit mono audio channels. The channels can be combined to deliver stereo. Unlike previous generations of computers, the Amiga delivers pulse-code modulated sound, i.e. sound based on samples. So the Amiga is a digital sound processor and was an important part of the digital sound revolution back in the day.
One of the Amigas largest arch-rivals was the Atari ST, which came with native MIDI ports. Because of this, it got more traction on the music scene than the Amiga, which raises an interesting point: That often software is not written to require extra hardware.
However, it was possible to do MIDI on the Amiga. For those interested, take a look at this video from RetroManCave:
As seen in the video, it takes additional hardware to make MIDI work on the Amiga. MIDI did not work out-of-the-box, and that was a real differentiator to the Atari ST.
One can just imagine what a machine the Amiga could have become, if it also shipped with build-in MIDI and input for samples. I sit here writting this, with the feeling that the Amiga could have been much more. 😕
The Amiga 500 shipped with the Paula audio chip, wich also handles other tasks, such as disk drive, mouse, and joysticks.
Paula supports four 8-bit audio channels, where volume and playback speed can be set for each individual channel. Because all sound revolves around playback of samples, the channels are said to be pulse-code modulated. This technique was a departure from the old school legendary chips like the MOS SID chip for the Commodore 64, that was based on waveforms.
In the C64, waveforms are the building blocks of sound, whereas in the amiga, the building blocks are samples. Samples, allows the Amiga to produce a very rich sound, that is hard to emulate with waveforms. For instance plucking a string on a guitar, will reveal it’s fundamental frequency, but also various overtones. All of this can be captured by sampling, but can be very complicated to reproduce with a mixture of waveforms.
Next, we’ll take a look at how to manipulate audio in machine code! 😃 🚀.
Programming the Digital Sound Processor
The Paula sound chip is programmed by setting a series of registers. It’s a rather simple design, yet very expressive. Let’s take a look at the registers, one by one.
The sample data are expected to have signed values, i.e. positive or negative, and it uses a technique called twos compliment to achieve that. Twos compliment is used on most, if not all, hardware today. It’s all explained in letter VIII 😉.
The values for the 8-bit sample data goes from -128 to 127, and that means we sample with uniform quantization. Depending on the signal, we might miss some information in the sample, through what is called a quantization error. This can have an effect on sound quality.
Here’s an example of a a simple sine wave, expressed as a sample. Samples are not confined to sinusoids, but can be complex sounds, such as pianos or speech. We will revisit this particular sample in a later post.
sample: dc.b 0,40,90,110,127,110,90,40,0,-40,-90,-110,-127,-110,-90,-40
Now, let’s combine all this stuff into a working program.
The Turntable Sound Effect
The first program in letter VIII is called mc0801. When the program starts, it plays a sample loop, while slowly increasing the volume and playback speed, until the desired volume and normal playback speed is reached. When the left mouse button is pressed, it decreases the playback speed and lowers the volume. It kind of reminds me of an old turntable starting and stopping.
Here’s a preview of what it sounds like:
This turntable effect is very easy to do in machine code. After the initial setup of the sound registers, the program enters a loop called up, that turns the volume and playback speed up. Then it just plays the sample over and over. When the mouse button is pressed, the program enters a loop called down, that works in reverse of the up loop. Voila - thats it! 😃.
I have annotated the source code for mc0801 below.
move.w #$0001,$dff096 ; DMACON disable audio channel 0 lea.l sample,a1 ; move sample address into a1 move.l a1,$dff0a0 ; AUD0LCH/AUD0LCL set audio channel 0 location to sample address move.w #48452,$dff0a4 ; AUD0LEN set audio channel 0 length to 48452 words move.w #700,$dff0a6 ; AUD0PER set audio channel 0 period to 700 clocks (less is faster) move.w #0,$dff0a8 ; AUD0VOL set audio channel 0 volume to 0 move.w #$8001,$dff096 ; DMACON enable audio channel 0 moveq #0,d1 ; quick move 0 into d1 (volume level) move.l #700,d2 ; move 700 into d2 (period clock) moveq #64,d7 ; quick move 64 into d7 (loop counter) up: ; begin up loop bsr wait ; branch to subroutine wait 1/50th of a second bsr wait ; branch to subroutine wait 1/50th of a second move.w d1,$dff0a8 ; AUD0VOL set to volume level stored in d1 move.w d2,$dff0a6 ; AUD0PER set to period clock stored in d2 addq.l #1,d1 ; increment volume level in d1 by 1 subq.l #8,d2 ; decrease period clock in d2 by 8 (makes it play faster) dbra d7,up ; check loop counter - if > -1 goto up waitmouse: ; wait for mouse button press btst #6,$bfe001 ; test CIAAPRA FIR0 is pressed bne waitmouse ; if not goto waitmouse moveq #64,d7 ; set loop counter d7 to 64 down: ; begin down loop bsr wait ; branch to subroutine wait 1/50th of a second bsr wait ; branch to subroutine wait 1/50th of a second move.w d1,$dff0a8 ; AUD0VOL set to volume level stored in d1 move.w d2,$dff0a6 ; AUD0PER set to period clock stored in d2 subq.l #1,d1 ; decrease volume level in d1 by 1 addq.l #8,d2 ; increase period clock in d2 by 8 (makes it play slower) dbra d7,down ; check loop counter - if > -1 goto up move.w #$0001,$dff096 ; DMACON disable audio channel 0 rts ; return from subroutine wait: ; wait subroutine - waits 1/50th of a second move.l $dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word asr.l #8,d0 ; algorithmic shift right d0 8 bits and.l #$1ff,d0 ; add mask - preserve 9 LSB cmp.w #200,d0 ; check if we reached line 200 bne wait ; if not goto wait wait2: ; second wait - part of the wait subroutine move.l $dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word asr.l #8,d0 ; algorithmic shift right d0 8 bits and.l #$1ff,d0 ; add mask - preserve 9 LSB cmp.w #201,d0 ; check if we reached line 201 bne wait2 ; if not goto wait2 rts ; return from wait subroutine sample: blk.w 48452,0 ; allocate 48452 words and set them to zero
To get the program up and running, you need to fetch Disk1 and locate the folder brev08. In this folder you find the mc0801 program and the sample. The sample has to be loaded into memory, before the program is run. Type the following into Seka assembler:
SEKA>r FILENAME>brev08/mc0801 SEKA>a OPTIONS> No Errors SEKA>ri FILENAME>brev08/sample BEGIN>sample END> SEKA>j
In the next post, we are going to look at another program, that uses a sampled periodic sine, to create wavetable synthesis!