/**
* \Buzzer_11_play_rtttl_nonblocking
*
* Versione non bloccante per suonare melodie in formato RTTTL
* e contemporaneamente far lampeggiare un LED.
*
* Utilizza una macchina a stati per eseguire operazioni senza bloccare il loop principale.
* Ciò consente di eseguire altre operazioni (come lampeggiare un LED) mentre suona la melodia.
*
* https://wokwi.com/projects/426124385658658817
* https://github.com/filippo-bilardo/ROBOTICA/blob/main/Buzzer/README.md
*
* @author Versione originale: Filippo Bilardo
* @version 1.0 22/03/25 - Versione non bloccante
*/
// Definizione dei pin
const int buzzerPin = 8;
const int ledPin = 13;
// Definizione della classe Buzzer non bloccante
class NonBlockingBuzzer
{
private:
int buzzerPin; // Pin del buzzer
unsigned long previousMillis; // Tempo dell'ultima azione
unsigned long noteDuration; // Durata della nota corrente in ms
unsigned long noteStartTime; // Tempo di inizio della nota corrente
bool isPlaying; // Indica se la melodia è in riproduzione
bool isNoteOn; // Indica se una nota è in riproduzione
const char* currentMelody; // Puntatore alla melodia corrente
const char* currentPosition; // Posizione corrente nella melodia
// Parametri della melodia RTTTL
int defaultDuration;
int defaultOctave;
int bpm;
int wholenote;
/**
* @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
int frequency = notes[index] * (1 << (octave - 4));
return frequency;
}
// Inizializza i parametri per una nuova melodia
void initMelody() {
defaultDuration = 4;
defaultOctave = 5;
bpm = 120;
isNoteOn = false;
// Estrazione del titolo e stampa
const char *p = strchr(currentMelody, ':');
if (p == nullptr) {
isPlaying = false;
return;
}
Serial.print("In riproduzione: ");
for (int j = 0; j < p - currentMelody; j++) {
Serial.print(currentMelody[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);
while (isdigit(*p)) p++;
}
// Salta eventuali separatori
if (*p == ',' || *p == ' ')
p++;
}
if (*p == '\0') {
isPlaying = false;
return;
}
p++; // Salta il carattere ':'
currentPosition = p;
// Calcolo del tempo di una nota intera in millisecondi
wholenote = (60 * 1000L / bpm) * 4;
isPlaying = true;
}
// Avanza alla prossima nota
void playNextNote() {
if (!isPlaying || currentPosition == nullptr || *currentPosition == '\0') {
isPlaying = false;
noTone(buzzerPin);
return;
}
// Estrazione della durata
int duration = defaultDuration;
if (isdigit(*currentPosition)) {
duration = 0;
while (isdigit(*currentPosition)) {
duration = duration * 10 + (*currentPosition - '0');
currentPosition++;
}
}
// Gestione della nota puntata (aumenta durata del 50%)
bool dotted = false;
if (*currentPosition == '.') {
dotted = true;
currentPosition++;
}
// Estrazione della nota e dell'ottava
char note = '\0';
int octave = defaultOctave;
int sharp_flat = 0;
if (isalpha(*currentPosition)) {
note = tolower(*currentPosition);
currentPosition++;
// Estrazione dell'eventuale diesis o bemolle
if (*currentPosition == '#' || *currentPosition == 'b') {
sharp_flat = (*currentPosition == '#') ? 1 : -1;
currentPosition++;
}
// Estrazione dell'ottava
if (isdigit(*currentPosition)) {
octave = *currentPosition - '0';
currentPosition++;
}
}
// Calcolo della durata della nota
noteDuration = wholenote / duration;
// Gestione della nota puntata
if (dotted) {
noteDuration = noteDuration * 3 / 2;
}
// Suono della nota
if (note == 'p') {
// Pausa
noTone(buzzerPin);
} else {
// Calcolo della frequenza
int frequency = getNoteFrequency(note, octave, sharp_flat);
if (frequency > 0) {
tone(buzzerPin, frequency);
}
}
isNoteOn = true;
noteStartTime = millis();
// Salta eventuali separatori
while (*currentPosition == ',' || *currentPosition == ' ' || *currentPosition == '\t')
currentPosition++;
}
public:
// Costruttore
NonBlockingBuzzer(int pin) : buzzerPin(pin), isPlaying(false), previousMillis(0) {
pinMode(buzzerPin, OUTPUT);
}
// Avvia la riproduzione di una melodia
void startPlay(const char* melody) {
if (melody == nullptr || strlen(melody) == 0) return;
currentMelody = melody;
isPlaying = true;
isNoteOn = false;
previousMillis = millis();
// Inizializza i parametri della melodia
initMelody();
// Inizia a suonare la prima nota
playNextNote();
}
// Ferma la riproduzione
void stop() {
isPlaying = false;
isNoteOn = false;
noTone(buzzerPin);
}
// Verifica se una melodia è in riproduzione
bool isPlayingMelody() {
return isPlaying;
}
// Aggiorna lo stato del buzzer (da chiamare in loop)
void update() {
unsigned long currentMillis = millis();
if (isPlaying && isNoteOn) {
// Controlla se è il momento di terminare la nota attuale (90% della durata)
if (currentMillis - noteStartTime >= noteDuration * 0.9) {
noTone(buzzerPin);
isNoteOn = false;
}
}
// Controlla se è il momento di passare alla nota successiva
if (isPlaying && !isNoteOn && currentMillis - noteStartTime >= noteDuration) {
playNextNote();
}
}
};
// Classe per gestire il LED lampeggiante
class BlinkingLed {
private:
int ledPin;
unsigned long previousMillis;
unsigned long interval;
bool ledState;
public:
BlinkingLed(int pin, unsigned long blinkInterval = 500) :
ledPin(pin), interval(blinkInterval), ledState(LOW), previousMillis(0) {
pinMode(ledPin, OUTPUT);
}
void setInterval(unsigned long blinkInterval) {
interval = blinkInterval;
}
void update() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// Cambia lo stato del LED
ledState = !ledState;
digitalWrite(ledPin, ledState);
}
}
};
// 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";
// Creazione degli oggetti
NonBlockingBuzzer buzzer(buzzerPin);
BlinkingLed led(ledPin, 250); // LED lampeggia ogni 250ms
// Stato del programma
enum State {
PLAY_TETRIS,
PAUSE1,
PLAY_MISSION,
PAUSE2,
PLAY_MARIO,
FINISHED
};
State currentState = PLAY_TETRIS;
unsigned long stateStartTime;
void setup()
{
// Inizializzazione della comunicazione seriale per debug
Serial.begin(9600);
Serial.println("RTTTL non-blocking music player with blinking LED");
stateStartTime = millis();
buzzer.startPlay(mTetris);
}
void loop() {
// Aggiorna lo stato del buzzer (controllo non bloccante)
buzzer.update();
// Aggiorna lo stato del LED (lampeggiamento)
led.update();
// Gestione della sequenza di riproduzione
unsigned long currentMillis = millis();
// Macchina a stati per riprodurre le melodie in sequenza
switch (currentState) {
case PLAY_TETRIS:
if (!buzzer.isPlayingMelody()) {
currentState = PAUSE1;
stateStartTime = currentMillis;
}
break;
case PAUSE1:
if (currentMillis - stateStartTime >= 1000) {
currentState = PLAY_MISSION;
buzzer.startPlay(mMission);
}
break;
case PLAY_MISSION:
if (!buzzer.isPlayingMelody()) {
currentState = PAUSE2;
stateStartTime = currentMillis;
}
break;
case PAUSE2:
if (currentMillis - stateStartTime >= 1000) {
currentState = PLAY_MARIO;
buzzer.startPlay(mSuperMario);
}
break;
case PLAY_MARIO:
if (!buzzer.isPlayingMelody()) {
currentState = FINISHED;
Serial.println("Tutte le melodie sono state riprodotte");
}
break;
default:
break;
}
}