The web site with sounds came back, so I’m scarfing them down and starting to install them. Should be mostly straightforward, he said. Except when my repo locked up.

The classicgaming.cc site has returned from being offline the last time I looked, so I’ve downloaded the sound zip files and will install them in the game this morning. Do take a look at the site, it’s rather interesting overall.

I’ll document the process of getting the sounds into Codea, for the record, and for the two or three people who may someday want to know how to do it. I’ve written it up once before, I think, in some Asteroids article.

First step is just to download the files, one at a time, onto the iPad. They show up in Files:

files view

When you browse them you can find the Uncompress menu item and uncompress them to get the real files:

unzip

Now, it turns out, we need to move these files to the Codea folder called “Dropbox”, which has nothing but a metaphorical connection to the product of the same name. In Files, browse and use the Select button at the top right to select your desired files:

selected

Down at the bottom of the screen, touch Move:

Navigate to Codea Dropbox Assets and select Copy.

copy

Once the real files are in Codea’s “Dropbox”, we can begin to bring them into Codea, and our project, as assets. I’m creating a class, Sound to hold them. That should also serve to help step through the somewhat odd importation process.

-- Sound
-- RJ 20200904

Sound = class()

function Sound:init()
end

Here’s my process FWIW. Add a sound call to the init:

function Sound:init()
    sound()
end

Touch on the parens of the sound to get the asset popup:

asset list

Click on the Dropbox item. If nothing shows up, close and reopen Codea: it may need to reread the folder. Possibly pressing Sync will work: I didn’t notice that until just now. Anyway, now we can press the Edit at top right.

Select all the files you want as assets, then touch Add To down at the bottom of the page, and select the offered project, which is the one you’re working on. (Invaders, in my case.)

add

Codea moves the files.

Now, when you select the sound() or other asset reference, you can browse into the project’s assets and pick them. My plan is to put them all into a table for now, with useful names.

To get the asset names right, I leave my empty sound call there, and click it to browse and select the asset names. Then I can paste them into the table statements above. If you trust your typing, you can type them. Codea replaces blanks in asset names with underbars, I think.

browse select

The first time I did this, the files weren’t there after the Add To. I repeated the process, and they were there. I have no explanation for this.

If you choose to type asset.something, Codea will look for available assets and you can click them from the bottom of the page:

asset bottom

I wind up with this class definition so far:

-- Sound
-- RJ 20200904

Sound = class()

function Sound:init(x)
    self.sounds = {}
    self.sounds.explosion = asset.explosion
    self.sounds.tone1 = asset.fastinvader1
    self.sounds.tone2 = asset.fastinvader2
    self.sounds.tone3 = asset.fastinvader3
    self.sounds.tone4 = asset.fastinvader4
    self.sounds.killed = asset.invaderkilled
    self.sounds.ufoLo = asset.ufo_lowpitch
    self.sounds.ufoHi = asset.ufo_highpitch
    self.sounds.shoot = asset.shoot
    self.tones = {self.sounds.tone1, self.sounds.tone2, self.sounds.tone3, self.sounds.tone4 }
end

I plan to use the tones table to play the ominous descending tones that dominate the game’s rich soundscape.1

I think this is a good time to commit this stuff. “Added sound files”.

Now, for fun, let’s make the player shot make a sound.

function fireMissile()
    local missile = Gunner.missile
    if missile.v == 0 then
        missile.pos = Gunner.pos + vec2(7,5)
        missile.v = 1
    end
end

Seems we should just say something like:

function fireMissile()
    local missile = Gunner.missile
    if missile.v == 0 then
        missile.pos = Gunner.pos + vec2(7,5)
        missile.v = 1
        Sounds:play("shoot")
    end
end

Maybe SoundPlayer would be a better name. I’ll use that. We need to create it as an instance of Sound. I’ll add this to setup:

    SoundPlayer = Sound()

And in Sound:

function Sound:play(aSoundName)
    sound(self.sounds[aSoundName])
end

And voila! it makes the shooting sound as advertised.

Commit: shooting sound.

Now, with your kind permission, I’m late for my chai from *$. Back soon.

More Sounds

Let’s do some more. There’s a killed invader sound.

function Invader:killedBy(missile)
    if not self.alive then return false end
    if self:isHit(missile) then
        self.alive = false
        Score = Score + self.score
        self.exploding =15
        SoundPlayer:play("killed")
        return true
    else
        return false
    end
end

There’s an explosion when the Player is hit:

function explode()
    Gunner.alive = false
    Gunner.count = 240
    SoundPlayer:play("explosion")
end

Let’s commit: invader and player explosion sounds.

We have the mystery ship sounds, but as yet we don’t support the mystery ship. Maybe next week. And we have the ominous tones. There’s a bit of information about those on the computerarchaeology site, viz:

The speed of the fleet tones does NOT match the actual speed of the alien rack. The delay-between-tones is read from a table and depends on how many aliens are left in play.

; Alien delay lists. First list is the number of aliens. The second list is the corresponding delay.
; This delay is only for the rate of change in the fleet's sound.
; The check takes the first num-aliens-value that is lower or the same as the actual num-aliens on screen.
;
; The game starts with 55 aliens. The aliens are move/drawn one per interrupt which means it
; takes 55 interrupts. The first delay value is 52 ... which is almost in sync with the number
; of aliens. It is a tad faster and you can observe the sound and steps getting out of sync.
;
1A11: 32 2B 24 1C 16 11 0D 0A 08 07 06 05 04 03 02 01
1A21: 34 2E 27 22 1C 18 15 13 10 0E 0D 0C 0B 09 07 05     
1A31: FF   ; ** Needless terminator. The list value "1" catches everything.
The first value in the table is a delay of 52 interrupts between sounds when there are 50 or more aliens. When there are 55 aliens the step sounds are faster than the rack. When there are 50 aliens the step sounds are slower than the rack.

When there are 43 aliens the step speed changes to 46 interrupts between changes. Again, the sounds are faster than the aliens. When there is only 1 alien left the delay minimizes to 5 interrupts between changes. Anything faster sounds unpleasing.

Even though the alien racks speeds up very smoothly as they die, the step sounds take sudden changes. If you wait a bit between killings you can hear the sudden changes -- especially at the beginning of the game when the deltas in the table are large.

So that’s interesting. We might as well plan to implement it that way. Our update cycle is 1/60 seconds as is the original game, so we can use the same numbers.

function Sound:init(x)
    self.sounds = {}
    self.sounds.explosion = asset.explosion
    self.sounds.tone1 = asset.fastinvader1
    self.sounds.tone2 = asset.fastinvader2
    self.sounds.tone3 = asset.fastinvader3
    self.sounds.tone4 = asset.fastinvader4
    self.sounds.killed = asset.invaderkilled
    self.sounds.ufoLo = asset.ufo_lowpitch
    self.sounds.ufoHi = asset.ufo_highpitch
    self.sounds.shoot = asset.shoot
    self.tones = {self.sounds.tone1, self.sounds.tone2, self.sounds.tone3, self.sounds.tone4 }
    self.invaderCounts = {0x32, 0x2B, 0x24, 0x1C, 0x16, 0x11, 0x0D, 0x0A, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}
    self.toneCounts = {0x34, 0x2E, 0x27, 0x22, 0x1C, 0x18, 0x15, 0x13, 0x10, 0x0E, 0x0D, 0x0C, 0x0B, 0x09, 0x07, 0x05}
end

My plan is to have our update cycle send update to SoundPlayer, which will do its thing. I’ll start with a constant tone interval.

function GameRunner:doUpdate()
    TheArmy:update()
    SoundPlayer:update()
end

function Sound:playRaw(aSound)
    sound(aSound)
end

function Sound:update()
    if self.toneCount < self.toneInterval then
        self.toneCount = self.toneCount + 1
    else
        self.toneCount = 0
        self.toneNumber = (self.toneNumber + 1)%4
        self:playRaw(self.tones[self.toneNumber+1])
    end
end

I added the playRaw function because my tones array already has the assets, not the names.

This plays the sounds as expected, usually, so I’ve committed: Tones plus a debug display. I will explain.

The tones usually tick along at about 1 per second, as expected. But sometimes they seem to go at 2 per second. That could only happen if doUpdate was occurring faster than the anticipated 1/60th of a second. So I set up to display the interval between the previous update and the current one:

1/60

But just after I took that picture, the time changed:

1/30

I don’t understand this 0.025 number. If it went to 1/30th I’d expect 0.033 not 0.025.

Now possibly Codea has found me doing so much on a tick that it has clocked down to 1/30th of a second from the usual 1/60th (and frequent 1/120th on this fast iPad). Another possibility is that my “do every 60th” code is wrong.

That’s what I’m betting.

Some messing about convinces me that the iPad is usually cycling at 1/120th and sometimes at 1/60th of a second. Once it drops to 1/60th, it stays that way for a while, then usually pops back up to the higher speed. When it goes to 1/60th, my tones slow down. (Recall that I’ve been thinking the display slowed down sometimes. I convinced myself that it wasn’t happening. Well, it is happening.)

I’m not sure what’s up with that. I think I’ll redo the “do every 60th” code to work a different way.

I have a broken unit test that is looking for sounds that are not there. And I noticed that a single missile shot now tears through the shield all the way again. I’m really wondering where that came from. I’ll load some previous versions and check.

Arrgh

And I don’t mean “talk like a pirate day”. I checked out yesterday’s last commit and the shots were going through there as well. When I tried to check out earlier commits, WorkingCopy refused, and got into a strange mode where I couldn’t check out my master branch any more: it was stuck on yesterday.

I managed to save a few of the files and I am now back to having the gunner shot make the ping and nothing else.

I’d much prefer to have some kind of versioning, and I don’t really need to be able to go very far back, so I think I’ll create a new repo in WorkingCopy. I’ve already send a crying bug report to the author and hope that he’ll sort it out later. For now I can be careful but I’d prefer to keep committing work in steps.

Wow, one of those yak-shaving mornings. The old repo is dead in the water. I’ve got a new one now, and I’ll remake the changes from this article, probably by copy-paste from here. Nice of me to keep a text backup, wasn’t it?

But it’s 1145 and time to decide a lunch plan. Wendy’s? Or a sandwich here? I think I deserve a break, so I’ll drive over to Wendy’s. (Not the other place.)

Later …

Anders is looking at the problem in Working Copy. Meanwhile I’ve just started a new repo in the middle. I’ll put the stuff I wrote about back without troubling you with it. I’ll look for the shoot-through problem as well. For now, a quick summation and we’re outa here.

Aside from the git client locking up, the addition of sound has gone quite nicely. It seems to add a lot to the game, much as I found with Asteroids and its sounds.

Ah. I think the shoot-through is here:

function Missile:update()
    if self.explodeCount ~= 0 or self.v == 0 then return end
    self.pos = self.pos + vec2(0,4)
    Shield:checkForShieldDamage(self)
    self:handleOffScreen()
end

We need to check the result of the check for shield damage and stop the missile. Easiest way to fix is probably this:

function Missile:update()
    if self.explodeCount ~= 0 or self.v == 0 then return end
    self.pos = self.pos + vec2(0,4)
    if Shield:checkForShieldDamage(self) then
        self.explodeCount = 15
        return
    end
    self:handleOffScreen()
end

Yes. That’s better. Refactoring failure, I reckon.

Anyway, the sound is going well, and aside from the issues with Working Copy locking up my repo, I think everything went well today.

See you next time!

Invaders.zip

  1. Well, soundscape anyway.