Saxaboom

Saxaboom.hex

Controls: Use the direction pad + A or B button for 8 different sound samples.

The memes can’t stop, won’t stop.

#include <Arduboy2.h>
#include <ArduboyPlaytune.h>

#include "particle.h"
#define dParticleMax 12

Particle dotsLanded[dParticleMax];


Arduboy2 arduboy;
ArduboyPlaytune tunes(arduboy.audio.enabled);

const unsigned char PROGMEM b1 [] = {
  //
  0x90, 62, 0, 62, 0x80, 0, 63, 0x90, 60, 0, 62, 0x80, 0, 63, 0x90, 62, 0, 62, 0x80, 0, 63, 0x90, 60, 0, 62, 0x80,
  0, 63, 0x90, 62, 0, 62, 0x80, 0, 63, 0x90, 60, 0, 62, 0x80, 0, 63, 0x90, 62, 0, 62, 0x80, 0, 63, 0x90, 57, 0, 125,
  0x90, 60, 0, 250, 0x90, 62, 0, 125, 0x80, 0, 125, 0x90, 65, 0, 250, 0x90, 67, 0, 250, 0x80, 0xF0
};

const unsigned char PROGMEM b2 [] = {
  //
  0x90, 53, 0, 125, 0x90, 54, 0, 125, 0x90, 55, 0, 125, 0x90, 57, 0, 125, 0x80, 0, 125, 0x90, 60, 0, 125, 0x80, 0, 125,
  0x90, 60, 0, 125, 0x80, 0, 125, 0x90, 60, 0, 125, 0x80, 0, 125, 0x90, 60, 0, 125, 0x90, 61, 0, 125, 0x80, 0, 125, 0x90, 62,
  0, 187, 0x80, 0xF0
};

const unsigned char PROGMEM b3 [] = {
  //
  0x90, 62, 0, 125, 0x90, 60, 0, 62, 0x80, 0, 188, 0x90, 60, 0, 62, 0x80, 0, 188, 0x90, 57, 0, 125, 0x90, 60, 0, 125,
  0x90, 62, 0, 125, 0x80, 0, 125, 0x90, 65, 0, 62, 0x80, 0, 188, 0x90, 67, 0, 125, 0x90, 67, 0, 125, 0x80, 0, 125, 0x90, 69,
  0, 125, 0x80, 0xF0
};

const unsigned char PROGMEM b4 [] = {
  //
  0x90, 56, 0, 125, 0x90, 55, 0, 125, 0x90, 56, 0, 125, 0x90, 55, 0, 125, 0x80, 0, 125, 0x90, 55, 0, 62, 0x80, 0, 63,
  0x90, 55, 0, 125, 0x90, 53, 0, 125, 0x90, 55, 0, 125, 0x90, 53, 0, 125, 0x80, 0, 125, 0x90, 53, 0, 62, 0x80, 0, 63,
  0x90, 53, 0, 125, 0x90, 52, 0, 125, 0x90, 53, 0, 125, 0x80, 0xF0
};

const unsigned char PROGMEM b5 [] = {
  //
  0x90, 62, 2, 175, 0x90, 61, 0, 63, 0x90, 61, 0, 62, 0x90, 60, 0, 63, 0x90, 61, 0, 62, 0x90, 61, 0, 63, 0x90, 62,
  0, 125, 0x80, 0, 125, 0x90, 60, 0, 125, 0x80, 0, 125, 0x90, 57, 0, 125, 0x90, 60, 0, 125, 0x80, 0, 125, 0x90, 62, 0, 125,
  0x80, 0xF0
};

const unsigned char PROGMEM b6 [] = {
  //
  0x90, 50, 0, 250, 0x90, 62, 0, 125, 0x90, 50, 0, 125, 0x80, 0, 62, 0x90, 62, 0, 125, 0x80, 0, 188, 0x90, 50, 0, 125,
  0x80, 0, 125, 0x90, 50, 0, 125, 0x90, 60, 0, 250, 0x90, 53, 0, 125, 0x90, 50, 0, 125, 0x80, 0, 125, 0x90, 50, 0, 125,
  0x80, 0xF0
};

const unsigned char PROGMEM b7 [] = {
  //
  0x90, 38, 0, 250, 0x80, 0, 250, 0x90, 50, 0, 250, 0x80, 0, 125, 0x90, 41, 0, 125, 0x80, 0, 125, 0x90, 41, 0, 125, 0x90, 48,
  0, 187, 0x80, 0, 63, 0x90, 45, 0, 125, 0x90, 43, 0, 125, 0x90, 44, 0, 125, 0x90, 45, 0, 125, 0x80, 0xF0
};

const unsigned char PROGMEM b8 [] = {
  //
  0, 250, 0x90, 74, 0x91, 38, 0, 125, 0x80, 0, 125, 0x90, 74, 0x81, 0, 125, 0x90, 74, 0, 125, 0x90, 45, 0, 125, 0x91, 74,
  0x90, 43, 0, 125, 0x90, 38, 0x81, 0, 125, 0x91, 74, 0, 125, 0x80, 0x81, 0, 125, 0x90, 74, 0x91, 38, 0, 125, 0x90, 74, 0x91, 38,
  0, 125, 0x80, 0, 125, 0x81, 1, 244, 0x90, 74, 0x91, 38, 0, 125, 0x80, 0, 125, 0x90, 74, 0x81, 0, 125, 0x90, 74, 0, 125,
  0x90, 43, 0, 125, 0x91, 74, 0x90, 45, 0, 125, 0x90, 38, 0x81, 0, 125, 0x91, 74, 0, 125, 0x80, 0x81, 0, 125, 0x90, 74, 0x91, 38,
  0, 125, 0x90, 74, 0x91, 38, 0, 125, 0x80, 0x81, 0, 125, 0x90, 41, 0, 250, 0x80, 0xF0
};


constexpr uint8_t Sax[] PROGMEM
{


  //Frame 0
  0x18, 0x1C, 0x12, 0x22, 0x23, 0x31, 0x29, 0x31, 0xF9, 0xC9, 0x47, 0x41, 0x81, 0x85, 0x8A, 0xB4, 0xC8, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x80, 0xF8, 0x24, 0xAE, 0x43, 0x54, 0x48, 0x49, 0x48, 0x49, 0x49, 0x49, 0x6D, 0xFF, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xE1, 0x33, 0x3F, 0xFE, 0xFE, 0xFE, 0x3E, 0xDE, 0xDE, 0x3E, 0xFE, 0xFE, 0xFF, 0x0F, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xB6, 0xB5, 0xCD, 0xFE, 0xFF, 0xFF, 0xC1, 0x20, 0xF0, 0x08, 0xE4, 0xF4, 0xF4, 0xE8, 0xD0, 0xA0, 0x40, 0x80, 0x00, 0x00,
  0x00, 0x00, 0xFE, 0xF3, 0x6D, 0xAD, 0xB3, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xA3, 0xA7, 0x40, 0x81, 0x80, 0x8F, 0xF8, 0xC1, 0x0F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x78, 0x87, 0x78,
  0xF0, 0x8F, 0x77, 0x77, 0x76, 0x8D, 0xFD, 0xFE, 0xFF, 0xDF, 0xBF, 0xB8, 0x77, 0x6F, 0xEF, 0xF7, 0xF8, 0xFF, 0xFF, 0xFF, 0x0F, 0x07, 0x03, 0x06, 0x06, 0x06, 0x02, 0x03, 0x01, 0x00,
  0x07, 0x0F, 0x3F, 0x3F, 0x7D, 0xFB, 0xF7, 0xF7, 0xFB, 0xFF, 0xC7, 0xBB, 0xBB, 0xBB, 0xC6, 0xFE, 0xFE, 0xFE, 0x3E, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x04, 0x06, 0x06, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x06, 0x06, 0x06, 0x04, 0x04, 0x00, 0x00, 0x00
};




void updateDots() {
  for ( int i = 0; i < dParticleMax; i++) {
    dotsLanded[i].update();
    if ( dotsLanded[i].render() ) {                                                             // the dot should be rendered
      int pSize = dotsLanded[i].getSize();
      if ( dotsLanded[i].getType() == 2) {
        arduboy.setCursor( dotsLanded[i].getX(), dotsLanded[i].getY() );
        //strcpy_P(buffer, (char *)pgm_read_word(&(goodWords[ dotsLanded[i].getWord() ])));
        //arduboy.print(buffer);
        arduboy.print("!");
      }
      else if ( dotsLanded[i].getType() == 3) {
        arduboy.setCursor( dotsLanded[i].getX(), dotsLanded[i].getY() );
        //arduboy.print( dotsLanded[i].getWord() );
      } else {
        if ( pSize == 1 ) {
          arduboy.drawPixel( dotsLanded[i].getX(), dotsLanded[i].getY(), 1);
        } else {
          arduboy.drawLine(dotsLanded[i].getX() + (pSize * .6), dotsLanded[i].getY(), dotsLanded[i].getX() + pSize, dotsLanded[i].getY() - (pSize * 1.8), 1);
          arduboy.drawCircle( dotsLanded[i].getX(), dotsLanded[i].getY(), pSize / 2, 1);
        }
      }
    }
  }
}





void setup()
{
  arduboy.beginDoFirst();
  arduboy.setFrameRate(60);


  tunes.initChannel(PIN_SPEAKER_1);
  tunes.initChannel(PIN_SPEAKER_2);

  for(int i=-60;i<1;i++){
    arduboy.clear();
      arduboy.drawBitmap(30, i, Sax, 30, 60, 1);
      delay(10);
      arduboy.display();
  }
}



bool song1 = 0;
bool song2 = 0;
bool song3 = 0;
bool song4 = 0;
bool song5 = 0;
bool song6 = 0;
bool song7 = 0;
bool song8 = 0;
int noteCounter;

void loop()
{
  // pause render until it's time for the next frame
  if (!(arduboy.nextFrame()))
    return;


  if (arduboy.pressed(A_BUTTON)) {

    if (arduboy.pressed(UP_BUTTON)) {



      if (!tunes.playing()) {
        clearSongs();
        song1 = 1;
        tunes.playScore(b1);
      }

    } else if (arduboy.pressed(DOWN_BUTTON)) {


      if (!tunes.playing()) {
        clearSongs();
        song2 = 1;
        tunes.playScore(b2);
      }

    } else if (arduboy.pressed(LEFT_BUTTON)) {



      if (!tunes.playing()) {
        clearSongs();
        song3 = 1;
        tunes.playScore(b3);
      }

    } else if (arduboy.pressed(RIGHT_BUTTON)) {

      if (!tunes.playing()) {
        clearSongs();
        song4 = 1;
        tunes.playScore(b4);
      }


    }


  } else if (arduboy.pressed(B_BUTTON)) {

    if (arduboy.pressed(UP_BUTTON)) {


      if (!tunes.playing()) {
        clearSongs();
        song5 = 1;
        tunes.playScore(b5);
      }

    } else if (arduboy.pressed(DOWN_BUTTON)) {

      if (!tunes.playing()) {
clearSongs();
        song6 = 1;
        tunes.playScore(b6);
      }

    } else if (arduboy.pressed(LEFT_BUTTON)) {


      if (!tunes.playing()) {
        clearSongs();
        song7 = 1;
        tunes.playScore(b7);
      }

    } else if (arduboy.pressed(RIGHT_BUTTON)) {



      if (!tunes.playing()) {
        clearSongs();
        song8 = 1;
        tunes.playScore(b8);
      }

    }


  }

  arduboy.clear();

  arduboy.drawBitmap(30, 0, Sax, 30, 60, 1);


  if (song1) {
    if (tunes.playing()) {
      arduboy.fillRect(41, 22, 2, 3, 0);
      noteCounter++;
      if (noteCounter > 10) {
                drawMusic();
        noteCounter = 0;
      }
    }
  }
  if (song2) {
    if (tunes.playing()) {
      arduboy.fillRect(40, 28, 2, 2, 0);
      noteCounter++;
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }
  if (song3) {
    if (tunes.playing()) {
      arduboy.fillRect(34, 34, 2, 2, 0);
      noteCounter++;
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }
  if (song4) {
    if (tunes.playing()) {
      arduboy.fillRect(35, 39, 2, 2, 0);
      noteCounter++;
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }
  if (song5) {
    if (tunes.playing()) {
      arduboy.fillRect(32, 44, 3, 3, 0);
      noteCounter++;
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }
  if (song6) {
    if (tunes.playing()) {
      arduboy.fillRect(41, 51, 3, 3, 0);
      noteCounter++;
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }
  if (song7) {
    if (tunes.playing()) {
      arduboy.fillRect(43, 39, 2, 5, 0);
      arduboy.fillRect(42, 40, 4, 3, 0);
      noteCounter++;
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }









  if (song8) {
    if (tunes.playing()) {
      updateRainbow();
      noteCounter++;
      if (noteCounter == 1) arduboy.fillRect(41, 22, 2, 3, 0);
      if (noteCounter == 2 ) arduboy.fillRect(40, 28, 2, 2, 0);
      if (noteCounter == 3 ) arduboy.fillRect(34, 34, 2, 2, 0);
      if (noteCounter == 4 ) arduboy.fillRect(35, 39, 2, 2, 0);
      if (noteCounter == 5 ) arduboy.fillRect(32, 44, 3, 3, 0);
      if (noteCounter == 6 ) arduboy.fillRect(41, 51, 3, 3, 0);
      if (noteCounter == 6 ) {
        arduboy.fillRect(43, 39, 2, 5, 0);
        arduboy.fillRect(42, 40, 4, 3, 0);
      }
      if (noteCounter > 10) {
        drawMusic();
        noteCounter = 0;
      }
    }
  }

  if (!tunes.playing()) {

clearSongs();

  }





  updateDots();


  arduboy.display();


}

void clearSongs(){
      song1 = 0;
    song2 = 0;
    song3 = 0;
    song4 = 0;
    song5 = 0;
    song6 = 0;
    song7 = 0;
    song8 = 0;
}

int songPlaying() {
  return  song1 +
          song2 +
          song3 +
          song4 +
          song5 +
          song6 +
          song7 +
          song8 ;
}

void drawMusic() {
  for ( int i = 0; i < 2; i++) {
    dotsLanded[i].setX( 50 );
    dotsLanded[i].setY( 36 );
    dotsLanded[i].setVelX( random(4, 6) );
    dotsLanded[i].setVelY( random(2, 5) );
    dotsLanded[i].setCounter( random(15, 100) );
    dotsLanded[i].setSize( random(2, 7) );
    dotsLanded[i].setType( 0 );
  }
}






void updateRainbow() {
  static uint8_t hue = 0;

  // Convert hue to RGB values.
  uint8_t r, g, b;
  hueToRGB(hue, r, g, b);

  // Set the RGB LED.
  simulateAnalogWriteRGB(r, g, b);

  // Move to the next hue.
  hue += 5;
}

void hueToRGB(uint8_t hue, uint8_t &r, uint8_t &g, uint8_t &b) {
  uint8_t region = hue / 43;
  uint8_t remainder = (hue - (region * 43)) * 6;

  uint8_t p = 0;
  uint8_t q = (255 - remainder);
  uint8_t t = remainder;

  switch (region) {
    case 0:
      r = 255; g = t; b = p;
      break;
    case 1:
      r = q; g = 255; b = p;
      break;
    case 2:
      r = p; g = 255; b = t;
      break;
    case 3:
      r = p; g = q; b = 255;
      break;
    case 4:
      r = t; g = p; b = 255;
      break;
    default:
      r = 255; g = p; b = q;
      break;
  }
}


void simulateAnalogWriteRGB(uint8_t r, uint8_t g, uint8_t b) {
  // Simulate varying brightness by toggling the LED on and off rapidly.
  // The values of r, g, b determine how long the LED stays on/off.
  for (int i = 0; i < 255; i += 5) {
    arduboy.digitalWriteRGB(i < r, i < g, i < b);
    delayMicroseconds(5); // Adjust this for smoother or faster changes.
    arduboy.digitalWriteRGB(1, 1, 1);
    delayMicroseconds(300); // Adjust this for smoother or faster changes.
  }
}

Note: Seems like the simulator is dropping some notes that the hardware plays alright? Seems like the pauses aren’t being handled quite right, there are a few places where there are supposed to be short pauses and it’s almost twice as long. I guess some kind of minor timer mismatch that propagates out?

2 Likes

Tested on hardware, it behaves similarly on my computer. On my phone, however, the audio (and overall performance, it seems) is choppy. This is weird, I haven’t really experience perf issues on mobile with other games before.

I can confirm it’s on 2 different pc’s both firefox and chrome. (Also on safari iphone)

You can hear it most pronounced on “left+b”

button 7.zip

Here is the midi file, which is how it sounds on the hardware.

Here’s a little screen cap I made, illustrating the glitch:

It seems to be making rests that are supposed to be 16th notes into 8th notes?

1 Like

Awesome! Tracked down and finally understand the emulation bug. It’s a bug with the 16-bit timer reg access. Working on a fix.

Thanks @bateske!

EDIT: Fixed. Also tracked down the performance issue with this particular timer config and it runs smoothly on mobile again.

3 Likes

Why the heck are you thanking me! Lol, thank you!

The sound is so much better now!!! In addition to fixing the glitch it seems to improved the overall quality too? Amazed.

For finding and documenting that bug, :bug: tracking that down was fun.

2 Likes

This fix also improved the level completion sound on Mystic Balloon :+1:

2 Likes

I defer all praise to Jack Black and the saxaboom, I am just a vessel