├── keywords.txt ├── .clang-format ├── examples ├── MusicEnginePlayOnce │ ├── readme.md │ └── MusicEnginePlayOnce.ino ├── MusicEngineBlink │ ├── readme.md │ └── MusicEngineBlink.ino ├── MusicEngineSoundFX │ ├── readme.md │ └── MusicEngineSoundFX.ino ├── readme.md └── MusicEngineSerial │ ├── readme.md │ └── MusicEngineSerial.ino ├── MusicEngine.h ├── README.md └── MusicEngine.cpp /keywords.txt: -------------------------------------------------------------------------------- 1 | MusicEngine KEYWORD1 2 | play KEYWORD2 3 | getIsPlaying KEYWORD2 -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: WebKit 2 | ColumnLimit: 100 3 | AllowShortFunctionsOnASingleLine: false -------------------------------------------------------------------------------- /examples/MusicEnginePlayOnce/readme.md: -------------------------------------------------------------------------------- 1 | # MusicEngine Play Once minimal example 2 | This example demonstrates the bare minimum: play one tune only once. 3 | -------------------------------------------------------------------------------- /examples/MusicEngineBlink/readme.md: -------------------------------------------------------------------------------- 1 | # MusicEngine 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/MusicEngineSoundFX/readme.md: -------------------------------------------------------------------------------- 1 | # MusicEngine SoundFX example 2 | This example demonstrates how the MusicEngine library can also be used to produce sound effects using a speaker/buzzer on a digital pin (eg. between GPIO14 and GND). 3 | The music is played using an ticker-interrupt routine that changes the PWM frequency according the note being played. 4 | This means we can do other things while the music keeps playing. In this example we play a series of effects, produced using the tone() and play() functions. 5 | -------------------------------------------------------------------------------- /examples/MusicEnginePlayOnce/MusicEnginePlayOnce.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MusicEngine library for ESP8266 in Arduino IDE minimal example 3 | * by MMOLE (@maxint-rd) 4 | * see https://github.com/maxint-rd/ESP-MusicEngine 5 | */ 6 | #include 7 | 8 | // Initialize MusicEngine object (buzzer between pin 14 and GND) 9 | MusicEngine 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/readme.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These examples are made to demonstrate the MusicEngine library for ESP8266 within the Arduino IDE.
4 | The examples were tested in the Arduino 1.6.10 IDE using an ESP-07 on a white breakout board with the buzzer connected to GPIO14 and the onboard LED on GPIO2. 5 | 6 | **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. 7 | 8 | For more information see the comments in the example code or read the library documentation. 9 | 10 | ESP-07 playing music (click to view on YouTube):
11 | [![ESP-07 playing music](https://img.youtube.com/vi/BSmXyXZrRK0/0.jpg)](https://www.youtube.com/watch?v=BSmXyXZrRK0) 12 | -------------------------------------------------------------------------------- /examples/MusicEngineSerial/readme.md: -------------------------------------------------------------------------------- 1 | # MusicEngine Serial example 2 | 3 | This example demonstrates playing tunes entered via the serial interface. 4 | Default baudrate is set to 115200. 5 | 6 | When the tune is done playing a callback function is called to notify completion. 7 | 8 | 9 | ### Output 10 | When the sketch compiled and flashed correctly, use the serial monitor to type the tune you want to play. 11 | Make sure to set it to the proper baudrate and select the option to send NL and CR (newline and return). 12 | 13 | ``` 14 | ================== 15 | Serial MusicEngine 16 | ================== 17 | Please type your notes and hit Enter. 18 | Example tune: T180 L8 CDEC. r CDEC. r EFG. r EFG. r GAGFEC. r GAGFEC. r L4 C
C. r CC. 19 | ..... 20 | Playing tune: 21 | T240 L16 CDEFGAB>CDEFGAB>CDEFGAB 22 | 23 | Done playing. 24 | Please type new notes to play something else... 25 | Playing tune: 26 | T240 L16 CDEFGAB>CDEFGAB>CDEFGAB>CDEFGAB>CDEFGAB 27 | 28 | Done playing. 29 | Please type new notes to play something else... 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/MusicEngineBlink/MusicEngineBlink.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MusicEngine Blink 3 | * MusicEngine library example for ESP8266 in Arduino IDE 4 | * see https://github.com/maxint-rd/ESP-MusicEngine 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 | MusicEngine 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.getIsPlaying()) { 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 | } -------------------------------------------------------------------------------- /examples/MusicEngineSerial/MusicEngineSerial.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MusicEngine Serial 3 | * MusicEngine library example for ESP8266/ATmega in Arduino IDE 4 | * see https://github.com/maxint-rd/ESP-MusicEngine 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 MusicEngine object 22 | #define BUZ_PIN 14 23 | MusicEngine 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 128 bytes on ESP8266 and only 64 bytes on ATmega328/168 28 | 29 | void fnCompleted(void) 30 | { // Callback function to notify completion 31 | // Note that this funtion is called in an interrupt, so keep it short. 32 | Serial.print(F("\nDone playing.\nPlease type new notes to play something else.")); 33 | } 34 | 35 | void setup() 36 | { // put your setup code here, to run once: 37 | Serial.begin(115200); 38 | Serial.println(); 39 | Serial.println(F("==================")); 40 | Serial.println(F("Serial MusicEngine")); 41 | Serial.println(F("==================")); 42 | Serial.println(F("Please type your notes and hit Enter.")); 43 | Serial.println(F("Example tune: T180 L8 CDEC. r CDEC. r EFG. r EFG. r GAGFEC. r GAGFEC. r L4 " 44 | "CC. r CC.")); 45 | music.play("T240 L16 O8 rCDEDC"); // give a short blurp 46 | } 47 | 48 | void loop() 49 | { // put your main code here, to run repeatedly: 50 | while (!Serial.available()) { // Wait for a serial string to be entered. 51 | // Sound a beep and and print a dot every 5 sec to indicate we're waiting. 52 | for (int n = 0; n < 50 && !Serial.available(); n++) 53 | delay(100); // just delay while waiting for input 54 | Serial.print("."); 55 | if (!music.getIsPlaying()) 56 | { 57 | music.setCompletionCallback(NULL); // prevent notification after sounding a little beep 58 | music.play("T250 L64 O7 B"); // give a short tick-like beep when waiting for input 59 | } 60 | } 61 | 62 | if (Serial.available() > 0) { // assume the string is valid MML and try to play it 63 | int nRead = Serial.readBytesUntil('\n', szBuf, sizeof(szBuf) - 1); 64 | szBuf[nRead] = '\0'; // terminate string 65 | Serial.println(F("\nPlaying tune:")); 66 | Serial.println(szBuf); 67 | music.setCompletionCallback(fnCompleted); // setup the callback to notify completion 68 | music.play(szBuf); // the buffer is global to reserve it while playing. 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MusicEngine.h: -------------------------------------------------------------------------------- 1 | #ifndef __MUSICENGINE_H__ 2 | #define __MUSICENGINE_H__ 3 | #include 4 | #if defined(ARDUINO_ARCH_ESP8266) 5 | #include 6 | #else 7 | // for ATmega we'll use redefinition of Timer2 (on 328/168) and portmanipulation 8 | #endif 9 | 10 | class MusicEngine { 11 | public: 12 | /** Creates an instance of the MusicEngine 13 | * @param pin pin used to generate the note frequencies 14 | */ 15 | MusicEngine(int pin); 16 | 17 | /** Support for Arduino tone() replacements, needed to use Timer2 on ATmega 18 | */ 19 | void tone(unsigned int frequency, unsigned long length=0); 20 | void noTone(); 21 | void waitTone(unsigned long length=0); 22 | 23 | 24 | /** Starts playing a new MML sequence. If one is already playing it is stopped and the new 25 | * sequences started. 26 | * @param mml string of MML commands to be played 27 | */ 28 | void play(const char* mml); 29 | 30 | /** Stop a currently playing sequence */ 31 | void stop(); 32 | 33 | /** Query the engine to determine if a MML sequence is currently being played. */ 34 | bool getIsPlaying() const 35 | { 36 | return _isPlaying; 37 | } 38 | 39 | /** Setup a callback function that will be executed when the music sequence ends. */ 40 | void setCompletionCallback(void (*function)(void)) 41 | { 42 | _completionCallback = function; 43 | } 44 | 45 | #if !defined(ARDUINO_ARCH_ESP8266) 46 | // Timer2 is used for atmega 47 | void _toneTim2(uint8_t pin, unsigned int frequency = 0, unsigned long length = 0); 48 | void _noToneTim2(void); 49 | void _waitToneTim2(unsigned long length); 50 | void _executeCommandTim2(void); 51 | #endif 52 | 53 | 54 | private: 55 | void executeCommand(); 56 | int getNumber(int min, int max); 57 | void skipWhiteSpace(); 58 | char getChar(); 59 | char peekChar(); 60 | void rewind(); 61 | 62 | private: 63 | int _pinPwm; 64 | bool _isPlaying; 65 | const char* _mml; 66 | int _mmlIndex; 67 | int _octave; 68 | float _duration; 69 | float _durationRatio; 70 | float _pause; 71 | int _tempo; 72 | int _volume; 73 | #if defined (ARDUINO_ARCH_ESP8266) 74 | Ticker _scheduler; 75 | #else 76 | //MsTimer2 _scheduler; 77 | #endif 78 | 79 | void (*_completionCallback)(void); 80 | static void musicTickerCallback(MusicEngine*); 81 | 82 | static const float WHOLE_NOTE_DURATION; 83 | static const float QUARTER_NOTE_DURATION; 84 | static const float QUARTER_NOTES_PER_MINUTE; 85 | 86 | static const float DEFAULT_TIMING; 87 | static const float LEGATO_TIMING; 88 | static const float STACCATO_TIMING; 89 | 90 | static const int NOTE_REST; 91 | static const int NOTE_C; 92 | static const int NOTE_CS; 93 | static const int NOTE_D; 94 | static const int NOTE_DS; 95 | static const int NOTE_E; 96 | static const int NOTE_F; 97 | static const int NOTE_FS; 98 | static const int NOTE_G; 99 | static const int NOTE_GS; 100 | static const int NOTE_A; 101 | static const int NOTE_AS; 102 | static const int NOTE_B; 103 | 104 | static const float PERIOD_TABLE[]; 105 | }; 106 | #endif //__MUSICENGINE_H__ 107 | -------------------------------------------------------------------------------- /examples/MusicEngineSoundFX/MusicEngineSoundFX.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MusicEngine SoundFX 3 | * MusicEngine 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 MusicEngine object 22 | #define BUZ_PIN 14 // pin 4 recommended on Pro Mini since it has perfect distance from GND 23 | MusicEngine 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.getIsPlaying()) 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 8 | MusicEngine class / Retro  Music Engine
9 | Original author: Chris Taylor (taylorza). Open source license: Apache 2.0
10 | see https://developer.mbed.org/users/taylorza/code/MusicEngine/
11 | Ported from mBed to Arduino by MMOLE (maxint-rd), inherited Apache license. 12 | 13 | MusicEngine provides a means to play Music Macro Language sequences asynchronously. Where the tone() API-function allows for playing one single note, the MusicEngine.play() method can play an entire music score.
14 | The music is played using an 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. 15 | 16 | Learn more about Music Macro Language (MML) on wikipedia:
17 | http://en.wikipedia.org/wiki/Music_Macro_Language
18 | For downloadable MML music see http://www.archeagemmllibrary.com/
19 | Extensive MML reference guide (not all commands supported):
20 | http://woolyss.com/chipmusic/chipmusic-mml/ppmck_guide.php
21 | Info about using PWM and other methods to generate sound:
22 | https://developer.mbed.org/users/4180_1/notebook/using-a-speaker-for-audio-output/ 23 | 24 | ================================ 25 | ### Installation/Usage 26 | The current version can be downloaded as an Arduino library using the Sketch|Library menu. Just add the zipfile library and the enclosed examples should appear in the menu automatically. On the ESP8266 the Ticker library is used. For ATmega a Timer2 interrupt is used. 27 | 28 | Initialisation outside of Setup(): 29 | ``` 30 | // define pin, include header and initialize class 31 | #include 32 | #define BUZ_PIN 14 33 | MusicEngine music(BUZ_PIN); 34 | ``` 35 | 36 | Then to play music, call the play method where you want: 37 | ``` 38 | music.play("T240 L16 O6 C D E F G"); 39 | ``` 40 | Note: the music will keep on playing using a Ticker interrupt. 41 | 42 | ### Supported MML Syntax 43 | Short syntax overview:
44 | 45 | Command | Description 46 | ------------ | ------------- 47 |   Tnnn | Set tempo [32-255]. Examples: T120, T240
48 |   Vnnn | Set volume [0-128]. Note: limited effect on PWM-volume. Examples: V1, T120
49 |   Lnn | Set default note length [1-64]. Examples: L8, L16
50 |   Mx   | Set timing. Mn=default, Ml=legato, Ms=staccato
51 |   On   | Set octave [0-7]. Examples: O6, O7
52 |   A-G  | Play whole note. Example: C
53 |   Ann-Gnn  | Play note of alternative length [1-64]. Example: C4, A16
54 |   Nnn | Play frequency [0-96]. Example: N48
55 |    #   | Play sharp note. Example: C#
56 |   +   | Alternative for #
57 |   −   | Play flat note. Example: D-
58 |    R    | Rest. Example:  CDEC r CDEC
59 |   P   | Alternative for R. Example: CDEC p CDEC
60 |   .   | Longer note. Example: CDEC. 
61 |   > | shift octave up. Example: CDE>CDE. 
62 |    < | shift octave down.  Example: CDE<CDE. 
63 | 64 | The supported MML-commands are a subset that may not completely cover all available music scores. 65 | 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. 66 | 67 | ### Features & limitations 68 | - The current version of this library supports ESP8266 and Atmel ATmega328 and ATmega168 MCUs. Support for ATtiny85 is being added, so stay tuned! 69 | - This version currently supports only single channel playback on a buzzer of speaker. Playback using a complex sound generator is under investigation. 70 | - Known bug: when ending the play-string with a number (eg. "T120 O4 G16") the player may read beyond the end of the string and play whatever is next in memory. Workaround: use alternative notation (eg. "T120 O4 L16 G") or an addional terminator (eg. "T120 O4 G16\0"). 71 | -------------------------------------------------------------------------------- /MusicEngine.cpp: -------------------------------------------------------------------------------- 1 | // MusicEngine class 2 | // Retro Music Engine 3 | // Author: Chris Taylor (taylorza) 4 | // see https://developer.mbed.org/users/taylorza/code/MusicEngine/ 5 | // Ported from mBed to ESP8266/Arduino by MMOLE (mm_rd@maxint.nl) 6 | /** MusicEngine provides a means to play Music Macro Language sequences asynchronously. 7 | * Learn more about Music Macro Language (MML) on wikipedia 8 | * http://en.wikipedia.org/wiki/Music_Macro_Language 9 | * For music see http://www.archeagemmllibrary.com/ 10 | */ 11 | 12 | #include "MusicEngine.h" 13 | //#include 14 | #if defined (ARDUINO_ARCH_ESP8266) 15 | #include 16 | #endif 17 | 18 | const float MusicEngine::WHOLE_NOTE_DURATION = 1.0f; 19 | const float MusicEngine::QUARTER_NOTE_DURATION = MusicEngine::WHOLE_NOTE_DURATION / 4.0f; 20 | const float MusicEngine::QUARTER_NOTES_PER_MINUTE = 60.0f / MusicEngine::QUARTER_NOTE_DURATION; 21 | 22 | const float MusicEngine::DEFAULT_TIMING = 7.0f / 8.0f; 23 | const float MusicEngine::LEGATO_TIMING = 1.0f; 24 | const float MusicEngine::STACCATO_TIMING = 3.0f / 4.0f; 25 | 26 | const int MusicEngine::NOTE_REST = 0; 27 | const int MusicEngine::NOTE_C = 1; 28 | const int MusicEngine::NOTE_CS = 2; 29 | const int MusicEngine::NOTE_D = 3; 30 | const int MusicEngine::NOTE_DS = 4; 31 | const int MusicEngine::NOTE_E = 5; 32 | const int MusicEngine::NOTE_F = 6; 33 | const int MusicEngine::NOTE_FS = 7; 34 | const int MusicEngine::NOTE_G = 8; 35 | const int MusicEngine::NOTE_GS = 9; 36 | const int MusicEngine::NOTE_A = 10; 37 | const int MusicEngine::NOTE_AS = 11; 38 | const int MusicEngine::NOTE_B = 12; 39 | 40 | MusicEngine::MusicEngine(int pin) 41 | : _pinPwm(pin) 42 | , _isPlaying(false) 43 | , _completionCallback(NULL) 44 | { 45 | pinMode(_pinPwm, OUTPUT); 46 | this->noTone(); 47 | } 48 | 49 | 50 | void MusicEngine::tone(unsigned int frequency, unsigned long length) // length=0 51 | { 52 | /* 53 | Serial.print(F("MusicEngine tone:")); 54 | Serial.print(frequency); 55 | Serial.print(F(", len ")); 56 | Serial.print(length); 57 | Serial.print(F(", pin ")); 58 | Serial.println(_pinPwm); 59 | */ 60 | #if defined (ARDUINO_ARCH_ESP8266) 61 | // Set the PWM frequency to that specified by the note being played 62 | analogWriteFreq(frequency); 63 | // Note that PWM has lots of harmonics, so volume control using the PWM 64 | // duty-cycle is not very good, but perhaps better than nothing. 65 | // The default pwm-range is 1024. A 50% duty-cycle (=512) gives highest volume 66 | // The volume command Vnnn has a range 0-128, so we multiply by 4 to get the PWM 67 | // value. 68 | analogWrite(_pinPwm, _volume * 4); 69 | #else 70 | _toneTim2(_pinPwm, frequency, length); 71 | #endif 72 | } 73 | 74 | void MusicEngine::noTone() 75 | { 76 | #if defined (ARDUINO_ARCH_ESP8266) 77 | analogWriteFreq(1000); // Note: analogWriteFreq(0); gives a spontaneous WDT reset 78 | analogWrite(_pinPwm, 0); // default range is 1024, start quiet using pulse-width zero 79 | #else 80 | _noToneTim2(); 81 | #endif 82 | } 83 | 84 | void MusicEngine::waitTone(unsigned long length) // length=0 85 | { // schedule wait time. length is specified in msec 86 | #if defined (ARDUINO_ARCH_ESP8266) 87 | _scheduler.once(length/1000.0, &MusicEngine::musicTickerCallback, this); 88 | #else 89 | _waitToneTim2(length); 90 | #endif 91 | } 92 | 93 | 94 | 95 | 96 | void MusicEngine::play(const char* mml) 97 | { 98 | //Serial.print(F("MusicEngine play:")); 99 | //Serial.println(mml); 100 | // __disable_irq(); 101 | _isPlaying = false; 102 | _mml = mml; 103 | _mmlIndex = 0; 104 | _octave = 4; 105 | _duration = QUARTER_NOTE_DURATION; 106 | _durationRatio = DEFAULT_TIMING; 107 | _tempo = 120; 108 | _volume = 128; 109 | //_pwm.period(0); 110 | //_pwm = 0.5f; 111 | this->noTone(); 112 | _pause = 0; 113 | _isPlaying = true; 114 | // __enable_irq(); 115 | MusicEngine::executeCommand(); 116 | } 117 | 118 | void MusicEngine::stop() 119 | { 120 | // __disable_irq(); 121 | _isPlaying = false; 122 | // __enable_irq(); 123 | } 124 | 125 | void MusicEngine::executeCommand() 126 | { 127 | // _scheduler.detach(); 128 | if (_pause < 0) 129 | _pause = 0; 130 | if (_pause != 0) { 131 | //_pwm.period(0); 132 | //_pwm = 0.0f; 133 | // _scheduler.attach(this, &MusicEngine::executeCommand, _pause); 134 | this->noTone(); 135 | this->waitTone(_pause*1000); 136 | _pause = 0; 137 | } else { 138 | int freqIndex = -1; 139 | do { 140 | skipWhiteSpace(); 141 | switch (getChar()) { 142 | case 'a': 143 | freqIndex = NOTE_A; 144 | break; 145 | case 'b': 146 | freqIndex = NOTE_B; 147 | break; 148 | case 'c': 149 | freqIndex = NOTE_C; 150 | break; 151 | case 'd': 152 | freqIndex = NOTE_D; 153 | break; 154 | case 'e': 155 | freqIndex = NOTE_E; 156 | break; 157 | case 'f': 158 | freqIndex = NOTE_F; 159 | break; 160 | case 'g': 161 | freqIndex = NOTE_G; 162 | break; 163 | 164 | case 'p': 165 | case 'r': 166 | freqIndex = NOTE_REST; 167 | break; 168 | 169 | case 'l': 170 | if (isdigit(peekChar())) 171 | _duration = (float)WHOLE_NOTE_DURATION / getNumber(1, 64); 172 | break; 173 | case 'o': 174 | if (isdigit(peekChar())) 175 | _octave = getNumber(0, 7); 176 | break; 177 | case 't': 178 | if (isdigit(peekChar())) 179 | _tempo = getNumber(32, 255); 180 | break; 181 | case 'v': 182 | if (isdigit(peekChar())) 183 | _volume = getNumber(0, 128); 184 | break; 185 | case 'm': 186 | switch (getChar()) { 187 | case 'n': 188 | _durationRatio = DEFAULT_TIMING; 189 | break; 190 | case 'l': 191 | _durationRatio = LEGATO_TIMING; 192 | break; 193 | case 's': 194 | _durationRatio = STACCATO_TIMING; 195 | break; 196 | } 197 | break; 198 | 199 | case 'n': 200 | if (isdigit(peekChar())) 201 | freqIndex = getNumber(0, 96); 202 | break; 203 | case '<': 204 | --_octave; 205 | if (_octave < 0) 206 | _octave = 0; 207 | break; 208 | case '>': 209 | ++_octave; 210 | if (_octave > 7) 211 | _octave = 7; 212 | break; 213 | 214 | case '\0': 215 | _isPlaying = false; 216 | break; 217 | } 218 | if (!_isPlaying) { 219 | //_pwm.period_ms(1); 220 | //_pwm.write(0.0); 221 | this->noTone(); 222 | if (_completionCallback) 223 | _completionCallback(); 224 | return; 225 | } 226 | 227 | float durationRatio = _durationRatio; 228 | float duration = _duration; 229 | 230 | if (freqIndex != -1) { 231 | bool fCminus = false; // MMOLE: ugly fix for nitwits that use C- in their music 232 | switch (getChar()) { 233 | case '+': 234 | case '#': 235 | ++freqIndex; 236 | break; 237 | case '-': 238 | --freqIndex; 239 | if (freqIndex == 0) 240 | fCminus = true; 241 | break; 242 | case '.': 243 | durationRatio = 3.0f / 2.0f; 244 | while (peekChar() == '.') { 245 | durationRatio *= 3.0f / 2.0f; 246 | getChar(); 247 | } 248 | break; 249 | default: 250 | rewind(); 251 | break; 252 | } 253 | 254 | if (isdigit(peekChar())) { 255 | duration = WHOLE_NOTE_DURATION / getNumber(1, 64); 256 | } 257 | 258 | if (freqIndex != NOTE_REST || (fCminus && _octave > 0)) { 259 | //_pwm.period(PERIOD_TABLE[freqIndex + (_octave * 12)]); 260 | //_pwm = 0.5; 261 | float ftFreq = 1.0 / PERIOD_TABLE[freqIndex + (_octave * 12)]; 262 | if (fCminus) 263 | ftFreq = 1.0 / PERIOD_TABLE[NOTE_B + ((_octave - 1) * 12)]; // MMOLE: ugly 264 | // fix: play C- 265 | // as B in lower 266 | // octave 267 | this->tone((int)ftFreq); // start playing the new tone 268 | } 269 | duration *= (QUARTER_NOTES_PER_MINUTE / _tempo); 270 | _pause=duration * durationRatio; 271 | this->waitTone(_pause*1000); // schedule to wait until tone is done 272 | _pause = duration * (1 - durationRatio); 273 | } 274 | } while (freqIndex == -1); 275 | } 276 | } 277 | 278 | int MusicEngine::getNumber(int min, int max) 279 | { 280 | char ch; 281 | int value = 0; 282 | while ((ch = getChar()) != 0) { 283 | if (!isdigit(ch)) { 284 | rewind(); 285 | break; 286 | } 287 | int digit = (int)ch - 48; 288 | value = (value * 10) + digit; 289 | } 290 | value = value < min ? min : value > max ? max : value; 291 | return value; 292 | } 293 | 294 | void MusicEngine::skipWhiteSpace() 295 | { 296 | while (isspace(peekChar())) 297 | getChar(); 298 | } 299 | 300 | char MusicEngine::getChar() 301 | { 302 | return tolower(_mml[_mmlIndex++]); 303 | } 304 | 305 | char MusicEngine::peekChar() 306 | { 307 | return tolower(_mml[_mmlIndex]); 308 | } 309 | 310 | void MusicEngine::rewind() 311 | { 312 | --_mmlIndex; 313 | } 314 | 315 | void MusicEngine::musicTickerCallback(MusicEngine* __thisMusicEngine) 316 | { 317 | __thisMusicEngine->executeCommand(); 318 | } 319 | 320 | 321 | 322 | // ATmega Timer2 tone function derived from ToneAC2 library code: https://bitbucket.org/teckel12/arduino-toneac2/ 323 | #if !defined(ARDUINO_ARCH_ESP8266) 324 | // #elif defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) 325 | // #elif defined (ARDUINO_UNO) || defined(ARDUINO_AVR_MEGA2560) 326 | unsigned long _tTim2_time; // Used to track end note with timer when playing note in the background. 327 | volatile uint8_t *_pinMode; // Pin mode. 328 | uint8_t _pinMask = 0; // Bitmask for pins 329 | volatile uint8_t *_pinOutput; // Output port register for pin. 330 | const int _tTim2_prescale[] = { 2, 16, 64, 128, 256, 512, 2048 }; // Prescaler. 331 | MusicEngine* __thisMusicEngine__; // TODO: unfortunately I know no better way to call an instance method from the ISR than by using a global reference 332 | 333 | void MusicEngine::_toneTim2(uint8_t pin, unsigned int frequency, unsigned long length) 334 | { // playing a tone 335 | long top; 336 | uint8_t prescaler; 337 | 338 | for (prescaler = 1; prescaler < 8; prescaler++) { // Find the appropriate prescaler 339 | top = F_CPU / (long) frequency / (long) _tTim2_prescale[prescaler - 1] - 1; // Calculate the top. 340 | if (top < 256) break; // Fits, break out of for loop. 341 | } 342 | if (top > 255) { _noToneTim2(); return; } // Frequency is out of range, turn off sound and return. 343 | 344 | if (length > 0) _tTim2_time = millis() + length - 1; else _tTim2_time = 0xFFFFFFFF; // Set when the note should end, or play "forever". 345 | 346 | if (_pinMask == 0) { // This gets the port register and bitmap for the pin and sets the pin to output mode. 347 | _pinMask = digitalPinToBitMask(pin); // Get the port register bitmask for pin. 348 | _pinOutput = portOutputRegister(digitalPinToPort(pin)); // Get the output port register for pin. 349 | _pinMode = (uint8_t *) portModeRegister(digitalPinToPort(pin)); // Get the port mode register for pin. 350 | *_pinMode |= _pinMask; // Set pin to Output mode. 351 | } 352 | 353 | // TODO: find out how to implement interrupts in class to get rid of globals and non-privates 354 | __thisMusicEngine__=this; 355 | 356 | OCR2A = top; // Set the top. 357 | if (TCNT2 > top) TCNT2 = top; // Counter over the top, put within range. 358 | TCCR2B = _BV(WGM22) | prescaler; // Set Fast PWM and prescaler. 359 | TCCR2A = _BV(WGM20) | _BV(WGM21); // Fast PWM and normal port operation, OC2A/OC2B disconnected. 360 | //TIMSK2 &= ~_BV(OCIE2A); // Stop timer 2 interrupt while we set the pin states. 361 | TIMSK2 |= _BV(OCIE2A); // Activate the timer interrupt. 362 | } 363 | 364 | void MusicEngine::_noToneTim2(void) 365 | { // stop playing any tone 366 | TIMSK2 &= ~_BV(OCIE2A); // Remove the timer interrupt. 367 | TCCR2B = _BV(CS22); // Default clock prescaler of 64. 368 | TCCR2A = _BV(WGM20); // Set to defaults so PWM can work like normal (PWM, phase corrected, 8bit). 369 | *_pinMode &= ~_pinMask; // Set pin to INPUT. 370 | _pinMask = 0; // Flag so we know note is no longer playing. 371 | } 372 | 373 | void MusicEngine::_executeCommandTim2(void) 374 | { 375 | executeCommand(); 376 | } 377 | 378 | void MusicEngine::_waitToneTim2(unsigned long length) 379 | { // set endtime of tone (or no tone) playing to allow callback at end of tone 380 | // TODO: find out how to implement interrupts in class to get rid of globals and non-privates 381 | __thisMusicEngine__=this; // need to be set in both tone and in wait to be able to call executeCommand 382 | 383 | _tTim2_time = millis() + length - 1; 384 | TIMSK2 |= _BV(OCIE2A); // Activate the timer interrupt. 385 | } 386 | 387 | ISR(TIMER2_COMPA_vect) 388 | { // Timer interrupt vector. 389 | if(!__thisMusicEngine__) 390 | return; 391 | if (millis() > _tTim2_time) 392 | { 393 | __thisMusicEngine__->_noToneTim2(); // Check to see if it's time for the note to end. 394 | __thisMusicEngine__->_executeCommandTim2(); // execute the next command 395 | } 396 | else 397 | *_pinOutput ^= _pinMask; // Toggle the pin state. 398 | } 399 | #endif 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | // clang-format off 408 | const float MusicEngine::PERIOD_TABLE[] = { 409 | 0, 410 | //1 2 3 4 5 6 7 8 9 10 11 12 411 | //C C# D D# E F F# G G# A A# B 412 | 1.0f / 16.35f, 1.0f / 17.32f, 1.0f / 18.35f, 1.0f / 19.45f, 1.0f / 20.60f, 1.0f / 21.83f, 1.0f / 23.12f, 1.0f / 24.50f, 1.0f / 25.96f, 1.0f / 27.50f, 1.0f / 29.14f, 1.0f / 30.87f, // Octave 0 413 | 1.0f / 32.70f, 1.0f / 34.65f, 1.0f / 36.71f, 1.0f / 38.89f, 1.0f / 41.20f, 1.0f / 43.65f, 1.0f / 46.25f, 1.0f / 49.00f, 1.0f / 51.91f, 1.0f / 55.00f, 1.0f / 58.27f, 1.0f / 61.74f, // Octave 1 414 | 1.0f / 65.41f, 1.0f / 69.30f, 1.0f / 73.42f, 1.0f / 77.78f, 1.0f / 82.41f, 1.0f / 87.31f, 1.0f / 92.50f, 1.0f / 98.00f, 1.0f / 103.83f, 1.0f / 110.00f, 1.0f / 116.54f, 1.0f / 123.47f, // Octave 2 415 | 1.0f / 130.81f, 1.0f / 138.59f, 1.0f / 146.83f, 1.0f / 155.56f, 1.0f / 164.81f, 1.0f / 174.62f, 1.0f / 185.00f, 1.0f / 196.00f, 1.0f / 207.65f, 1.0f / 220.00f, 1.0f / 233.08f, 1.0f / 246.94f, // Octave 3 416 | 1.0f / 261.63f, 1.0f / 277.18f, 1.0f / 293.67f, 1.0f / 311.13f, 1.0f / 329.63f, 1.0f / 349.23f, 1.0f / 370.00f, 1.0f / 392.00f, 1.0f / 415.31f, 1.0f / 440.00f, 1.0f / 466.17f, 1.0f / 493.89f, // Octave 4 417 | 1.0f / 523.25f, 1.0f / 554.37f, 1.0f / 587.33f, 1.0f / 622.26f, 1.0f / 659.26f, 1.0f / 698.46f, 1.0f / 739.99f, 1.0f / 783.99f, 1.0f / 830.61f, 1.0f / 880.00f, 1.0f / 932.33f, 1.0f / 987.77f, // Octave 5 418 | 1.0f / 1046.51f, 1.0f / 1108.74f, 1.0f / 1174.67f, 1.0f / 1244.51f, 1.0f / 1318.52f, 1.0f / 1396.92f, 1.0f / 1479.99f, 1.0f / 1567.99f, 1.0f / 1661.23f, 1.0f / 1760.01f, 1.0f / 1864.66f, 1.0f / 1975.54f, // Octave 6 419 | 1.0f / 2093.02f, 1.0f / 2217.47f, 1.0f / 2349.33f, 1.0f / 2489.03f, 1.0f / 2637.03f, 1.0f / 2793.84f, 1.0f / 2959.97f, 1.0f / 3135.98f, 1.0f / 3322.45f, 1.0f / 3520.02f, 1.0f / 3729.33f, 1.0f / 3951.09f, // Octave 7 420 | }; 421 | // clang-format on 422 | --------------------------------------------------------------------------------