// File: CascadeSwitch16Inputs-74HC165.ino
//
// Version 1, 5 August 2021, by Koepel
//     Initial version.
// Version 2, 5 August 2021, by Koepel
//     Layout of the wiring made better.
// Version 3, 13 August 2021, by Koepel
//     Changed 'SCK' to 'clockPin'.
// Version 4, 6 August 2023 by Gabriel Balbuena
//     Changed to use push buttons and 16 inputs
//
// Cascade of two 74HC165 shift-in registers.
// Only three pins are used on the Arduino board, to read 16 switches.
//
// Using the 74HC165 is safe, because a pulse to the Latch pin
// ('PL' on the 74HC165) will make a new start every time.
// In case of an error or a wrong clock pulse by noise,
// it synchronizes the data when inputs are read the next time.
//
// Based on:
//   (1)
//     Demo sketch to read from a 74HC165 input shift register
//     by Nick Gammon, https://www.gammon.com.au/forum/?id=11979
//   (2)
//     74HC165 Shift register input example
//     by Uri Shaked, https://wokwi.com/arduino/projects/306031380875182657
//

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void displayInit() {
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  display.clearDisplay();              // Clear the buffer
  display.setTextColor(SSD1306_WHITE); // Set text color
  display.setTextSize(2);              // Set text size
  display.setCursor(0, 0);             // Set text position
  display.println("Hello, world!");    // Print text to buffer
  display.display();                   // Show buffer contents on the display
}

void displayText(String txt) {
  display.clearDisplay();

  // Set text color
  display.setTextColor(SSD1306_WHITE);

  // Set text size
  display.setTextSize(2);

  // Set text position
  display.setCursor(0, 0);

  // Print text to buffer
  display.println(txt);

  // Show buffer contents on the display
  display.display();
}

const int buzzer = 7;

const byte latchPin = 9;// to latch the inputs into the registers
const byte clockPin = 13; // I choose the SCK pin
const byte dataPin  = 12; // I choose the MISO pin

uint16_t oldOptionSwitch = 0; // previous state of all the inputs

const int pulseWidth = 10; // pulse width in microseconds

void inputInit() {
  // Serial.println("Top row is switch 0 (right) to switch 7 (left)");
  // Serial.println("Second row is 8 to 15, and so on");

  pinMode(clockPin, OUTPUT); // clock signal, idle LOW
  pinMode(latchPin, OUTPUT); // latch (copy input into registers), idle HIGH
  digitalWrite(latchPin, HIGH);
}

void setup() {
  Serial.begin(115200);

  displayInit();

  inputInit();
}

void loop() {
  // Give a pulse to the parallel load latch of all 74HC165
  digitalWrite(latchPin, LOW);
  delayMicroseconds(pulseWidth);
  digitalWrite(latchPin, HIGH);

  // Reading one 74HC165 at a time and combining them into a 16 bit variable
  uint16_t optionSwitch = 0;
  for (int i = 8; i >= 0; i -= 8) {
    optionSwitch |= ((uint16_t)ReadOne165()) << i;
  }

  for (int i = 0; i < 16; i++) {
    if (bitRead(optionSwitch, i) != bitRead(oldOptionSwitch, i)) {
      // Serial.print("Switch ");
      // if (i < 10) {
      //   Serial.print(" ");
      // }
      // Serial.print(i);
      // Serial.print(" is now ");
      // Serial.println(bitRead(optionSwitch, i) == 0 ? "down ↓" : "up   ↑");
      
      if (bitRead(optionSwitch, i) != 0) {
        play(i);
      } else {
        noTone(buzzer);
      }
      
    }
  }

  oldOptionSwitch = optionSwitch;
  delay(25);      // slow down the sketch to avoid switch bounce
}

// The ReadOne165() function reads only 8 bits,
// because of the similar functions shiftIn() and SPI.transfer()
// which both use 8 bits.
//
// The shiftIn() can not be used here, because the clock is set idle low
// and the shiftIn() makes the clock high to read a bit.
// The 74HC165 require to read the bit first and then give a clock pulse.
byte ReadOne165()
{
  byte ret = 0x00;

  // The first one that is read is the highest bit (input D7 of the 74HC165).
  for (int i = 7; i >= 0; i--)
  {
    if (digitalRead(dataPin) == HIGH)
      bitSet(ret, i);

    digitalWrite(clockPin, HIGH);
    delayMicroseconds(pulseWidth);
    digitalWrite(clockPin, LOW);
  }

  return (ret);
}


#define NUM_NOTES 12
#define NUM_OCTAVES 7

char* noteNames[NUM_NOTES] = {"C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B"};
char* noteNamesSpanish[NUM_NOTES] = {"Do", "Do#/Reb", "Re", "Re#/Mib", "Mi", "Fa", "Fa#/Solb", "Sol", "Sol#/Lab", "La", "La#/Sib", "Si"};


// This function returns the frequency of a note.
float getNoteFrequency(int noteNumber) {
  return 440.0 * pow(2.0, (noteNumber - 57) / 12.0); // A4 is the 57th key on the piano and it's usually tuned to 440Hz.
}

#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_D5 587
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_G5 784
#define NOTE_A5 880
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_D6 1175

const int TONES[] = {
  NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4,
  NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5,
  NOTE_C6, NOTE_D6
};

String getNote(int freq) {
  for(int octave = 0; octave < NUM_OCTAVES; octave++) {
    for(int note = 0; note < NUM_NOTES; note++) {
      int noteNumber = octave * NUM_NOTES + note; // Get the note number
      float noteFreq = getNoteFrequency(noteNumber); // Compute the frequency
      
      if(abs(freq - noteFreq) < 10) { // tolerance of 10Hz
        return noteNames[note]+String(octave);
        // return noteNamesSpanish[note]+String(octave);
      }
    }
  }
}

void play(int16_t btn) {
  int freq = TONES[btn];
  tone(buzzer, freq);
  String note = getNote(TONES[btn]);
  Serial.println("btn="+String(btn)+", note="+note);
  displayText(note);
}







74HC165
74HC165