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

Dependents:   PwmSoundTest

Revision:
1:67056b9df9ff
Parent:
0:185bcd9f8e19
--- a/PwmSound.cpp	Sun Mar 30 22:50:27 2014 +0000
+++ b/PwmSound.cpp	Tue May 06 13:16:28 2014 +0000
@@ -6,8 +6,8 @@
  * 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!
+ * Class to play tones, various sounds, musical notes 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.
@@ -37,57 +37,37 @@
  * Ver  Date    By  Details
  * 0.00 25Mar14 PG  File created.
  * 1.00 30Mar14 PG  Initial release.
+ * 2.00 06May14 PG  Added play() etc to support MML music. Removed tune() etc.
  *
  ******************************************************************************/
 
 #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)
+#include "FastOut.h"
 
-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,
-};
+extern Serial pc;       //for debugging, comment out if not needed
+FastOut<LED1> led1;
 
 // 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;
-    }
+	_dutyCycle = 0.5;
+	_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)
+// Uses: current duty cycle
 
 void PwmSound::tone(float frequency, float duration) {
     _pin.period(1.0 / frequency);
-    _pin = _volume / 2.0;
+    _pin = _dutyCycle;
     if (duration == 0.0) {
         _playing = true;
         return;
@@ -96,17 +76,6 @@
     _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) {
@@ -114,42 +83,53 @@
     _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
+// Set timbre (tonal quality)
 //
 // Parameters:
-//    volume - crude volume control (0.0 - 1.0), alters PWM duty cycle 0 - 50%
+//    timbre - (1-4): sets PWM duty cycle to 12.5%, 25%, 37.5% or 50%
 // Returns: nothing
 
-void PwmSound::volume(float volume) {
-    _volume = volume;
+void PwmSound::timbre(int t) {
+	if (t >= 1 && t <= 4) {
+		_dutyCycle = t / 8.0;
+	}
 }
-
 // Beeps of various types and other sounds
-// Note: buzz, siren and trill use a ticker and callback to
-//       support continuous sound in background
+// Note: All sounds below except phone permit continuous sound in background
+//       To invoke this call the function with a zero parameter
+//       Call stop() to end the sound
 //
 // Parameters:
-//    n - number of cycles, 0 for continuous sound in background
+//    n - number of cycles, 0 for continuous sound in background (not phone)
 // Returns: nothing
 
 void PwmSound::bip(int n) {
+    if (n == 0) {
+        _setup(1047.0, 0.1, 0.0, 0.03);
+        return;
+    }
     for (int i = 0; i < n; i++) {
         tone(1047.0, 0.10);
         wait(0.03);
     }
 }
 
+void PwmSound::bop(int n) {
+    if (n == 0) {
+        _setup(700.0, 0.1, 0.0, 0.03);
+        return;
+    }
+    for (int i = 0; i < n; i++) {
+        tone(700.0, 0.10);
+        wait(0.03);
+    }
+}
+
 void PwmSound::beep(int n) {
+    if (n == 0) {
+        _setup(969.0, 0.3, 0.0, 0.1);
+        return;
+    }
     for (int i = 0; i < n; i++) {
         tone(969.0, 0.3);
         wait(0.1);
@@ -157,6 +137,10 @@
 }
 
 void PwmSound::bleep(int n) {
+    if (n == 0) {
+        _setup(800.0, 0.4, 0.0, 0.1);
+        return;
+    }
     for (int i = 0; i < n; i++) {
         tone(800.0, 0.4);
         wait(0.1);
@@ -165,7 +149,7 @@
 
 void PwmSound::buzz(int n) {
     if (n == 0) {
-        _setup(1900.0, 300.0, 0.01);
+        _setup(1900.0, 0.01, 300.0, 0.01);
         return;
     }
     for (int i = 0; i < n; i++) {
@@ -178,7 +162,7 @@
 
 void PwmSound::siren(int n) {
     if (n == 0) {
-        _setup(969.0, 800.0, 0.5);
+        _setup(969.0, 0.5, 800.0, 0.5);
         return;
     }
     for (int i = 0; i < n; i++) {
@@ -189,7 +173,7 @@
 
 void PwmSound::trill(int n) {
     if (n == 0) {
-        _setup(969.0, 800.0, 0.05);
+        _setup(969.0, 0.05, 800.0, 0.05);
         return;
     }
     for (int i = 0; i < n; i++) {
@@ -218,117 +202,40 @@
 }   
 
 // Continuous sound setup and callback routines
+// _sustain() has been optimised for speed. On a 96MHz LPC1768 it takes 8.5us.
+// Non-optimised version with floating point freqency & duration took 11.4us.
+// Execution times measured with 'scope on LED1 pin.
 
-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
+void PwmSound::_setup(float freq1, float dur1, float freq2, float dur2) {
+    _period1 = (int) (1000000.0 / freq1);
+    _period2 = (int) (1000000.0 / freq2);
+    _dur1 = (unsigned int) (1000000.0 * dur1);
+    _dur2 = (unsigned int) (1000000.0 * dur2);
+    _phase = false;
+    _sustainTmo.attach_us(this, &PwmSound::_sustain, _dur1);
+    _pin.period_us(_period1);      //start the sound
+    _pin = _dutyCycle;
     _playing = true;
 }
         
 void PwmSound::_sustain(void) {
+    //led1 = 1;
     if (_playing == false) {
-        _sustainTkr.detach();   //detach callback to stop sound
+        //kill pwm and no more callbacks
         _pin = 0.0;
     } else {
-        _beat = !_beat;
-        tone( (_beat ? _freq2 : _freq1), 0.0);
+        _phase = !_phase;
+        if (_phase) {
+            _pin.period_us(_period2);
+            _pin = _dutyCycle;
+            _sustainTmo.attach_us(this, &PwmSound::_sustain, _dur2);
+        } else {
+            _pin.period_us(_period1);
+            _pin = _dutyCycle;
+            _sustainTmo.attach_us(this, &PwmSound::_sustain, _dur1);
+        }
     }
-}
-    
-// 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);    
+    //led1 = 0;
 }
 
-// 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
+// END of PwmSound.cpp