├── MmlMusicPWM.cpp ├── MmlMusicPWM.h ├── README.md ├── examples ├── MmlMusicPWMBlink │ ├── MmlMusicPWMBlink.ino │ └── readme.md ├── MmlMusicPWMPlayOnce │ ├── MmlMusicPWMPlayOnce.ino │ └── readme.md ├── MmlMusicPWMSerial │ ├── MmlMusicPWMSerial.ino │ └── readme.md ├── MmlMusicPWMSoundFX │ ├── MmlMusicPWMSoundFX.ino │ └── readme.md └── readme.md ├── keywords.txt └── library.properties /MmlMusicPWM.cpp: -------------------------------------------------------------------------------- 1 | // MmlMusicPWM class, inherits from MmlMusic class\ 2 | // 3 | // MmlMusic provides a means to play Music Macro Language sequences. 4 | // MmlMusicPWM is the PWM driver for the MmlMusic library for Arduino. 5 | // It implements playing MML music asynchronously on a chosen output pin. 6 | // It generates sound using PWM and timer interrupts. 7 | // On ESP8266/ESP32, the Ticker library is used for tone scheduling. 8 | // On the ATmega 328/168 it reuses the Timer2 interrupt to combine generating 9 | // PWM sounds with scheduling starting and stopping playing the sound. 10 | // ATtiny85 is also supported but it has limited resources. 11 | // On that MCU Timer1 is used. 12 | // 13 | // For more information see: 14 | // http://www.github.com/maxint-rd/MmlMusic 15 | // http://www.github.com/maxint-rd/MmlMusicPWM 16 | // 17 | 18 | #include "MmlMusicPWM.h" 19 | 20 | //#include 21 | #if defined (ARDUINO_ARCH_ESP8266) 22 | #include 23 | #endif 24 | 25 | MmlMusicPWM::MmlMusicPWM(int pin) // pin=255 for default constructor without setting _pinPwm 26 | : MmlMusic() 27 | , _pinPwm(pin) 28 | { 29 | if(_pinPwm!=255) 30 | { 31 | pinMode(_pinPwm, OUTPUT); 32 | this->noTone(); 33 | } 34 | } 35 | 36 | void MmlMusicPWM::playTone(unsigned int frequency, unsigned long length, uint8_t nTrack, uint8_t nVolume) // nTrack=0, nVolume=0 37 | { 38 | if(frequency==0 && length==0) 39 | { 40 | #if defined (ARDUINO_ARCH_ESP8266) 41 | //analogWriteFreq(1000); // Note: analogWriteFreq(0); gives a spontaneous WDT reset 42 | //analogWrite(_pinPwm, 0); // default range is 1024, start quiet using pulse-width zero 43 | ::noTone(_pinPwm); 44 | #elif defined(__AVR_ATtiny85__) 45 | _noToneTim1(); 46 | #else 47 | _noToneTim2(); 48 | #endif 49 | } 50 | else if(frequency>0) 51 | { 52 | #if defined (ARDUINO_ARCH_ESP8266) 53 | // Set the PWM frequency to that specified by the note being played 54 | // analogWriteFreq(frequency); 55 | // Note that PWM has lots of harmonics, so volume control using the PWM 56 | // duty-cycle is not very good, but perhaps better than nothing. 57 | // The default pwm-range is 1024. A 50% duty-cycle (=512) gives highest volume 58 | // The volume command Vnnn has a range 0-128, so we multiply by 4 to get the PWM 59 | // value. 60 | //analogWrite(_pinPwm, nVolume * 4); 61 | ::tone(_pinPwm, frequency); 62 | //Serial.println(frequency, DEC); 63 | 64 | if(length>0) 65 | _scheduler.once(length/1000.0, &MmlMusicPWM::musicTickerStopCallback, this); // MMOLE 190420: schedule when the tone should stop 66 | #elif defined(__AVR_ATtiny85__) 67 | _toneTim1(_pinPwm, frequency, length); 68 | #else 69 | _toneTim2(_pinPwm, frequency, length); 70 | #endif 71 | } 72 | else 73 | { 74 | #if defined (ARDUINO_ARCH_ESP8266) 75 | _scheduler.once(length/1000.0, &MmlMusicPWM::musicTickerCallback, this); 76 | #elif defined(__AVR_ATtiny85__) 77 | _waitToneTim1(length); 78 | #else 79 | _waitToneTim2(length); 80 | #endif 81 | } 82 | } 83 | 84 | #if defined(ARDUINO_ARCH_ESP8266) 85 | void MmlMusicPWM::musicTickerCallback(MmlMusicPWM* __thisMmlMusicPWM) 86 | { 87 | __thisMmlMusicPWM->continuePlaying(); 88 | } 89 | void MmlMusicPWM::musicTickerStopCallback(MmlMusicPWM* __thisMmlMusicPWM) 90 | { 91 | __thisMmlMusicPWM->noTone(); 92 | } 93 | #elif defined(__AVR_ATtiny85__) 94 | /* 95 | On ATtiny85 we use Timer1 to generate the required timer interrupts. 96 | ATtiny85 has two timers. When using the ATtiny "core" by D.A. Mellis, Timer0 is normally used for delay() and millis(). 97 | Timer1 is used for PWM using analogWrite(), so no PWM is available when using this library. 98 | */ 99 | 100 | // Timer1 interrupt for ATtiny85 (using ATtiny "core" by D.A. Mellis) 101 | // See http://forum.arduino.cc/index.php?topic=75546.0 102 | // 103 | // Use ATtiny85 Timer/Counter1 to call a function every xx ms. 104 | // In setup(), call setupTimer1() and pass it the name of your function which will 105 | // run as part of the interrupt service routine (ISR) every 10ms. This function must take no 106 | // arguments, return nothing, and should run as quickly as possible. The delay() and millis() 107 | // functions will not work while the ISR is running and so should be avoided. 108 | // Works with 1MHz or 8MHz system clock (others may be possible, but I got lazy). 109 | // Scotchware License: If we meet some day, and you think this is worth it, you can buy me a scotch. 110 | // 111 | // Jack Christensen 18Oct2011 112 | // Modified 20171211 by Maxint: use millis() for more accurate timing regardless MCU frequency 113 | // 114 | 115 | /* 116 | //Copy code from here down, AND uncomment the following line and move it near the top of your sketch BEFORE the setup() function. 117 | //void setupTimer1(void (*isrPointer)(void)); //function prototype for setupTimer1() 118 | void (*_userISR)(void); //pointer to the user's ISR 119 | 120 | uint16_t _nTimerms=0; 121 | void setupTimer1(uint16_t ms, void (*isrPointer)(void)) { 122 | _userISR = isrPointer; //save the pointer to the user's ISR 123 | _nTimerms=ms; //save the time to fire the user's ISR 124 | //set timer1 up to generate an interrupt every millisecond 125 | TCCR1 |= _BV(CTC1); //clear timer1 when it matches the value in OCR1C 126 | TIMSK |= _BV(OCIE1A); //enable interrupt when OCR1A matches the timer value 127 | sei(); //enable global interrupts 128 | OCR1A = 124; //set the match value for interrupt 129 | OCR1C = 124; //and the same match value to clear the timer 130 | #if F_CPU == 1000000 //1MHz system clock 131 | TCCR1 |= _BV(CS12); //set prescaler to divide by 8 (this starts the timer) 132 | #elif F_CPU == 8000000 //8MHz system clock 133 | TCCR1 |= _BV(CS12) | _BV(CS11) | _BV(CS10); //set prescaler to divide by 64 (this starts the timer) 134 | #else 135 | Clock must be 1MHz or 8MHz! //Error, only 1MHz or 8MHz clock (F_CPU) supported. 136 | #endif 137 | } 138 | */ 139 | MmlMusicPWM* __thisMmlMusicPWM__; // TODO: unfortunately I know no better way to call an instance method from the ISR than by using a global reference 140 | 141 | void MmlMusicPWM::_toneTim1(uint8_t pin, unsigned int frequency, unsigned long length) 142 | { // playing a tone 143 | 144 | // tune the frequency to compensate for some timing differences that cause frequency to bit 2% too high 145 | frequency-=frequency/38; // 38 is the magic number for ATtiny85 @ 8MHz 146 | 147 | if (length > 0) _tTim1_time = millis() + length - 1; else _tTim1_time = 0xFFFFFFFF; // Set when the note should end, or play "forever". 148 | 149 | if (_pinMask == 0) { // This gets the port register and bitmap for the pin and sets the pin to output mode. 150 | _pinMask = digitalPinToBitMask(pin); // Get the port register bitmask for pin. 151 | _pinOutput = portOutputRegister(digitalPinToPort(pin)); // Get the output port register for pin. 152 | _pinMode = (uint8_t *) portModeRegister(digitalPinToPort(pin)); // Get the port mode register for pin. 153 | *_pinMode |= _pinMask; // Set pin to Output mode. 154 | } 155 | 156 | // TODO: find out how to implement interrupts in class to get rid of globals and non-privates 157 | __thisMmlMusicPWM__=this; 158 | 159 | // Tone function, see also http://w8bh.net/avr/TrinketTone.pdf 160 | // scan through prescalars to find the best fit 161 | uint32_t ocr = F_CPU/frequency/2; 162 | uint8_t prescalar = 1; 163 | while(ocr>255) 164 | { 165 | prescalar++; 166 | ocr /= 2; 167 | } 168 | OCR1C = ocr-1; // Set the top. OCR1C is tied to CTC mode. 169 | if (TCNT1 > ocr) TCNT1 = ocr; // Counter over the top, put within range. 170 | TCCR1 = 0x90 | prescalar; // Set Fast PWM and prescaler 171 | // Value 0x90 sets bits 7 & 4, which turn on CTC mode and set the output pin OCR1 to toggle on a match, respectively. 172 | TIMSK |= _BV(OCIE1A); // Activate the timer interrupt. 173 | } 174 | 175 | void MmlMusicPWM::_noToneTim1(void) 176 | { // stop playing any tone 177 | TIMSK &= ~_BV(OCIE1A); // Remove the timer interrupt. 178 | TCCR1 = 0x90 | 0x02; // Default clock prescaler of 64 (TODO: check if 2 is indeed correct prescaler for 64_. 179 | *_pinMode &= ~_pinMask; // Set pin to INPUT. 180 | _pinMask = 0; // Flag so we know note is no longer playing. 181 | } 182 | 183 | void MmlMusicPWM::_executeCommandTim1(void) 184 | { 185 | continuePlaying(); 186 | } 187 | 188 | void MmlMusicPWM::_waitToneTim1(unsigned long length) 189 | { // set endtime of tone (or no tone) playing to allow callback at end of tone 190 | // TODO: find out how to implement interrupts in class to get rid of globals and non-privates 191 | __thisMmlMusicPWM__=this; // need to be set in both tone and in wait to be able to call continuePlaying() 192 | 193 | _tTim1_time = millis() + length - 1; 194 | TIMSK |= _BV(OCIE1A); // Activate the timer interrupt. 195 | } 196 | 197 | 198 | ISR(TIMER1_COMPA_vect) 199 | { //handles the Timer1 Compare Match A interrupt 200 | if(!__thisMmlMusicPWM__) 201 | return; 202 | if (millis() > __thisMmlMusicPWM__->_tTim1_time) 203 | { 204 | __thisMmlMusicPWM__->_noToneTim1(); // Check to see if it's time for the note to end. 205 | __thisMmlMusicPWM__->_executeCommandTim1(); // execute the next command 206 | } 207 | else 208 | *(__thisMmlMusicPWM__->_pinOutput) ^= __thisMmlMusicPWM__->_pinMask; // Toggle the pin state. 209 | } 210 | #else 211 | // ATmega Timer2 tone function derived from ToneAC2 library code: https://bitbucket.org/teckel12/arduino-toneac2/ 212 | 213 | // #elif defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) 214 | // #elif defined (ARDUINO_UNO) || defined(ARDUINO_AVR_MEGA2560) 215 | MmlMusicPWM* __thisMmlMusicPWM__; // TODO: unfortunately I know no better way to call an instance method from the ISR than by using a global reference 216 | 217 | void MmlMusicPWM::_toneTim2(uint8_t pin, unsigned int frequency, unsigned long length) 218 | { // playing a tone 219 | long top; 220 | uint8_t prescaler; 221 | 222 | for (prescaler = 1; prescaler < 8; prescaler++) { // Find the appropriate prescaler 223 | top = F_CPU / (long) frequency / (long) _tTim2_prescale[prescaler - 1] - 1; // Calculate the top. 224 | if (top < 256) break; // Fits, break out of for loop. 225 | } 226 | if (top > 255) { _noToneTim2(); return; } // Frequency is out of range, turn off sound and return. 227 | 228 | if (length > 0) _tTim2_time = millis() + length - 1; else _tTim2_time = 0xFFFFFFFF; // Set when the note should end, or play "forever". 229 | 230 | if (_pinMask == 0) { // This gets the port register and bitmap for the pin and sets the pin to output mode. 231 | _pinMask = digitalPinToBitMask(pin); // Get the port register bitmask for pin. 232 | _pinOutput = portOutputRegister(digitalPinToPort(pin)); // Get the output port register for pin. 233 | _pinMode = (uint8_t *) portModeRegister(digitalPinToPort(pin)); // Get the port mode register for pin. 234 | *_pinMode |= _pinMask; // Set pin to Output mode. 235 | } 236 | 237 | // TODO: find out how to implement interrupts in class to get rid of globals and non-privates 238 | __thisMmlMusicPWM__=this; 239 | 240 | OCR2A = top; // Set the top. 241 | if (TCNT2 > top) TCNT2 = top; // Counter over the top, put within range. 242 | TCCR2B = _BV(WGM22) | prescaler; // Set Fast PWM and prescaler. 243 | TCCR2A = _BV(WGM20) | _BV(WGM21); // Fast PWM and normal port operation, OC2A/OC2B disconnected. 244 | //TIMSK2 &= ~_BV(OCIE2A); // Stop timer 2 interrupt while we set the pin states. 245 | TIMSK2 |= _BV(OCIE2A); // Activate the timer interrupt. 246 | } 247 | 248 | void MmlMusicPWM::_noToneTim2(void) 249 | { // stop playing any tone 250 | TIMSK2 &= ~_BV(OCIE2A); // Remove the timer interrupt. 251 | TCCR2B = _BV(CS22); // Default clock prescaler of 64. 252 | TCCR2A = _BV(WGM20); // Set to defaults so PWM can work like normal (PWM, phase corrected, 8bit). 253 | *_pinMode &= ~_pinMask; // Set pin to INPUT. 254 | _pinMask = 0; // Flag so we know note is no longer playing. 255 | } 256 | 257 | void MmlMusicPWM::_executeCommandTim2(void) 258 | { 259 | continuePlaying(); 260 | } 261 | 262 | void MmlMusicPWM::_waitToneTim2(unsigned long length) 263 | { // set endtime of tone (or no tone) playing to allow callback at end of tone 264 | // TODO: find out how to implement interrupts in class to get rid of globals and non-privates 265 | __thisMmlMusicPWM__=this; // need to be set in both tone and in wait to be able to call continuePlaying() 266 | 267 | _tTim2_time = millis() + length - 1; 268 | TIMSK2 |= _BV(OCIE2A); // Activate the timer interrupt. 269 | } 270 | 271 | ISR(TIMER2_COMPA_vect) 272 | { // Timer interrupt vector. 273 | if(!__thisMmlMusicPWM__) 274 | return; 275 | if (millis() > __thisMmlMusicPWM__->_tTim2_time) 276 | { 277 | __thisMmlMusicPWM__->_noToneTim2(); // Check to see if it's time for the note to end. 278 | __thisMmlMusicPWM__->_executeCommandTim2(); // execute the next command 279 | } 280 | else 281 | *(__thisMmlMusicPWM__->_pinOutput) ^= __thisMmlMusicPWM__->_pinMask; // Toggle the pin state. 282 | } 283 | #endif 284 | -------------------------------------------------------------------------------- /MmlMusicPWM.h: -------------------------------------------------------------------------------- 1 | #ifndef __MMLMUSICPWM_H__ 2 | #define __MMLMUSICPWM_H__ 3 | #include "MmlMusic.h" 4 | //#include 5 | #if defined(ARDUINO_ARCH_ESP8266) 6 | #include 7 | #else 8 | // for ATmega we'll use redefinition of Timer2 (on 328/168) or Timer2 (on ATtiny) and portmanipulation 9 | #endif 10 | 11 | class MmlMusicPWM : public MmlMusic { 12 | public: 13 | /** Creates an instance of the MmlMusic 14 | * @param pin pin used to generate the note frequencies 15 | */ 16 | MmlMusicPWM(int pin=255); // Constructor 17 | void playTone(unsigned int frequency, unsigned long length, uint8_t nTrack=0, uint8_t nVolume=0); // public to allow access from callback 18 | 19 | private: 20 | int _pinPwm; 21 | 22 | 23 | #if defined (ARDUINO_ARCH_ESP8266) 24 | Ticker _scheduler; 25 | static void musicTickerCallback(MmlMusicPWM*); 26 | static void musicTickerStopCallback(MmlMusicPWM*); 27 | #else 28 | //MsTimer2 _scheduler; 29 | #endif 30 | 31 | #if defined(ARDUINO_ARCH_ESP8266) 32 | #elif defined(__AVR_ATtiny85__) 33 | public: 34 | // Timer1 is used for ATtiny85 35 | void _toneTim1(uint8_t pin, unsigned int frequency = 0, unsigned long length = 0); 36 | void _noToneTim1(void); 37 | void _waitToneTim1(unsigned long length); 38 | void _executeCommandTim1(void); 39 | 40 | // member variables called by ISR need to be public 41 | unsigned long _tTim1_time; // Used to track end note with timer when playing note in the background. 42 | volatile uint8_t *_pinMode; // Pin mode. 43 | uint8_t _pinMask = 0; // Bitmask for pins 44 | volatile uint8_t *_pinOutput; // Output port register for pin. 45 | #else 46 | // ATmega Timer2 tone function derived from ToneAC2 library code: https://bitbucket.org/teckel12/arduino-toneac2/ 47 | public: 48 | // Timer2 is used for atmega 49 | void _toneTim2(uint8_t pin, unsigned int frequency = 0, unsigned long length = 0); 50 | void _noToneTim2(void); 51 | void _waitToneTim2(unsigned long length); 52 | void _executeCommandTim2(void); 53 | 54 | // member variables called by ISR need to be public 55 | unsigned long _tTim2_time; // Used to track end note with timer when playing note in the background. 56 | volatile uint8_t *_pinMode; // Pin mode. 57 | uint8_t _pinMask = 0; // Bitmask for pins 58 | volatile uint8_t *_pinOutput; // Output port register for pin. 59 | const int _tTim2_prescale[7] = { 2, 16, 64, 128, 256, 512, 2048 }; // Prescaler. 60 | #endif 61 | 62 | private: 63 | static const float PERIOD_TABLE[]; 64 | }; 65 | #endif //__MMLMUSICPWM_H__ 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MmlMusicPWM 2 | Arduino library to play MML music using a piezo speaker on an output pin. Implemented using the [MmlMusic](https://github.com/maxint-rd/MmlMusic) base library. 3 | 4 | ### Introduction 5 | MmlMusicPWM provides a means to play Music Macro Language sequences asynchronously. Where the Arduino tone() function allows for playing one single note, the MmlMusicPWM::play() method can play an entire music score.
6 | It produces sound by means of a PWM signal on an output pin, which can be connected to a piezo speaker, or via an amplifier to a regular speaker. The music is played using a timer interrupt routine that changes the PWM frequency according the specific notes being played. This means we can do other things while the music keeps playing! 7 | 8 | ### Device independant base class 9 | This library is an extension to the [MmlMusic base library](https://github.com/maxint-rd/MmlMusic). It is implemented as a child class of the device independant MmlMusic class. That class requires additional code via either a callback function or the implementation of a child class to implement the actual production of sound. 10 | 11 | ### Support for different MCUs 12 | This library supports playing MML music on different MCUs such as ESP8266, ATmega 328, 168 and ATtiny85. Depending on the MCU, it uses different timers and interrupts to produce the sound and to schedule playback of notes and silences. The music will keep on playing using a timer interrupt. On the ESP8266 the Ticker library is used. For ATmega 328/168 a Timer2 interrupt is used and a replacement method for tone() is provided. On the ATtiny85 resources are limited. There a Timer1 interrupt is used, which impacts regular PWM output.
13 | BTW: The [MmlMusic Tone](https://github.com/maxint-rd/MmlMusic/tree/master/examples/MmlMusicTone) example shows how to play notes without using a timer interrupt. 14 | 15 | ### Installation/Usage 16 | The library can be downloaded and installed as an Arduino library using the Sketch|Library menu. Just add the zipfile library and the enclosed examples should appear in the menu automatically. 17 | 18 | Initialisation outside of Setup(): 19 | ``` 20 | // include header and initialize class 21 | #include 22 | MmlMusicPWM music(); 23 | ``` 24 | 25 | Then to play music, call the play method where you want: 26 | ``` 27 | music.play("T240 L16 O6 C D E F G"); 28 | ``` 29 | 30 | When playing the rest of the sequence, the isPlaying() method can be used to determine if the sequence is still playing. 31 | ``` 32 | if(music.isPlaying() 33 | { 34 | ... 35 | } 36 | ``` 37 | Alterternatively a callback function can be used to signal the start or end of playing the tune. See the included examples for more detailed instructions on how to use this library. 38 | 39 | ### Supported MML Syntax 40 | Short syntax overview:
41 | 42 | Command | Description 43 | ------------ | ------------- 44 |   Tnnn | Set tempo [32-255]. Examples: T120, T240
45 |   Vnnn | Set volume [0-128]. Note: limited effect on PWM-volume. Examples: V1, T120
46 |   Lnn | Set default note length [1-64]. Examples: L8, L16
47 |   Mx   | Set timing. Mn=default, Ml=legato, Ms=staccato
48 |   On   | Set octave [0-7]. Examples: O6, O7
49 |   A-G  | Play whole note. Example: C
50 |   Ann-Gnn  | Play note of alternative length [1-64]. Example: C4, A16
51 |   Nnn | Play frequency [0-96]. Example: N48
52 |    #   | Play sharp note. Example: C#
53 |   +   | Alternative for #
54 |   −   | Play flat note. Example: D-
55 |    R    | Rest. Example:  CDEC r CDEC
56 |   P   | Alternative for R. Example: CDEC p CDEC
57 |   .   | Longer note. Example: CDEC. 
58 |   > | shift octave up. Example: CDE>CDE. 
59 |    < | shift octave down.  Example: CDE<CDE. 
60 |    , | play multiple tracks  Example: CDE<CDE.,EDC<ECD. 
61 | 62 | The supported MML-commands are a subset that may not completely cover all available music scores. 63 | If notes seem missing, check your score against the syntax above and replace unknown commands by equivalent supported alternatives. The music notation is case-insensitive. Spaces are not required but can be used for readability. 64 | 65 | ### Features & limitations 66 | - Playing back multiple tracks is supported by the base library, but not (yet) by this library. Only a single pulse-stream is generated. The documentation of the comma-command above is maintained for future purposes. 67 | - This library was tested in the Arduino IDE v1.6.10 and v1.8.2. The current version of this library supports ESP8266, Atmel ATmega328 and ATmega168 MCUs. Support for ATtiny85 was also added, but since the ATtiny85 has limited resources, available memory limits it usage to simple applications. On ATtiny85 Timer1 is used, impacting the use of the regular PWM analogWrite() function. 68 | - There was a bug in the MmlMusic base library that could impact playback of notes and the duration of the delay() function. When ending the play-string with a number (eg. "T120 O4 G16"), the player could read beyond the end of the string, play whatever was next in memory and mess up the timer callback. This bug has been fixed. Please use the latest version of the [MmlMusic base library](https://github.com/maxint-rd/MmlMusic). See [this issue](https://github.com/maxint-rd/MmlMusic/issues/1) for details. 69 | - In ESP8266 cores 2.5.1 and higher reading a float array from PROGMEM had a [byte alignment issue](https://github.com/maxint-rd/MmlMusicPWM/issues/2) causing incorrect playback of notes. This issue was [fixed](https://github.com/esp8266/Arduino/pull/6593) in ESP8266 [core 2.6.0](https://github.com/esp8266/Arduino/releases/tag/2.6.0) and has a workaround in the [MmlMusic base library](https://github.com/maxint-rd/MmlMusic) for earlier cores. See [this issue](https://github.com/maxint-rd/MmlMusicPWM/issues/2) for details. 70 | 71 | ### Credits 72 | The base library is based on the MusicEngine library by Chris Taylor, ported from mBed to Arduino. It is a follow-up of the [ESP-MusicEngine library](https://github.com/maxint-rd/ESP-MusicEngine). 73 | 74 | ### Links 75 | - Learn more about Music Macro Language (MML) on wikipedia:
76 | http://en.wikipedia.org/wiki/Music_Macro_Language
77 | - For downloadable MML music see http://www.archeagemmllibrary.com/
78 | - Extensive MML reference guide (not all commands supported):
79 | http://woolyss.com/chipmusic/chipmusic-mml/ppmck_guide.php
80 | - Info about using PWM and other methods to generate sound:
81 | https://developer.mbed.org/users/4180_1/notebook/using-a-speaker-for-audio-output/ 82 | 83 | ### Disclaimer 84 | - All code on this GitHub account, including this library is provided to you on an as-is basis without guarantees and with all liability dismissed. It may be used at your own risk. Unfortunately I have no means to provide support. 85 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMBlink/MmlMusicPWMBlink.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MmlMusicPWM Blink 3 | * MmlMusicPWM library example for ESP8266 and ATmega in Arduino IDE 4 | * see https://github.com/maxint-rd/MmlMusicPWM 5 | * 6 | * by MMOLE (@maxint-rd) 7 | * Based on ESP8266 Blink by Simon Peter 8 | * This example code is in the public domain 9 | * 10 | * Blink the onboard LED on the ESP-07/ESP-12 module while playing a tune. 11 | * The music is played using an ticker-interrupt routine that changes the 12 | * PWM frequency according the note being played. This means we can do other 13 | * things while the music keeps playing. In this example we simply wait. 14 | * 15 | * Note that this sketch uses LED_BUILTIN to find the pin with the internal LED 16 | * The blue onboard LED on the ESP-01 module is connected to GPIO1 17 | * (which is also the TXD pin; so we cannot use Serial.print() at the same time) 18 | * On the ESP-07 and ESP-12 modules the LED is connected to GPIO2. 19 | * When using the board "Generic ESP8266 Module" LED_BUILTIN needs to be redefined. 20 | */ 21 | #include 22 | 23 | // define the pin used and initialize a MusicEngine object 24 | #define BUZ_PIN 14 25 | MmlMusicPWM music(BUZ_PIN); 26 | 27 | #define LED_BUILTIN 2 // pin 2 is used on ESP-07 and ESP-12 modules 28 | 29 | void setup() 30 | { 31 | pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output 32 | 33 | // Start playing some music (if impatient use the short tune of the lower line). 34 | music.play("t112v127l12d2cg2d4cg2d4cc-cd2cg2d4cg2d4cc-c<" 35 | "a2d6de4.eb+8&b8a8&g8g&a&b&a6&e&f+4&d6de4.el8b+&ba&g>d.d6dg6fe6dc6d2.r8d2cg2d4cg2d4cc-cd2c" 37 | "g2d4cg2d4cc-cdg1&g2.gggg8"); 38 | // music.play("T180 L8 CDEC. r CDEC. r EFG. r EFG. r GAGFEC. r GAGFEC. r L4 CC. r CC."); 39 | 40 | // Wait until the music is done playing, show fast blinking while playing 41 | while (music.isPlaying()) { 42 | digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level 43 | // but actually the LED is on; this is because 44 | // it is acive low on the ESP module) 45 | delay(50); 46 | digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH 47 | delay(200); 48 | } 49 | } 50 | 51 | // the loop function runs over and over again forever 52 | void loop() 53 | { 54 | digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (active LOW) 55 | music.play("T240 O8 E64"); // give a short beep 56 | delay(1000); // Wait for one second to show the led switched on 57 | digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off (output HIGH) 58 | music.play("T240 O8 G64"); // give a shorter slightly higher beep 59 | delay(2000); // Wait for two second to show the led switched off 60 | } 61 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMBlink/readme.md: -------------------------------------------------------------------------------- 1 | # MmlMusicPWM Blink example 2 | This example demonstrates playing some music while blinking a LED. 3 | 4 | Blink the onboard LED on the ESP-07/ESP-12 module while playing a tune. 5 | The music is played using a ticker-interrupt routine that changes the 6 | PWM frequency according the note being played. This means we can do other 7 | things while the music keeps playing. In this example we simply wait. 8 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMPlayOnce/MmlMusicPWMPlayOnce.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MmlMusicPWM library for ESP8266/ATmega in Arduino IDE minimal example 3 | * by MMOLE (@maxint-rd) 4 | * see https://github.com/maxint-rd/MmlMusicPWM 5 | */ 6 | #include 7 | 8 | // Initialize MusicEngine object (buzzer between pin 14 and GND) 9 | MmlMusicPWM music(14); 10 | 11 | void setup() 12 | { 13 | // only play one tune 14 | music.play("T180 L8 CDEC. r CDEC. r EFG. r EFG. r GAGFEC. r GAGFEC. r L4 CC. r CC."); 15 | } 16 | 17 | void loop() 18 | { 19 | // nothing to do 20 | } 21 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMPlayOnce/readme.md: -------------------------------------------------------------------------------- 1 | # MmlMusicPWM Play Once minimal example 2 | This example demonstrates the bare minimum: play one tune only once. 3 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMSerial/MmlMusicPWMSerial.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MmlMusicPWM Serial 3 | * MmlMusicPWM library example for ESP8266/Atmega in Arduino IDE 4 | * see https://github.com/maxint-rd/MmlMusicPWM 5 | * 6 | * by MMOLE (@maxint-rd) 7 | * This example code is in the public domain 8 | * 9 | * This example uses the serial console to receive music notes to be played 10 | * using a speaker/buzzer on a digital pin (eg. between GPIO14 and GND). 11 | * The music is played using an ticker-interrupt routine that changes the 12 | * PWM frequency according the note being played. This means we can do other 13 | * things while the music keeps playing. In this example we check the serial 14 | * line for new input. 15 | * 16 | * 17 | * 18 | */ 19 | #include 20 | 21 | // define pin and initialize MmlMusicPWM object 22 | #define BUZ_PIN 14 // pin 4 recommended on Pro Mini since it has perfect distance from GND 23 | MmlMusicPWM music(BUZ_PIN); 24 | 25 | // Reserve a buffer for playing the notes received via the serial console. 26 | // Note that this buffer should remain available while playing. 27 | char szBuf[128]; // serial buffer seems to be only 128 bytes on ESP, only 64 on ATmega 28 | 29 | void fnCompleted(void) 30 | { // callback function to notify completion 31 | Serial.print(F("\nDone playing.\nPlease type new notes to play something else.")); 32 | } 33 | 34 | void setup() 35 | { // put your setup code here, to run once: 36 | Serial.begin(115200); 37 | Serial.println(); 38 | Serial.println(F("==================")); 39 | Serial.println(F("Serial MmlMusicPWM")); 40 | Serial.println(F("==================")); 41 | Serial.println(F("Please type your notes and hit Enter.")); 42 | Serial.println(F("Example tune: T180 L8 CDEC. r CDEC. r EFG. r EFG. r GAGFEC. r GAGFEC. r L4 " 43 | "CC. r CC.")); 44 | music.play("T240 L16 O8 rCDEDC"); // give a short blurp 45 | } 46 | 47 | void loop() 48 | { // put your main code here, to run repeatedly: 49 | while (!Serial.available()) { // Wait for a serial string to be entered. 50 | // Sound a beep and and print a dot every 5 sec to indicate we're waiting. 51 | for (int n = 0; n < 50 && !Serial.available(); n++) 52 | delay(100); // just delay while waiting for input 53 | Serial.print("."); 54 | if (!music.isPlaying()) 55 | { 56 | music.setCompletionCallback(NULL); // prevent notification after sounding a little beep 57 | music.play("T250 L64 O7 B"); // give a short tick-like beep when waiting for input 58 | } 59 | } 60 | 61 | if (Serial.available() > 0) { // assume the string is valid MML and try to play it 62 | int nRead = Serial.readBytesUntil('\n', szBuf, sizeof(szBuf) - 1); 63 | szBuf[nRead] = '\0'; // terminate string 64 | Serial.println(F("\nPlaying tune:")); 65 | Serial.println(szBuf); 66 | music.setCompletionCallback(fnCompleted); // setup the callback to notify completion 67 | music.play(szBuf); // the buffer is global to reserve it while playing. 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMSerial/readme.md: -------------------------------------------------------------------------------- 1 | # MmlMusicPWM Serial example 2 | 3 | This example demonstrates playing tunes entered via the serial interface. 4 | Default baudrate is set to 115200. 5 | 6 | 7 | ### Output 8 | When the sketch compiled and flashed correctly, use the serial monitor to type the tune you want to play. 9 | Make sure to set it to the proper baudrate and select the option to send NL and CR (newline and return). 10 | 11 | ``` 12 | ================== 13 | Serial MusicEngine 14 | ================== 15 | Please type your notes and hit Enter. 16 | Example tune: T180 L8 CDEC. r CDEC. r EFG. r EFG. r GAGFEC. r GAGFEC. r L4 CC. r CC. 17 | ..... 18 | Playing tune: 19 | T240 L16 CDEFGAB>CDEFGAB>CDEFGAB 20 | 21 | Please type new notes to play something else... 22 | Playing tune: 23 | T240 L16 CDEFGAB>CDEFGAB>CDEFGAB>CDEFGAB>CDEFGAB 24 | 25 | Please type new notes to play something else... 26 | ``` 27 | -------------------------------------------------------------------------------- /examples/MmlMusicPWMSoundFX/MmlMusicPWMSoundFX.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MmlMusicPWM SoundFX 3 | * MmlMusicPWM library example for ESP8266/ATmega in Arduino IDE 4 | * see https://github.com/maxint-rd/MusicEngine 5 | * 6 | * by MMOLE (@maxint-rd) 7 | * This example code is in the public domain 8 | * 9 | * This example demonstrates how the library can also be used to produce 10 | * sound effects using a speaker/buzzer on a digital pin (eg. between GPIO14 and GND). 11 | * The music is played using an ticker-interrupt routine that changes the 12 | * PWM frequency according the note being played. This means we can do other 13 | * things while the music keeps playing. In this example we play a series of effects, 14 | * produced using the tone() and play() functions. 15 | * 16 | * 17 | * 18 | */ 19 | #include 20 | 21 | // define pin and initialize MmlMusicPWM object 22 | #define BUZ_PIN 14 // pin 4 recommended on Pro Mini since it has perfect distance from GND 23 | MmlMusicPWM music(BUZ_PIN); 24 | 25 | // Define a pin for the blinking LED. Pin 2 is used on ESP-07 and ESP-12 modules 26 | // On ESP-01 the LED is on pin 1. Most Arduino boards have the LED on pin 13. 27 | // On ESP the LED is active low, on Arduino is active HIGH 28 | #define LED_BUILTIN 2 29 | 30 | void blinkWhilePlaying() 31 | { 32 | pinMode(LED_BUILTIN, OUTPUT); 33 | while(music.isPlaying()) 34 | { 35 | digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (On ESP the LED is active LOW) 36 | delay(50); 37 | digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH 38 | delay(100); 39 | } 40 | } 41 | 42 | void soundSiren(int nTimes=10) 43 | { 44 | for(int nLoop=0; nLoop100; nFreq-=10) 52 | { 53 | music.tone(nFreq); 54 | delay(1); 55 | } 56 | } 57 | } 58 | 59 | void soundNoise(int nLength=30) 60 | { 61 | srand(analogRead(A0)); 62 | for(int nLoop=0; nLoop 4 | The examples were tested in the Arduino 1.6.10 IDE using an ESP-12F on a WeMos D1 Mini board with the buzzer connected to GPIO14 and the onboard LED on GPIO2. 5 | With minor pin adjustments, they should also work on Atmel ATmega 328/168 boards such as the Arduino Uno, Nano and Pro-mini. 6 | In the release of the MmlMusicPWM library additional support was added for the ATtiny85, but one must take into account the resource limits of that MCU. 7 | 8 | **WARNING**: Depending the specifications connecting a buzzer directly to an output-pin may draw too much current and can damage your ESP module. Although my ESPs don't seem to mind, yours may fail. If you don't mind taking risks, go ahead; otherwise use a current limiting resistor or drive the buzzer via a transistor. 9 | 10 | For more information see the comments in the example code or read the library documentation. 11 | 12 | ESP-07 playing music (click to view on YouTube):
13 | [![ESP-07 playing music](https://img.youtube.com/vi/BSmXyXZrRK0/0.jpg)](https://www.youtube.com/watch?v=BSmXyXZrRK0) 14 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | MmlMusicPWM KEYWORD1 2 | play KEYWORD2 3 | isPlaying KEYWORD2 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=MML Music PWM 2 | version=0.2.0 3 | author=Maxint 4 | maintainer=Maxint R&D 5 | sentence=Play MML music using a piezo speaker on an output pin. Requires the MmlMusic base library. 6 | paragraph=Use this library to parse MML music and play it back on an output pin connected to a piezo or amplified speaker. Examples included. 7 | category=Signal Input/Output 8 | url=https://github.com/maxint-rd/MmlMusicPWM 9 | architectures=* 10 | --------------------------------------------------------------------------------