├── README.md └── rtttl ├── examples ├── ProgmemSong │ └── ProgmemSong.ino ├── RamSong │ └── RamSong.ino └── ToneSong │ └── ToneSong.ino ├── notes.h └── rtttl.h /README.md: -------------------------------------------------------------------------------- 1 | # Non-blocking playback of RTTTL melodies 2 | 3 | RTTTL is Nokia's ***R***ing ***T***one ***T***ext ***T***ransfer ***L***anguage, from when mobile phones could only play a single, squeaky note at a time. People could text each other ringtones like this example... 4 | 5 | ```BringMeSunshine:d=8,o=5,b=140:c.6,b.,4a.,2e.,c.6,b.,2a,p,c.6,b.,4a,16p,2f.,16p,c.6,b.,2a,p, e.6,d#.6,4d.6,c6,b.,4a#.,16p,g.,g#.,a.,f.,a,e.6,4d.6,16p,c.6,b.,4a.,d.6,c.6,4b.,e.6,e.6,2c.6``` 6 | 7 | Combine the term "RTTTL" and your favourite old-skool tune in a google search for more examples. 8 | 9 | This Arduino library provides non-blocking playback of RTTTL melodies using a Piezoelectric transducer. Non-blocking means it can update the tones being played by an ATMEGA chip (controlled by [hardware timers](https://learn.adafruit.com/multi-tasking-the-arduino-part-2/timers)) to match with a loaded melody, allowing other functions continue to be performed by the microcontroller alongside the melody. 10 | 11 | It has been tested on [Shrimp](http://start.shrimping.it/project/shrimp/), and Arduino Uno. Wire it like the Piezo part of our [Alarm Clock build](http://start.shrimping.it/project/alarmclock/build.html#step12) (Shrimp) or [this 'Tone' example](https://www.arduino.cc/en/Tutorial/ToneMelody) (Arduino). [Example sketches](https://github.com/cefn/non-blocking-rtttl-arduino/tree/master/rtttl/examples) showing RTTTL playback are provided. 12 | 13 | If you have followed our [@ShrimpingIt configuration steps](http://start.shrimping.it/project/shrimp/program.html) then the RTTTL library is already installed. Otherwise, follow [these instructions](https://www.arduino.cc/en/Guide/Libraries) to use the library on its own. 14 | 15 | Although based on [ponty's arduino-rttl-player](https://github.com/ponty/arduino-rtttl-player) changing it to non-blocking playback leaves very little left (@ponty's was a blocking library). Thanks to @ponty for all the RTTTL decoding logic, and the stimulus to create this library. 16 | 17 | # Key functions: 18 | 19 | * **pollSong()** since the hardware handles timer-driven playback, you can continue to service other microcontroller functions between calls to pollSong(), which will only change the tone output if it is time to do so (according to the melody) and returns immediately. 20 | * **stepSong()** this progresses the melody, but also blocks until a single note is finished. It is useful, for example, to write simple code which couples lighting effects to notes. 21 | * **finishSong()** this plays out the whole melody once, blocking until it's finished. It's useful if a tune needs to be completed before the next action should be taken (e.g. playout when a game has ended and before it resets). 22 | 23 | ***N.B. If you use this non-blocking strategy, ```pollSong()``` should be called regularly alongside the other operations in your sketch to ensure the melody timing is correct.*** 24 | -------------------------------------------------------------------------------- /rtttl/examples/ProgmemSong/ProgmemSong.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //const char song_P[] PROGMEM = "Ghostbusters:d=4,o=5,b=180:4c5,4c5,8e5,8f5,8g5,8p, 4a#5,4a#5,4f5,4f5,4c5,4c5,8e5,8f5,8g5,8p,4a#5,4a#5,4f5"; 4 | //const char song_P[] PROGMEM = "dkong_level:d=4,o=5,b=200:c6,32p,8d6,8p,f6,8p,8d6,16p,8c6,16p,8d6,16p,a#"; 5 | //const char song_P[] PROGMEM = "PacMan:b=160:32b,32p,32b6,32p,32f#6,32p,32d#6,32p,32b6,32f#6,16p,16d#6,16p,32c6,32p,32c7,32p,32g6,32p,32e6,32p,32c7,32g6,16p,16e6,16p,32b,32p,32b6,32p,32f#6,32p,32d#6,32p,32b6,32f#6,16p,16d#6,16p,32d#6,32e6,32f6,32p,32f6,32f#6,32g6,32p,32g6,32g#6,32a6,32p,32b.6"; 6 | //const char song_P[] PROGMEM = "DonkeyKong:d=4,o=5,b=200:8a#,8p,8d6,16p,16f.6,16g.6,16f.6,8a#,8p,8d6,16p,16f.6,16g.6,16f.6,8a#,8p,8d6,16p,16f.6,16g.6,16f.6,8a#,8p,8d6,16p,16f.6,16g.6,16f.6"; 7 | //const char song_P[] PROGMEM = "Dambuste:d=4,o=5,b=63:4f6,8a#6,8f6,8f6,16d#6,16d6,8d#6,8f6,4d6,8f6,8d6,8d6,16c6,16a#,8a,8c6,8a#.,16c6,8d6,8g6,8f.6,16d6,4f6,8c6,8f6,16g6,16a6,8a#6,4a6,4p"; 8 | //const char song[] = "AxelF:d=4,o=5,b=125:32p,8g,8p,16a#.,8p,16g,16p,16g,8c6, 8g,8f,8g,8p,16d.6,8p,16g,16p,16g,8d#6,8d6,8a#,8g,8d6,8g6, 16g,16f,16p,16f,8d,8a#,2g,p,SS,16f6,8d6,8c6,8a#,g,8a#.,16g, 16p,16g,8c6,8g,8f,g,8d.6,16g,16p,16g,8d#6,8d6,8a#,8g,8d6, 8g6,16g,16f,16p,16f,8d,8a#,2g"; 9 | const char song_P[] PROGMEM = "Tetris:d=4,o=5,b=200:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a,8a,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c6,e6,8d6,8c6,b,8b,8c6,d6,e6,c6,a,a"; 10 | 11 | unsigned long printedTime = -1; 12 | unsigned long printedPeriod = 500; 13 | 14 | ProgmemPlayer player(13); 15 | 16 | void setup(void) 17 | { 18 | Serial.begin(115200); 19 | player.setSong(song_P); 20 | } 21 | 22 | void loop(void) 23 | { 24 | 25 | Serial.println("First Play"); 26 | 27 | //play the song for the first time 28 | player.finishSong(); 29 | 30 | Serial.println("Song rewound automatically"); 31 | 32 | //play the tune again, this time blocking per note 33 | while(player.stepSong()){ 34 | Serial.println("Second Play"); 35 | } 36 | 37 | Serial.println("Song rewound automatically"); 38 | 39 | //play the tune with no blocking 40 | while(player.pollSong()){ 41 | tryPrint("Third Play"); 42 | } 43 | 44 | Serial.println("Play has ended"); 45 | while(true){ 46 | //keep the thread in this loop - circuit now silent 47 | } 48 | 49 | } 50 | 51 | void tryPrint(String msg){ 52 | if(millis() - printedTime > printedPeriod){ 53 | Serial.println(msg); 54 | printedTime = millis(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rtttl/examples/RamSong/RamSong.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //note this is not declared in PROGMEM 4 | const char song[] = "AxelF:d=4,o=5,b=125:32p,8g,8p,16a#.,8p,16g,16p,16g,8c6,8g,8f,8g,8p,16d.6,8p,16g,16p,16g,8d#6,8d6,8a#,8g,8d6,8g6,16g,16f,16p,16f,8d,8a#,2g,p,SS,16f6,8d6,8c6,8a#,g,8a#.,16g,16p,16g,8c6,8g,8f,g,8d.6,16g,16p,16g,8d#6,8d6,8a#,8g,8d6,8g6,16g,16f,16p,16f,8d,8a#,2g"; 5 | 6 | unsigned long printedTime = -1; 7 | unsigned long printedPeriod = 500; 8 | 9 | RamPlayer player(13); 10 | 11 | void setup(void) 12 | { 13 | Serial.begin(115200); 14 | player.setSong(song); 15 | } 16 | 17 | void loop(void) 18 | { 19 | 20 | Serial.println("First Play"); 21 | 22 | //play the song for the first time 23 | player.finishSong(); 24 | 25 | Serial.println("Song rewound automatically"); 26 | 27 | //play the tune again, this time blocking per note 28 | while(player.stepSong()){ 29 | Serial.println("Second Play"); 30 | } 31 | 32 | Serial.println("Song rewound automatically"); 33 | 34 | //play the tune with no blocking 35 | while(player.pollSong()){ 36 | tryPrint("Third Play"); 37 | } 38 | 39 | Serial.println("Play has ended"); 40 | while(true){ 41 | //keep the thread in this loop - circuit now silent 42 | } 43 | 44 | } 45 | 46 | void tryPrint(String msg){ 47 | if(millis() - printedTime > printedPeriod){ 48 | Serial.println(msg); 49 | printedTime = millis(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rtttl/examples/ToneSong/ToneSong.ino: -------------------------------------------------------------------------------- 1 | //Identical to ProgmemSong.pde 2 | //except the external Tone library is used 3 | #include 4 | 5 | #include 6 | 7 | const char song_P[] PROGMEM = "PacMan:b=160:32b,32p,32b6,32p,32f#6,32p,32d#6,32p,32b6,32f#6,16p,16d#6,16p,32c6,32p,32c7,32p,32g6,32p,32e6,32p,32c7,32g6,16p,16e6,16p,32b,32p,32b6,32p,32f#6,32p,32d#6,32p,32b6,32f#6,16p,16d#6,16p,32d#6,32e6,32f6,32p,32f6,32f#6,32g6,32p,32g6,32g#6,32a6,32p,32b.6"; 8 | 9 | unsigned long printedTime = -1; 10 | unsigned long printedPeriod = 500; 11 | 12 | ProgmemPlayer player(13); 13 | 14 | void setup(void) 15 | { 16 | Serial.begin(115200); 17 | player.setSong(song_P); 18 | } 19 | 20 | void loop(void) 21 | { 22 | 23 | Serial.println("First Play"); 24 | 25 | //play the song for the first time 26 | player.finishSong(); 27 | 28 | Serial.println("Song rewound automatically"); 29 | 30 | //play the tune again, this time blocking per note 31 | while(player.stepSong()){ 32 | Serial.println("Second Play"); 33 | } 34 | 35 | Serial.println("Song rewound automatically"); 36 | 37 | //play the tune with no blocking 38 | while(player.pollSong()){ 39 | tryPrint("Third Play"); 40 | } 41 | 42 | Serial.println("Play has ended"); 43 | while(true){ 44 | //keep the thread in this loop - circuit now silent 45 | } 46 | 47 | } 48 | 49 | void tryPrint(String msg){ 50 | if(millis() - printedTime > printedPeriod){ 51 | Serial.println(msg); 52 | printedTime = millis(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /rtttl/notes.h: -------------------------------------------------------------------------------- 1 | #define NOTE_B0 31 2 | #define NOTE_C1 33 3 | #define NOTE_CS1 35 4 | #define NOTE_D1 37 5 | #define NOTE_DS1 39 6 | #define NOTE_E1 41 7 | #define NOTE_F1 44 8 | #define NOTE_FS1 46 9 | #define NOTE_G1 49 10 | #define NOTE_GS1 52 11 | #define NOTE_A1 55 12 | #define NOTE_AS1 58 13 | #define NOTE_B1 62 14 | #define NOTE_C2 65 15 | #define NOTE_CS2 69 16 | #define NOTE_D2 73 17 | #define NOTE_DS2 78 18 | #define NOTE_E2 82 19 | #define NOTE_F2 87 20 | #define NOTE_FS2 93 21 | #define NOTE_G2 98 22 | #define NOTE_GS2 104 23 | #define NOTE_A2 110 24 | #define NOTE_AS2 117 25 | #define NOTE_B2 123 26 | #define NOTE_C3 131 27 | #define NOTE_CS3 139 28 | #define NOTE_D3 147 29 | #define NOTE_DS3 156 30 | #define NOTE_E3 165 31 | #define NOTE_F3 175 32 | #define NOTE_FS3 185 33 | #define NOTE_G3 196 34 | #define NOTE_GS3 208 35 | #define NOTE_A3 220 36 | #define NOTE_AS3 233 37 | #define NOTE_B3 247 38 | #define NOTE_C4 262 39 | #define NOTE_CS4 277 40 | #define NOTE_D4 294 41 | #define NOTE_DS4 311 42 | #define NOTE_E4 330 43 | #define NOTE_F4 349 44 | #define NOTE_FS4 370 45 | #define NOTE_G4 392 46 | #define NOTE_GS4 415 47 | #define NOTE_A4 440 48 | #define NOTE_AS4 466 49 | #define NOTE_B4 494 50 | #define NOTE_C5 523 51 | #define NOTE_CS5 554 52 | #define NOTE_D5 587 53 | #define NOTE_DS5 622 54 | #define NOTE_E5 659 55 | #define NOTE_F5 698 56 | #define NOTE_FS5 740 57 | #define NOTE_G5 784 58 | #define NOTE_GS5 831 59 | #define NOTE_A5 880 60 | #define NOTE_AS5 932 61 | #define NOTE_B5 988 62 | #define NOTE_C6 1047 63 | #define NOTE_CS6 1109 64 | #define NOTE_D6 1175 65 | #define NOTE_DS6 1245 66 | #define NOTE_E6 1319 67 | #define NOTE_F6 1397 68 | #define NOTE_FS6 1480 69 | #define NOTE_G6 1568 70 | #define NOTE_GS6 1661 71 | #define NOTE_A6 1760 72 | #define NOTE_AS6 1865 73 | #define NOTE_B6 1976 74 | #define NOTE_C7 2093 75 | #define NOTE_CS7 2217 76 | #define NOTE_D7 2349 77 | #define NOTE_DS7 2489 78 | #define NOTE_E7 2637 79 | #define NOTE_F7 2794 80 | #define NOTE_FS7 2960 81 | #define NOTE_G7 3136 82 | #define NOTE_GS7 3322 83 | #define NOTE_A7 3520 84 | #define NOTE_AS7 3729 85 | #define NOTE_B7 3951 86 | #define NOTE_C8 4186 87 | #define NOTE_CS8 4435 88 | #define NOTE_D8 4699 89 | #define NOTE_DS8 4978 90 | -------------------------------------------------------------------------------- /rtttl/rtttl.h: -------------------------------------------------------------------------------- 1 | /** TODO:CH 2 | * Introduce skip logic which handles any whitespace insertion; cases skip("o=") ["o=", "o = "] or skip(",") [",", " , "]. 3 | * Introduce note detection which is case-insensitive 4 | * Handle variations in sequence by permitting valid instructions in any sequence 5 | * */ 6 | 7 | #pragma once 8 | 9 | #if defined(ARDUINO) && ARDUINO >= 100 10 | # include "Arduino.h" 11 | #else 12 | # include "WProgram.h" 13 | # include "pins_arduino.h" 14 | #endif 15 | 16 | #include 17 | #include 18 | 19 | #define isdigit(n) (n >= '0' && n <= '9') 20 | 21 | #ifndef NULL 22 | #define NULL 0 23 | #endif 24 | 25 | //TODO CH incorporate alternative return values - not boolean but various integers 26 | //as below - allowing true/false logic to remain unchanged 27 | #define SONG_FINISHED 0 28 | #define STEP_UNCHANGED 1 29 | #define STEP_NOTE 2 30 | #define STEP_REST 3 31 | 32 | const uint16_t notes[] PROGMEM = 33 | { 0, // 34 | NOTE_C4, // 35 | NOTE_CS4, // 36 | NOTE_D4, // 37 | NOTE_DS4, // 38 | NOTE_E4, // 39 | NOTE_F4, // 40 | NOTE_FS4, // 41 | NOTE_G4, // 42 | NOTE_GS4, // 43 | NOTE_A4, // 44 | NOTE_AS4, // 45 | NOTE_B4, // 46 | 47 | NOTE_C5, // 48 | NOTE_CS5, // 49 | NOTE_D5, // 50 | NOTE_DS5, // 51 | NOTE_E5, // 52 | NOTE_F5, // 53 | NOTE_FS5, // 54 | NOTE_G5, // 55 | NOTE_GS5, // 56 | NOTE_A5, // 57 | NOTE_AS5, // 58 | NOTE_B5, // 59 | 60 | NOTE_C6, // 61 | NOTE_CS6, // 62 | NOTE_D6, // 63 | NOTE_DS6, // 64 | NOTE_E6, // 65 | NOTE_F6, // 66 | NOTE_FS6, // 67 | NOTE_G6, // 68 | NOTE_GS6, // 69 | NOTE_A6, // 70 | NOTE_AS6, // 71 | NOTE_B6, // 72 | 73 | NOTE_C7, // 74 | NOTE_CS7, // 75 | NOTE_D7, // 76 | NOTE_DS7, // 77 | NOTE_E7, // 78 | NOTE_F7, // 79 | NOTE_FS7, // 80 | NOTE_G7, // 81 | NOTE_GS7, // 82 | NOTE_A7, // 83 | NOTE_AS7, // 84 | NOTE_B7, // 85 | 86 | 2*NOTE_C7, // 87 | 2*NOTE_CS7, // 88 | 2*NOTE_D7, // 89 | 2*NOTE_DS7, // 90 | 2*NOTE_E7, // 91 | 2*NOTE_F7, // 92 | 2*NOTE_FS7, // 93 | 2*NOTE_G7, // 94 | 2*NOTE_GS7, // 95 | 2*NOTE_A7, // 96 | 2*NOTE_AS7, // 97 | 2*NOTE_B7, // 98 | 0 99 | }; 100 | 101 | class Player { 102 | 103 | protected: 104 | 105 | uint8_t tonePin; //the pin to which the speaker is attached 106 | 107 | int nextPos; //the integer position of the next byte to read from the song stream 108 | 109 | unsigned long periodStart; //when the last note or rest was started 110 | unsigned long periodLength; //the length of the last note or rest 111 | 112 | bool silent; //indicates whether the tone is currently playing or not 113 | 114 | int bpm; //the beats per minute (speed) of the song 115 | unsigned long wholenote; //duration of a whole note in milliseconds 116 | byte defaultNoteDenominator; //default fraction of a wholenote (if not specified against a note) 117 | 118 | byte defaultOctave; //the default octave (if not specified against a note) 119 | uint8_t transposeOctaves; //always transpose by this number of octaves before playing notes 120 | 121 | #ifdef _Tone_h 122 | Tone m_tone; 123 | #endif 124 | 125 | public: 126 | 127 | Player(uint8_t tonePin){ 128 | this->tonePin = tonePin; 129 | this->nextPos = 0; 130 | this->defaultNoteDenominator = 4; 131 | this->defaultOctave = 6; 132 | this->bpm = 63; 133 | this->silent = true; 134 | } 135 | 136 | void transpose(int octaves){ 137 | this->transposeOctaves = octaves; 138 | } 139 | 140 | bool initSong() 141 | { 142 | 143 | int num; //a temporary integer working space for various calculations 144 | 145 | //Serial.println("initSong() started"); 146 | 147 | //TODO CH, figure out if this can be called multiple times? THink not, could explain some bugs 148 | #ifdef _Tone_h 149 | this->m_tone.begin(tonePin); 150 | #endif 151 | 152 | //prepare variables which will keep track of place in the song 153 | periodStart = -1; 154 | nextPos = 0; 155 | 156 | // format: d=N,o=N,b=NNN: 157 | // find the start (skip name, etc) 158 | 159 | while (peek_byte() != ':'){ 160 | pop_byte(); //(ignore name characters) 161 | } 162 | 163 | pop_byte(); // skip the ':' character 164 | 165 | //Serial.println("END NAME"); 166 | 167 | // get default duration 168 | if (peek_byte() == 'd') 169 | { 170 | pop_byte(); 171 | pop_byte(); // skip "d=" 172 | num = 0; 173 | while (isdigit(peek_byte())) 174 | { 175 | num = (num * 10) + (pop_byte() - '0'); 176 | } 177 | if (num > 0) 178 | defaultNoteDenominator = num; 179 | pop_byte(); // skip comma 180 | } 181 | 182 | //Serial.println("END DURATION"); 183 | 184 | // get default octave 185 | if (peek_byte() == 'o') 186 | { 187 | pop_byte(); 188 | pop_byte(); // skip "o=" 189 | num = pop_byte() - '0'; 190 | if (num >= 3 && num <= 7) 191 | defaultOctave = num; 192 | pop_byte(); // skip comma 193 | } 194 | 195 | //Serial.println("END DEF OCT"); 196 | 197 | // get BPM 198 | if (peek_byte() == 'b') 199 | { 200 | pop_byte(); 201 | pop_byte(); // skip "b=" 202 | num = 0; 203 | while (isdigit(peek_byte())) 204 | { 205 | num = (num * 10) + (pop_byte() - '0'); 206 | } 207 | bpm = num; 208 | pop_byte(); // skip colon 209 | } 210 | 211 | //Serial.println("END BPM"); 212 | 213 | // BPM usually expresses the number of quarter notes per minute 214 | wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds) 215 | 216 | return true; 217 | 218 | } 219 | 220 | bool stepDue(){ 221 | return periodStart == -1 || (millis() - periodStart) > periodLength; 222 | } 223 | 224 | /** If a change in tone, or a rest is due, then this 225 | * function will complete the change, and then return immediately 226 | * with any tone begun as part of the rtttl melody continuing 227 | * to play. This should be executed very regularly, without 228 | * any long delays between calls to maintain the impression 229 | * of a continuous melody. */ 230 | bool pollSong() 231 | { 232 | //see if a new Step needs to be processed 233 | if(stepDue()){ 234 | //process next Step, and check if it is the last 235 | if(!nextStep()){ 236 | initSong(); 237 | return false; //no more steps in the melody 238 | } 239 | } 240 | return true; //note was played 241 | } 242 | 243 | /** Triggers the next individual step (note or rest) and blocks 244 | * until the next is due. Notes keep playing, and rests 245 | * stay silent even after this has returned. */ 246 | bool stepSong(){ 247 | if(!nextStep()){ 248 | initSong(); 249 | return false; //no more steps in the melody 250 | } 251 | else{ //a Step just started 252 | awaitStepDue(); 253 | return true; //step time has finished 254 | } 255 | } 256 | 257 | void awaitStepDue(){ 258 | while(!stepDue()){ //block while the Step is finished 259 | delay(1); 260 | } 261 | } 262 | 263 | /** Plays the whole tune, blocking until it is finished. */ 264 | void finishSong(){ 265 | while(this->stepSong()){ 266 | //do nothing 267 | } 268 | } 269 | 270 | /** Start a beep which will finish when pollBeep() 271 | * returns false. N.B. you must keep polling from time 272 | * to time. */ 273 | void beep(uint16_t freq, unsigned long length){ 274 | periodStart = millis(); 275 | periodLength = length; 276 | _tone(freq); 277 | } 278 | 279 | /** Start a beep which will only finish when you call silence() */ 280 | void beep(uint16_t freq){ 281 | periodStart = millis(); 282 | periodLength = -1; 283 | _tone(freq); 284 | } 285 | 286 | /** Returns true as long as the beep is still playing, 287 | * false otherwise. N.B. this will always return true 288 | * after calling beep(...) without a duration argument. */ 289 | bool pollBeep(){ 290 | if(stepDue()){ 291 | silence(); 292 | return false; 293 | } 294 | return true; 295 | } 296 | 297 | void silence(){ 298 | periodStart = -1; 299 | periodLength = -1; 300 | _noTone(); 301 | } 302 | 303 | bool isSilent(){ 304 | return silent; 305 | } 306 | 307 | private: 308 | 309 | 310 | #ifdef NewTone_h 311 | void _tone(uint16_t freq) 312 | { 313 | NewTone(this->tonePin, freq); 314 | silent = false; 315 | } 316 | 317 | void _noTone() 318 | { 319 | noNewTone(this->tonePin); 320 | silent = true; 321 | } 322 | #else 323 | #ifdef _Tone_h 324 | void _tone(uint16_t freq) 325 | { 326 | this->m_tone.play(freq); 327 | silent = false; 328 | } 329 | 330 | void _noTone() 331 | { 332 | this->m_tone.stop(); 333 | silent = true; 334 | } 335 | #else 336 | void _tone(uint16_t freq) 337 | { 338 | tone(this->tonePin, freq); 339 | silent = false; 340 | } 341 | 342 | void _noTone() 343 | { 344 | noTone(this->tonePin); 345 | silent = true; 346 | } 347 | #endif 348 | #endif 349 | 350 | 351 | bool nextStep() 352 | { 353 | 354 | //Serial.println("nextStep() started"); 355 | //remember when it started 356 | periodStart = millis(); 357 | 358 | byte note; 359 | byte scale; 360 | 361 | // read a single byte - test for end of song 362 | if(!peek_byte()){ 363 | _noTone(); 364 | return false; 365 | } 366 | 367 | //Serial.println("SONG CONTINUING"); 368 | 369 | // first, get note duration, if available 370 | int denominator = 0; 371 | while (isdigit(peek_byte())) 372 | { 373 | denominator = (denominator * 10) + (pop_byte() - '0'); 374 | } 375 | 376 | //Serial.println("END DURATION"); 377 | 378 | if (denominator) 379 | periodLength = wholenote / denominator; 380 | else 381 | periodLength = wholenote / defaultNoteDenominator; // we will need to check if we are a dotted note after 382 | 383 | // now get the note 384 | note = 0; 385 | 386 | switch (pop_byte()) 387 | { 388 | case 'c': 389 | case 'C': 390 | note = 1; 391 | break; 392 | case 'd': 393 | case 'D': 394 | note = 3; 395 | break; 396 | case 'e': 397 | case 'E': 398 | note = 5; 399 | break; 400 | case 'f': 401 | case 'F': 402 | note = 6; 403 | break; 404 | case 'g': 405 | case 'G': 406 | note = 8; 407 | break; 408 | case 'a': 409 | case 'A': 410 | note = 10; 411 | break; 412 | case 'b': 413 | case 'B': 414 | note = 12; 415 | break; 416 | case 'p': 417 | case 'P': 418 | default: 419 | note = 0; 420 | } 421 | 422 | //Serial.println("END NOTE"); 423 | 424 | // now, get optional '#' sharp 425 | if (peek_byte() == '#') 426 | { 427 | pop_byte(); 428 | note++; 429 | } 430 | 431 | //Serial.println("END #"); 432 | 433 | // now, get optional '.' dotted note 434 | if (peek_byte() == '.') 435 | { 436 | pop_byte(); 437 | periodLength += periodLength / 2; 438 | } 439 | 440 | //Serial.println("END ."); 441 | 442 | // now, get scale 443 | if (isdigit(peek_byte())) 444 | { 445 | scale = pop_byte() - '0'; 446 | } 447 | else 448 | { 449 | scale = defaultOctave; 450 | } 451 | 452 | scale += transposeOctaves; 453 | 454 | //Serial.println("END SCALE"); 455 | 456 | if (peek_byte() == ',') 457 | pop_byte(); // skip comma for next note (or we may be at the end) 458 | 459 | // now play the note 460 | 461 | if (note) 462 | { 463 | uint16_t note_word = pgm_read_word(¬es[(scale - 4) * 12 + note]); 464 | _tone(note_word); 465 | //Serial.print("Playing note: "); 466 | //Serial.println(note_word); 467 | } 468 | else 469 | { 470 | //Serial.println("Playing rest"); 471 | _noTone(); 472 | } 473 | 474 | return true; 475 | 476 | } 477 | 478 | char peek_byte(){ 479 | return get_byte(nextPos); 480 | /* 481 | char read = get_byte(nextPos); 482 | Serial.print("Peeked: '"); 483 | Serial.write(read); 484 | Serial.println("'"); 485 | return read; 486 | */ 487 | } 488 | 489 | char pop_byte(){ 490 | get_byte(nextPos++); 491 | /* 492 | char read = get_byte(nextPos++); 493 | //Serial.print("Popped: '"); 494 | //Serial.write(read); 495 | //Serial.println("'"); 496 | return read; 497 | */ 498 | } 499 | 500 | virtual char get_byte(int pos){ return 0; }; 501 | 502 | }; 503 | 504 | class ConstPlayer: public Player{ 505 | 506 | protected: 507 | const char* songStart; //the string containing the song (in RTTTL format) 508 | 509 | public: 510 | 511 | ConstPlayer(uint8_t tonePin) 512 | :Player(tonePin) //call superclass constructor 513 | { 514 | //do nothing 515 | } 516 | 517 | void setSong(const char* song){ 518 | this->songStart = song; 519 | Player::initSong(); 520 | } 521 | 522 | }; 523 | 524 | 525 | class ProgmemPlayer: public ConstPlayer{ 526 | 527 | public: 528 | ProgmemPlayer(uint8_t tonePin) 529 | :ConstPlayer(tonePin) 530 | { 531 | //do nothing 532 | } 533 | 534 | private: 535 | char get_byte(int pos) 536 | { 537 | return pgm_read_byte(songStart + pos); 538 | } 539 | 540 | }; 541 | 542 | class RamPlayer: public ConstPlayer{ 543 | 544 | public: 545 | RamPlayer(uint8_t tonePin) 546 | :ConstPlayer(tonePin) 547 | { 548 | //do nothing 549 | } 550 | 551 | 552 | private: 553 | char get_byte(int pos) 554 | { 555 | return *(songStart + pos); 556 | } 557 | 558 | }; 559 | --------------------------------------------------------------------------------