/**
 * IMPORTANT!!!
 * IN THE TOP RIGHT CORNER OF THE SCREEN YOU WILL SEE THE SIMULATION SPEED,
 * SO DO NOT BE SURPRISED IF THE MUSIC SOUNDS STRANGE (SLOWER) WHEN
 * THE SIMULATOR IS AT 60% - 65%
**/

#include <LiquidCrystal.h>
#include <TimerOne.h>
#include "pitches.h"

LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

const byte STEP1[8] = {
 0b01110,
 0b10001,
 0b10001,
 0b01110,
 0b11111,
 0b00100,
 0b01010,
 0b10001
};
const byte STEP2[8] = {
 0b01110,
 0b10001,
 0b10001,
 0b01110,
 0b11111,
 0b00100,
 0b01010,
 0b01010
};
const byte STEP3[8] = {
 0b01110,
 0b10001,
 0b10001,
 0b01110,
 0b11111,
 0b00100,
 0b00100,
 0b00100
};

// notes in the melody – constant values defining frequency for each used note
const int MELODY[] = {
  NOTE_C4,
  NOTE_G3,
  NOTE_G3,
  NOTE_A3,
  NOTE_G3,
  0,
  NOTE_B3,
  NOTE_C4
};

// note durations: 4 = quarter note, 8 = eighth note, etc.:
const int NOTE_DURATIONS[] = {
  4, // NOTE_C4,
  8, // NOTE_G3,
  8, // NOTE_G3,
  4, // NOTE_A3,
  4, // NOTE_G3,
  4, // 0,
  4, // NOTE_B3,
  4  // NOTE_C4
};

// Based on Example #4, we take the base value as 1000 milliseconds, in other words one second
// Then we need to consider the values from the noteDurations array ... 1/4 --> 250 ms, 1/8 --> 125 ms
// The pause between the notes also needs to be taken into account, so 250*1.3 = 325 ms, 125*1.3 = 162 ms
// The greatest common factor between these four numbers is GCF([125, 162, 250, 325]) = 1 ms
// So, in other words, we need to cycle the interrupt service routine every 1 ms
const int ISR_PERIOD_TIME = 1000; // 10^3 μseconds --> 1 millisecond (10^-3 seconds)

// Change this to increase / decrease the speed of the walking animation
const unsigned int WALKING_ANIMATION_DELAY = 100;

volatile unsigned int currentNote;
unsigned int playArrayInternalIndex;
unsigned int melodyArrLength;

byte currentImage;

int *playArray;

void setup(void) {
  melodyArrLength = sizeof(MELODY) / sizeof(int); // Get the length of the melody array
  calculatePlayArray();
  lcd.createChar(0, STEP1);
  lcd.createChar(1, STEP2);
  lcd.createChar(2, STEP3);
  Timer1.initialize(ISR_PERIOD_TIME); // init the timing interval for event triggering (1s = 10-6s)
  Timer1.attachInterrupt(playMusic); // The function is called at the preset time interval
}

void playMusic() {
  if (currentNote % 2 == 0) {
    const int currentMelodyIndex = currentNote / 2;
    tone(8, MELODY[currentMelodyIndex], playArray[currentNote]);
  } else {
    tone(8, 0, playArray[currentNote]);
  }
  if (playArrayInternalIndex < playArray[currentNote]) {
    playArrayInternalIndex++;
  } else {
    playArrayInternalIndex = 0;
    currentNote++;
    if (currentNote > melodyArrLength * 2) {
      currentNote = 0;
    }
    noTone(8);
  }
}

void playAnimation() {
  lcd.setCursor(0,0);
  lcd.write(currentImage);
  if (currentImage == 2) {
    currentImage = 0;
  } else {
    currentImage++;
  }
}

void calculatePlayArray() {  
  // melodyLength * 2, because you need a pause after every note
  playArray = (int *) malloc(2 * melodyArrLength * sizeof(int));
  
  for (unsigned int i = 0; i < 2 * melodyArrLength; i++) {
    const unsigned int currentMelodyIndex = i / 2;
    const unsigned int durationInMilliSeconds = 1000 / NOTE_DURATIONS[currentMelodyIndex];
    // Every even note starting with 0 will be a note, odds will be a pause
    if (i % 2 == 0) {
      playArray[i] = durationInMilliSeconds;
    } else {
      playArray[i] = durationInMilliSeconds * 1.3;
    }
  }
}

void loop(void) {
  playAnimation();
  delay(WALKING_ANIMATION_DELAY);
}