Class to play tones, various sounds and music in MML format using a PWM channel.

Dependents:   PwmSoundTest

PwmSound.cpp

Committer:
paulg
Date:
2014-03-30
Revision:
0:185bcd9f8e19
Child:
1:67056b9df9ff

File content as of revision 0:185bcd9f8e19:

/******************************************************************************
 * File:      PwmSound.cpp
 * Author:    Paul Griffith
 * Created:   25 Mar 2014
 * Last Edit: see below
 * Version:   see below
 *
 * Description:
 * Class to play tones, various sounds and simple tunes using a PWM channel.
 * Inspired by Jim Hamblem's Speaker class. Thanks Jim!
 *
 * Refer to the tutorial "Using a Speaker for Audio Output" in the Cookbook.
 * The mbed LPC1768 PWM pins will drive a small speaker without amplification.
 * Connect speaker via a 220R resistor and 100uF 10V capacitor (+ve to mbed).
 *
 * Copyright (c) 2014 Paul Griffith, MIT License
 *
 * 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.
 *
 * Modifications:
 * Ver  Date    By  Details
 * 0.00 25Mar14 PG  File created.
 * 1.00 30Mar14 PG  Initial release.
 *
 ******************************************************************************/

#include "mbed.h"
#include "PwmSound.h"

extern Serial pc;

// Standard note pitches in Hz
// First entry is a dummy, real note numbers start at 1
// Seven octaves, twelve notes per octave
// C, C#, D, D#, E, F, F#, G, G#, A, A#, B
// Middle C is element 37 (262Hz)

int notePitches[1+84] = {
    1,                                                      //dummy
    33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62,
    65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123,
    131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
    262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
    523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
    1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,
    2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
};

// Constructor

PwmSound::PwmSound(PinName pin) : _pin(pin) {
        _duration = 0.25;
        _volume = 1.0;
        _tempo = 120;   //120 beats/minute
        _length = 4;    //quarter note (crotchet)
        _style = PwmSound::NORMAL;
        _pin = 0.0;
        _playing = false;
    }

// Play a tone on output pin
// 2 versions for convenience when using default duration
//
// Parameters:
//    frequency - frequency of tone in Hz
//    duration - duration of tone in seconds
//               if duration = 0.0, tone continues in background until stopped
//    volume - crude volume control (0.0-1.0), alters PWM duty cycle 0-50%
// Returns: nothing
// Uses: current volume (duty cycle)

void PwmSound::tone(float frequency, float duration) {
    _pin.period(1.0 / frequency);
    _pin = _volume / 2.0;
    if (duration == 0.0) {
        _playing = true;
        return;
    }
    wait(duration);
    _pin = 0.0;
}

void PwmSound::tone(float frequency) {
    _pin.period(1.0 / frequency);
    _pin = _volume / 2.0;
    if (_duration == 0.0) {
        _playing = true;
        return;
    }
    wait(_duration);
    _pin = 0.0;
}

// Stop background tone or sound generation

void PwmSound::stop(void) {
    _playing = false;
    _pin = 0.0;
}

// Set default duration
//
// Parameters:
//    duration - duration of tone in seconds
// Returns: nothing

void PwmSound::duration(float duration) {
    _duration = duration;
}

// Set default volume
//
// Parameters:
//    volume - crude volume control (0.0 - 1.0), alters PWM duty cycle 0 - 50%
// Returns: nothing

void PwmSound::volume(float volume) {
    _volume = volume;
}

// Beeps of various types and other sounds
// Note: buzz, siren and trill use a ticker and callback to
//       support continuous sound in background
//
// Parameters:
//    n - number of cycles, 0 for continuous sound in background
// Returns: nothing

void PwmSound::bip(int n) {
    for (int i = 0; i < n; i++) {
        tone(1047.0, 0.10);
        wait(0.03);
    }
}

void PwmSound::beep(int n) {
    for (int i = 0; i < n; i++) {
        tone(969.0, 0.3);
        wait(0.1);
    }
}

void PwmSound::bleep(int n) {
    for (int i = 0; i < n; i++) {
        tone(800.0, 0.4);
        wait(0.1);
    }
}

void PwmSound::buzz(int n) {
    if (n == 0) {
        _setup(1900.0, 300.0, 0.01);
        return;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < 20; j++) {
            tone(1900.0, 0.01);
            tone(300.0, 0.01);
        }
    }
}

void PwmSound::siren(int n) {
    if (n == 0) {
        _setup(969.0, 800.0, 0.5);
        return;
    }
    for (int i = 0; i < n; i++) {
        tone(969.0, 0.5);
        tone(800.0, 0.5);
    }
}

void PwmSound::trill(int n) {
    if (n == 0) {
        _setup(969.0, 800.0, 0.05);
        return;
    }
    for (int i = 0; i < n; i++) {
        if (i > 0) {
            tone(800.0, 0.05); //make the trills sound continouus
        }
        tone(969.0, 0.05);
        tone(800.0, 0.05);
        tone(969.0, 0.05);
        tone(800.0, 0.05);
        tone(969.0, 0.05);
        tone(800.0, 0.05);
        tone(969.0, 0.05);
        tone(800.0, 0.05);
        tone(969.0, 0.05);
    }
}

void PwmSound::phone(int n) {
    for (int i = 0; i < n; i++) {
        trill();
        wait(0.10);
        trill();
        wait(0.7);
    }
}   

// Continuous sound setup and callback routines

void PwmSound::_setup(float freq1, float freq2, float duration) {
    _freq1 = freq1;
    _freq2 = freq2;
    _beat = false;
    _sustainTkr.attach(this, &PwmSound::_sustain, duration);
    tone(freq1, 0.0);      //start the sound
    _playing = true;
}
        
void PwmSound::_sustain(void) {
    if (_playing == false) {
        _sustainTkr.detach();   //detach callback to stop sound
        _pin = 0.0;
    } else {
        _beat = !_beat;
        tone( (_beat ? _freq2 : _freq1), 0.0);
    }
}
    
// Play a musical note on output pin
//
// Parameters:
//    number - 0 = rest, notes from 1 to 84, middle C (262Hz) = 37
//    length - duration of note (1-64), 1 = whole note (semibreve)
//                                      2 = half note (minim)
//                                      4 = quarter note (crotchet) = 1 beat
//                                      8 = eighth note (quaver)
//                                      etc
// Returns: nothing
// Uses: current tempo, music style and volume (duty cycle)

void PwmSound::note(int number, int length) {
    int frequency;
    float duration, play, rest;

    //pc.printf("<number %d, length %d, volume %f, ", number, length, volume);
    if (number < 1 || number > 84) {    //convert bad note to a rest
        number = 0;
    }
    frequency = notePitches[number];

    duration = 240.0 / (_tempo * length);
    if (_style == PwmSound::STACCATO) {           //staccato
        play = duration * 0.75;
        rest = duration * 0.25;
    } else if (_style == PwmSound::LEGATO) {    //legato
        play = duration;
        rest = 0;
    } else {                    //normal
        play = duration * 0.875;
        rest = duration * 0.125;
    }
    //pc.printf("f %d, d %f, p %f, r %f>\n", frequency, duration, play, rest);
    if (number > 0) {
        _pin.period(1.0 / frequency);
        _pin = _volume / 2.0;
    }
    wait(play);
    _pin = 0.0;
    wait(rest);    
}

// Set default tempo
//
// Parameters:
//    tempo - tempo in BPM
// Returns: nothing

void PwmSound::tempo(int tempo) {
    _tempo = tempo;
}

// Set default music style
//
// Parameters:
//    style - STAACATO, NORMAL or LEGATO
// Returns: nothing

void PwmSound::style(MusicStyle style) {
    _style = style;
}

// Play a simple tune from note data in an array.
//
// Parameters:
//    tune - pointer to char array containing tune data
//           first entry is tempo
//           second entry is music style (STACCATO, NORMAL, LEGATO)
//           subsequent entries are note number/length pairs
//           final entry must be 0, 0
// Returns: nothing
// Uses: current volume

void PwmSound::tune(unsigned char* tune) {
    int t, number, length;

    t = *tune++;
    if (t == 0) {
        return;
    }
    tempo(t);
    style( (MusicStyle) *tune++);
    while (true) {
        number = *tune++;
        length = *tune++;
        if (number == 0 && length == 0) {
            break;
        }
        note(number, length);
    }
}

// END of PwmSound.cpp