2018-07-24 22:30:05 -03:00
|
|
|
#include "MMLPlayer.h"
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
2018-07-24 23:11:41 -03:00
|
|
|
#include <AP_Math/AP_Math.h>
|
2019-09-02 22:25:18 -03:00
|
|
|
#include <AP_Notify/AP_Notify.h>
|
2018-07-24 22:30:05 -03:00
|
|
|
|
2019-08-30 23:45:24 -03:00
|
|
|
#if HAL_WITH_UAVCAN
|
|
|
|
#include <AP_UAVCAN/AP_UAVCAN.h>
|
|
|
|
#include <AP_BoardConfig/AP_BoardConfig_CAN.h>
|
|
|
|
#endif
|
|
|
|
|
2018-07-24 22:30:05 -03:00
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
void MMLPlayer::update()
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
// Check if note is over
|
|
|
|
if (_playing && AP_HAL::micros()-_note_start_us > _note_duration_us) {
|
|
|
|
next_action();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-24 01:12:34 -03:00
|
|
|
void MMLPlayer::prepare_to_play_string(const char* string)
|
2018-07-24 23:00:24 -03:00
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
stop();
|
|
|
|
|
|
|
|
_string = string;
|
|
|
|
_next = 0;
|
|
|
|
_tempo = 120;
|
|
|
|
_default_note_length = 4;
|
|
|
|
_note_mode = MODE_NORMAL;
|
|
|
|
_octave = 4;
|
|
|
|
_volume = 255;
|
|
|
|
_silence_duration = 0;
|
|
|
|
_repeat = false;
|
|
|
|
|
|
|
|
_playing = true;
|
2020-03-24 01:12:34 -03:00
|
|
|
_note_duration_us = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MMLPlayer::play(const char* string)
|
|
|
|
{
|
|
|
|
prepare_to_play_string(string);
|
2018-07-24 22:30:05 -03:00
|
|
|
next_action();
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
void MMLPlayer::stop()
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
_playing = false;
|
2018-07-27 04:13:07 -03:00
|
|
|
hal.util->toneAlarm_set_buzzer_tone(0,0,0);
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
void MMLPlayer::start_silence(float duration)
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
_note_start_us = AP_HAL::micros();
|
|
|
|
_note_duration_us = duration*1e6;
|
2018-07-27 04:13:07 -03:00
|
|
|
hal.util->toneAlarm_set_buzzer_tone(0, 0, 0);
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
void MMLPlayer::start_note(float duration, float frequency, float volume)
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
_note_start_us = AP_HAL::micros();
|
|
|
|
_note_duration_us = duration*1e6;
|
2018-07-27 04:13:07 -03:00
|
|
|
hal.util->toneAlarm_set_buzzer_tone(frequency, volume, _note_duration_us/1000U);
|
2019-08-30 23:45:24 -03:00
|
|
|
|
|
|
|
#if HAL_WITH_UAVCAN
|
|
|
|
// support CAN buzzers too
|
|
|
|
uint8_t can_num_drivers = AP::can().get_num_drivers();
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < can_num_drivers; i++) {
|
|
|
|
AP_UAVCAN *uavcan = AP_UAVCAN::get_uavcan(i);
|
|
|
|
if (uavcan != nullptr) {
|
|
|
|
uavcan->set_buzzer_tone(frequency, _note_duration_us*1.0e-6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
char MMLPlayer::next_char()
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
while (_string[_next] != '\0' && isspace(_string[_next])) {
|
|
|
|
_next++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return toupper(_string[_next]);
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
uint8_t MMLPlayer::next_number()
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
uint8_t ret = 0;
|
|
|
|
while (isdigit(next_char())) {
|
|
|
|
ret = (ret*10) + (next_char() - '0');
|
|
|
|
_next++;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
size_t MMLPlayer::next_dots()
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
size_t ret = 0;
|
|
|
|
while (next_char() == '.') {
|
|
|
|
ret++;
|
|
|
|
_next++;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
float MMLPlayer::rest_duration(uint32_t rest_length, uint8_t dots)
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
float whole_note_period = 240.0f / _tempo;
|
|
|
|
if (rest_length == 0) {
|
|
|
|
rest_length = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
float rest_period = whole_note_period/rest_length;
|
|
|
|
float dot_extension = rest_period/2;
|
|
|
|
|
|
|
|
while (dots--) {
|
|
|
|
rest_period += dot_extension;
|
|
|
|
dot_extension *= 0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rest_period;
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
void MMLPlayer::next_action()
|
|
|
|
{
|
2018-07-24 22:30:05 -03:00
|
|
|
if (_silence_duration > 0) {
|
|
|
|
start_silence(_silence_duration);
|
|
|
|
_silence_duration = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t note = 0;
|
|
|
|
uint8_t note_length;
|
|
|
|
|
|
|
|
while (note == 0) {
|
|
|
|
char c = next_char();
|
|
|
|
if (c == '\0') {
|
|
|
|
if (_repeat) {
|
2020-03-24 01:12:34 -03:00
|
|
|
// don't "play" here, as we may have been called from
|
|
|
|
// there, and it turns out infinite recursion on
|
|
|
|
// invalid strings is suboptimal. The next call to
|
|
|
|
// update() will push things out as appropriate.
|
|
|
|
prepare_to_play_string(_string);
|
2018-07-24 22:30:05 -03:00
|
|
|
} else {
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_next++;
|
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
switch (c) {
|
|
|
|
case 'V': {
|
|
|
|
_volume = next_number();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'L': {
|
|
|
|
_default_note_length = next_number();
|
|
|
|
if (_default_note_length == 0) {
|
|
|
|
stop();
|
|
|
|
return;
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
2018-07-24 23:00:24 -03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'O':
|
|
|
|
_octave = next_number();
|
|
|
|
if (_octave > 6) {
|
|
|
|
_octave = 6;
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
2018-07-24 23:00:24 -03:00
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
if (_octave > 0) {
|
|
|
|
_octave--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
if (_octave < 6) {
|
|
|
|
_octave++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'M':
|
|
|
|
c = next_char();
|
|
|
|
if (c == '\0') {
|
|
|
|
stop();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_next++;
|
|
|
|
switch (c) {
|
|
|
|
case 'N':
|
|
|
|
_note_mode = MODE_NORMAL;
|
2018-07-24 22:30:05 -03:00
|
|
|
break;
|
2018-07-24 23:00:24 -03:00
|
|
|
case 'L':
|
|
|
|
_note_mode = MODE_LEGATO;
|
2018-07-24 22:30:05 -03:00
|
|
|
break;
|
2018-07-24 23:00:24 -03:00
|
|
|
case 'S':
|
|
|
|
_note_mode = MODE_STACCATO;
|
2018-07-24 22:30:05 -03:00
|
|
|
break;
|
2018-07-24 23:00:24 -03:00
|
|
|
case 'F':
|
|
|
|
_repeat = false;
|
2018-07-24 22:30:05 -03:00
|
|
|
break;
|
2018-07-24 23:00:24 -03:00
|
|
|
case 'B':
|
|
|
|
_repeat = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
stop();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
|
|
case 'P': {
|
|
|
|
uint8_t num = next_number();
|
|
|
|
uint8_t dots = next_dots();
|
|
|
|
start_silence(rest_duration(num, dots));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case 'T':
|
|
|
|
_tempo = next_number();
|
|
|
|
if (_tempo < 32) {
|
|
|
|
stop();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'N':
|
|
|
|
note = next_number();
|
|
|
|
note_length = _default_note_length;
|
|
|
|
if (note > 84) {
|
|
|
|
stop();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (note == 0) {
|
2018-07-24 22:30:05 -03:00
|
|
|
uint8_t num = next_number();
|
|
|
|
uint8_t dots = next_dots();
|
|
|
|
start_silence(rest_duration(num, dots));
|
|
|
|
return;
|
|
|
|
}
|
2018-07-24 23:00:24 -03:00
|
|
|
break;
|
|
|
|
case 'A':
|
|
|
|
case 'B':
|
|
|
|
case 'C':
|
|
|
|
case 'D':
|
|
|
|
case 'E':
|
|
|
|
case 'F':
|
|
|
|
case 'G': {
|
2018-07-24 23:01:59 -03:00
|
|
|
static const uint8_t note_tab[] = {9,11,0,2,4,5,7};
|
2018-07-24 23:00:24 -03:00
|
|
|
note = note_tab[c-'A'] + (_octave*12) + 1;
|
2018-07-24 22:30:05 -03:00
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
c = next_char();
|
2018-07-24 22:30:05 -03:00
|
|
|
|
2018-07-24 23:00:24 -03:00
|
|
|
switch (c) {
|
|
|
|
case '#':
|
|
|
|
case '+':
|
|
|
|
if (note < 84) {
|
|
|
|
note++;
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
2018-07-24 23:00:24 -03:00
|
|
|
_next++;
|
|
|
|
break;
|
|
|
|
case '-':
|
|
|
|
if (note > 1) {
|
|
|
|
note--;
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
2018-07-24 23:00:24 -03:00
|
|
|
_next++;
|
2018-07-24 22:30:05 -03:00
|
|
|
break;
|
|
|
|
default:
|
2018-07-24 23:00:24 -03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
note_length = next_number();
|
|
|
|
if (note_length == 0) {
|
|
|
|
note_length = _default_note_length;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
stop();
|
|
|
|
return;
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid division by zero
|
|
|
|
if (_tempo == 0 || note_length == 0) {
|
|
|
|
stop();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
float note_period = 240.0f / (float)_tempo / (float)note_length;
|
|
|
|
|
|
|
|
switch (_note_mode) {
|
2018-07-24 23:00:24 -03:00
|
|
|
case MODE_NORMAL:
|
|
|
|
_silence_duration = note_period/8;
|
|
|
|
break;
|
|
|
|
case MODE_STACCATO:
|
|
|
|
_silence_duration = note_period/4;
|
|
|
|
break;
|
|
|
|
case MODE_LEGATO:
|
|
|
|
_silence_duration = 0;
|
|
|
|
break;
|
2018-07-24 22:30:05 -03:00
|
|
|
}
|
|
|
|
note_period -= _silence_duration;
|
|
|
|
|
|
|
|
float dot_extension = note_period/2;
|
|
|
|
uint8_t dots = next_dots();
|
|
|
|
while (dots--) {
|
|
|
|
note_period += dot_extension;
|
|
|
|
dot_extension *= 0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
float note_frequency = 880.0f * expf(logf(2.0f) * ((int)note - 46) / 12.0f);
|
|
|
|
float note_volume = _volume/255.0f;
|
2019-09-02 22:25:18 -03:00
|
|
|
note_volume *= AP::notify().get_buzz_volume() * 0.01;
|
|
|
|
note_volume = constrain_float(note_volume, 0, 1);
|
2018-07-24 22:30:05 -03:00
|
|
|
|
2018-07-24 23:11:41 -03:00
|
|
|
note_frequency = constrain_float(note_frequency, 10, 22000);
|
|
|
|
|
2018-07-24 22:30:05 -03:00
|
|
|
start_note(note_period, note_frequency, note_volume);
|
|
|
|
}
|