z80:Sound Routines
Sound require headphones to plug in. Since nearly no one has these, generally do not include sound into a game. ONLY use sound if that is the main purpose of the program.
This page needs more info. Members of this site can edit pages.
(Note to readers : the first real addition to this page is by Zeda, and I am not very familiar with the finer points of making sound. I only experimented, took pointers from others and eventually used a routine in Axe (after optimising it slightly). Anyways, there are others that are much more versed in this topic!)
= 1-Channel Sound
=
The idea behind making sound on the calculators is to toggle a line on the link port at a given frequency. The link port has two lines making 1 and 2 channel sound relatively okay, but there are some who have made 4-channel sound work using clever masking. Here is a very simple routine for 1-channel sound (optimised from Axe) :
p_FreqOut: ;Inputs: ; HL is the duration of the note ; BC is the frequency xor a __FreqOutLoop1: push bc xor %00000011 ;this will toggle the lower two bits (the data being sent to the link port) ld e,a __FreqOutLoop2: ld a,h or l jr z,__FreqOutDone cpd jp pe,__FreqOutLoop2 ld a,e scf __FreqOutDone: pop bc out (0),a jr c,__FreqOutLoop1 xor b out (0),a ;reset the port, else the user will be really annoyed. ret
An example duration would be 4096 and frequencies can be made to correspond to certain notes. If Duration is shorter than Frequency, nothing is done to the link port. Here is an LUT (Lookup Table) of frequencies that correspond to 96 notes (starting with the lowest, going to highest):
FrequencyLUT: .dw 2100,1990,1870,1770,1670,1580,1490,1400,1320,1250,1180,1110 .dw 1050, 996, 940, 887, 837, 790, 746, 704, 665, 627, 592, 559 .dw 527, 498, 470, 444, 419, 395, 373, 352, 332, 314, 296, 279 .dw 264, 249, 235, 222, 209, 198, 186, 176, 166, 157, 148, 140 .dw 132, 124, 117, 111, 105, 99, 93, 88, 83, 78, 74, 70 .dw 66, 62, 59, 55, 52, 49, 47, 44, 42, 39, 37, 35 .dw 33, 31, 29, 28, 26, 25, 23, 22, 21, 20, 19, 18 .dw 17, 16, 15, 14, 13, 12, 11, 10, 10, 9, 9, 8 .dw 8, 7, 7, 7, 7, 6, 6, 5, 5, 5, 5, 4
So to play a given note A for a duration of D, your routine might look like this:
ld hl,FrequencyLUT add a,l ld l,a jr nc,$+3 inc h ld c,(hl) inc hl ld b,(hl) ;now BC is the frequency for the note SoundLoop: push bc ld hl,4096 call p_FreqOut pop bc dec bc ld a,b or c jr nz,SoundLoop ret
In this case, if input D is 16, the note will last a noticeable amount of time. This method allows you to make the duration up to 4096*256 (and a multiple of 4096), but there is a downside. If we look at the first value of the LUT, 2100, using a duration of 4096 means that the port is toggled only once (we need 2100*2 for the duration in order to toggle twice). The toggling is what creates the sound. If we had used a duration of 16384, the port would be toggled 7 times, but with this method, 16384=4*4096, so we would get it toggled only 4 times. This makes for choppy sound.
That is not to say that the above routine is bad-- it is a great option for playing sound in games where you cannot devote the full processor time to sound and it allows you to extend duration beyond 16 bits. However, if the latter is what is sought, we can use a modified routine to have 24-bit durations:
p_FreqOut: ;Inputs: ; DHL is the duration of the note ; BC is the frequency xor a __FreqOutLoop1: push bc xor %00000011 ld e,a __FreqOutLoop2: ld a,h or l jr nz,$+6 dec d jp m,__FreqOutDone cpd jp pe,__FreqOutLoop2 ld a,e scf __FreqOutDone: pop bc out (0),a jr c,__FreqOutLoop1 xor b out (0),a ret
Now that should remove some of the choppiness from the sound for lower notes with longer durations, though the change might not be noticeable.