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

Dependents:   PwmSoundTest

Revision:
0:185bcd9f8e19
Child:
1:67056b9df9ff
diff -r 000000000000 -r 185bcd9f8e19 PwmSound.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PwmSound.cpp	Sun Mar 30 22:50:27 2014 +0000
@@ -0,0 +1,334 @@
+/******************************************************************************
+ * 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
\ No newline at end of file