Files
awtrix-light/lib/Melody Player/src/melody_player.h
Stephan Mühl adb5102869 release
2023-03-22 12:15:18 +01:00

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