Cocoa Port pt 5 - Sound

The only thing left is to add sound. The sound samples for Space Invaders are widely available on the internet, search for them like "MAME Space Invaders sound samples". The pack I used had files named 0.wav, 1.wav, etc. The Computer Archeology notes notes have a list of ports and the sounds mapping to them.

The game uses OUT 3 and OUT 5 to communicate to its analog sound circuitry. So all we have to do is watch the output bits to change, and play the sound when they do. I'll use variables to save the previous and current values of port 3 and port 5. When the state changes from zero to one, play the sound:

   -(void) PlaySounds    
   {    
       if (out_port3 != last_out_port3)    
       {    
           if ( (out_port3 & 0x1) && !(last_out_port3 & 0x1))    
            ;    
            // We'll discuss the UFO separately

        //Player shot    
           if ( (out_port3 & 0x2) && !(last_out_port3 & 0x2))    
               [[NSSound soundNamed:@"1.wav"] play];    

        //Player die    
           if ( (out_port3 & 0x4) && !(last_out_port3 & 0x4))    
               [[NSSound soundNamed:@"2.wav"] play];    

            //Invader blows up    
           if ( (out_port3 & 0x8) && !(last_out_port3 & 0x8))    
               [[NSSound soundNamed:@"3.wav"] play];    

           last_out_port3 = out_port3;    
       }    
       if (out_port5 != last_out_port5)    
       {    
        // Invaders "bomp" sound #1    
           if ( (out_port5 & 0x1) && !(last_out_port5 & 0x1))    
               [[NSSound soundNamed:@"4.wav"] play];    

        /*.... etc. .......*/    

           last_out_port5 = out_port5;    
       }    
   }    

The ufo sound repeats the whole time the UFO is visible on the screen. I'll make a class variable to hold the UFO's sound, and save it. I start the sound playing in a loop, and when the UFO's output port bit changes back to zero, I'll stop and release the sound.

    if ( (out_port3 & 0x1) && !(last_out_port3 & 0x1))    
    {    
        //start UFO    
        ufo = [NSSound soundNamed:@"0.wav"];    
        [ufo setLoops:YES];    
        [ufo play];    
    }    
    else if ( !(out_port3 & 0x1) && (last_out_port3 & 0x1))    
    {    
        //stop UFO    
        if (ufo)    
        {    
            [ufo stop];    
            [ufo release];    
            ufo = NULL;    
        }    
    }

I call [self playSounds] in the code that handles the OUT instruction.

The original version of my sound code used a simple piece of code that just allocated a AVAudioPlayer and played a file. When I updated and tested the code for iOS 9, I discovered that wasn't working any more. When I was debugging that, I noticed that if I stepped through the code slowly, sometimes I heard some of the sound. The theory I formed was that I needed to hold onto the AVAudioPlayer object until the sound was finished, that what was happening was that the player was getting deleted when it went out of scope.

I made a instance variable to hold onto the object. That worked - I got sound, but only one at a time. When I reassigned the player object, the old player got deleted immediately, which resulted in clipping the previous sound if it wasn't finished.

So the solution I settled on was having a NSMutableArray of AVPlayer objects. When I start playing a sound, I put the player into the array so the system will hold onto it. Then in the audioPlayerDidFinishPlaying callback, I remove the player's reference from the array. Since the player no longer has a reference, the objective-c ARC system cleans it up. I used Instruments to verify that I didn't have a leak there.

   - (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag    
   {    
       [self.soundeffects removeObject:player];    
   }

   -(void) playSoundFile:(NSString*)name    
   {    
       NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:NULL];    
       NSError *error;    
       AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];    
       player.delegate = self;    
       [player play];    
       [self.soundeffects addObject:player];    
   }    

You can view the source to my completed Cocoa port on github under CocoaPart5-Sound. Again, use it for reference but write your own code, or you won't get the full learning experience.

← Prev: cocoa-port-pt-4---keyboard   Next: cocoa-port-pt-6---performance →