123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- #include "MMLPlayer.h"
- #include <ctype.h>
- #include <math.h>
- #include <AP_HAL/AP_HAL.h>
- #include <AP_Math/AP_Math.h>
- #include <AP_Notify/AP_Notify.h>
- #if HAL_WITH_UAVCAN
- #include <AP_UAVCAN/AP_UAVCAN.h>
- #include <AP_BoardConfig/AP_BoardConfig_CAN.h>
- #endif
- extern const AP_HAL::HAL& hal;
- void MMLPlayer::update()
- {
- // Check if note is over
- if (_playing && AP_HAL::micros()-_note_start_us > _note_duration_us) {
- next_action();
- }
- }
- void MMLPlayer::play(const char* string)
- {
- 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;
- next_action();
- }
- void MMLPlayer::stop()
- {
- _playing = false;
- hal.util->toneAlarm_set_buzzer_tone(0,0,0);
- }
- void MMLPlayer::start_silence(float duration)
- {
- _note_start_us = AP_HAL::micros();
- _note_duration_us = duration*1e6;
- hal.util->toneAlarm_set_buzzer_tone(0, 0, 0);
- }
- void MMLPlayer::start_note(float duration, float frequency, float volume)
- {
- _note_start_us = AP_HAL::micros();
- _note_duration_us = duration*1e6;
- hal.util->toneAlarm_set_buzzer_tone(frequency, volume, _note_duration_us/1000U);
- #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
- }
- char MMLPlayer::next_char()
- {
- while (_string[_next] != '\0' && isspace(_string[_next])) {
- _next++;
- }
- return toupper(_string[_next]);
- }
- uint8_t MMLPlayer::next_number()
- {
- uint8_t ret = 0;
- while (isdigit(next_char())) {
- ret = (ret*10) + (next_char() - '0');
- _next++;
- }
- return ret;
- }
- size_t MMLPlayer::next_dots()
- {
- size_t ret = 0;
- while (next_char() == '.') {
- ret++;
- _next++;
- }
- return ret;
- }
- float MMLPlayer::rest_duration(uint32_t rest_length, uint8_t dots)
- {
- 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;
- }
- void MMLPlayer::next_action()
- {
- 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) {
- play(_string);
- } else {
- stop();
- }
- return;
- }
- _next++;
- switch (c) {
- case 'V': {
- _volume = next_number();
- break;
- }
- case 'L': {
- _default_note_length = next_number();
- if (_default_note_length == 0) {
- stop();
- return;
- }
- break;
- }
- case 'O':
- _octave = next_number();
- if (_octave > 6) {
- _octave = 6;
- }
- 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;
- break;
- case 'L':
- _note_mode = MODE_LEGATO;
- break;
- case 'S':
- _note_mode = MODE_STACCATO;
- break;
- case 'F':
- _repeat = false;
- break;
- 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) {
- uint8_t num = next_number();
- uint8_t dots = next_dots();
- start_silence(rest_duration(num, dots));
- return;
- }
- break;
- case 'A':
- case 'B':
- case 'C':
- case 'D':
- case 'E':
- case 'F':
- case 'G': {
- static const uint8_t note_tab[] = {9,11,0,2,4,5,7};
- note = note_tab[c-'A'] + (_octave*12) + 1;
- c = next_char();
- switch (c) {
- case '#':
- case '+':
- if (note < 84) {
- note++;
- }
- _next++;
- break;
- case '-':
- if (note > 1) {
- note--;
- }
- _next++;
- break;
- default:
- break;
- }
- note_length = next_number();
- if (note_length == 0) {
- note_length = _default_note_length;
- }
- break;
- }
- default:
- stop();
- return;
- }
- }
- // 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) {
- 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;
- }
- 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;
- note_volume *= AP::notify().get_buzz_volume() * 0.01;
- note_volume = constrain_float(note_volume, 0, 1);
- note_frequency = constrain_float(note_frequency, 10, 22000);
- start_note(note_period, note_frequency, note_volume);
- }
|