From 8c676d8e71e6ccfdbf5a969ed8075d91fb0ee13d Mon Sep 17 00:00:00 2001 From: Rhys Weatherley Date: Wed, 16 May 2012 11:17:59 +1000 Subject: [PATCH] Melody class for playing simple tunes using tone() --- doc/Doxyfile | 2 +- libraries/Melody/Melody.cpp | 219 ++++++++++++++++++ libraries/Melody/Melody.h | 155 +++++++++++++ .../Melody/examples/PlayTone/PlayTone.pde | 19 ++ libraries/Melody/keywords.txt | 8 + 5 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 libraries/Melody/Melody.cpp create mode 100644 libraries/Melody/Melody.h create mode 100644 libraries/Melody/examples/PlayTone/PlayTone.pde create mode 100644 libraries/Melody/keywords.txt diff --git a/doc/Doxyfile b/doc/Doxyfile index b41daaaf..b578e86b 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -610,7 +610,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../libraries/FreetronicsLCD ../libraries/BlinkLED ../libraries/BitBangI2C . +INPUT = ../libraries/FreetronicsLCD ../libraries/BlinkLED ../libraries/BitBangI2C ../libraries/Melody . # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/libraries/Melody/Melody.cpp b/libraries/Melody/Melody.cpp new file mode 100644 index 00000000..5b2d88a6 --- /dev/null +++ b/libraries/Melody/Melody.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2012 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "Melody.h" +#include + +/** + * \class Melody Melody.h + * \brief Plays a melody on a digital output pin using tone(). + * + * The following example plays a simple tone three times on digital pin 8: + * + * \code + * #include + * + * int notes[] = { + * NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, + * NOTE_REST, NOTE_B3, NOTE_C4, NOTE_REST + * }; + * byte lengths[] = {4, 8, 8, 4, 4, 4, 4, 4, 2}; + * + * Melody melody(8); + * + * void setup() { + * melody.setMelody(notes, lengths, sizeof(lengths)); + * melody.setLoopCount(3); + * melody.play(); + * } + * + * void loop() { + * melody.run(); + * } + * \endcode + * + * The \c notes array contains the frequency of the notes to be played, + * with the special value \c NOTE_REST indicating a rest where no notes + * are playing. The \c lengths array contains the lengths of each of the + * notes; a value of 4 indicates a quarter note, a value of 8 indicates + * an eighth note, etc. + * + * The run() method must be called from the application's main + * loop() method to ensure that the melody advances from + * one note to the next. It will not * block the application while + * notes are playing. + * + * The number of loops can also be specified with setLoopDuration() which + * sets a maximum amount of time that the melody will play before stopping. + * The following example plays the melody for no more than 60 seconds: + * + * \code + * void setup() { + * melody.setMelody(notes, lengths, sizeof(lengths)); + * melody.setLoopDuration(60000UL); + * melody.play(); + * } + * \endcode + */ + +/** + * \brief Constructs a new melody playing object for \a pin. + */ +Melody::Melody(uint8_t pin) + : _pin(pin) + , playing(false) + , _loopCount(0) + , loopsLeft(0) + , notes(0) + , lengths(0) + , size(0) + , posn(0) + , duration(0) + , startNote(0) +{ +} + +/** + * \fn bool Melody::isPlaying() const + * \brief Returns true if the melody is currently playing; false if not. + */ + +/** + * \fn int Melody::loopCount() const + * \brief Returns the number of times the melody should loop before stopping. + * + * The default value is zero, indicating that the melody will loop + * indefinitely. + * + * \sa setLoopCount(), setLoopDuration(), play() + */ + +/** + * \fn void Melody::setLoopCount(int count) + * \brief Sets the number of times the melody should loop to \a count. + * + * If \a count is zero, then the melody will loop indefinitely. + * + * \sa loopCount(), setLoopDuration() + */ + +/** + * \brief Sets the maximum number of loops to last no longer than \a ms milliseconds. + * + * This function must be called after the melody is specified with setMelody() + * as it uses the length of the melody and \a ms to determine the loopCount(). + * + * \sa loopCount(), setLoopCount() + */ +void Melody::setLoopDuration(unsigned long ms) +{ + unsigned long duration = 0; + for (unsigned int index = 0; index < size; ++index) + duration += (1000 / lengths[index]) * 13 / 10; + _loopCount = (int)(ms / duration); + if (!_loopCount) + _loopCount = 1; // Play the melody at least once. +} + +/** + * \brief Starts playing the melody, or restarts it if already playing. + * + * \sa setMelody(), stop(), loopCount() + */ +void Melody::play() +{ + stop(); + if (size == 0) + return; // No melody to play. + loopsLeft = _loopCount; + posn = 0; + playing = true; + nextNote(); +} + +/** + * \brief Stops playing the melody. + * + * \sa play() + */ +void Melody::stop() +{ + if (!playing) + return; + playing = false; + noTone(_pin); +} + +/** + * \brief Sets the melody to the \a size elements of \a notes and \a lengths. + * + * If a melody is currently playing, then this function will stop playback. + * + * The \a notes array contains the frequency of the notes to be played, + * with the special value \c NOTE_REST indicating a rest where no notes + * are playing. The \a lengths array contains the lengths of each of the + * notes; a value of 4 indicates a quarter note, a value of 8 indicates + * an eighth note, etc. + * + * \sa play() + */ +void Melody::setMelody(const int *notes, const uint8_t *lengths, unsigned int size) +{ + stop(); + this->notes = notes; + this->lengths = lengths; + this->size = size; +} + +/** + * \brief Runs the melody control loop. + * + * This function must be called by the application's main loop() + * function to cause the melody to advance from note to note. It will not + * block the application while notes are playing. + */ +void Melody::run() +{ + if (!playing) + return; + if ((millis() - startNote) >= duration) { + noTone(_pin); + nextNote(); + } +} + +void Melody::nextNote() +{ + if (posn >= size) { + if (loopsLeft != 0 && --loopsLeft <= 0) { + stop(); + return; + } + posn = 0; + } + duration = 1000 / lengths[posn]; + if (notes[posn] != NOTE_REST) + tone(_pin, notes[posn], duration); + ++posn; + duration = duration * 13 / 10; // i.e., duration * 1.3 + startNote = millis(); +} diff --git a/libraries/Melody/Melody.h b/libraries/Melody/Melody.h new file mode 100644 index 00000000..f14a2819 --- /dev/null +++ b/libraries/Melody/Melody.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2012 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef Melody_h +#define Melody_h + +#include + +// Note frequencies from http://arduino.cc/en/Tutorial/Tone +#define NOTE_B0 31 +#define NOTE_C1 33 +#define NOTE_CS1 35 +#define NOTE_D1 37 +#define NOTE_DS1 39 +#define NOTE_E1 41 +#define NOTE_F1 44 +#define NOTE_FS1 46 +#define NOTE_G1 49 +#define NOTE_GS1 52 +#define NOTE_A1 55 +#define NOTE_AS1 58 +#define NOTE_B1 62 +#define NOTE_C2 65 +#define NOTE_CS2 69 +#define NOTE_D2 73 +#define NOTE_DS2 78 +#define NOTE_E2 82 +#define NOTE_F2 87 +#define NOTE_FS2 93 +#define NOTE_G2 98 +#define NOTE_GS2 104 +#define NOTE_A2 110 +#define NOTE_AS2 117 +#define NOTE_B2 123 +#define NOTE_C3 131 +#define NOTE_CS3 139 +#define NOTE_D3 147 +#define NOTE_DS3 156 +#define NOTE_E3 165 +#define NOTE_F3 175 +#define NOTE_FS3 185 +#define NOTE_G3 196 +#define NOTE_GS3 208 +#define NOTE_A3 220 +#define NOTE_AS3 233 +#define NOTE_B3 247 +#define NOTE_C4 262 +#define NOTE_CS4 277 +#define NOTE_D4 294 +#define NOTE_DS4 311 +#define NOTE_E4 330 +#define NOTE_F4 349 +#define NOTE_FS4 370 +#define NOTE_G4 392 +#define NOTE_GS4 415 +#define NOTE_A4 440 +#define NOTE_AS4 466 +#define NOTE_B4 494 +#define NOTE_C5 523 +#define NOTE_CS5 554 +#define NOTE_D5 587 +#define NOTE_DS5 622 +#define NOTE_E5 659 +#define NOTE_F5 698 +#define NOTE_FS5 740 +#define NOTE_G5 784 +#define NOTE_GS5 831 +#define NOTE_A5 880 +#define NOTE_AS5 932 +#define NOTE_B5 988 +#define NOTE_C6 1047 +#define NOTE_CS6 1109 +#define NOTE_D6 1175 +#define NOTE_DS6 1245 +#define NOTE_E6 1319 +#define NOTE_F6 1397 +#define NOTE_FS6 1480 +#define NOTE_G6 1568 +#define NOTE_GS6 1661 +#define NOTE_A6 1760 +#define NOTE_AS6 1865 +#define NOTE_B6 1976 +#define NOTE_C7 2093 +#define NOTE_CS7 2217 +#define NOTE_D7 2349 +#define NOTE_DS7 2489 +#define NOTE_E7 2637 +#define NOTE_F7 2794 +#define NOTE_FS7 2960 +#define NOTE_G7 3136 +#define NOTE_GS7 3322 +#define NOTE_A7 3520 +#define NOTE_AS7 3729 +#define NOTE_B7 3951 +#define NOTE_C8 4186 +#define NOTE_CS8 4435 +#define NOTE_D8 4699 +#define NOTE_DS8 4978 + +// Special note value that indicates a rest. +#define NOTE_REST 0 + +class Melody { +public: + Melody(uint8_t pin); + + bool isPlaying() const { return playing; } + + int loopCount() const { return _loopCount; } + void setLoopCount(int count) { _loopCount = count; } + + void setLoopDuration(unsigned long ms); + + void play(); + void stop(); + + void setMelody(const int *notes, const uint8_t *lengths, unsigned int size); + + void run(); + +private: + uint8_t _pin; + bool playing; + int _loopCount; + int loopsLeft; + const int *notes; + const uint8_t *lengths; + unsigned int size; + unsigned int posn; + unsigned long duration; + unsigned long startNote; + + void nextNote(); +}; + +#endif diff --git a/libraries/Melody/examples/PlayTone/PlayTone.pde b/libraries/Melody/examples/PlayTone/PlayTone.pde new file mode 100644 index 00000000..98fd5890 --- /dev/null +++ b/libraries/Melody/examples/PlayTone/PlayTone.pde @@ -0,0 +1,19 @@ +#include + +int notes[] = { + NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, + NOTE_REST, NOTE_B3, NOTE_C4, NOTE_REST +}; +byte lengths[] = {4, 8, 8, 4, 4, 4, 4, 4, 2}; + +Melody melody(8); + +void setup() { + melody.setMelody(notes, lengths, sizeof(lengths)); + melody.setLoopCount(3); + melody.play(); +} + +void loop() { + melody.run(); +} diff --git a/libraries/Melody/keywords.txt b/libraries/Melody/keywords.txt new file mode 100644 index 00000000..cfd30942 --- /dev/null +++ b/libraries/Melody/keywords.txt @@ -0,0 +1,8 @@ +Melody KEYWORD1 + +play KEYWORD2 +stop KEYWORD2 +setMelody KEYWORD2 +loopCount KEYWORD2 +setLoopCount KEYWORD2 +isPlaying KEYWORD2