release
This commit is contained in:
114
lib/Melody Player/src/melody.h
Normal file
114
lib/Melody Player/src/melody.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#ifndef MELODY_H
|
||||
#define MELODY_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* A note and its duration.
|
||||
*/
|
||||
struct NoteDuration {
|
||||
// The note frequency.
|
||||
unsigned short frequency;
|
||||
// The note duration. The representation can be either relative (fixed-point, decimal
|
||||
// part = 1 bit) to time units or absolute (in milliseconds)
|
||||
unsigned short duration;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class stores the data melody (notes and metadata).
|
||||
* To ease the creation of a melody, you should use MelodyFactory class.
|
||||
*/
|
||||
class Melody {
|
||||
public:
|
||||
Melody() : notes(nullptr){};
|
||||
|
||||
Melody(String title, unsigned short timeUnit, std::shared_ptr<std::vector<NoteDuration>> notes, bool automaticSilence)
|
||||
: title(title), timeUnit(timeUnit), notes(notes), automaticSilence(automaticSilence){};
|
||||
|
||||
/**
|
||||
* Return the title of the melody.
|
||||
*/
|
||||
String getTitle() const {
|
||||
return title;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the time unit (i.e. the minimum length of a note), in milliseconds.
|
||||
*/
|
||||
unsigned short getTimeUnit() const {
|
||||
return timeUnit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of notes.
|
||||
*/
|
||||
unsigned short getLength() const {
|
||||
if (notes == nullptr) return 0;
|
||||
return (*notes).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the note at the given position.
|
||||
* If the melody or the position is invalid, return a zeroed NoteDuration.
|
||||
*/
|
||||
NoteDuration getNote(unsigned short i) const {
|
||||
if (i < getLength()) { return (*notes)[i]; }
|
||||
return { 0, 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the melody should be played with a small delay between each note.
|
||||
*/
|
||||
bool getAutomaticSilence() const {
|
||||
return automaticSilence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the melody is valid, false otherwise.
|
||||
*/
|
||||
bool isValid() const {
|
||||
return notes != nullptr && (*notes).size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the melody is valid, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const {
|
||||
return isValid();
|
||||
}
|
||||
|
||||
private:
|
||||
String title;
|
||||
// in milliseconds
|
||||
unsigned short timeUnit;
|
||||
std::shared_ptr<std::vector<NoteDuration>> notes;
|
||||
const static unsigned short maxLength = 1000;
|
||||
bool automaticSilence;
|
||||
|
||||
// Enable debug messages over serial port
|
||||
const static bool debug = false;
|
||||
};
|
||||
|
||||
#endif // END MELODY_H
|
||||
229
lib/Melody Player/src/melody_factory.cpp
Normal file
229
lib/Melody Player/src/melody_factory.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#include "melody_factory.h"
|
||||
#include "notes_array.h"
|
||||
#include <LittleFS.h>
|
||||
#include <algorithm>
|
||||
|
||||
static void removeCarriageReturn(String& s) {
|
||||
if (s.charAt(s.length() - 1) == '\r') { s = s.substring(0, s.length() - 1); }
|
||||
}
|
||||
|
||||
Melody MelodyFactoryClass::load(String filepath, FS& fs) {
|
||||
File f = LittleFS.open(filepath, "r");
|
||||
f.setTimeout(0);
|
||||
|
||||
if (!f) {
|
||||
if (debug) Serial.println("Opening file error");
|
||||
return Melody();
|
||||
}
|
||||
|
||||
// Skip multi-line comments at the begin of the file
|
||||
String line = f.readStringUntil('\n');
|
||||
while (line.charAt(0) == '#') { line = f.readStringUntil('\n'); }
|
||||
|
||||
bool success = false;
|
||||
success = loadTitle(line);
|
||||
if (!success) { return Melody(); }
|
||||
|
||||
success = loadTimeUnit(f.readStringUntil('\n'));
|
||||
if (!success) { return Melody(); }
|
||||
|
||||
success = loadNumberOfNotes(f.readStringUntil('\n'));
|
||||
if (!success) { return Melody(); }
|
||||
|
||||
NoteFormat noteFormat = loadNoteFormat(f.readStringUntil('\n'));
|
||||
if (noteFormat == NoteFormat::ERROR) {
|
||||
return Melody();
|
||||
} else {
|
||||
this->noteFormat = noteFormat;
|
||||
}
|
||||
|
||||
if (debug)
|
||||
Serial.println(String("This melody object will take at least: ") + (sizeof(NoteDuration) * nNotes) + "bytes");
|
||||
if (nNotes < maxLength) {
|
||||
notes = std::make_shared<std::vector<NoteDuration>>();
|
||||
notes->reserve(nNotes);
|
||||
bool error = false;
|
||||
while (f.available() && notes->size() < nNotes && !error) {
|
||||
// get a token
|
||||
String noteDuration = f.readStringUntil('|');
|
||||
error = !loadNote(noteDuration);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (debug) Serial.println("error during the tokens loading!");
|
||||
return Melody();
|
||||
}
|
||||
}
|
||||
|
||||
return Melody(title, timeUnit, notes, true);
|
||||
}
|
||||
|
||||
Melody MelodyFactoryClass::load(String title, unsigned short timeUnit, String notesToLoad[],
|
||||
unsigned short nNotesToLoad, bool autoSilence) {
|
||||
if (title.length() == 0 && timeUnit <= 20) { return Melody(); }
|
||||
if (nNotesToLoad == 0 || nNotesToLoad > maxLength) { return Melody(); }
|
||||
|
||||
if (notesToLoad == nullptr) { return Melody(); }
|
||||
|
||||
notes = std::make_shared<std::vector<NoteDuration>>();
|
||||
notes->reserve(nNotesToLoad);
|
||||
noteFormat = NoteFormat::STRING;
|
||||
bool error = false;
|
||||
while (this->notes->size() < nNotesToLoad && !error) {
|
||||
String noteDuration = notesToLoad[notes->size()] + ",1";
|
||||
error = !loadNote(noteDuration);
|
||||
}
|
||||
if (error) { return Melody(); }
|
||||
|
||||
return Melody(title, timeUnit, notes, autoSilence);
|
||||
}
|
||||
|
||||
Melody MelodyFactoryClass::load(String title, unsigned short timeUnit, int frequenciesToLoad[],
|
||||
unsigned short nFrequenciesToLoad, bool autoSilence) {
|
||||
if (title.length() == 0 && timeUnit <= 20) { return Melody(); }
|
||||
if (nFrequenciesToLoad == 0 || nFrequenciesToLoad > maxLength) { return Melody(); }
|
||||
|
||||
if (frequenciesToLoad == nullptr) { return Melody(); }
|
||||
|
||||
notes = std::make_shared<std::vector<NoteDuration>>();
|
||||
notes->reserve(nFrequenciesToLoad);
|
||||
noteFormat = NoteFormat::INTEGER;
|
||||
bool error = false;
|
||||
while (this->notes->size() < nFrequenciesToLoad && !error) {
|
||||
String noteDuration = String(frequenciesToLoad[notes->size()]) + ",1";
|
||||
error = !loadNote(noteDuration);
|
||||
}
|
||||
if (error) { return Melody(); }
|
||||
|
||||
return Melody(title, timeUnit, notes, autoSilence);
|
||||
}
|
||||
|
||||
bool MelodyFactoryClass::loadTitle(String line) {
|
||||
removeCarriageReturn(line);
|
||||
if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length());
|
||||
if (line.startsWith("title")) {
|
||||
// Skip also '='
|
||||
String title = line.substring(6);
|
||||
this->title = title;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MelodyFactoryClass::loadTimeUnit(String line) {
|
||||
removeCarriageReturn(line);
|
||||
if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length());
|
||||
if (line.startsWith("timeUnit")) {
|
||||
// Skip '='
|
||||
String t = line.substring(9);
|
||||
this->timeUnit = t.toInt();
|
||||
if (debug) Serial.println(this->timeUnit);
|
||||
if (this->timeUnit > 20) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MelodyFactoryClass::loadNumberOfNotes(String line) {
|
||||
removeCarriageReturn(line);
|
||||
if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length());
|
||||
if (line.startsWith("length")) {
|
||||
// Skip also '='
|
||||
String len = line.substring(7);
|
||||
this->nNotes = len.toInt();
|
||||
if (debug) Serial.println(this->nNotes);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MelodyFactoryClass::NoteFormat MelodyFactoryClass::loadNoteFormat(String line) {
|
||||
removeCarriageReturn(line);
|
||||
if (debug) Serial.println(String("Reading line:--") + line + "-- Len:" + line.length());
|
||||
String format;
|
||||
if (line.startsWith("format")) {
|
||||
// Skip also '='
|
||||
format = line.substring(7);
|
||||
if (debug) Serial.println(format);
|
||||
}
|
||||
|
||||
NoteFormat noteFormat = NoteFormat::ERROR;
|
||||
if (format == "string") {
|
||||
noteFormat = NoteFormat::STRING;
|
||||
} else if (format == "integer") {
|
||||
noteFormat = NoteFormat::INTEGER;
|
||||
}
|
||||
|
||||
return noteFormat;
|
||||
}
|
||||
|
||||
bool MelodyFactoryClass::loadNote(String token) {
|
||||
token.trim();
|
||||
NoteDuration note;
|
||||
|
||||
if (debug) Serial.println(String("note+duration: ") + token);
|
||||
|
||||
String aux;
|
||||
unsigned int j = 0;
|
||||
|
||||
// Get the frequency
|
||||
while (j < token.length() && token.charAt(j) != ',') {
|
||||
aux += token.charAt(j);
|
||||
j++;
|
||||
}
|
||||
|
||||
if (noteFormat == NoteFormat::STRING) {
|
||||
auto n = std::find_if(noteMapping.cbegin(), noteMapping.cend(),
|
||||
[&aux](std::pair<StringView, unsigned short> e) {
|
||||
return e.first == aux.c_str();
|
||||
});
|
||||
|
||||
if (n != noteMapping.cend()) {
|
||||
note.frequency = n->second;
|
||||
} else {
|
||||
if (debug) Serial.println(String("This note doesn't exist: ") + aux);
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (noteFormat == NoteFormat::INTEGER) {
|
||||
note.frequency = aux.toInt();
|
||||
}
|
||||
if (debug) Serial.println(String("freq: ") + note.frequency);
|
||||
|
||||
j++;
|
||||
aux = "";
|
||||
while (j < token.length()) {
|
||||
aux += token.charAt(j);
|
||||
j++;
|
||||
}
|
||||
|
||||
note.duration = aux.toInt();
|
||||
|
||||
if (debug) Serial.println(String("duration: ") + note.duration);
|
||||
// The representation of relative note duration is fixed-point with decimal part length = 1bit
|
||||
note.duration *= 2;
|
||||
|
||||
notes->push_back(note);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MelodyFactoryClass MelodyFactory;
|
||||
163
lib/Melody Player/src/melody_factory.h
Normal file
163
lib/Melody Player/src/melody_factory.h
Normal file
@@ -0,0 +1,163 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#ifndef MELODY_FACTORY_H
|
||||
#define MELODY_FACTORY_H
|
||||
|
||||
#include "melody.h"
|
||||
|
||||
#include <FS.h>
|
||||
#ifdef ESP32
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
|
||||
class MelodyFactoryClass {
|
||||
public:
|
||||
/**
|
||||
* Load the melody from file in MelodyPlayer format.
|
||||
*/
|
||||
Melody load(String filePath, FS& fs = SPIFFS);
|
||||
|
||||
/**
|
||||
* Load melody from file in RTTTL format. The file must contain only one melody.
|
||||
*/
|
||||
Melody loadRtttlFile(String filePath, FS& fs = SPIFFS);
|
||||
|
||||
/**
|
||||
* Load melody with the given title from a file containing multiple RTTTL melody (one Melody per
|
||||
* line).
|
||||
*/
|
||||
Melody loadRtttlDB(String filepath, String title, FS& fs = SPIFFS);
|
||||
|
||||
/**
|
||||
* Load melody from string in RTTTL format.
|
||||
*/
|
||||
Melody loadRtttlString(const char rtttlMelody[]);
|
||||
|
||||
/**
|
||||
* Create a melody with the given parameters.
|
||||
* Notes are represented as string accordigly to english notation (i.e. "C4", "G3", "G6").
|
||||
* This method assumes that each note lasts 1 beat.
|
||||
* frequenciesToLoad are integer numbers expressing the real reproduced frequency.
|
||||
* automaticSilence, if true, automatically inserts a small silence between 2 consecutive notes.
|
||||
*/
|
||||
Melody load(String title, unsigned short timeUnit, String notesToLoad[],
|
||||
unsigned short nNotesToLoad, bool autoSilence = true);
|
||||
|
||||
/**
|
||||
* Create a melody with the given parameters.
|
||||
* This method assumes that each note lasts 1 beat.
|
||||
* frequenciesToLoad are integer numbers expressing the real reproduced frequency.
|
||||
* The last parameter, automaticSilence, if true, automatically inserts a small silence between 2
|
||||
* consecutive notes.
|
||||
*/
|
||||
Melody load(String title, unsigned short timeUnit, int frequenciesToLoad[],
|
||||
unsigned short nFrequenciesToLoad, bool autoSilence = true);
|
||||
|
||||
private:
|
||||
enum class NoteFormat { ERROR, STRING, INTEGER };
|
||||
|
||||
String title;
|
||||
unsigned short timeUnit;
|
||||
NoteFormat noteFormat;
|
||||
std::shared_ptr<std::vector<NoteDuration>> notes;
|
||||
// Used to check how many notes are stored in a file.
|
||||
unsigned short nNotes;
|
||||
const unsigned short maxLength = 1000;
|
||||
|
||||
///////////// RTTTL helpers
|
||||
/**
|
||||
* The default duration of a note. For example,
|
||||
* "4" means that each note with no duration specifier
|
||||
* is by default considered a quarter note. Possibile values:
|
||||
* 1 - whole note
|
||||
* 2 - half note
|
||||
* 4 - quarter note
|
||||
* 8 - eighth note
|
||||
* 16 - sixteenth note
|
||||
* 32 - thirty-second note
|
||||
*/
|
||||
const unsigned short defaultDuration = 4;
|
||||
unsigned short duration;
|
||||
|
||||
/**
|
||||
* The default octave. There are four octaves in the RTTTL format [4-7].
|
||||
*/
|
||||
const unsigned short defaultOctave = 6;
|
||||
unsigned short octave;
|
||||
|
||||
/**
|
||||
* The default BPM (beats per minute) value. BPM is arbitrarily limited between 10 and 300. Look
|
||||
* at the implementation of parseBeat for more info.
|
||||
*/
|
||||
const unsigned short defaultBeat = 63;
|
||||
unsigned short beat;
|
||||
|
||||
/**
|
||||
* Try to parse the default parameters of RTTTL melody.
|
||||
* If user-defined defaults are not found it sets the default values as prescribed by RTTTL
|
||||
* specification.
|
||||
*/
|
||||
void parseDefaultValues(String values);
|
||||
|
||||
unsigned int parseDuration(const String& s, int& startFrom);
|
||||
unsigned int parseOctave(const String& s, int& startFrom);
|
||||
unsigned int parseBeat(const String& s, int& startFrom);
|
||||
bool parseRtttlNote(const String& s);
|
||||
|
||||
//////////// END RTTTL helpers
|
||||
|
||||
/**
|
||||
* Parse the title from the given string.
|
||||
* Return true on success.
|
||||
*/
|
||||
bool loadTitle(String line);
|
||||
|
||||
/**
|
||||
* Parse the time unit from the given string.
|
||||
* Return true on success.
|
||||
*/
|
||||
bool loadTimeUnit(String line);
|
||||
|
||||
/**
|
||||
* Parse the number of notes from the given string.
|
||||
* Return true on success.
|
||||
*/
|
||||
bool loadNumberOfNotes(String line);
|
||||
|
||||
/**
|
||||
* Parse the note's format from the given string.
|
||||
*/
|
||||
NoteFormat loadNoteFormat(String line);
|
||||
|
||||
/**
|
||||
* Parse a token (a note and its duration) from the given string.
|
||||
* The format of this token is:
|
||||
* <note> + ',' + <duration>.
|
||||
* Return true if the parsing succeeds, false otherwise.
|
||||
*/
|
||||
bool loadNote(String token);
|
||||
|
||||
// Enable debug messages over serial port
|
||||
static const bool debug = false;
|
||||
};
|
||||
|
||||
extern MelodyFactoryClass MelodyFactory;
|
||||
|
||||
#endif // END MELODY_FACTORY_H
|
||||
368
lib/Melody Player/src/melody_factory_rtttl.cpp
Normal file
368
lib/Melody Player/src/melody_factory_rtttl.cpp
Normal file
@@ -0,0 +1,368 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#include "melody_factory.h"
|
||||
#include "notes.h"
|
||||
#include <LittleFS.h>
|
||||
// clang-format off
|
||||
const uint16_t sourceNotes[] = {
|
||||
0,
|
||||
NOTE_C4,
|
||||
NOTE_CS4,
|
||||
NOTE_D4,
|
||||
NOTE_DS4,
|
||||
NOTE_E4,
|
||||
NOTE_F4,
|
||||
NOTE_FS4,
|
||||
NOTE_G4,
|
||||
NOTE_GS4,
|
||||
NOTE_A4,
|
||||
NOTE_AS4,
|
||||
NOTE_B4,
|
||||
|
||||
NOTE_C5,
|
||||
NOTE_CS5,
|
||||
NOTE_D5,
|
||||
NOTE_DS5,
|
||||
NOTE_E5,
|
||||
NOTE_F5,
|
||||
NOTE_FS5,
|
||||
NOTE_G5,
|
||||
NOTE_GS5,
|
||||
NOTE_A5,
|
||||
NOTE_AS5,
|
||||
NOTE_B5,
|
||||
|
||||
NOTE_C6,
|
||||
NOTE_CS6,
|
||||
NOTE_D6,
|
||||
NOTE_DS6,
|
||||
NOTE_E6,
|
||||
NOTE_F6,
|
||||
NOTE_FS6,
|
||||
NOTE_G6,
|
||||
NOTE_GS6,
|
||||
NOTE_A6,
|
||||
NOTE_AS6,
|
||||
NOTE_B6,
|
||||
|
||||
NOTE_C7,
|
||||
NOTE_CS7,
|
||||
NOTE_D7,
|
||||
NOTE_DS7,
|
||||
NOTE_E7,
|
||||
NOTE_F7,
|
||||
NOTE_FS7,
|
||||
NOTE_G7,
|
||||
NOTE_GS7,
|
||||
NOTE_A7,
|
||||
NOTE_AS7,
|
||||
NOTE_B7,
|
||||
|
||||
2 * NOTE_C7,
|
||||
2 * NOTE_CS7,
|
||||
2 * NOTE_D7,
|
||||
2 * NOTE_DS7,
|
||||
2 * NOTE_E7,
|
||||
2 * NOTE_F7,
|
||||
2 * NOTE_FS7,
|
||||
2 * NOTE_G7,
|
||||
2 * NOTE_GS7,
|
||||
2 * NOTE_A7,
|
||||
2 * NOTE_AS7,
|
||||
2 * NOTE_B7,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
Melody MelodyFactoryClass::loadRtttlFile(String filepath, FS& fs) {
|
||||
File f = LittleFS.open(filepath, "r");
|
||||
f.setTimeout(0);
|
||||
|
||||
if (!f) {
|
||||
if (debug) Serial.println("Opening file error");
|
||||
return Melody();
|
||||
}
|
||||
|
||||
String title = f.readStringUntil(':');
|
||||
title.trim();
|
||||
if (debug) Serial.println(String("Title:") + title);
|
||||
if (title.length() == 0) { return Melody(); }
|
||||
|
||||
String values = f.readStringUntil(':');
|
||||
values.trim();
|
||||
if (debug) Serial.println(String("Default values:") + values);
|
||||
if (values.length() == 0) { return Melody(); }
|
||||
|
||||
parseDefaultValues(values);
|
||||
|
||||
// 32 because it is the shortest note!
|
||||
int timeUnit = 60 * 1000 * 4 / beat / 32;
|
||||
|
||||
notes = std::make_shared<std::vector<NoteDuration>>();
|
||||
bool result = true;
|
||||
while (f.available() && notes->size() < maxLength && result) {
|
||||
String s = f.readStringUntil(',');
|
||||
s.trim();
|
||||
result = parseRtttlNote(s);
|
||||
}
|
||||
if (result && notes->size() > 0) { return Melody(title, timeUnit, notes, false); }
|
||||
|
||||
return Melody();
|
||||
}
|
||||
|
||||
Melody MelodyFactoryClass::loadRtttlDB(String filepath, String title, FS& fs) {
|
||||
File f = LittleFS.open(filepath, "r");
|
||||
f.setTimeout(0);
|
||||
|
||||
if (!f) {
|
||||
if (debug) Serial.println("Opening file error");
|
||||
return Melody();
|
||||
}
|
||||
|
||||
if (title.length() == 0) {
|
||||
if (debug) Serial.println("Title length = 0");
|
||||
return Melody();
|
||||
}
|
||||
|
||||
if (!f.find(title.c_str())) {
|
||||
if (debug) Serial.println("Unable to find melody with title: " + String(title));
|
||||
return Melody();
|
||||
}
|
||||
f.readStringUntil(':');
|
||||
|
||||
String values = f.readStringUntil(':');
|
||||
values.trim();
|
||||
if (debug) Serial.println(String("Default values:") + values);
|
||||
if (values.length() == 0) { return Melody(); }
|
||||
|
||||
parseDefaultValues(values);
|
||||
|
||||
// 32 because it is the shortest note!
|
||||
int timeUnit = 60 * 1000 * 4 / beat / 32;
|
||||
|
||||
size_t position = f.position();
|
||||
int bytesUntilNewLine = f.readStringUntil('\n').length();
|
||||
f.seek(position);
|
||||
|
||||
notes = std::make_shared<std::vector<NoteDuration>>();
|
||||
bool result = true;
|
||||
while (f.available() && notes->size() < maxLength && result && bytesUntilNewLine > 0) {
|
||||
String s = f.readStringUntil(',');
|
||||
if (s.length() > bytesUntilNewLine) { s = s.substring(0, bytesUntilNewLine); }
|
||||
bytesUntilNewLine -= s.length() + 1;
|
||||
s.trim();
|
||||
result = parseRtttlNote(s);
|
||||
}
|
||||
if (result && notes->size() > 0) { return Melody(title, timeUnit, notes, false); }
|
||||
|
||||
return Melody();
|
||||
}
|
||||
|
||||
Melody MelodyFactoryClass::loadRtttlString(const char rtttlMelody[]) {
|
||||
String title;
|
||||
int i = 0;
|
||||
while (rtttlMelody[i] != 0 && rtttlMelody[i] != ':') {
|
||||
title.concat(rtttlMelody[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (title.length() == 0 || rtttlMelody[i] == 0) { return Melody(); }
|
||||
|
||||
// skip ':'
|
||||
i++;
|
||||
|
||||
String defaultParameters;
|
||||
while (rtttlMelody[i] != 0 && rtttlMelody[i] != ':') {
|
||||
defaultParameters.concat(rtttlMelody[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (rtttlMelody[i] == 0) { return Melody(); }
|
||||
|
||||
defaultParameters.trim();
|
||||
parseDefaultValues(defaultParameters);
|
||||
|
||||
// 32 because it is the shortest note!
|
||||
int timeUnit = 60 * 1000 * 4 / beat / 32;
|
||||
|
||||
// skip ':'
|
||||
i++;
|
||||
|
||||
notes = std::make_shared<std::vector<NoteDuration>>();
|
||||
// Read notes
|
||||
while (rtttlMelody[i] != 0) {
|
||||
String note;
|
||||
while (rtttlMelody[i] != 0 && rtttlMelody[i] != ',') {
|
||||
note.concat(rtttlMelody[i]);
|
||||
i++;
|
||||
}
|
||||
note.trim();
|
||||
parseRtttlNote(note);
|
||||
if (rtttlMelody[i] == ',') { i++; }
|
||||
}
|
||||
if (notes->size() > 0) { return Melody(title, timeUnit, notes, false); }
|
||||
|
||||
return Melody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an unsigned integer starting from the given startFrom to the first non-digit char.
|
||||
* Return zero if it cannot parse a number. *startFrom* will point to the first non-digit char.
|
||||
*/
|
||||
unsigned int getUnsignedInt(const String& s, int& startFrom) {
|
||||
unsigned int temp = 0;
|
||||
while (isDigit(s.charAt(startFrom))) {
|
||||
temp = (temp * 10) + s.charAt(startFrom) - '0';
|
||||
startFrom++;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
unsigned int MelodyFactoryClass::parseDuration(const String& s, int& startFrom) {
|
||||
// Skip '='
|
||||
startFrom++;
|
||||
unsigned int temp = getUnsignedInt(s, startFrom);
|
||||
if (temp != 1 && temp != 2 && temp != 4 && temp != 8 && temp != 16 && temp != 32) { return 0; }
|
||||
// Discard ','
|
||||
startFrom++;
|
||||
return temp;
|
||||
}
|
||||
|
||||
unsigned int MelodyFactoryClass::parseOctave(const String& s, int& startFrom) {
|
||||
// Skip '='
|
||||
startFrom++;
|
||||
unsigned int temp = getUnsignedInt(s, startFrom);
|
||||
if (temp < 4 || temp > 7) { return 0; }
|
||||
// Discard ','
|
||||
startFrom++;
|
||||
return temp;
|
||||
}
|
||||
|
||||
unsigned int MelodyFactoryClass::parseBeat(const String& s, int& startFrom) {
|
||||
// Skip '='
|
||||
startFrom++;
|
||||
unsigned int temp = getUnsignedInt(s, startFrom);
|
||||
|
||||
// BPM is arbitrarily limited to 300. You may try to increase it, but remember that
|
||||
// actually, the minimum note length is 60(seconds)/300(bpm)/32(minimum note length) = 6.25ms.
|
||||
// If you reduce this duration, you may not be able to keep up the pace to play a smooth
|
||||
// async playback while doing other operations.
|
||||
if (!(10 <= temp && temp <= 300)) { return 0; }
|
||||
// Discard ','
|
||||
startFrom++;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool MelodyFactoryClass::parseRtttlNote(const String& s) {
|
||||
int i = 0;
|
||||
|
||||
unsigned short relativeDuration = this->duration;
|
||||
// Optional number: note duration (e.g 4=quarter note, ...)
|
||||
if (isdigit(s.charAt(i))) {
|
||||
unsigned int temp = getUnsignedInt(s, i);
|
||||
if (temp) { relativeDuration = temp; }
|
||||
}
|
||||
|
||||
// To match struct NoteDuration format, I need the direct
|
||||
// note length, instead RTTTL provides the denominator
|
||||
// of the whole note
|
||||
if (relativeDuration == 32) {
|
||||
relativeDuration = 1;
|
||||
} else if (relativeDuration == 16) {
|
||||
relativeDuration = 2;
|
||||
} else if (relativeDuration == 8) {
|
||||
relativeDuration = 4;
|
||||
} else if (relativeDuration == 4) {
|
||||
relativeDuration = 8;
|
||||
} else if (relativeDuration == 2) {
|
||||
relativeDuration = 16;
|
||||
} else if (relativeDuration == 1) {
|
||||
relativeDuration = 32;
|
||||
} else {
|
||||
relativeDuration = 0;
|
||||
}
|
||||
|
||||
// note (p is silence)
|
||||
int note = 0;
|
||||
switch (s.charAt(i)) {
|
||||
case 'c': note = 1; break;
|
||||
case 'd': note = 3; break;
|
||||
case 'e': note = 5; break;
|
||||
case 'f': note = 6; break;
|
||||
case 'g': note = 8; break;
|
||||
case 'a': note = 10; break;
|
||||
case 'b': note = 12; break;
|
||||
case 'p':
|
||||
default: note = 0;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
// Optional #
|
||||
if (s.charAt(i) == '#') {
|
||||
note++;
|
||||
i++;
|
||||
}
|
||||
|
||||
// The representation of relative note duration is fixed-point with decimal part length = 1bit
|
||||
relativeDuration *= 2;
|
||||
// get optional '.' dotted note
|
||||
// This note will last 50% more
|
||||
if (s.charAt(i) == '.') {
|
||||
relativeDuration += relativeDuration / 2;
|
||||
i++;
|
||||
}
|
||||
|
||||
int scale;
|
||||
// now, get scale
|
||||
if (isdigit(s.charAt(i))) {
|
||||
scale = s.charAt(i) - '0';
|
||||
i++;
|
||||
} else {
|
||||
scale = octave;
|
||||
}
|
||||
|
||||
unsigned short freq;
|
||||
if (note) {
|
||||
freq = sourceNotes[(scale - 4) * 12 + note];
|
||||
} else {
|
||||
freq = 0;
|
||||
}
|
||||
|
||||
notes->push_back({ .frequency = freq, .duration = relativeDuration });
|
||||
return true;
|
||||
}
|
||||
|
||||
void MelodyFactoryClass::parseDefaultValues(String values) {
|
||||
int i = 0;
|
||||
|
||||
if (values.charAt(i) == 'd') { i++; }
|
||||
duration = parseDuration(values, i);
|
||||
if (duration == 0) { duration = defaultDuration; }
|
||||
|
||||
if (values.charAt(i) == 'o') { i++; }
|
||||
octave = parseOctave(values, i);
|
||||
if (octave == 0) { octave = defaultOctave; }
|
||||
|
||||
if (values.charAt(i) == 'b') {
|
||||
i++;
|
||||
beat = parseBeat(values, i);
|
||||
}
|
||||
if (beat == 0) { beat = defaultBeat; }
|
||||
}
|
||||
230
lib/Melody Player/src/melody_player.cpp
Normal file
230
lib/Melody Player/src/melody_player.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#include "melody_player.h"
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/24609271/errormake-unique-is-not-a-member-of-std
|
||||
*/
|
||||
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
void MelodyPlayer::play() {
|
||||
if (melodyState == nullptr) { return; }
|
||||
|
||||
turnOn();
|
||||
state = State::PLAY;
|
||||
|
||||
melodyState->advance();
|
||||
while (melodyState->getIndex() + melodyState->isSilence() < melodyState->melody.getLength()) {
|
||||
NoteDuration computedNote = melodyState->getCurrentComputedNote();
|
||||
if (debug)
|
||||
Serial.println(String("Playing: frequency:") + computedNote.frequency
|
||||
+ " duration:" + computedNote.duration);
|
||||
if (melodyState->isSilence()) {
|
||||
#ifdef ESP32
|
||||
ledcWriteTone(pwmChannel, 0);
|
||||
#else
|
||||
noTone(pin);
|
||||
#endif
|
||||
delay(0.3f * computedNote.duration);
|
||||
} else {
|
||||
#ifdef ESP32
|
||||
ledcWriteTone(pwmChannel, computedNote.frequency);
|
||||
#else
|
||||
tone(pin, computedNote.frequency);
|
||||
#endif
|
||||
delay(computedNote.duration);
|
||||
}
|
||||
melodyState->advance();
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
void MelodyPlayer::play(Melody& melody) {
|
||||
if (!melody) { return; }
|
||||
melodyState = make_unique<MelodyState>(melody);
|
||||
play();
|
||||
}
|
||||
|
||||
void changeTone(MelodyPlayer* player) {
|
||||
// The last silence is not reproduced
|
||||
player->melodyState->advance();
|
||||
if (player->melodyState->getIndex() + player->melodyState->isSilence()
|
||||
< player->melodyState->melody.getLength()) {
|
||||
NoteDuration computedNote(player->melodyState->getCurrentComputedNote());
|
||||
|
||||
float duration = player->melodyState->getRemainingNoteDuration();
|
||||
if (duration > 0) {
|
||||
player->melodyState->resetRemainingNoteDuration();
|
||||
} else {
|
||||
if (player->melodyState->isSilence()) {
|
||||
duration = 0.3f * computedNote.duration;
|
||||
} else {
|
||||
duration = computedNote.duration;
|
||||
}
|
||||
}
|
||||
if (player->debug)
|
||||
Serial.println(String("Playing async: freq=") + computedNote.frequency + " dur=" + duration
|
||||
+ " iteration=" + player->melodyState->getIndex());
|
||||
|
||||
if (player->melodyState->isSilence()) {
|
||||
#ifdef ESP32
|
||||
ledcWriteTone(player->pwmChannel, 0);
|
||||
#else
|
||||
tone(player->pin, 0);
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
player->ticker.once_ms(duration, changeTone, player);
|
||||
#else
|
||||
player->ticker.once_ms_scheduled(duration, std::bind(changeTone, player));
|
||||
#endif
|
||||
} else {
|
||||
#ifdef ESP32
|
||||
ledcWriteTone(player->pwmChannel, computedNote.frequency);
|
||||
#else
|
||||
tone(player->pin, computedNote.frequency);
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
player->ticker.once_ms(duration, changeTone, player);
|
||||
#else
|
||||
player->ticker.once_ms_scheduled(duration, std::bind(changeTone, player));
|
||||
#endif
|
||||
}
|
||||
player->supportSemiNote = millis() + duration;
|
||||
} else {
|
||||
player->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MelodyPlayer::playAsync() {
|
||||
if (melodyState == nullptr) { return; }
|
||||
|
||||
turnOn();
|
||||
state = State::PLAY;
|
||||
|
||||
// Start immediately
|
||||
#ifdef ESP32
|
||||
ticker.once(0, changeTone, this);
|
||||
#else
|
||||
ticker.once_scheduled(0, std::bind(changeTone, this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void MelodyPlayer::playAsync(Melody& melody) {
|
||||
if (!melody) { return; }
|
||||
melodyState = make_unique<MelodyState>(melody);
|
||||
playAsync();
|
||||
}
|
||||
|
||||
void MelodyPlayer::stop() {
|
||||
if (melodyState == nullptr) { return; }
|
||||
|
||||
haltPlay();
|
||||
state = State::STOP;
|
||||
melodyState->reset();
|
||||
}
|
||||
|
||||
void MelodyPlayer::pause() {
|
||||
if (melodyState == nullptr) { return; }
|
||||
|
||||
haltPlay();
|
||||
state = State::PAUSE;
|
||||
melodyState->saveRemainingNoteDuration(supportSemiNote);
|
||||
}
|
||||
|
||||
void MelodyPlayer::transferMelodyTo(MelodyPlayer& destPlayer) {
|
||||
if (melodyState == nullptr) { return; }
|
||||
|
||||
destPlayer.stop();
|
||||
|
||||
bool playing = isPlaying();
|
||||
|
||||
haltPlay();
|
||||
state = State::STOP;
|
||||
melodyState->saveRemainingNoteDuration(supportSemiNote);
|
||||
destPlayer.melodyState = std::move(melodyState);
|
||||
|
||||
if (playing) {
|
||||
destPlayer.playAsync();
|
||||
} else {
|
||||
destPlayer.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
void MelodyPlayer::duplicateMelodyTo(MelodyPlayer& destPlayer) {
|
||||
if (melodyState == nullptr) { return; }
|
||||
|
||||
destPlayer.stop();
|
||||
destPlayer.melodyState = make_unique<MelodyState>(*(this->melodyState));
|
||||
destPlayer.melodyState->saveRemainingNoteDuration(supportSemiNote);
|
||||
|
||||
if (isPlaying()) {
|
||||
destPlayer.playAsync();
|
||||
} else {
|
||||
destPlayer.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
MelodyPlayer::MelodyPlayer(unsigned char pin, unsigned char pwmChannel, bool offLevel)
|
||||
: pin(pin), pwmChannel(pwmChannel), offLevel(offLevel), state(State::STOP), melodyState(nullptr) {
|
||||
pinMode(pin, OUTPUT);
|
||||
digitalWrite(pin, offLevel);
|
||||
};
|
||||
#else
|
||||
MelodyPlayer::MelodyPlayer(unsigned char pin, bool offLevel)
|
||||
: pin(pin), offLevel(offLevel), state(State::STOP), melodyState(nullptr) {
|
||||
pinMode(pin, OUTPUT);
|
||||
digitalWrite(pin, offLevel);
|
||||
};
|
||||
#endif
|
||||
|
||||
void MelodyPlayer::haltPlay() {
|
||||
// Stop player, but do not reset the melodyState
|
||||
ticker.detach();
|
||||
turnOff();
|
||||
}
|
||||
|
||||
void MelodyPlayer::turnOn() {
|
||||
#ifdef ESP32
|
||||
const int resolution = 8;
|
||||
// 2000 is a frequency, it will be changed at the first play
|
||||
ledcSetup(pwmChannel, 2000, resolution);
|
||||
ledcAttachPin(pin, pwmChannel);
|
||||
ledcWrite(pwmChannel, 125);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MelodyPlayer::turnOff() {
|
||||
#ifdef ESP32
|
||||
ledcWrite(pwmChannel, 0);
|
||||
ledcDetachPin(pin);
|
||||
#else
|
||||
// Remember that this will set LOW output, it doesn't mean that buzzer is off (look at offLevel
|
||||
// for more info).
|
||||
noTone(pin);
|
||||
#endif
|
||||
|
||||
pinMode(pin, LOW);
|
||||
digitalWrite(pin, offLevel);
|
||||
}
|
||||
248
lib/Melody Player/src/melody_player.h
Normal file
248
lib/Melody Player/src/melody_player.h
Normal file
@@ -0,0 +1,248 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#ifndef MELODY_PLAYER_H
|
||||
#define MELODY_PLAYER_H
|
||||
|
||||
#include "melody.h"
|
||||
#include <Ticker.h>
|
||||
#include <memory>
|
||||
|
||||
class MelodyPlayer {
|
||||
public:
|
||||
#ifdef ESP32
|
||||
/**
|
||||
* pwmChannel is optional and you have to configure it only if you play will
|
||||
* simultaneous melodies.
|
||||
*/
|
||||
MelodyPlayer(unsigned char pin, unsigned char pwmChannel = 0, bool offLevel = HIGH);
|
||||
#else
|
||||
MelodyPlayer(unsigned char pin, bool offLevel = HIGH);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Play the last melody in a synchrounus (blocking) way.
|
||||
* If the melody is not valid, this call has no effect.
|
||||
*/
|
||||
void play();
|
||||
|
||||
/**
|
||||
* Play the given melody in a synchronous (blocking) way.
|
||||
* If the melody is not valid, this call has no effect.
|
||||
*/
|
||||
void play(Melody& melody);
|
||||
|
||||
/**
|
||||
* Play the last melody in asynchronous way (return immediately).
|
||||
* If the melody is not valid, this call has no effect.
|
||||
*/
|
||||
void playAsync();
|
||||
|
||||
/**
|
||||
* Play the given melody in asynchronous way (return immediately).
|
||||
* If the melody is not valid, this call has no effect.
|
||||
*/
|
||||
void playAsync(Melody& melody);
|
||||
|
||||
/**
|
||||
* Stop the current melody.
|
||||
* Then, if you will call play() or playAsync(), the melody restarts from the begin.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Pause the current melody.
|
||||
* Then, if you will call play() or playAsync(), the melody continues from
|
||||
* where it was paused.
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* Tell if playing.
|
||||
*/
|
||||
bool isPlaying() const {
|
||||
return state == State::PLAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current melody and player's state to the given destination Player.
|
||||
* The source player stops and lose the reference to the actual melody (i.e. you have to call
|
||||
* play(Melody) to make the source player play again).
|
||||
*/
|
||||
void transferMelodyTo(MelodyPlayer& destination);
|
||||
|
||||
/**
|
||||
* Duplicate the current melody and player's state to the given destination Player.
|
||||
* Both players remains indipendent from each other (e.g. the melody can be independently
|
||||
* stopped/paused/played).
|
||||
*/
|
||||
void duplicateMelodyTo(MelodyPlayer& destination);
|
||||
|
||||
private:
|
||||
unsigned char pin;
|
||||
|
||||
#ifdef ESP32
|
||||
unsigned char pwmChannel;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The voltage to turn off the buzzer.
|
||||
*
|
||||
* NOTE: Passive buzzers have 2 states: the "rest" state (no power consumption) and the "active"
|
||||
* state (high power consumption). To emit sound, it have to oscillate between these 2 states. If
|
||||
* it stops in the active state, it doesn't emit sound, but it continues to consume energy,
|
||||
* heating the buzzer and possibly damaging itself.
|
||||
*/
|
||||
bool offLevel;
|
||||
|
||||
/**
|
||||
* Store the playback state of a melody and provide the methods to control it.
|
||||
*/
|
||||
class MelodyState {
|
||||
public:
|
||||
MelodyState() : first(true), index(0), remainingNoteTime(0){};
|
||||
MelodyState(const Melody& melody)
|
||||
: melody(melody), first(true), silence(false), index(0), remainingNoteTime(0){};
|
||||
Melody melody;
|
||||
|
||||
unsigned short getIndex() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
bool isSilence() const {
|
||||
return silence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the melody index by one step. If there is a pending partial note it hasn't any
|
||||
* effect.
|
||||
*/
|
||||
void advance() {
|
||||
if (first) {
|
||||
first = false;
|
||||
return;
|
||||
}
|
||||
if (remainingNoteTime != 0) { return; }
|
||||
|
||||
if (melody.getAutomaticSilence()) {
|
||||
if (silence) {
|
||||
index++;
|
||||
silence = false;
|
||||
} else {
|
||||
silence = true;
|
||||
}
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of the melody (i.e. a melody just loaded).
|
||||
*/
|
||||
void reset() {
|
||||
first = true;
|
||||
index = 0;
|
||||
remainingNoteTime = 0;
|
||||
silence = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the time to finish the current note.
|
||||
*/
|
||||
void saveRemainingNoteDuration(unsigned long supportSemiNote) {
|
||||
remainingNoteTime = supportSemiNote - millis();
|
||||
// Ignore partial reproduction if the current value is below the threshold. This is needed
|
||||
// since Ticker may struggle with tight timings.
|
||||
if (remainingNoteTime < 10) { remainingNoteTime = 0; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the partial note duration. It should be called after reproduction of the partial note
|
||||
* and it is propedeutic to advance() method.
|
||||
*/
|
||||
void resetRemainingNoteDuration() {
|
||||
remainingNoteTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining duration of the latest "saved" note.
|
||||
*/
|
||||
unsigned short getRemainingNoteDuration() const {
|
||||
return remainingNoteTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current note. The duration is absolute and expressed in milliseconds.
|
||||
*/
|
||||
NoteDuration getCurrentComputedNote() const {
|
||||
NoteDuration note = melody.getNote(getIndex());
|
||||
note.duration = melody.getTimeUnit() * note.duration;
|
||||
// because the fixed point notation
|
||||
note.duration /= 2;
|
||||
return note;
|
||||
}
|
||||
|
||||
private:
|
||||
bool first;
|
||||
bool silence;
|
||||
unsigned short index;
|
||||
|
||||
/**
|
||||
* Variable to support precise pauses and move/duplicate melodies between Players.
|
||||
* Value are expressed in milliseconds.
|
||||
*/
|
||||
unsigned short remainingNoteTime;
|
||||
};
|
||||
|
||||
enum class State { STOP, PLAY, PAUSE };
|
||||
|
||||
State state;
|
||||
|
||||
std::unique_ptr<MelodyState> melodyState;
|
||||
|
||||
unsigned long supportSemiNote;
|
||||
|
||||
Ticker ticker;
|
||||
|
||||
const static bool debug = false;
|
||||
|
||||
/**
|
||||
* Change the current note with the next one.
|
||||
*/
|
||||
friend void changeTone(MelodyPlayer* melody);
|
||||
|
||||
/**
|
||||
* Halt the advancement of the melody reproduction.
|
||||
* This is the shared method for pause and stop.
|
||||
*/
|
||||
void haltPlay();
|
||||
|
||||
/**
|
||||
* Configure pin to emit PWM.
|
||||
*/
|
||||
void turnOn();
|
||||
|
||||
/**
|
||||
* Disable PWM and put the buzzer is low-power state.
|
||||
* This calls will fails if PWM is not initialized!
|
||||
*/
|
||||
void turnOff();
|
||||
};
|
||||
|
||||
#endif // END MELODY_PLAYER_H
|
||||
89
lib/Melody Player/src/notes.h
Normal file
89
lib/Melody Player/src/notes.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#define NOTE_B0 31
|
||||
#define NOTE_C1 33
|
||||
#define NOTE_CS1 35
|
||||
#define NOTE_D1 37
|
||||
#define NOTE_DS1 39
|
||||
#define NOTE_E1 41
|
||||
#define NOTE_F1 44
|
||||
#define NOTE_FS1 46
|
||||
#define NOTE_G1 49
|
||||
#define NOTE_GS1 52
|
||||
#define NOTE_A1 55
|
||||
#define NOTE_AS1 58
|
||||
#define NOTE_B1 62
|
||||
#define NOTE_C2 65
|
||||
#define NOTE_CS2 69
|
||||
#define NOTE_D2 73
|
||||
#define NOTE_DS2 78
|
||||
#define NOTE_E2 82
|
||||
#define NOTE_F2 87
|
||||
#define NOTE_FS2 93
|
||||
#define NOTE_G2 98
|
||||
#define NOTE_GS2 104
|
||||
#define NOTE_A2 110
|
||||
#define NOTE_AS2 117
|
||||
#define NOTE_B2 123
|
||||
#define NOTE_C3 131
|
||||
#define NOTE_CS3 139
|
||||
#define NOTE_D3 147
|
||||
#define NOTE_DS3 156
|
||||
#define NOTE_E3 165
|
||||
#define NOTE_F3 175
|
||||
#define NOTE_FS3 185
|
||||
#define NOTE_G3 196
|
||||
#define NOTE_GS3 208
|
||||
#define NOTE_A3 220
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 247
|
||||
#define NOTE_C4 262
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_D4 294
|
||||
#define NOTE_DS4 311
|
||||
#define NOTE_E4 330
|
||||
#define NOTE_F4 349
|
||||
#define NOTE_FS4 370
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_GS4 415
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_AS4 466
|
||||
#define NOTE_B4 494
|
||||
#define NOTE_C5 523
|
||||
#define NOTE_CS5 554
|
||||
#define NOTE_D5 587
|
||||
#define NOTE_DS5 622
|
||||
#define NOTE_E5 659
|
||||
#define NOTE_F5 698
|
||||
#define NOTE_FS5 740
|
||||
#define NOTE_G5 784
|
||||
#define NOTE_GS5 831
|
||||
#define NOTE_A5 880
|
||||
#define NOTE_AS5 932
|
||||
#define NOTE_B5 988
|
||||
#define NOTE_C6 1047
|
||||
#define NOTE_CS6 1109
|
||||
#define NOTE_D6 1175
|
||||
#define NOTE_DS6 1245
|
||||
#define NOTE_E6 1319
|
||||
#define NOTE_F6 1397
|
||||
#define NOTE_FS6 1480
|
||||
#define NOTE_G6 1568
|
||||
#define NOTE_GS6 1661
|
||||
#define NOTE_A6 1760
|
||||
#define NOTE_AS6 1865
|
||||
#define NOTE_B6 1976
|
||||
#define NOTE_C7 2093
|
||||
#define NOTE_CS7 2217
|
||||
#define NOTE_D7 2349
|
||||
#define NOTE_DS7 2489
|
||||
#define NOTE_E7 2637
|
||||
#define NOTE_F7 2794
|
||||
#define NOTE_FS7 2960
|
||||
#define NOTE_G7 3136
|
||||
#define NOTE_GS7 3322
|
||||
#define NOTE_A7 3520
|
||||
#define NOTE_AS7 3729
|
||||
#define NOTE_B7 3951
|
||||
#define NOTE_C8 4186
|
||||
#define NOTE_CS8 4435
|
||||
#define NOTE_D8 4699
|
||||
#define NOTE_DS8 4978
|
||||
147
lib/Melody Player/src/notes_array.h
Normal file
147
lib/Melody Player/src/notes_array.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/***************************************************************************
|
||||
* This file is part of Melody Player, a library for Arduino *
|
||||
* to play notes on piezoelectric buzzers. *
|
||||
* *
|
||||
* Copyright (C) 2020-2022 Fabiano Riccardi *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2.1 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
#ifndef PITCHES_UNORDERED_MAP_H
|
||||
#define PITCHES_UNORDERED_MAP_H
|
||||
|
||||
#include <array>
|
||||
|
||||
/**
|
||||
* This class resembles the std::string_view class introduced with c++17. MelodyPlayer uses this
|
||||
* version to ensure retro-compatibility with esp8266-core v2.x.x, which uses GCC v4.8.2.
|
||||
*/
|
||||
struct StringView {
|
||||
StringView() = delete;
|
||||
constexpr StringView(const char* s) : str(s), lenght(__builtin_strlen(s)) {}
|
||||
|
||||
constexpr bool operator==(const StringView& other) const {
|
||||
return lenght == other.lenght && (__builtin_memcmp(str, other.str, lenght) == 0);
|
||||
}
|
||||
|
||||
constexpr size_t length() const {
|
||||
return lenght;
|
||||
}
|
||||
|
||||
constexpr const char* data() const {
|
||||
return str;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* str;
|
||||
size_t lenght;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
constexpr std::array<std::pair<StringView, unsigned short>, 92> noteMapping{{
|
||||
{"SILENCE", 0 },
|
||||
{ "B0", 31 },
|
||||
{ "C1", 33 },
|
||||
{ "CS1", 35 },
|
||||
{ "D1", 37 },
|
||||
{ "DS1", 39 },
|
||||
{ "D1", 37 },
|
||||
{ "DS1", 39 },
|
||||
{ "E1", 41 },
|
||||
{ "F1", 44 },
|
||||
{ "FS1", 46 },
|
||||
{ "G1", 49 },
|
||||
{ "GS1", 52 },
|
||||
{ "A1", 55 },
|
||||
{ "AS1", 58 },
|
||||
{ "B1", 62 },
|
||||
{ "C2", 65 },
|
||||
{ "CS2", 69 },
|
||||
{ "D2", 73 },
|
||||
{ "DS2", 78 },
|
||||
{ "E2", 82 },
|
||||
{ "F2", 87 },
|
||||
{ "FS2", 93 },
|
||||
{ "G2", 98 },
|
||||
{ "GS2", 104 },
|
||||
{ "A2", 110 },
|
||||
{ "AS2", 117 },
|
||||
{ "B2", 123 },
|
||||
{ "C3", 131 },
|
||||
{ "CS3", 139 },
|
||||
{ "D3", 147 },
|
||||
{ "DS3", 156 },
|
||||
{ "E3", 165 },
|
||||
{ "F3", 175 },
|
||||
{ "FS3", 185 },
|
||||
{ "G3", 196 },
|
||||
{ "GS3", 208 },
|
||||
{ "A3", 220 },
|
||||
{ "AS3", 233 },
|
||||
{ "B3", 247 },
|
||||
{ "C4", 262 },
|
||||
{ "CS4", 277 },
|
||||
{ "D4", 294 },
|
||||
{ "DS4", 311 },
|
||||
{ "E4", 330 },
|
||||
{ "F4", 349 },
|
||||
{ "FS4", 370 },
|
||||
{ "G4", 392 },
|
||||
{ "GS4", 415 },
|
||||
{ "A4", 440 },
|
||||
{ "AS4", 466 },
|
||||
{ "B4", 494 },
|
||||
{ "C5", 523 },
|
||||
{ "CS5", 554 },
|
||||
{ "D5", 587 },
|
||||
{ "DS5", 622 },
|
||||
{ "E5", 659 },
|
||||
{ "F5", 698 },
|
||||
{ "FS5", 740 },
|
||||
{ "G5", 784 },
|
||||
{ "GS5", 831 },
|
||||
{ "A5", 880 },
|
||||
{ "AS5", 932 },
|
||||
{ "B5", 988 },
|
||||
{ "C6", 1047},
|
||||
{ "CS6", 1109},
|
||||
{ "D6", 1175},
|
||||
{ "DS6", 1245},
|
||||
{ "E6", 1319},
|
||||
{ "F6", 1397},
|
||||
{ "FS6", 1480},
|
||||
{ "G6", 1568},
|
||||
{ "GS6", 1661},
|
||||
{ "A6", 1760},
|
||||
{ "AS6", 1865},
|
||||
{ "B6", 1976},
|
||||
{ "C7", 2093},
|
||||
{ "CS7", 2217},
|
||||
{ "D7", 2349},
|
||||
{ "DS7", 2489},
|
||||
{ "E7", 2637},
|
||||
{ "F7", 2794},
|
||||
{ "FS7", 2960},
|
||||
{ "G7", 3136},
|
||||
{ "GS7", 3322},
|
||||
{ "A7", 3520},
|
||||
{ "AS7", 3729},
|
||||
{ "B7", 3951},
|
||||
{ "C8", 4186},
|
||||
{ "CS8", 4435},
|
||||
{ "D8", 4699},
|
||||
{ "DS8", 4978}
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
#endif // END PITCHES_UNORDERED_MAP_H
|
||||
Reference in New Issue
Block a user