Music Engine plays Music Macro Language compositions in the background

Dependents:   RETRO_BallsAndPaddle RETRO_BallAndHoles MusicBoxForFathersDay USBSec_mbed-os_dev

Music Engine is a simply library to execute Music Macro Language sequences asynchronously. Learn more about MML on wikipedia http://en.wikipedia.org/wiki/Music_Macro_Language

The following sample plays a simple tune

#include "mbed.h"
#include "MusicEngine.h"

// Play music on MCU Pin 0.18
// The pin should support PWM
MusicEngine music(P0_18);

main()
{
    music.play("T224L8O5CL16>C<P16GP16L8EL16P16>C<GP16L8E.L16P16L8C#L16>C#<P16G#P16L8FL16P16>C#<G#P16L8F.L16P16L8CL16>C<P16GP16L8EL16P16>C<GP16L8E.L16P16D#EFP16FF#GP16GG#AP16L8>C<P8L4>C");
    
    while(1)
    {
    }
}
Committer:
taylorza
Date:
Thu Feb 05 04:18:39 2015 +0000
Revision:
0:d873d5d62d3b
Child:
2:4f7c4255997a
Music Engine Library initial commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
taylorza 0:d873d5d62d3b 1 ///////////////////////////////////////////////////////////////////////////////
taylorza 0:d873d5d62d3b 2 // Retro Music Engine
taylorza 0:d873d5d62d3b 3 // Author: Chris Taylor (taylorza)
taylorza 0:d873d5d62d3b 4
taylorza 0:d873d5d62d3b 5 #include "MusicEngine.h"
taylorza 0:d873d5d62d3b 6 #include <cctype>
taylorza 0:d873d5d62d3b 7
taylorza 0:d873d5d62d3b 8 const float MusicEngine::WHOLE_NOTE_DURATION = 1.0f;
taylorza 0:d873d5d62d3b 9 const float MusicEngine::QUARTER_NOTE_DURATION = MusicEngine::WHOLE_NOTE_DURATION / 4.0f;
taylorza 0:d873d5d62d3b 10 const float MusicEngine::QUARTER_NOTES_PER_MINUTE = 60.0f / MusicEngine::QUARTER_NOTE_DURATION;
taylorza 0:d873d5d62d3b 11
taylorza 0:d873d5d62d3b 12 const float MusicEngine::DEFAULT_TIMING = 7.0f / 8.0f;
taylorza 0:d873d5d62d3b 13 const float MusicEngine::LEGATO_TIMING = 1.0f;
taylorza 0:d873d5d62d3b 14 const float MusicEngine::STACCATO_TIMING = 3.0f / 4.0f;
taylorza 0:d873d5d62d3b 15
taylorza 0:d873d5d62d3b 16 const int MusicEngine::NOTE_REST = 0;
taylorza 0:d873d5d62d3b 17 const int MusicEngine::NOTE_C = 1;
taylorza 0:d873d5d62d3b 18 const int MusicEngine::NOTE_CS = 2;
taylorza 0:d873d5d62d3b 19 const int MusicEngine::NOTE_D = 3;
taylorza 0:d873d5d62d3b 20 const int MusicEngine::NOTE_DS = 4;
taylorza 0:d873d5d62d3b 21 const int MusicEngine::NOTE_E = 5;
taylorza 0:d873d5d62d3b 22 const int MusicEngine::NOTE_F = 6;
taylorza 0:d873d5d62d3b 23 const int MusicEngine::NOTE_FS = 7;
taylorza 0:d873d5d62d3b 24 const int MusicEngine::NOTE_G = 8;
taylorza 0:d873d5d62d3b 25 const int MusicEngine::NOTE_GS = 9;
taylorza 0:d873d5d62d3b 26 const int MusicEngine::NOTE_A = 10;
taylorza 0:d873d5d62d3b 27 const int MusicEngine::NOTE_AS = 11;
taylorza 0:d873d5d62d3b 28 const int MusicEngine::NOTE_B = 12;
taylorza 0:d873d5d62d3b 29
taylorza 0:d873d5d62d3b 30 MusicEngine::MusicEngine(PinName pin) :
taylorza 0:d873d5d62d3b 31 _pwm(pin),
taylorza 0:d873d5d62d3b 32 _isPlaying(false)
taylorza 0:d873d5d62d3b 33 {
taylorza 0:d873d5d62d3b 34 _pwm = 0.0f;
taylorza 0:d873d5d62d3b 35 _pwm.period(0);
taylorza 0:d873d5d62d3b 36 }
taylorza 0:d873d5d62d3b 37
taylorza 0:d873d5d62d3b 38 void MusicEngine::play(char *mml)
taylorza 0:d873d5d62d3b 39 {
taylorza 0:d873d5d62d3b 40 __disable_irq();
taylorza 0:d873d5d62d3b 41 _isPlaying = false;
taylorza 0:d873d5d62d3b 42 _mml = mml;
taylorza 0:d873d5d62d3b 43 _mmlIndex = 0;
taylorza 0:d873d5d62d3b 44 _octave = 4;
taylorza 0:d873d5d62d3b 45 _duration = QUARTER_NOTE_DURATION;
taylorza 0:d873d5d62d3b 46 _durationRatio = DEFAULT_TIMING;
taylorza 0:d873d5d62d3b 47 _tempo = 120;
taylorza 0:d873d5d62d3b 48 _pwm.period(0);
taylorza 0:d873d5d62d3b 49 _pwm = 0.5f;
taylorza 0:d873d5d62d3b 50 _pause = 0;
taylorza 0:d873d5d62d3b 51 _isPlaying = true;
taylorza 0:d873d5d62d3b 52 __enable_irq();
taylorza 0:d873d5d62d3b 53
taylorza 0:d873d5d62d3b 54 MusicEngine::executeCommand();
taylorza 0:d873d5d62d3b 55 }
taylorza 0:d873d5d62d3b 56
taylorza 0:d873d5d62d3b 57 void MusicEngine::stop()
taylorza 0:d873d5d62d3b 58 {
taylorza 0:d873d5d62d3b 59 __disable_irq();
taylorza 0:d873d5d62d3b 60 _isPlaying = false;
taylorza 0:d873d5d62d3b 61 __enable_irq();
taylorza 0:d873d5d62d3b 62 }
taylorza 0:d873d5d62d3b 63
taylorza 0:d873d5d62d3b 64 void MusicEngine::executeCommand()
taylorza 0:d873d5d62d3b 65 {
taylorza 0:d873d5d62d3b 66 if (_pause != 0)
taylorza 0:d873d5d62d3b 67 {
taylorza 0:d873d5d62d3b 68 _pwm.period(0);
taylorza 0:d873d5d62d3b 69 _pwm = 0.0f;
taylorza 0:d873d5d62d3b 70 _scheduler.attach(this, &MusicEngine::executeCommand, _pause);
taylorza 0:d873d5d62d3b 71 _pause = 0;
taylorza 0:d873d5d62d3b 72 }
taylorza 0:d873d5d62d3b 73 else
taylorza 0:d873d5d62d3b 74 {
taylorza 0:d873d5d62d3b 75 int freqIndex = -1;
taylorza 0:d873d5d62d3b 76 do
taylorza 0:d873d5d62d3b 77 {
taylorza 0:d873d5d62d3b 78 skipWhiteSpace();
taylorza 0:d873d5d62d3b 79 switch(getChar())
taylorza 0:d873d5d62d3b 80 {
taylorza 0:d873d5d62d3b 81 case 'a': freqIndex = NOTE_A; break;
taylorza 0:d873d5d62d3b 82 case 'b': freqIndex = NOTE_B; break;
taylorza 0:d873d5d62d3b 83 case 'c': freqIndex = NOTE_C; break;
taylorza 0:d873d5d62d3b 84 case 'd': freqIndex = NOTE_D; break;
taylorza 0:d873d5d62d3b 85 case 'e': freqIndex = NOTE_E; break;
taylorza 0:d873d5d62d3b 86 case 'f': freqIndex = NOTE_F; break;
taylorza 0:d873d5d62d3b 87 case 'g': freqIndex = NOTE_G; break;
taylorza 0:d873d5d62d3b 88
taylorza 0:d873d5d62d3b 89 case 'p':
taylorza 0:d873d5d62d3b 90 case 'r': freqIndex = NOTE_REST; break;
taylorza 0:d873d5d62d3b 91
taylorza 0:d873d5d62d3b 92 case 'l': if (isdigit(peekChar())) _duration = (float)WHOLE_NOTE_DURATION / getNumber(1, 64); break;
taylorza 0:d873d5d62d3b 93 case 'o': if (isdigit(peekChar())) _octave = getNumber(0, 7); break;
taylorza 0:d873d5d62d3b 94 case 't': if (isdigit(peekChar())) _tempo = getNumber(32, 255); break;
taylorza 0:d873d5d62d3b 95 case 'm':
taylorza 0:d873d5d62d3b 96 switch(getChar())
taylorza 0:d873d5d62d3b 97 {
taylorza 0:d873d5d62d3b 98 case 'n': _durationRatio = DEFAULT_TIMING; break;
taylorza 0:d873d5d62d3b 99 case 'l': _durationRatio = LEGATO_TIMING; break;
taylorza 0:d873d5d62d3b 100 case 's': _durationRatio = STACCATO_TIMING; break;
taylorza 0:d873d5d62d3b 101 }
taylorza 0:d873d5d62d3b 102 break;
taylorza 0:d873d5d62d3b 103
taylorza 0:d873d5d62d3b 104 case 'n': if (isdigit(peekChar())) freqIndex = getNumber(0, 96); break;
taylorza 0:d873d5d62d3b 105 case '<': --_octave; if (_octave < 0) _octave = 0; break;
taylorza 0:d873d5d62d3b 106 case '>': ++_octave; if (_octave > 7) _octave = 7; break;
taylorza 0:d873d5d62d3b 107
taylorza 0:d873d5d62d3b 108 case '\0':
taylorza 0:d873d5d62d3b 109 _isPlaying = false;
taylorza 0:d873d5d62d3b 110 break;
taylorza 0:d873d5d62d3b 111 }
taylorza 0:d873d5d62d3b 112
taylorza 0:d873d5d62d3b 113 if (!_isPlaying)
taylorza 0:d873d5d62d3b 114 {
taylorza 0:d873d5d62d3b 115 _pwm = 0.0;
taylorza 0:d873d5d62d3b 116 _pwm.period(0);
taylorza 0:d873d5d62d3b 117 _completionCallback.call();
taylorza 0:d873d5d62d3b 118 return;
taylorza 0:d873d5d62d3b 119 }
taylorza 0:d873d5d62d3b 120
taylorza 0:d873d5d62d3b 121 float durationRatio = _durationRatio;
taylorza 0:d873d5d62d3b 122 float duration = _duration;
taylorza 0:d873d5d62d3b 123
taylorza 0:d873d5d62d3b 124 if (freqIndex != -1)
taylorza 0:d873d5d62d3b 125 {
taylorza 0:d873d5d62d3b 126 switch(getChar())
taylorza 0:d873d5d62d3b 127 {
taylorza 0:d873d5d62d3b 128 case '+':
taylorza 0:d873d5d62d3b 129 case '#': ++freqIndex; break;
taylorza 0:d873d5d62d3b 130
taylorza 0:d873d5d62d3b 131 case '-': --freqIndex; break;
taylorza 0:d873d5d62d3b 132 case '.':
taylorza 0:d873d5d62d3b 133 durationRatio = 3.0f / 2.0f;
taylorza 0:d873d5d62d3b 134 while(peekChar() == '.')
taylorza 0:d873d5d62d3b 135 {
taylorza 0:d873d5d62d3b 136 durationRatio *= 3.0f / 2.0f;
taylorza 0:d873d5d62d3b 137 getChar();
taylorza 0:d873d5d62d3b 138 }
taylorza 0:d873d5d62d3b 139 break;
taylorza 0:d873d5d62d3b 140 default:
taylorza 0:d873d5d62d3b 141 rewind();
taylorza 0:d873d5d62d3b 142 break;
taylorza 0:d873d5d62d3b 143 }
taylorza 0:d873d5d62d3b 144
taylorza 0:d873d5d62d3b 145 if (isdigit(peekChar()))
taylorza 0:d873d5d62d3b 146 {
taylorza 0:d873d5d62d3b 147 duration = WHOLE_NOTE_DURATION / getNumber(1, 64);
taylorza 0:d873d5d62d3b 148 }
taylorza 0:d873d5d62d3b 149
taylorza 0:d873d5d62d3b 150 if (freqIndex != NOTE_REST)
taylorza 0:d873d5d62d3b 151 {
taylorza 0:d873d5d62d3b 152 _pwm.period(PERIOD_TABLE[freqIndex + (_octave * 12)]);
taylorza 0:d873d5d62d3b 153 _pwm = 0.5;
taylorza 0:d873d5d62d3b 154 }
taylorza 0:d873d5d62d3b 155
taylorza 0:d873d5d62d3b 156 duration *= (QUARTER_NOTES_PER_MINUTE / _tempo);
taylorza 0:d873d5d62d3b 157 _scheduler.attach(this, &MusicEngine::executeCommand, duration * durationRatio);
taylorza 0:d873d5d62d3b 158 _pause = duration * (1 - durationRatio);
taylorza 0:d873d5d62d3b 159 }
taylorza 0:d873d5d62d3b 160 } while (freqIndex == -1);
taylorza 0:d873d5d62d3b 161 }
taylorza 0:d873d5d62d3b 162 }
taylorza 0:d873d5d62d3b 163
taylorza 0:d873d5d62d3b 164 int MusicEngine::getNumber(int min, int max)
taylorza 0:d873d5d62d3b 165 {
taylorza 0:d873d5d62d3b 166 char ch;
taylorza 0:d873d5d62d3b 167 int value = 0;
taylorza 0:d873d5d62d3b 168 while ((ch = getChar()) != 0)
taylorza 0:d873d5d62d3b 169 {
taylorza 0:d873d5d62d3b 170 if (!isdigit(ch))
taylorza 0:d873d5d62d3b 171 {
taylorza 0:d873d5d62d3b 172 rewind();
taylorza 0:d873d5d62d3b 173 break;
taylorza 0:d873d5d62d3b 174 }
taylorza 0:d873d5d62d3b 175 int digit = (int)ch - 48;
taylorza 0:d873d5d62d3b 176 value = (value * 10) + digit;
taylorza 0:d873d5d62d3b 177 }
taylorza 0:d873d5d62d3b 178 value = value < min ? min : value > max ? max : value;
taylorza 0:d873d5d62d3b 179 return value;
taylorza 0:d873d5d62d3b 180 }
taylorza 0:d873d5d62d3b 181
taylorza 0:d873d5d62d3b 182 void MusicEngine::skipWhiteSpace()
taylorza 0:d873d5d62d3b 183 {
taylorza 0:d873d5d62d3b 184 while (isspace(peekChar())) getChar();
taylorza 0:d873d5d62d3b 185 }
taylorza 0:d873d5d62d3b 186
taylorza 0:d873d5d62d3b 187 char MusicEngine::getChar()
taylorza 0:d873d5d62d3b 188 {
taylorza 0:d873d5d62d3b 189 return tolower(_mml[_mmlIndex++]);
taylorza 0:d873d5d62d3b 190 }
taylorza 0:d873d5d62d3b 191
taylorza 0:d873d5d62d3b 192 char MusicEngine::peekChar()
taylorza 0:d873d5d62d3b 193 {
taylorza 0:d873d5d62d3b 194 return tolower(_mml[_mmlIndex]);
taylorza 0:d873d5d62d3b 195 }
taylorza 0:d873d5d62d3b 196
taylorza 0:d873d5d62d3b 197 void MusicEngine::rewind()
taylorza 0:d873d5d62d3b 198 {
taylorza 0:d873d5d62d3b 199 --_mmlIndex;
taylorza 0:d873d5d62d3b 200 }
taylorza 0:d873d5d62d3b 201
taylorza 0:d873d5d62d3b 202 const float MusicEngine::PERIOD_TABLE[] =
taylorza 0:d873d5d62d3b 203 {
taylorza 0:d873d5d62d3b 204 0,
taylorza 0:d873d5d62d3b 205 //1 2 3 4 5 6 7 8 9 10 11 12
taylorza 0:d873d5d62d3b 206 //C C# D D# E F F# G G# A A# B
taylorza 0:d873d5d62d3b 207 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
taylorza 0:d873d5d62d3b 208 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
taylorza 0:d873d5d62d3b 209 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
taylorza 0:d873d5d62d3b 210 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
taylorza 0:d873d5d62d3b 211 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
taylorza 0:d873d5d62d3b 212 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
taylorza 0:d873d5d62d3b 213 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
taylorza 0:d873d5d62d3b 214 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
taylorza 0:d873d5d62d3b 215 };