z80:Sound Routines

From Learn @ Cemetech
Revision as of 21:34, 3 February 2016 by KermMartian (talk | contribs) (Fixed heading)
Jump to navigationJump to search

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.