231 lines
7.0 KiB
C++
231 lines
7.0 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/> *
|
|
***************************************************************************/
|
|
#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);
|
|
}
|