Simplest Sound Effects Library for BASIC Programs
Commander X16 has pretty powerful sound capabilities, however they are not easy to take advantage of, especially from BASIC programs. I wrote a library to help with that, in fact I wrote two libraries with slightly different goals and ease of use.
I cut my teeth on Oric-1 back in the eighties and as I was learning BASIC I loved how easy it was to include sounds in programs. For fast and easy effects it had commands like PING, SHOOT, EXPLODE and ZAP and for more complex music it had MUSIC and PLAY commands.
Project Specs
- Emulator Version 37
- Use interrupts to play in the background without pausing BASIC program execution
- This version only uses one channel (15) so only one sound can play at the time
- Use should be very simple, without complex parameters
Usage
Because of its simplicity the library can be stored in a 1K space below basic programs, starting at $0400.
Save the EFFECTS.PRG program in the directory where your BASIC program is stored, which would typically be in the same directory from where you are running the emulator.
Load library with LOAD command:
LOAD”EFFECTS.PRG”,8,1,$0400
Since this routine after the first run adds a new interrupt handler it is good practice to only load it once. It is therefore recommended to do a simple check:
IF PEEK($400)=0 THEN LOAD”EFFECTS.PRG”,8,1,$0400
And that is it. Now you only need to call the needed sound effect from the BASIC program with a simple SYS command. We have four different effects so four different addresses can be called:
SYS $400 | PING | Is designed for events like picking coins or rewards. |
---|---|---|
SYS $403 | SHOOT | Effect that can be used for shooting the gun or other weapon. |
SYS $406 | ZAP | Electricity zapping or perhaps a laser gun sound. |
SYS $409 | EXPLODE | Long explosion for when we successfully blow something up |
How does it work? In order to understand the code we have to understand how Interrupts work on 65C02 (or 6502, 6510, 65816). I suggest you check short description at:
If you are interested in more details, this links contains pretty much everything you could ever learn about interrupts on 6502:
For our purposes we will just assume that by default the Commander X16 triggers Interrupt request 60 times per second (it is coming from VERA which refreshes the screen 60 times per second). We just need to inject our play routine before the system interrupt handler.
Sound Envelope / Modulation
In sound generation ADSR envelope is most commonly used to design different sounds. ADSR stands for Attack, Decay, Sustain and Release which describe four different phases of a sound. For our simple effects we can reduce this to just one phase that goes from maximum volume to zero in the required time period. Music instruments can have some variations in frequency e.g. vibrato or similar effects. We will need to implement frequency change to make convincing ZAP or laser sound.
The changes to volume and/or the frequency is called modulation (modulation means changing the property of sound over time) and you probably already guessed that we can do that 60 times per second if we use IRQ routine. In other words we have to define total duration and changes to our sound in 1/60th of the second intervals or steps which comes to every 16.6 milliseconds,
which is more than enough for basic sound effects we are trying to achieve.
In our program we will split code into two parts.
- First part will set up the necessary variables for the chosen sound effect.
- Second part will be the actual sound player.
Variables
Before we start with the code let’s take a look at data and variables we will use. Note that there is no such thing as variables in Assembly, just memory locations where we store values and we can name so the source code is more readable.
First two variables are status indicators. First one (running) tells our program if the play interrupt handler is already running so we don’t need to inject it before the system handler again.
The second one (phase) is an indicator whether any sound effect is currently playing, we need to start a new one or nothing is being played and we can immediately exit.
Variable release_count contains the number of cycles (1/60 of the second) that the sound effect will play and is decremented in every cycle.
Frequency is 16 bit value of the sound frequency.
Waveform is one of the four frequencies supported by VERA (pulse, sawtooth, triangle or noise).
Next three variables are 16 bit values describing starting or maximum volume, volume change per every cycle and frequency change per cycle. Since frequency is 16 bit value in VERA it makes sense that also change is stored as 16 bit value. But why do we store volume in 16 bits if maximum volume is 63 (6 bits)? The reason is of course that 1/60th of a second is a pretty short interval so for most sounds it would be very imprecise to calculate change in full numbers. We also don’t want to deal with floating points and fractions so the simpler solution is to simply use volume as high byte of the word and do relatively fast 16 bit subtraction. With that we essentially increase precision from 0 - 63 to 0 - 16,128. When writing the volume to VERA we also don’t need to do any data manipulation just copy the most significant byte directly.
At the end we have actual sound definitions that get written into above described variables based on the desired effect.
Setup
We can finally look at some code, let’s dive right into it:
We start with four jumps to four starting points. This enables us to always have SYS $400, SYS $403, SYS $406 and SYS $409 available even if we change the code below.
Next we load index into sound envelope definitions into X register and jump to common area, which simply copies 10 bytes from definitions into variables.
By storing value 255 into the phase variable we tell our interrupt handler that a new sound effect is ready to be played.
Next section simply checks if the interrupt handler is already running (meaning it is injected before the system one) and if it is not it is inserted.
At last the indicator is set to 1 so next time we start the sound effect it will not be added again.
IRQ Player
The core of this library is the Interrupt handler. If you are new to assembly language it might look a little intimidating at first but it is in fact fairly straight forward.
First part (lines 6 - 16) just check for the phase we are in. It is an assembly implementation of switch/case statement where we three possible values or states and we react accordingly.
In lines 20 - 29 we process starting values in VERA PSG. We populate all four registers so at this point the computer will start playing the sound. Right after that we exit our routine and continue with the system (Kernel) interrupt handler.
1/60th of the second later we will enter the IRQ handler again, however now the phase value will be 1 so we will jump straight to line 34. Here we first check if we are at the end of the duration of the current sound effect. If so we turn it off by setting volume to 0, update phase to 0 and exit our routine.
If we are still inside the duration sound effect we calculate next values for volume and frequency or to sound more fancy, we modulate it. This is done through two 16 bit subtractions in lines 45 - 59. Finally we update VERA PSG registers with new values and decrement the counter. We keep doing that until the counter is at 0 and until the next sound effect is played.
Complete Source code can be found here
Binary can be download here
Alternative binary named EFFECTSHI.PRG that loads into memory at $9000 can be found here. Of course calls are then $9000 for PING, $9003 for SHOOT, $9006 for ZAP and $9009 for EXPLODE.
Have fun and show your programs using these sound effects and let me know if you would like to see any changes or improvements.
Comments
Post a Comment