← Back to Courses
module
32

Three Events, Three Noises

Introduction

The game has a title screen, three lives and an ending, and it makes no sound whatsoever. S27 went through the sound chip register by register and nothing since has used it.

That is not an oversight you can fix by dropping S27's example into the game. S27's program makes a note and then waits for it to finish, which is fine when making the note is all the program has to do. A game cannot wait. Twenty milliseconds from now there is a frame to draw whether the explosion has finished or not.

So the question this section answers is not "how do I make a noise" - you know that. It is where a noise goes in a loop that cannot stop.

A sound is a thing you start, not a thing you do

The shape is one you have used twice already. S24 lit the backdrop on a collision and let a one-byte timer put it out. S29 called that one of the three pieces of glue a game needs. A sound is the same object with a different output:

  • Something happens. Write the registers that start the noise, and set a counter.
  • Every frame, wherever the game already is, count the counter down.
  • When it reaches zero, write the registers that stop the noise.

Nothing waits. The noise lasts as long as the counter says, and the frame it started in was no slower than any other.

The sounds, as data

Three events want three noises: the shot, a rock coming apart, and a rock reaching the ship. Each is four numbers - what to put in the mixer, which period register to use, what to put in it, and how many frames it lasts:

; Each entry: the mixer byte, which period register, what to put in it,
; and how many frames it lasts.
sounds: DEFB 126, 0, 100, 6      ; 0 shot  - tone A, 1250 Hz, an eighth of a second
        DEFB 119, 6, 10,  10     ; 1 rock  - noise, short and bright
        DEFB 119, 6, 25,  25     ; 2 ship  - noise, lower, as long as the flash

This is S13's table doing what S13 said tables are for. There is no formula connecting a shot to 1250 Hz; somebody chose it because it sounded right. Adding a fourth sound is four bytes, and tuning the third one is one byte.

The 100 is a period, not a pitch. S27's formula turns it into one: 2,000,000 / (16 x 100) = 1250 Hz. The 200 in the edit at the end of this section is 625 Hz, an octave down, which is a good way to feel what that division is doing.

Starting one

; --- start sound number A
sfx:    ADD A,A
        ADD A,A                 ; four bytes an entry
        LD L,A
        LD H,0
        LD DE,sounds
        ADD HL,DE
        LD A,7                  ; the mixer, with the keyboard's bits kept
        OUT (2),A
        LD A,(HL)
        OUT (3),A
        INC HL
        LD A,(HL)               ; which period register
        OUT (2),A
        INC HL
        LD A,(HL)               ; and the period
        OUT (3),A
        INC HL
        LD A,8                  ; channel A, full volume
        OUT (2),A
        LD A,15
        OUT (3),A
        LD A,(HL)               ; how long it lasts
        LD (soundT),A
        RET

Two ADD A,A because the entries are four bytes each - S14's doubling, done twice. Then the same walk along HL you have written a dozen times.

Stopping one

; --- every frame: count the sound down, and stop it when it runs out
sound:  LD A,(soundT)
        OR A
        RET Z
        DEC A
        LD (soundT),A
        RET NZ
; --- volume off, mixer back to silence
silence:
        LD A,8
        OUT (2),A
        LD A,0
        OUT (3),A
        LD A,7
        OUT (2),A
        LD A,127
        OUT (3),A
        RET

sound goes into the playing state's list of things that happen every frame, after flash. Most frames it does nothing but read a zero and return, which is exactly the cost you want a silent frame to have.

Note that silence is two writes, not one. Volume 0 is genuinely silent - not quiet, silent - and turning the mixer bits back off as well means the next sound starts from a known state rather than from whatever the last one left. silence gets its own label because the title and game over screens call it directly: a game that dies mid-explosion should not take the explosion with it to the menu.

The register the keyboard is also using

There is one trap here and it is worth meeting deliberately, because when it bites the symptom looks nothing like a sound problem.

Register 7 is not only the mixer. Its top two bits say which way the chip's two IO ports face, and the keyboard matrix from S25 depends on them: the game sets that register to 127 at start-up to make port A an output and port B an input, and reads the matrix through registers 14 and 15 ever afterwards.

So every mixer value in the table has to keep those two bits as they were. 127 is 01111111; 126 is 01111110, tone A on; 119 is 01110111, noise A on. Both leave bit 6 set and bit 7 clear.

Write a mixer byte that forgets them - 00000110, say, which looks like a perfectly reasonable "tone A and noise A on" - and the sound comes out exactly right while the keyboard stops answering. Hold a direction and the ship does not move.

The nasty part is how long it lasts. That wrong value is only in the register until the sound ends and silence puts 127 back, so the controls do not die - they go deaf for the six frames of a shot and then recover, every time you fire. What reaches the player is not a broken game but a sluggish one, and nothing on screen connects it to the sound chip. Leave the same byte in permanently and the controls stop dead, which is at least easier to find.

Where the three calls go

All three sit where the event already happens, and none of them is more than two lines.

The shot, in fire, before anything else - a bullet that cannot be fired should not make a noise:

fire:   LD A,(bullA)
        OR A
        RET NZ
        LD A,0          ; the shot
        CALL sfx

The rock coming apart, in hits, beside the score it already awards:

        CALL score1     ; ...score...
        LD A,1          ; ...a noise...
        CALL sfx
        CALL spawn      ; ...next rock

And the ship being hit, beside the flash it already starts:

        LD A,25         ; the rock reached the ship: half a second of red
        LD (flashT),A
        LD A,2          ; and a longer, lower noise
        CALL sfx
        CALL spawn

That last pair is worth looking at twice. The flash lasts 25 frames and so does the sound, because they are the same event told twice - once to the eyes and once to the ears. They are two separate timers counting the same number, which is a small redundancy you could remove and probably should not: the day you want the sound to outlast the flash, you change one number.

The code

Everything above goes into S31's program. Five small additions:

  1. Setup writes 0 to register 1, tone A's coarse period, once. The table only sets the fine byte.
  2. sounds, sfx, sound and silence as printed above, plus soundT: DEFB 0 with the other variables.
  3. CALL sound joins the playing state's every-frame list, after flash.
  4. Three CALL sfx at the three events.
  5. CALL silence on the way into the title and game over states.

What you should see

Nothing new. That is the point - the screen is exactly as S31 left it.

What you should hear: a short high blip every time you fire, a brighter crack when a rock comes apart, and a longer, lower one when a rock reaches the ship, arriving at the same moment the screen goes red. The title screen and the game over screen are silent.

And the game runs at the same speed it did before. If it does not, something is waiting that should be counting.

Change one thing

  • Change the shot's period from 100 to 200. The shot drops an octave, to 625 Hz. Now try 400. The assembler warns you and the note comes out wrong - work out from S27's formula what note you actually got, and what the table would need to be able to hold for 400 to work.
  • Delete CALL sound from the playing state's list. Fire once. What stops? What does that tell you about how much of this section is the starting and how much is the stopping?
  • Give the ship-hit sound a mixer of 126 instead of 119, leaving everything else alone. What does the explosion become, and why is the period suddenly the wrong number for it?
  • Set the ship-hit sound's length to 100 and lose a life. The flash and the sound now disagree. Which of the two does the event feel like it lasted?

Exercises

32.1 - A sound for starting. Play something when a game begins. The trouble is that newgame is called from the title state, and the sound would be cut off the moment the playing state runs silence... except that it is not. Work out why, then decide whether you want it to be.

32.2 - Falling pitch. Make the ship-hit sound slide downwards over its 25 frames instead of sitting on one note. sound already runs every frame and already knows how much time is left.

32.3 - Two things at once. The chip has three channels and this section uses one. Give the shot its own channel so that firing while a rock explodes does not cut the explosion short. What has to change in sfx, and what has to change in sound?

32.4 - A quieter game. Volume 15 for everything is a blunt instrument. Add a volume column to the table and pick a level per sound. Which of the three wants to be loudest, and is that the one you expected?

32.5 - Silence on purpose. Add a key that mutes the game. It should stop sound that is already playing, and it should not cost a frame.

When it goes wrong

Symptom Cause
The sound is right and the controls have stopped working A mixer byte that did not preserve register 7's top two bits. The keyboard matrix reads through the same register.
The controls feel sluggish, and only while the game is making a noise The same fault, but the wrong value is being put back by silence a few frames later. Input is lost for exactly as long as each sound lasts.
One shot and then a note that never stops Nothing is counting it down. sound has to run every frame, not only when something is sounding.
The note is wrong and the assembler warned about a value A period over 255 will not fit the table's byte. Tone period is 12 bits across two registers; this table only fills the low one.
The explosion is silent but the shot works The mixer enables tone and noise with different bits - bit 0 and bit 3. Enabling the wrong one leaves the generator running with nothing routed to the output.
A sound carries on into the title screen The states that do not run sound have to call silence themselves.
The game slows down when it makes a noise Something is waiting for the sound rather than counting frames while it plays.

Summary

  • A game cannot wait for a sound. Start it, count it, stop it - the same one-byte timer as the collision flash, with the chip on the end instead of the backdrop.
  • Sounds are data: a mixer byte, a period, a length. Tuning one is editing a number, not a routine.
  • Silence is two writes. Volume to zero and the mixer back off, so the next sound starts from a known chip.
  • Register 7 belongs to the keyboard as well as the mixer. Preserve its top two bits in every value you write, or the controls die and the sound gives you no clue.
  • Put the call where the event already is. The three noises in this game cost six lines between them.

Next

S33. Where You Go From Here. The game has a beginning, an end and a voice, which means the course has run out of things it set out to teach. What is left is what you can now do with it, what this course deliberately did not cover, and where the ground is still unbroken.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.