249 lines
7.3 KiB
C++
249 lines
7.3 KiB
C++
/***************************************************************************
|
|
* 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
|