Retro Invaders a space invaders clone by Chris Favreau. Written for the RetroMbuino development board from outrageouscircuits.com for the game programming contest.

Dependencies:   mbed

This is a space invaders clone written for the Retro Mbuino from outrageous circuits.

Development board: http://www.outrageouscircuits.com/shop/product/15 ).

The game itself is basic space invaders. Shoot them before they get to the bottom of the screen. It has a UFO saucer which you can shoot for extra points. You get 4 shields and each shield can be hit up to 4 times before it is gone. Hmm... as each level increases the speed of the invaders shots goes up. The invaders only speed up when there is less of them. You complete the level when you shoot all the invaders. The game ends when a) you run out of lives (you start with 3) or the invaders get to the bottom.

The LEDs turned out to be a pretty cool addition to the game. I wrote a class that blinks them and turns them on for a specified amount of time. They add a nice extra to the game. I use them on the intro screen and when the UFO is present.

The sound turned out to be really difficult for a few reasons. The biggest was that I had never written a sound engine before. The interrupt service routine working off the timer was the easier part. I also had a lot of trouble because there is no filter to filter out the PWM frequency to the speaker... so I had to run the PWM frequency way up there 30 kHz.

The graphics turned out to be a bit of a bear too. Thanks to Chris Taylor for his really great LCD API. I picked up a couple of frames per second from that. I had modified the DisplayN18 class for blitting a single line buffer to the LCD panel however his is a little faster for some reason? I used a different approach to doing the graphics (as I have very little experience with anything other than double buffered displays). I have a tile map and a list of sprites. Each tile/sprite is 1 bit 8x8. They could be bigger. I ran out of time. That much is not special. What is different from what I can tell is that I use a 1 line buffer that is 160 shorts long. The render function first adds the tile map data into the line buffer first. Then the sprites are added over the existing data. You can have a great deal of different sprites and maps going to the screen and just have to rewrite the LCD memory once per frame. After each line is composited, the line is then drawn to the LCD. Kind of like an Atari 2600. Each sprite/tile has a foreground and background color and can be different from the other tiles/sprites. There is one color reserved for Transparency.

There are 16 colors to choose from. I chose a palette based on the Macintosh OS 4.1 palette I found on WikiPedia. It is a very nice mix of colors.

I found a sprite editor called SpriteX ( https://code.google.com/p/spritesx-ed/ )... it works nicely except that the 16x16 sprites are in a weird format. Time limited me to 8x8 sprites. Oh well.

I used nokring to make the music. It makes RTTTL formatted ring tones which my sound api can play. Here is a useful site that has lots of arcade/video game ring tones with a link to nokring in the utilities page. http://arcadetones.emuunlim.com/files.htm

Other than all that stuff I used state machines to do most of the game logic. Please excuse the horrible coding as I tried to comment a lot of it however it is not very nice to look at. Lots of long functions...

Revision:
0:c79e1f29f029
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sound/sound.cpp	Tue Mar 03 04:26:01 2015 +0000
@@ -0,0 +1,727 @@
+#include <string.h>
+#include "sound.h"
+#include "lpc111x.h"
+
+// Useful Macros
+#define ISNUMBER(a) ((a >= '0') && (a <= '9'))
+#define ISALPHA(a) (((a >= 'a') && (a <= 'z')) || ((a >= 'A') && (a <= 'Z')))
+#define TOLOWER(a) if ((a >= 'A') && (a <= 'Z')) a = (a - 'A') + 'a';
+
+//  ======== Waveform Definitions ========
+// 64 elements
+const char sine[] = {
+    0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
+    0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
+    0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
+    0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,
+    0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc,
+    0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3,
+    0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83,
+    0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51,
+    0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27,
+    0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a,
+    0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08,
+    0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23,
+    0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c,
+    0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c};
+    
+const char square[] =  {
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
+
+const char sawtooth[] = {
+    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
+    0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
+    0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
+    0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
+    0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
+    0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
+    0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
+    0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
+    0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+    0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+    0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
+    0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
+    0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
+    0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
+    0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
+    0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff};
+
+const char triangle[] = {
+    0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e,
+    0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e,
+    0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e,
+    0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e,
+    0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e,
+    0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe,
+    0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde,
+    0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe,
+    0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xef,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1,
+    0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcf,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1,
+    0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xaf,0xab,0xa9,0xa7,0xa5,0xa3,0xa1,
+    0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8f,0x8b,0x89,0x87,0x85,0x83,0x81,
+    0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6f,0x6b,0x69,0x67,0x65,0x63,0x61,
+    0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4f,0x4b,0x49,0x47,0x45,0x43,0x41,
+    0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2f,0x2b,0x29,0x27,0x25,0x23,0x21,
+    0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0f,0x0b,0x09,0x07,0x05,0x03,0x01};
+
+
+// For vibrato - 64 elements in length - thanks lft
+const int8_t sinetable[] = {
+    0, 12, 25, 37, 49, 60, 71, 81, 90, 98, 106, 112, 117, 122, 125, 126,
+    127, 126, 125, 122, 117, 112, 106, 98, 90, 81, 71, 60, 49, 37, 25, 12,
+    0, -12, -25, -37, -49, -60, -71, -81, -90, -98, -106, -112, -117, -122,
+    -125, -126, -127, -126, -125, -122, -117, -112, -106, -98, -90, -81,
+    -71, -60, -49, -37, -25, -12
+};
+
+// -=-=-=-=-=-=-= End Waveforms -=-=-=-=-=-=-=-=-=
+
+#define PWM_FREQUENCY       23448       // 23.448 kHz => Match value of ~2047 (Convenient for binary math)
+#define PWM_MATCH_VAL       (48000000 / PWM_FREQUENCY)
+#define EFFECT_CALL_FREQ    50          // Hz
+#define EFFECT_CALL_WAIT    PWM_FREQUENCY / EFFECT_CALL_FREQ
+#define MAX_FREQ            8000                                      // 8 kHz (something pratical.. and not ear splitting)
+#define MAX_FREQ_INC        ((65535 * MAX_FREQ) / PWM_FREQUENCY)
+#define SLIDE_SCALER        ((65535 * 10) / PWM_FREQUENCY)            // 10 Hz per tick...
+
+// Volatile Interrupt Variables
+volatile int timer32_0_counter;
+Sound *pSound = NULL;
+volatile unsigned short int effect_wait = EFFECT_CALL_WAIT;
+
+// Interrupt ---------------------------------------
+
+void TIMER32_0_IRQHandler(void)
+{    
+    // Check to see if the PWM interrupt is triggered (MR3)
+    if (TMR32B0IR & BIT3)
+    {
+        if (!pSound) return;
+        
+        effect_wait--;
+        if (effect_wait == 0)
+        {
+            // Do the effects at 50 Hz ... why because everyone does it that way
+            pSound->DoEffects();
+            effect_wait = EFFECT_CALL_WAIT;
+            // Do the song at 50 Hz
+            pSound->DoSong();
+        }
+        
+        static unsigned int noiseseed = 1;
+        unsigned char newbit;
+        
+        newbit = 0;
+        if(noiseseed & 0x80000000L) newbit ^= 1;
+        if(noiseseed & 0x01000000L) newbit ^= 1;
+        if(noiseseed & 0x00000040L) newbit ^= 1;
+        if(noiseseed & 0x00000200L) newbit ^= 1;
+        noiseseed = (noiseseed << 1) | newbit;
+        
+        // Reset the interrupt flag
+        TMR32B0IR = BIT3;
+        
+        // Increment the counter
+        timer32_0_counter++;
+        
+        register int master_acc = 0;
+        register int acc = 0;
+        register int channels = 0;
+        bool bMuted = pSound->bMuted;
+        
+        for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
+        {
+            SOUND_CHANNEL *pChannel = &pSound->channel[i];
+            if ((pChannel->active) && (pChannel->volume > 0))
+            {
+                pChannel->phase_acc += pChannel->phase_inc;
+                acc = pChannel->pWaveform[pChannel->phase_acc >> 8];    // -128 to 127
+                if (pChannel->use_noise) acc = (noiseseed & 0xFF) - 127;// -128 to 127
+                acc *= pChannel->volume;                                // 0 to 65535
+                acc = acc >> 8;                                         // Rescale to -128 to 127
+                acc += 128;                                             // 0 to 255
+                master_acc += acc;
+                channels++;
+            }
+        }
+        
+        if (channels > 1) master_acc /= channels;
+        master_acc = master_acc << 3;                               // 0 to 2047 (PWM_MATCH_VAL)
+        //master_acc = acc << 3;
+        if (master_acc > PWM_MATCH_VAL) master_acc = PWM_MATCH_VAL;
+        
+        // Check to see if we are muted
+        if (bMuted) master_acc = 0;
+        
+        // Bounds check the master accumulator
+        TMR32B0MR0 = master_acc;  // ( xx >> 8 equivalent to x / 256)
+    }
+    
+    return;
+}
+
+// Implementation ----------------------------------
+
+Sound::Sound()
+{
+    timer32_0_counter = 0;
+    pSound = this;
+    for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
+        channel[i].active = false;
+    // Initialize the song stuff
+    bPlaySong = false;
+    bLoopSong = false;
+    m_pSong = NULL;
+    m_pSongBuffer = NULL;
+    iSongChannel = 0;
+    iSongNoteOffset = 0;
+    iNoteCounter = 0;
+    iTicksPerBeat = 0;
+    bMuted = false;
+}
+    
+Sound::~Sound()
+{
+}
+
+void Sound::Init()
+{
+    SetupDefaults();
+    
+    // DISABLE the TImer
+    TMR32B0TCR = 0;
+    
+    // DEBUG - do not enable the output pin so we do not make so much noise!!!
+    // Initialize the PWM Match Output
+    // Configure IO control register IOCON_PIO0_18 offset 0x40044048 with CT32B0_MAT0 bit set (0x02)
+    REGISTER_32(0x40044048) = BIT1 | BIT4;
+    
+    // Configure the interrupt
+    NVIC_SetVector(TIMER_32_0_IRQn, (uint32_t)TIMER32_0_IRQHandler);
+    NVIC_SetPriority(TIMER_32_1_IRQn,0);
+    NVIC_EnableIRQ(TIMER_32_0_IRQn);
+    
+    // CT32B0 base address 0x40014000
+    // Turn ON CT32B0 => System Clock Control Register => SYSAHBCLKCTRL Bit 9
+    SYSAHBCLKCTRL |= BIT9;
+    // Set our frequency (counter value) in Match Register 0 (as it does not map directly to a pin) => Match Register 0 MR0
+    TMR32B0MR3 = PWM_MATCH_VAL;        // PWM Frequency
+    TMR32B0MR0 = 0;        // 0% Duty for now (OFF)
+    // Configure Reset TC on Match with MR0 MR0R (Bit 1) MR0I (Bit 0) => Match Control Register MCR
+    // Reset on Match Register 3 MR3 (as this is the register we use for the base frequency ... MR0 is used to set the duty cycle)
+    TMR32B0MCR = BIT10 | BIT9;
+    // PWM Control Register
+    TMR32B0PWMC = BIT0;
+    // Zero the counter 0 => Timer Counter Register TC
+    TMR32B0TC = 0;
+    // Set the PreScale Register
+    TMR32B0PR = 0;
+    // Clear the Prescale Counter
+    TMR32B0PC = 0;
+    // Enable the timer => Timer Control Register CR
+    TMR32B0TCR = BIT0;
+    
+    __enable_irq();     // Enable Interrupts
+}
+
+void Sound::PlaySound(int iChannel, int iFreqHz, int iLength)
+{
+    // Check the channel
+    if (iChannel < 0) return;
+    if (iChannel >= SOUND_MAX_CHANNELS) return;
+    
+    if (iFreqHz == 0)
+    {
+        StopSound(iChannel);
+        return;
+    }
+    
+    // Get a pointer to the sound channel structure
+    SOUND_CHANNEL *pChannel = &channel[iChannel];
+    
+    // Calculate a phase increment value
+    int inc = (65535 * iFreqHz) / PWM_FREQUENCY;
+    pChannel->phase_inc = (unsigned short)inc;
+    pChannel->orig_phase_inc = (unsigned short)inc;
+    // Clear the phase accumulator
+    pChannel->phase_acc = 0;
+    
+    // Set the play length (0 to 255, 0 = INFINITE, 1 to 255 delay in 50 Hz ticks)
+    pChannel->play_length = iLength;
+    
+    // Reset the volume level to its initial setting
+    pChannel->volume = pChannel->vol_level;
+    
+    // Reset the change wait to the change speed
+    pChannel->change_wait = pChannel->change_speed;
+    
+    // Set the active flag last
+    pChannel->active = true;
+}
+
+void Sound::StopSound(int iChannel)
+{
+    // Check the channel
+    if (iChannel < 0) return;
+    if (iChannel >= SOUND_MAX_CHANNELS) return;
+    
+    // Clear the active flag
+    channel[iChannel].active = false;
+}
+
+bool Sound::IsBusy(int iChannel)
+{
+    // Check the channel
+    if (iChannel < 0) return false;
+    if (iChannel >= SOUND_MAX_CHANNELS) return false;
+    
+    return (channel[iChannel].active && (channel[iChannel].volume > 0));
+}
+
+int Sound::GetCounter(void)
+{
+    return timer32_0_counter;
+}
+
+void Sound::SetInstrument(int iChannel, int iWaveform, int iVolume, int iDecayTime, int iSlide, int iChangeAmount, int iChangeSpeed, int iRepeatSpeed, int iVibratoDepth, int iVibratoSpeed)
+{
+    // Bounds check the channel
+    if (iChannel < 0) return;
+    if (iChannel >= SOUND_MAX_CHANNELS) return;
+    
+    // Set the waveform
+    channel[iChannel].use_noise = false;
+    switch (iWaveform)
+    {
+    default:
+    case (SOUND_SQUARE):
+        channel[iChannel].pWaveform = (unsigned char *)square;
+        break;
+    case (SOUND_SAW):
+        channel[iChannel].pWaveform = (unsigned char *)sawtooth;
+        break;
+    case (SOUND_TRIANGLE):
+        channel[iChannel].pWaveform = (unsigned char *)triangle;
+        break;
+    case (SOUND_SINE):
+        channel[iChannel].pWaveform = (unsigned char *)sine;
+        break;
+    case (SOUND_NOISE):
+        channel[iChannel].pWaveform = (unsigned char *)square;
+        channel[iChannel].use_noise = true;
+        break;
+    }
+    
+    // Set the volume level
+    channel[iChannel].vol_level = iVolume;
+    channel[iChannel].volume = iVolume;
+    
+    // Set the Decay time
+    channel[iChannel].decay_amount = iDecayTime;
+    
+    // Set the Slide value
+    channel[iChannel].slide = iSlide;
+    
+    // Set the Change Amount and Change Speed
+    int inc = (65535 * iChangeAmount / PWM_FREQUENCY);
+    channel[iChannel].change_inc = (signed short)inc; // frequency amount in phase increments
+    channel[iChannel].change_speed = iChangeSpeed;                                          // change delay in 50 hz ticks
+    channel[iChannel].change_wait = iChangeSpeed;
+    
+    // Set the repeat speed
+    channel[iChannel].repeat_speed = iRepeatSpeed;  // effect repeat speed in 50 hz ticks (really a delay)
+    channel[iChannel].repeat_wait = iRepeatSpeed;
+    
+    // Set the vibrato parameters
+    channel[iChannel].vibrato_depth = iVibratoDepth;                // Vibrato amplitude
+    channel[iChannel].vibrato_speed = iVibratoSpeed;                // Vibrato speed in 50 Hz ticks (delay)
+    channel[iChannel].vibrato_phase = 0;
+    channel[iChannel].vibrato_inc = 0;
+}
+    
+// Setup the default instruments and stuff
+void Sound::SetupDefaults(void)
+{
+    for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
+    {
+        // Set the default instrument parameters
+        SetInstrument(i, SOUND_SQUARE, 255, 0, 0, 0, 0, 0, 0, 0);
+    }
+}
+
+void Sound::DoEffects(void)
+{
+    for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
+    {
+    
+        if (!channel[i].active) continue;
+        
+        // Set the play length (0 to 255, 0 = INFINITE, 1 to 255 delay in 50 Hz ticks)
+        if (channel[i].play_length > 0)
+        {
+            // Subtract 1 tick
+            channel[i].play_length--;
+            // If we get to 0 set the channel to inactive
+            if (channel[i].play_length == 0) channel[i].active = false;
+        }
+    
+        // Volume Related Effects
+        if (channel[i].volume > 0)
+        {
+            short int vol = channel[i].volume;
+            // Decay
+            if (channel[i].decay_amount)
+            {
+                if (channel[i].decay_amount < vol)
+                    vol -= channel[i].decay_amount;
+                else
+                    vol = 0;
+            }
+            // Set the adjusted volume level
+            channel[i].volume = vol;
+        }
+        // If the volume is 0 then who cares?
+        // Do not turn the channel off as Repeat will not work?
+        /*
+        if (channel[i].volume == 0)
+        {
+            // Turn this channel off for now
+            channel[i].active = false;
+        }
+        */
+        
+        // Slide (Frequency Shifting) Effects
+        if (channel[i].slide != 0)
+        {
+            int inc = (int)channel[i].phase_inc + ((int)channel[i].slide * SLIDE_SCALER);
+            if ((inc > 0) && (inc < MAX_FREQ_INC))
+                channel[i].phase_inc = inc;
+            else
+                //channel[i].active = false;
+                channel[i].volume = 0;
+        }
+        
+        // Change (Frequency Change after Delay)
+        if (channel[i].change_wait > 0)
+        {
+            channel[i].change_wait--;
+            if (channel[i].change_wait == 0)
+                channel[i].phase_inc += channel[i].change_inc;
+        }
+        
+        // Vibrato
+        if (channel[i].vibrato_speed > 0)
+        {
+            // Calculate the vibrato waveform's phase
+            channel[i].vibrato_phase += (channel[i].vibrato_speed * SLIDE_SCALER);
+            // Calculate the sinewave values
+            int inc = sinetable[channel[i].vibrato_phase & 63] * channel[i].vibrato_depth;
+            // Rescale (from 65535 to 255)
+            inc = inc >> 8;
+            
+            inc = inc + (int)channel[i].phase_inc;
+            channel[i].phase_inc = inc;
+        }
+        
+        // Repeat (resets parameters after a certain mount of time ... pew pew pew)
+        if (channel[i].repeat_speed > 0)
+        {
+            channel[i].repeat_wait--;
+            if (channel[i].repeat_wait == 0)
+            {
+                // Reset the repeat wait (so we can do this again!
+                channel[i].repeat_wait = channel[i].repeat_speed;
+                // Reset the frequency and amplitude to the original
+                channel[i].phase_inc = channel[i].orig_phase_inc;
+                // Reset the volume (amplitude) to the original
+                channel[i].volume = channel[i].vol_level;
+            }
+        }
+    }
+}
+
+void Sound::PlaySong(char *pRTTTL, int iChannel, int iBPM, bool bLoop)
+{
+    // Check the RTTTL pointer
+    if (!pRTTTL) return;
+    
+    // Stop the song temporarily
+    bPlaySong = false;
+    
+    // Setup the song variables
+    iSongChannel = iChannel;
+    SetSongTempo(iBPM);
+    bLoopSong = bLoop;
+    iNoteCounter = 0;
+    
+    // Get the length of this song (in characters)
+    int iNewLen = strlen(pRTTTL);
+    
+    // Copy and allocate the song buffer
+    
+    // Deal with the song buffer if it has already allocated memory
+    if (m_pSongBuffer)
+    {
+        // Check to see if we need to allocate more memory
+        if (iSongBufferLen < iNewLen)
+        {
+            // Cleanup the old and allocate a new buffer
+            delete [] m_pSongBuffer;
+            m_pSongBuffer = NULL;
+        }
+    }
+    // Allocate a new song buffer if we need to
+    if (!m_pSongBuffer)
+    {
+        m_pSongBuffer = new char[iNewLen + 1];
+    }
+    // Copy the song into the song buffer
+    memcpy(m_pSongBuffer, pRTTTL, iNewLen);
+    // Add an extra NULL terminator just incase
+    m_pSongBuffer[iNewLen] = 0;
+    
+    // Set the song pointer to the beginning
+    m_pSong = m_pSongBuffer;
+    
+    // Start the song
+    bPlaySong = true;
+}
+
+#define TICKSPERMINUTE  (60 * 50)
+
+void Sound::SetSongTempo(int iBPM)
+{
+    // Setup the note wait time in 50Hz ticks
+    iTicksPerBeat = TICKSPERMINUTE / iBPM;
+    if (iTicksPerBeat < 1) iTicksPerBeat = 1;
+    if (iTicksPerBeat > TICKSPERMINUTE) iTicksPerBeat = TICKSPERMINUTE;
+}
+
+void Sound::TransposeSong(int iOffset)
+{
+    iSongNoteOffset = iOffset;
+}
+
+void Sound::StopSong(void)
+{
+    // clear the play sound
+    bPlaySong = false;
+    // Silence
+    StopSound(iSongChannel);
+    // All done with this function
+    return;
+}
+
+void Sound::DoSong(void)
+{
+    // Check to see if we are playing a song
+    if (!bPlaySong) return;
+    
+    // Check to see if there is a song to play
+    if (!m_pSong) return;
+    
+    // Check to see if we need to play the next note
+    iNoteCounter--;
+    if (iNoteCounter > 0) return;
+    
+    int duration = 4;
+    int octave = 5;
+    int freq = NOTE_C4;
+    char note = 'c';
+    bool bSharp = false;
+    bool bDotted = false;
+    int volume = channel[iSongChannel].volume;
+    while (*m_pSong != ',')
+    {
+        // Parse and play a note
+        // Check for duration
+        if (ISNUMBER(*m_pSong))
+        {
+            duration = *m_pSong - '0';
+            m_pSong++;
+            if (ISNUMBER(*m_pSong))
+            {
+                duration = (duration * 10) + (*m_pSong - '0');
+                m_pSong++;
+            }
+        }
+        // Check for the note
+        if (ISALPHA(*m_pSong))
+        {
+            TOLOWER(*m_pSong);
+            note = *m_pSong;
+            m_pSong++;
+        }
+        // Check for a sharp
+        if (*m_pSong == '#') { bSharp = true; m_pSong++; }
+        // Check for dotted
+        if (*m_pSong == '.') { bDotted = true; m_pSong++; }
+        // Check for the octave
+        if (ISNUMBER(*m_pSong))
+        {
+            octave = *m_pSong - '0';
+            m_pSong++;
+        }
+        // Check to see if we need to start over
+        if (*m_pSong == 0)
+        {  
+            if (bLoopSong)
+            {
+                // Set the song buffer to the beginning
+                m_pSong = m_pSongBuffer;
+                // and process the first note all over again
+                continue;
+            }
+            else
+            {
+                StopSong();
+                // All done with this function
+                return;
+            }
+        }
+        // Next character so we do not get stuck
+        if (*m_pSong != ',') m_pSong++;
+        // Check for silence
+        if (note != 'p')
+        {
+            // Convert the note to a frequency
+            freq = SongGetFreq(note, octave, bSharp);
+            // Play the note
+            PlaySound(iSongChannel, freq, volume + 1);
+        }
+        else
+        {
+            // Silence (Pause)
+            StopSound(iSongChannel);
+        }
+        
+        // Set the note counter so we play the note for so many frames
+        switch(duration)
+        {
+        case (1): iNoteCounter = 4 * iTicksPerBeat; break;
+        case (2): iNoteCounter = 2 * iTicksPerBeat; break;
+        default:
+        case (4): iNoteCounter = iTicksPerBeat; break;
+        case (8): iNoteCounter = iTicksPerBeat >> 1; break;
+        case (16): iNoteCounter = iTicksPerBeat >> 2; break;
+        case (32): iNoteCounter = iTicksPerBeat >> 3; break;
+        }
+        if (bDotted) iNoteCounter += iNoteCounter >> 1;
+        
+        //char cTemp[32];
+        //snprintf(cTemp, 32, "N(%c%c)D(%02d)O(%d)", note, bSharp ? '#' : ' ', duration, octave);
+        //disp.drawString(0, 16, cTemp, DisplayN18::RED, DisplayN18::BLACK);
+
+    }
+    m_pSong++;
+}
+
+int Sound::SongGetFreq(char note, int octave, bool bSharp)
+{
+    // Bounds check the octave
+    if (octave < 4) octave = 4;
+    if (octave > 6) octave = 6;
+    
+    switch(octave)
+    {
+    case (4):
+        switch(note)
+        {
+        case ('a'):
+            if (bSharp) return NOTE_As4;
+            return NOTE_A4;
+        case ('b'):
+            if (bSharp) return NOTE_C5;
+            return NOTE_B4;
+        case ('c'):
+            if (bSharp) return NOTE_Cs4;
+            return NOTE_C4;
+        case ('d'):
+            if (bSharp) return NOTE_Ds4;
+            return NOTE_D4;
+        case ('e'):
+            if (bSharp) return NOTE_F4;
+            return NOTE_E4;
+        case ('f'):
+            if (bSharp) return NOTE_Fs4;
+            return NOTE_F4;
+        case ('g'):
+            if (bSharp) return NOTE_Gs4;
+            return NOTE_G4;
+        }
+    case (5):
+        switch(note)
+        {
+        case ('a'):
+            if (bSharp) return NOTE_As5;
+            return NOTE_A5;
+        case ('b'):
+            if (bSharp) return NOTE_C6;
+            return NOTE_B5;
+        case ('c'):
+            if (bSharp) return NOTE_Cs5;
+            return NOTE_C5;
+        case ('d'):
+            if (bSharp) return NOTE_Ds5;
+            return NOTE_D5;
+        case ('e'):
+            if (bSharp) return NOTE_F5;
+            return NOTE_E5;
+        case ('f'):
+            if (bSharp) return NOTE_Fs5;
+            return NOTE_F5;
+        case ('g'):
+            if (bSharp) return NOTE_Gs5;
+            return NOTE_G5;
+        }
+    case (6):
+        switch(note)
+        {
+        case ('a'):
+            if (bSharp) return NOTE_As6;
+            return NOTE_A6;
+        case ('b'):
+            if (bSharp) return NOTE_C7;
+            return NOTE_B6;
+        case ('c'):
+            if (bSharp) return NOTE_Cs6;
+            return NOTE_C6;
+        case ('d'):
+            if (bSharp) return NOTE_Ds6;
+            return NOTE_D6;
+        case ('e'):
+            if (bSharp) return NOTE_F6;
+            return NOTE_E6;
+        case ('f'):
+            if (bSharp) return NOTE_Fs6;
+            return NOTE_F6;
+        case ('g'):
+            if (bSharp) return NOTE_Gs6;
+            return NOTE_G6;
+        }
+    }
+    
+    return NOTE_C7;
+}
+
+void Sound:: Mute(bool mute)
+{
+    bMuted = mute;   
+}