/*************************************************************************** * 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 * ***************************************************************************/ #ifndef MELODY_PLAYER_H #define MELODY_PLAYER_H #include "melody.h" #include #include 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; 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