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) { } }
Diff: MusicEngine.cpp
- Revision:
- 0:d873d5d62d3b
- Child:
- 2:4f7c4255997a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MusicEngine.cpp Thu Feb 05 04:18:39 2015 +0000 @@ -0,0 +1,215 @@ +/////////////////////////////////////////////////////////////////////////////// +// Retro Music Engine +// Author: Chris Taylor (taylorza) + +#include "MusicEngine.h" +#include <cctype> + +const float MusicEngine::WHOLE_NOTE_DURATION = 1.0f; +const float MusicEngine::QUARTER_NOTE_DURATION = MusicEngine::WHOLE_NOTE_DURATION / 4.0f; +const float MusicEngine::QUARTER_NOTES_PER_MINUTE = 60.0f / MusicEngine::QUARTER_NOTE_DURATION; + +const float MusicEngine::DEFAULT_TIMING = 7.0f / 8.0f; +const float MusicEngine::LEGATO_TIMING = 1.0f; +const float MusicEngine::STACCATO_TIMING = 3.0f / 4.0f; + +const int MusicEngine::NOTE_REST = 0; +const int MusicEngine::NOTE_C = 1; +const int MusicEngine::NOTE_CS = 2; +const int MusicEngine::NOTE_D = 3; +const int MusicEngine::NOTE_DS = 4; +const int MusicEngine::NOTE_E = 5; +const int MusicEngine::NOTE_F = 6; +const int MusicEngine::NOTE_FS = 7; +const int MusicEngine::NOTE_G = 8; +const int MusicEngine::NOTE_GS = 9; +const int MusicEngine::NOTE_A = 10; +const int MusicEngine::NOTE_AS = 11; +const int MusicEngine::NOTE_B = 12; + +MusicEngine::MusicEngine(PinName pin) : + _pwm(pin), + _isPlaying(false) +{ + _pwm = 0.0f; + _pwm.period(0); +} + +void MusicEngine::play(char *mml) +{ + __disable_irq(); + _isPlaying = false; + _mml = mml; + _mmlIndex = 0; + _octave = 4; + _duration = QUARTER_NOTE_DURATION; + _durationRatio = DEFAULT_TIMING; + _tempo = 120; + _pwm.period(0); + _pwm = 0.5f; + _pause = 0; + _isPlaying = true; + __enable_irq(); + + MusicEngine::executeCommand(); +} + +void MusicEngine::stop() +{ + __disable_irq(); + _isPlaying = false; + __enable_irq(); +} + +void MusicEngine::executeCommand() +{ + if (_pause != 0) + { + _pwm.period(0); + _pwm = 0.0f; + _scheduler.attach(this, &MusicEngine::executeCommand, _pause); + _pause = 0; + } + else + { + int freqIndex = -1; + do + { + skipWhiteSpace(); + switch(getChar()) + { + case 'a': freqIndex = NOTE_A; break; + case 'b': freqIndex = NOTE_B; break; + case 'c': freqIndex = NOTE_C; break; + case 'd': freqIndex = NOTE_D; break; + case 'e': freqIndex = NOTE_E; break; + case 'f': freqIndex = NOTE_F; break; + case 'g': freqIndex = NOTE_G; break; + + case 'p': + case 'r': freqIndex = NOTE_REST; break; + + case 'l': if (isdigit(peekChar())) _duration = (float)WHOLE_NOTE_DURATION / getNumber(1, 64); break; + case 'o': if (isdigit(peekChar())) _octave = getNumber(0, 7); break; + case 't': if (isdigit(peekChar())) _tempo = getNumber(32, 255); break; + case 'm': + switch(getChar()) + { + case 'n': _durationRatio = DEFAULT_TIMING; break; + case 'l': _durationRatio = LEGATO_TIMING; break; + case 's': _durationRatio = STACCATO_TIMING; break; + } + break; + + case 'n': if (isdigit(peekChar())) freqIndex = getNumber(0, 96); break; + case '<': --_octave; if (_octave < 0) _octave = 0; break; + case '>': ++_octave; if (_octave > 7) _octave = 7; break; + + case '\0': + _isPlaying = false; + break; + } + + if (!_isPlaying) + { + _pwm = 0.0; + _pwm.period(0); + _completionCallback.call(); + return; + } + + float durationRatio = _durationRatio; + float duration = _duration; + + if (freqIndex != -1) + { + switch(getChar()) + { + case '+': + case '#': ++freqIndex; break; + + case '-': --freqIndex; break; + case '.': + durationRatio = 3.0f / 2.0f; + while(peekChar() == '.') + { + durationRatio *= 3.0f / 2.0f; + getChar(); + } + break; + default: + rewind(); + break; + } + + if (isdigit(peekChar())) + { + duration = WHOLE_NOTE_DURATION / getNumber(1, 64); + } + + if (freqIndex != NOTE_REST) + { + _pwm.period(PERIOD_TABLE[freqIndex + (_octave * 12)]); + _pwm = 0.5; + } + + duration *= (QUARTER_NOTES_PER_MINUTE / _tempo); + _scheduler.attach(this, &MusicEngine::executeCommand, duration * durationRatio); + _pause = duration * (1 - durationRatio); + } + } while (freqIndex == -1); + } +} + +int MusicEngine::getNumber(int min, int max) +{ + char ch; + int value = 0; + while ((ch = getChar()) != 0) + { + if (!isdigit(ch)) + { + rewind(); + break; + } + int digit = (int)ch - 48; + value = (value * 10) + digit; + } + value = value < min ? min : value > max ? max : value; + return value; +} + +void MusicEngine::skipWhiteSpace() +{ + while (isspace(peekChar())) getChar(); +} + +char MusicEngine::getChar() +{ + return tolower(_mml[_mmlIndex++]); +} + +char MusicEngine::peekChar() +{ + return tolower(_mml[_mmlIndex]); +} + +void MusicEngine::rewind() +{ + --_mmlIndex; +} + +const float MusicEngine::PERIOD_TABLE[] = +{ + 0, + //1 2 3 4 5 6 7 8 9 10 11 12 + //C C# D D# E F F# G G# A A# B + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 +}; \ No newline at end of file