/**
 * \Buzzer_09_play_rtttl
 *
 * Questo sketch suona melodie in formato RTTTL (Ring Tone Text Transfer Language)
 * utilizzando un buzzer passivo. Le melodie sono memorizzate come stringhe
 * e vengono riprodotte utilizzando la funzione tone().
 * 
 * La funzione play() analizza la stringa RTTTL e suona le note corrispondenti.
 * 
 * La stringa RTTTL ha il seguente formato:
 * <title>:<defaults>:<note>,<note>,<note>,...
 * Dove:
 * - <title> è il titolo della melodia
 * - <defaults> sono i parametri predefiniti (durata, ottava, bpm)
 * - <note> sono le note da suonare: <note>[#|b][<octave>][.<duration>] 
 *
 * https://wokwi.com/projects/425889424760583169
 * https://github.com/filippo-bilardo/ROBOTICA/blob/main/Buzzer/README.md
 * 
 * @author Fippo Bilardo
 * @version 1.0  20/03/25 - Versione iniziale
 */
// Definizione della classe Buzzer
class Buzzer
{
private:
    int buzzerPin; // Pin del buzzer
    /**
     * @brief Funzione per calcolare la frequenza di una nota 
     * comprese i diesis e i bemolli
     * 
     * @param note la nota (C, D, E, F, G, A, B) //case insensitive
     * @param octave l'ottava (0-8)
     * @param sharp_flat 1 per diesis, -1 per bemolle, 0 per naturale
     * @return int la frequenza della nota
     */
    int getNoteFrequency(char note, int octave, int sharp_flat)
    {
        // Frequenze base delle note nell'ottava 4 (A4 = 440Hz)
        const int notes[] = {
            0,   // Non usato
            262, // C 
            277, // C#, Db
            294, // D
            311, // D#, Eb
            330, // E
            349, // F 
            370, // F#, Gb
            392, // G
            415, // G#, Ab
            440, // A
            466, // A#, Bb  
            494  // B
        };
        int index = 0;
        // Trasforma la nota in minuscolo per uniformità
        note = tolower(note);
        // Mappatura diretta note-indici
        switch(tolower(note)) {
            case 'c': index = 1; break;
            case 'd': index = 3; break;
            case 'e': index = 5; break;
            case 'f': index = 6; break;
            case 'g': index = 8; break;
            case 'a': index = 10; break;
            case 'b': case 'h': index = 12; break; // h è usato in alcuni paesi europei
            default: return 0; // Nota non valida
        }
        // Aggiungi diesis o bemolle
        index += sharp_flat;
        if(index < 1) index = 1;
        if(index > 12) index = 12;
        // Calcolo della frequenza in base all'ottava
        // Utilizziamo l'operatore bit shift per calcolare potenze di 2 (più efficiente)
        // se octave = 0 la frequenza è 0
        // se octave = 1 la frequenza è notes[index] * 2^(-3) = notes[index] / 8
        // se octave = 2 la frequenza è notes[index] * 2^(-2) = notes[index] / 4
        // se octave = 3 la frequenza è notes[index] * 2^(-1) = notes[index] / 2
        // se octave = 4 la frequenza è notes[index] * 2^(0) = notes[index]
        // se octave = 5 la frequenza è notes[index] * 2^(1) = notes[index] * 2
        // se octave = 6 la frequenza è notes[index] * 2^(2) = notes[index] * 4
        // se octave = 7 la frequenza è notes[index] * 2^(3) = notes[index] * 8
        // se octave = 8 la frequenza è notes[index] * 2^(4) = notes[index] * 16
        int frequency = notes[index] * (1 << (octave - 4));
        return frequency;
    }
public:
    // Costruttore
    Buzzer(int pin) : buzzerPin(pin) {
        pinMode(buzzerPin, OUTPUT);
    }
    // Funzione per suonare una melodia
    void play(const char *melody)
    {
        // Verifica che la stringa non sia vuota
        if (melody == nullptr || strlen(melody) == 0) return;
        // Parsing della stringa RTTTL
        int defaultDuration = 4; // Durata predefinita (4 = quarto)
        int defaultOctave = 5;   // Ottava predefinita
        int bpm = 120;           // Battiti al minuto
        // Estrazione del titolo e stampa
        const char *p = strchr(melody, ':'); 
        if (p == nullptr) return; // Formato non valido
        //*p = '\0'; Serial.print("Playing: "); Serial.println(melody); *p = ':';
        Serial.print("In riproduzione: ");
        for (int j = 0; j < p - melody; j++) {Serial.print(melody[j]);}
        Serial.println();
        // Estrazione dei parametri dalla stringa RTTTL
        p++; // Salta il carattere ':'
        while (*p != '\0' && *p != ':') 
        {
            if (strncmp(p, "d=", 2) == 0)
            {
                p += 2; // Salta il prefisso "d="
                defaultDuration = atoi(p); 
                while (isdigit(*p)) p++;
            }
            else if (strncmp(p, "o=", 2) == 0)
            {
                p += 2; // Salta il prefisso "o="
                defaultOctave = atoi(p);
                while (isdigit(*p)) p++;
            }
            else if (strncmp(p, "b=", 2) == 0)
            {
                p += 2; // Salta il prefisso "b="
                bpm = atoi(p); //atoi converte una stringa in un intero, p punta alla stringa
                while (isdigit(*p)) p++;
            }
            // Salta eventuali separatori
            if (*p == ',' || *p == ' ')
                p++;
        }
        if (*p == '\0')
            return; // Formato non valido
        p++;        // Salta il carattere ':'
        // Calcolo del tempo di una nota
        int wholenote = (60 * 1000L / bpm) * 4; // Tempo di una nota intera in millisecondi
        // Ciclo per suonare ogni nota
        while (*p)
        {
            // Estrazione della durata
            int duration = defaultDuration;
            if (isdigit(*p)) // Se è un numero //(*p >= '0' && *p <= '9')
            {
                duration = 0;
                while (isdigit(*p))
                {
                    duration = duration * 10 + (*p - '0');
                    p++;
                }
            }
            // Gestione della nota puntata (aumenta durata del 50%)
            bool dotted = false;
            if (*p == '.')
            {
                dotted = true;
                p++;
            }
            // Estrazione della nota e dell'ottava
            char note = '\0';
            int octave = defaultOctave;
            int sharp_flat = 0;
            
            if (isalpha(*p)) // Se è una lettera //(*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
            {
                note = tolower(*p); // if (*p >= 'A' && *p <= 'Z') *p = *p + 32;
                p++;
                // Estrazione dell'eventuale diesis o bemolle
                if(*p == '#' || *p == 'b') {
                    sharp_flat = (*p == '#') ? 1 : -1;
                    p++;
                }
                // Estrazione dell'ottava
                if (isdigit(*p))
                {
                    octave = *p - '0';
                    p++;
                }
            }
            // Calcolo della durata della nota
            int noteDuration = wholenote / duration;
            // Gestione della nota puntata
            if (dotted)
            {
                noteDuration = noteDuration * 3 / 2;
            }
            // Gestione della pausa
            if (note == 'p')
            {
                delay(noteDuration);
            }
            else
            {
                // Calcolo della frequenza
                int frequency = getNoteFrequency(note, octave, sharp_flat);
                if (frequency > 0)
                {
                    // Suono della nota
                    tone(buzzerPin, frequency, noteDuration * 0.9);
                    // Attesa tra le note (90% della durata per separare le note)
                    delay(noteDuration);
                    noTone(buzzerPin);
                }
            }
            // Salta eventuali separatori
            while (*p == ',' || *p == ' ' || *p == '\t')
                p++;
        }
    }
};
// Definizione delle melodie
const char *mTetris = "Tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a";
const char* mMission = "Mission Impossible:d=16,o=6,b=95:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,a#,g,2d,32p,a#,g,2c#,32p,a#,g,2c,a#5,8c,2p,32p,a#5,g5,2f#,32p,a#5,g5,2f,32";
const char* mSuperMario = "Super Mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b";
// Definizione del pin del buzzer
const int buzzerPin = 8;
// Creazione dell'oggetto Buzzer
Buzzer buzzer(buzzerPin);
void setup()
{
    // Inizializzazione della comunicazione seriale per debug
    Serial.begin(9600);
    Serial.println("RTTTL music player...");
    buzzer.play(mTetris); delay(1000);
    buzzer.play(mMission); delay(1000);
    buzzer.play(mSuperMario); delay(1000);
}
void loop() {}