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)
    {
    }
}

MusicEngine.cpp

Committer:
taylorza
Date:
2015-02-06
Revision:
2:4f7c4255997a
Parent:
0:d873d5d62d3b

File content as of revision 2:4f7c4255997a:

///////////////////////////////////////////////////////////////////////////////
// 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.period_ms(1);
    _pwm.write(0.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.period_ms(1);
                _pwm.write(0.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
};