9static const char *
const TAG =
"rtttl";
12static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
13 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047,
14 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217,
15 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951};
17#if defined(USE_OUTPUT) || defined(USE_SPEAKER)
18static const uint32_t DOUBLE_NOTE_GAP_MS = 10;
22static const size_t SAMPLE_BUFFER_SIZE = 2048;
30 static const double PI_ON_180 = 4.0 * atan(1.0) / 180.0;
31 return degrees * PI_ON_180;
35#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
37PROGMEM_STRING_TABLE(RtttlStateStrings,
"State::STOPPED",
"State::INIT",
"State::STARTING",
"State::RUNNING",
38 "State::STOPPING",
"UNKNOWN");
40static const LogString *state_to_string(
State state) {
41 return RtttlStateStrings::get_log_str(
static_cast<uint8_t
>(
state), RtttlStateStrings::LAST_INDEX);
45static uint8_t note_index_from_char(
char note) {
115 SpeakerSample sample[SAMPLE_BUFFER_SIZE + 2];
127 sample[
x].left =
val;
128 sample[
x].right =
val;
142 size_t bytes_to_send =
x *
sizeof(SpeakerSample);
143 size_t send = this->
speaker_->
play((uint8_t *) (&sample), bytes_to_send);
144 if (send != bytes_to_send) {
167 this->note_duration_ = this->
wholenote_ / num;
169 this->note_duration_ =
178 if (this->
rtttl_[this->position_] ==
'#') {
189 if (scale < 4 || scale > 7) {
190 ESP_LOGE(TAG,
"Octave must be between 4 and 7 (it is %d)", scale);
196 if (this->
rtttl_[this->position_] ==
'.') {
197 this->note_duration_ += this->note_duration_ / 2;
202 bool need_note_gap =
false;
204 auto note_index = (scale - 4) * 12 + note;
205 if (note_index < 0 || note_index >= (
int) (
sizeof(NOTES) /
sizeof(NOTES[0]))) {
206 ESP_LOGE(TAG,
"Note out of range (note: %d, scale: %d, index: %d, max: %d)", note, scale, note_index,
207 (
int) (
sizeof(NOTES) /
sizeof(NOTES[0])));
211 auto freq = NOTES[note_index];
217 ESP_LOGVV(TAG,
"playing note: %d for %dms", note, this->note_duration_);
219 ESP_LOGVV(TAG,
"waiting: %dms", this->note_duration_);
224 if (this->
output_ !=
nullptr) {
225 if (need_note_gap && this->note_duration_ > DOUBLE_NOTE_GAP_MS) {
227 delay(DOUBLE_NOTE_GAP_MS);
228 this->note_duration_ -= DOUBLE_NOTE_GAP_MS;
258 ESP_LOGVV(TAG,
"- Calc play time: wish: %d gets: %d (div: %d spw: %d)", samples_wish, this->
samples_count_,
271 size_t len = (pos != std::string::npos) ?
pos : this->
rtttl_.length();
272 ESP_LOGW(TAG,
"Already playing: %.*s", (
int)
len, this->
rtttl_.c_str());
276 this->
rtttl_ = std::move(rtttl);
290 ESP_LOGE(TAG,
"Unable to determine name; missing ':'");
294 ESP_LOGD(TAG,
"Playing song %.*s", (
int) this->
position_, this->
rtttl_.c_str());
297 this->position_ = this->
rtttl_.find(
"d=", this->position_);
298 if (this->position_ == std::string::npos) {
299 ESP_LOGE(TAG,
"Missing 'd='");
302 this->position_ += 2;
309 this->position_ = this->
rtttl_.find(
"o=", this->position_);
310 if (this->position_ == std::string::npos) {
311 ESP_LOGE(TAG,
"Missing 'o=");
314 this->position_ += 2;
316 if (num >= 3 && num <= 7) {
321 this->position_ = this->
rtttl_.find(
"b=", this->position_);
322 if (this->position_ == std::string::npos) {
323 ESP_LOGE(TAG,
"Missing b=");
326 this->position_ += 2;
332 this->position_ = this->
rtttl_.find(
':', this->position_);
333 if (this->position_ == std::string::npos) {
334 ESP_LOGE(TAG,
"Missing second ':'");
347 if (this->
output_ !=
nullptr) {
363 if (this->
output_ !=
nullptr) {
383 ESP_LOGV(TAG,
"Rtttl::finish_()");
386 if (this->
output_ !=
nullptr) {
394 SpeakerSample sample[2];
399 this->
speaker_->
play((uint8_t *) (&sample),
sizeof(sample));
413 ESP_LOGV(TAG,
"State changed from %s to %s", LOG_STR_ARG(state_to_string(old_state)),
414 LOG_STR_ARG(state_to_string(
state)));
420 ESP_LOGD(TAG,
"Playback finished");
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
void set_level(float state)
Set the level of this float output, this is called from the front-end.
virtual void update_frequency(float frequency)
Set the frequency of the output for PWM outputs.
int samples_per_wave_
The number of samples for one full cycle of a note's waveform, in Q10 fixed-point format.
uint16_t wholenote_
The duration of a whole note in milliseconds.
uint32_t last_note_
The time the last note was started.
int samples_sent_
The number of samples sent.
uint16_t note_duration_
The duration of the current note in milliseconds.
int samples_gap_
The number of samples for the gap between notes.
int sample_rate_
The sample rate of the speaker.
output::FloatOutput * output_
The output to write the sound to.
void dump_config() override
void set_state_(State state)
void finish_()
Finalizes the playback of the RTTTL string.
float gain_
The gain of the output.
uint32_t output_freq_
The frequency of the current note in Hz.
uint16_t default_duration_
The default duration of a note (e.g. 4 for a quarter note).
size_t position_
The current position in the RTTTL string.
uint16_t default_octave_
The default octave for a note.
State state_
The current state of the RTTTL player.
int samples_count_
The total number of samples to send.
speaker::Speaker * speaker_
The speaker to write the sound to.
CallbackManager< void()> on_finished_playback_callback_
The callback to call when playback is finished.
void play(std::string rtttl)
std::string rtttl_
The RTTTL string to play.
virtual size_t play(const uint8_t *data, size_t length)=0
Plays the provided audio data.
double deg2rad(double degrees)
PROGMEM_STRING_TABLE(RtttlStateStrings, "State::STOPPED", "State::INIT", "State::STARTING", "State::RUNNING", "State::STOPPING", "UNKNOWN")
void IRAM_ATTR HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()