LCOV - code coverage report
Current view: top level - AP_Notify - MMLPlayer.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 205 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 11 0.0 %

          Line data    Source code
       1             : #include "MMLPlayer.h"
       2             : 
       3             : #include <ctype.h>
       4             : #include <math.h>
       5             : #include <AP_HAL/AP_HAL.h>
       6             : #include <AP_Math/AP_Math.h>
       7             : #include <AP_Notify/AP_Notify.h>
       8             : 
       9             : #if HAL_CANMANAGER_ENABLED
      10             : #include <AP_DroneCAN/AP_DroneCAN.h>
      11             : #include <AP_CANManager/AP_CANManager.h>
      12             : #endif
      13             : 
      14             : extern const AP_HAL::HAL& hal;
      15             : 
      16           0 : void MMLPlayer::update()
      17             : {
      18             :     // Check if note is over
      19           0 :     if (_playing && AP_HAL::micros()-_note_start_us > _note_duration_us) {
      20           0 :         next_action();
      21             :     }
      22           0 : }
      23             : 
      24           0 : void MMLPlayer::prepare_to_play_string(const char* string)
      25             : {
      26           0 :     stop();
      27             : 
      28           0 :     _string = string;
      29           0 :     _next = 0;
      30           0 :     _tempo = 120;
      31           0 :     _default_note_length = 4;
      32           0 :     _note_mode = MODE_NORMAL;
      33           0 :     _octave = 4;
      34           0 :     _volume = 255;
      35           0 :     _silence_duration = 0;
      36           0 :     _repeat = false;
      37             : 
      38           0 :     _playing = true;
      39           0 :     _note_duration_us = 0;
      40           0 : }
      41             : 
      42           0 : void MMLPlayer::play(const char* string)
      43             : {
      44           0 :     prepare_to_play_string(string);
      45           0 :     next_action();
      46           0 : }
      47             : 
      48           0 : void MMLPlayer::stop()
      49             : {
      50           0 :     _playing = false;
      51           0 :     hal.util->toneAlarm_set_buzzer_tone(0,0,0);
      52           0 : }
      53             : 
      54           0 : void MMLPlayer::start_silence(float duration)
      55             : {
      56           0 :     _note_start_us = AP_HAL::micros();
      57           0 :     _note_duration_us = duration*1e6;
      58           0 :     hal.util->toneAlarm_set_buzzer_tone(0, 0, 0);
      59           0 : }
      60             : 
      61           0 : void MMLPlayer::start_note(float duration, float frequency, float volume)
      62             : {
      63           0 :     _note_start_us = AP_HAL::micros();
      64           0 :     _note_duration_us = duration*1e6;
      65           0 :     hal.util->toneAlarm_set_buzzer_tone(frequency, volume, _note_duration_us/1000U);
      66             : 
      67             : #if HAL_ENABLE_DRONECAN_DRIVERS
      68             :     // support CAN buzzers too
      69           0 :     uint8_t can_num_drivers = AP::can().get_num_drivers();
      70             :     uavcan_equipment_indication_BeepCommand msg;
      71             : 
      72           0 :     for (uint8_t i = 0; i < can_num_drivers; i++) {
      73           0 :         AP_DroneCAN *uavcan = AP_DroneCAN::get_dronecan(i);
      74           0 :         if (uavcan != nullptr &&
      75           0 :             (AP::notify().get_buzzer_types() & uint8_t(AP_Notify::BuzzerType::UAVCAN))) {
      76           0 :             msg.frequency = frequency;
      77           0 :             msg.duration = _note_duration_us*1.0e-6;
      78           0 :             uavcan->buzzer.broadcast(msg);
      79             :         }
      80             :     }
      81             : #endif
      82           0 : }
      83             : 
      84           0 : char MMLPlayer::next_char()
      85             : {
      86           0 :     while (_string[_next] != '\0' && isspace(_string[_next])) {
      87           0 :         _next++;
      88             :     }
      89             : 
      90           0 :     return toupper(_string[_next]);
      91             : }
      92             : 
      93           0 : uint8_t MMLPlayer::next_number()
      94             : {
      95           0 :     uint8_t ret = 0;
      96           0 :     while (isdigit(next_char())) {
      97           0 :         ret = (ret*10) + (next_char() - '0');
      98           0 :         _next++;
      99             :     }
     100           0 :     return ret;
     101             : }
     102             : 
     103           0 : size_t MMLPlayer::next_dots()
     104             : {
     105           0 :     size_t ret = 0;
     106           0 :     while (next_char() == '.') {
     107           0 :         ret++;
     108           0 :         _next++;
     109             :     }
     110           0 :     return ret;
     111             : }
     112             : 
     113           0 : float MMLPlayer::rest_duration(uint32_t rest_length, uint8_t dots) const
     114             : {
     115           0 :     float whole_note_period = 240.0f / _tempo;
     116           0 :     if (rest_length == 0) {
     117           0 :         rest_length = 1;
     118             :     }
     119             : 
     120           0 :     float rest_period = whole_note_period/rest_length;
     121           0 :     float dot_extension = rest_period * 0.5f;
     122             : 
     123           0 :     while (dots--) {
     124           0 :         rest_period += dot_extension;
     125           0 :         dot_extension *= 0.5f;
     126             :     }
     127             : 
     128           0 :     return rest_period;
     129             : }
     130             : 
     131           0 : void MMLPlayer::next_action()
     132             : {
     133           0 :     if (_silence_duration > 0) {
     134           0 :         start_silence(_silence_duration);
     135           0 :         _silence_duration = 0;
     136           0 :         return;
     137             :     }
     138             : 
     139           0 :     uint8_t note = 0;
     140             :     uint8_t note_length;
     141             : 
     142           0 :     while (note == 0) {
     143           0 :         char c = next_char();
     144           0 :         if (c == '\0') {
     145           0 :             if (_repeat) {
     146             :                 // don't "play" here, as we may have been called from
     147             :                 // there, and it turns out infinite recursion on
     148             :                 // invalid strings is suboptimal.  The next call to
     149             :                 // update() will push things out as appropriate.
     150           0 :                 prepare_to_play_string(_string);
     151             :             } else {
     152           0 :                 stop();
     153             :             }
     154           0 :             return;
     155             :         }
     156             : 
     157           0 :         _next++;
     158             : 
     159           0 :         switch (c) {
     160           0 :         case 'V': {
     161           0 :             _volume = next_number();
     162           0 :             break;
     163             :         }
     164           0 :         case 'L': {
     165           0 :             _default_note_length = next_number();
     166           0 :             if (_default_note_length == 0) {
     167           0 :                 stop();
     168           0 :                 return;
     169             :             }
     170           0 :             break;
     171             :         }
     172           0 :         case 'O':
     173           0 :             _octave = next_number();
     174           0 :             if (_octave > 6) {
     175           0 :                 _octave = 6;
     176             :             }
     177           0 :             break;
     178           0 :         case '<':
     179           0 :             if (_octave > 0) {
     180           0 :                 _octave--;
     181             :             }
     182           0 :             break;
     183           0 :         case '>':
     184           0 :             if (_octave < 6) {
     185           0 :                 _octave++;
     186             :             }
     187           0 :             break;
     188           0 :         case 'M':
     189           0 :             c = next_char();
     190           0 :             if (c == '\0') {
     191           0 :                 stop();
     192           0 :                 return;
     193             :             }
     194           0 :             _next++;
     195           0 :             switch (c) {
     196           0 :             case 'N':
     197           0 :                 _note_mode = MODE_NORMAL;
     198           0 :                 break;
     199           0 :             case 'L':
     200           0 :                 _note_mode = MODE_LEGATO;
     201           0 :                 break;
     202           0 :             case 'S':
     203           0 :                 _note_mode = MODE_STACCATO;
     204           0 :                 break;
     205           0 :             case 'F':
     206           0 :                 _repeat = false;
     207           0 :                 break;
     208           0 :             case 'B':
     209           0 :                 _repeat = true;
     210           0 :                 break;
     211           0 :             default:
     212           0 :                 stop();
     213           0 :                 return;
     214             :             }
     215           0 :             break;
     216           0 :         case 'R':
     217             :         case 'P': {
     218           0 :             uint8_t num = next_number();
     219           0 :             uint8_t dots = next_dots();
     220           0 :             start_silence(rest_duration(num, dots));
     221           0 :             return;
     222             :         }
     223           0 :         case 'T':
     224           0 :             _tempo = next_number();
     225           0 :             if (_tempo < 32) {
     226           0 :                 stop();
     227           0 :                 return;
     228             :             }
     229           0 :             break;
     230           0 :         case 'N':
     231           0 :             note = next_number();
     232           0 :             note_length = _default_note_length;
     233           0 :             if (note > 84) {
     234           0 :                 stop();
     235           0 :                 return;
     236             :             }
     237           0 :             if (note == 0) {
     238           0 :                 uint8_t num = next_number();
     239           0 :                 uint8_t dots = next_dots();
     240           0 :                 start_silence(rest_duration(num, dots));
     241           0 :                 return;
     242             :             }
     243           0 :             break;
     244           0 :         case 'A':
     245             :         case 'B':
     246             :         case 'C':
     247             :         case 'D':
     248             :         case 'E':
     249             :         case 'F':
     250             :         case 'G': {
     251             :             static const uint8_t note_tab[] = {9,11,0,2,4,5,7};
     252           0 :             note = note_tab[c-'A'] + (_octave*12) + 1;
     253             : 
     254           0 :             c = next_char();
     255             : 
     256           0 :             switch (c) {
     257           0 :             case '#':
     258             :             case '+':
     259           0 :                 if (note < 84) {
     260           0 :                     note++;
     261             :                 }
     262           0 :                 _next++;
     263           0 :                 break;
     264           0 :             case '-':
     265           0 :                 if (note > 1) {
     266           0 :                     note--;
     267             :                 }
     268           0 :                 _next++;
     269           0 :                 break;
     270           0 :             default:
     271           0 :                 break;
     272             :             }
     273           0 :             note_length = next_number();
     274           0 :             if (note_length == 0) {
     275           0 :                 note_length = _default_note_length;
     276             :             }
     277           0 :             break;
     278             :         }
     279           0 :         default:
     280           0 :             stop();
     281           0 :             return;
     282             :         }
     283             :     }
     284             : 
     285             :     // Avoid division by zero
     286           0 :     if (_tempo == 0 || note_length == 0) {
     287           0 :         stop();
     288           0 :         return;
     289             :     }
     290             : 
     291           0 :     float note_period = 240.0f / (float)_tempo / (float)note_length;
     292             : 
     293           0 :     switch (_note_mode) {
     294           0 :     case MODE_NORMAL:
     295           0 :         _silence_duration = note_period/8;
     296           0 :         break;
     297           0 :     case MODE_STACCATO:
     298           0 :         _silence_duration = note_period/4;
     299           0 :         break;
     300           0 :     case MODE_LEGATO:
     301           0 :         _silence_duration = 0;
     302           0 :         break;
     303             :     }
     304           0 :     note_period -= _silence_duration;
     305             : 
     306           0 :     float dot_extension = note_period * 0.5f;
     307           0 :     uint8_t dots = next_dots();
     308           0 :     while (dots--) {
     309           0 :         note_period += dot_extension;
     310           0 :         dot_extension *= 0.5f;
     311             :     }
     312             : 
     313           0 :     float note_frequency = 880.0f * expf(logf(2.0f) * ((int)note - 46) / 12.0f);
     314           0 :     float note_volume = _volume/255.0f;
     315           0 :     note_volume *= AP::notify().get_buzz_volume() * 0.01;
     316           0 :     note_volume = constrain_float(note_volume, 0, 1);
     317             : 
     318           0 :     note_frequency = constrain_float(note_frequency, 10, 22000);
     319             : 
     320           0 :     start_note(note_period, note_frequency, note_volume);
     321             : }

Generated by: LCOV version 1.14