Class to play tones, various sounds and music in MML format using a PWM channel.
Revision 1:67056b9df9ff, committed 2014-05-06
- Comitter:
- paulg
- Date:
- Tue May 06 13:16:28 2014 +0000
- Parent:
- 0:185bcd9f8e19
- Commit message:
- Added play() function to play music written in Music Macro Language (MML) format. Removed tune() as no longer required.
Changed in this revision
diff -r 185bcd9f8e19 -r 67056b9df9ff PwmSound.cpp --- 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
diff -r 185bcd9f8e19 -r 67056b9df9ff PwmSound.h --- a/PwmSound.h Sun Mar 30 22:50:27 2014 +0000 +++ b/PwmSound.h Tue May 06 13:16:28 2014 +0000 @@ -32,6 +32,7 @@ * 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. * ******************************************************************************/ @@ -44,17 +45,14 @@ //private: public: - enum MusicStyle { STACCATO, NORMAL, LEGATO }; - PwmSound(PinName pin); - void tone(float frequency, float duration); //tones - void tone(float frequency); + void tone(float frequency, float duration = 0.0); //tones void stop(void); - void duration(float duration); - void volume(float volume); + void timbre(int timbre); void bip(int n = 1); //beeps and other sounds + void bop(int n = 1); void beep(int n = 1); void bleep(int n = 1); void buzz(int n = 1); @@ -62,31 +60,43 @@ void trill(int n = 1); void phone(int n = 1); - void note(int number, int length); //musical note - void tempo(int tempo); - void style(MusicStyle style); - - void tune(unsigned char* t); //play tune from data in array + int play(char* m, int options = 0); //play tune in MML format private: PwmOut _pin; - float _duration; //duration of tone in seconds - float _volume; //crude volume 0.0-1.0 => 0-50% duty cycle + float _dutyCycle; //PWM duty cycle 0-50% + + //the following support continuous two-tone sounds in background + void _setup(float freq1, float dur1, float freq2, float dur2); + void _sustain(void); + Timeout _sustainTmo; + int _period1; //in us + unsigned int _dur1; //in us + int _period2; + unsigned int _dur2; + bool _phase; + bool _playing; + + //the following support play + void _note(int number, int length, int dots = 0); + char _getChar(void); + char _nextChar(void); + int _getNumber(void); + int _getDots(void); + int _octave; //current octave + int _shift; //octave shift from < and > int _tempo; //pace of music in beats per minute (32-255) //one beat equals one quarter note (ie a crotchet) int _length; //length of note (1-64), 1 = whole note, 4 = quarter etc - MusicStyle _style; - - //the following support continuous two-tone sounds in background - void _setup(float freq1, float freq2, float duration); - void _sustain(void); - Ticker _sustainTkr; - float _freq1; - float _freq2; - bool _beat; - bool _playing; + float _1dot; //note length extension factors + float _2dots; + float _3dots; + int _style; //music style (1-8), 6 = Staccato, 7 = Normal, 8 = Legato + char* _mp; //current position in music string + char _nextCh; + bool _haveNext; }; #endif -// END of PwmSound.h \ No newline at end of file +// END of PwmSound.h
diff -r 185bcd9f8e19 -r 67056b9df9ff play.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/play.cpp Tue May 06 13:16:28 2014 +0000 @@ -0,0 +1,449 @@ +/****************************************************************************** + * File: play.cpp + * Author: Paul Griffith + * Created: 28 Mar 2014 + * Last Edit: see below + * Version: see below + * + * Description: + * Play melody from music data written in Music Macro Language (MML) format. + * Part of PwmSound class. + * + * 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 28Mar14 PG File created. + * 1.00 06May14 PG Initial release. + * + ******************************************************************************/ +/* + * Music Macro Language (MML) is a music description language used in sequencing + * music on computer and video game systems. For further details refer to the + * Wikipedia article of the same name. + * + * There are many dialects of MML. The one used here is essentially that of + * the PLAY statement from Microsoft GW-BASIC. The original Microsoft + * documentation is available online by following the References from the + * Wikipedia GW-BASIC article. The MML data format is described below: + * + * A-G Play note using the letter names of the scale. The letter may be + * followed by either a # or + for a sharp, or a - for a flat. Any note + * letter followed by a #, + or - must refer to a black key on a piano. + * + * K Keyboard. Stops play() polling the PC keyboard during playback. + * If K is not found, any PC keystroke will stop playback. + * + * Ln Sets the length of each note. n is a decimal number between 1 and 64 + * L1 is a whole note (semi-breve), L4 is a quarter note (crotchet) and + * so on. The L value persists until the next L command is encountered. + * + * A length number may also follow a note letter name to change the length + * for that note only. For example, D8 is equivalent to L8D. + * + * ML Music Legato. The note is played the for the full length of its + * specified time. There is no rest between notes. + * + * MN Music Normal. The note is played for 7/8 of its specified time, and + * 1/8 of the specified time is a rest between notes. This is the default. + * + * MS Music Staccato. The note is played for 3/4 of its specified time, and + * 1/4 of the specified time is a rest between notes. + * + * Nn Play note. n is a decimal number between 0 and 84. n = 1 is the lowest + * note and n = 84 is the highest note. n set to 0 indicates a rest. N can + * be used as an alternative to defining a note by an octave and a letter. + * For example, N37 = middle C. + * + * On Sets the current octave. n is a decimal number between 0 and 6. The + * default octave is 4. Middle C starts octave 3, i.e. O3C = middle C. + * + * Note: The above convention differs from standard piano octave numbering + * where middle C starts octave 4, i.e. O4C = middle C. + * + * Pn Pause, or rest, for a length defined by n. P works in the same way as + * the L command above. For example, P2 = a half rest. + * + * Qn Sets the tonal quality (timbre). n is a decimal number between 1 and 4. + * It sets the PWM duty cycle to n / 8, (i.e. 12.5%, 25%, 37.5% or 50%). + * The default is 4 (50% duty cycle). + * + * Rn Rest, for a length defined by n. Alternative form of the P command. + * + * Tn Sets the tempo (the pace at which the music plays) in beats per minute. + * n is a decimal number between 32 and 255. The default tempo is 120. One + * beat corresponds to a quarter note (L4). + * + * . Dot. A dot can follow a letter note or a pause. It extends the duration + * of the note or pause by half (to 150%). More than one dot may be used. + * + * Note: The Microsoft documentation states that multiple dots extend the + * duration as follows: + * 2 dots = 1.5 ^ 2 = 225%, 3 dots = 1.5 ^ 3 = 337.5%. + * This differs from standard musical notation where multiple dots + * provide successively smaller extensions as follows: + * 2 dots = 1 + 1/2 + 1/4 = 175%. + * 3 dots = 1 + 1/2 + 1/4 + 1/8 = 187.5%. + * + * The standard notation extensions are used here. + * + * > Play the following note in the next higher octave. For example, + * O3C >D E is equivalent to O3C O4D O3E. + * + * < Play the following note in the next lower octave. For example, + * O3C <D E is equivalent to O3C O2D O3E. + * + * Note: Some dialects of MML appear to treat < and > as persistent + * commands that affect all following notes. + * + * Note: The Microsoft documentation does not say whether or not < and > + * should act on notes specified by number, such as N37. In this + * implementation it does, so >N37 = N49. + * + * : # Treat remainder of line as a comment. Note: line must end with '\n'. + * + * Note: Some dialects of MML use # to start a comment. We accept either. + * + * White space and line endings (CR, LF) are ignored (except in comments). + * + * Note: The play() function has a second parameter which supports some MML + * dialect alternatives. Refer to the comments below for details. + */ + +#include "mbed.h" +#include "PwmSound.h" +#include "ctype.h" + +extern Serial pc; //for debug, comment out of not needed + + // Standard note pitches in Hz +// From Wikipedia: http://en.wikipedia.org/wiki/Scientific_pitch_notation + // 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 (261.63Hz) is element 37 + + float notePitches[1+84] = { + 1.0, //dummy + 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, //first octave + 46.249, 48.999, 51.913, 55.000, 58.270, 61.735, + 65.406, 69.296, 73.416, 77.782, 82.407, 87.307, //second octave + 92.499, 97.999, 103.83, 110.00, 116.54, 123.47, + 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, //third octave + 185.00, 196.00, 207.65, 220.00, 233.08, 246.94, + 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, //fourth octave + 369.99, 392.00, 415.30, 440.00, 466.16, 493.88, + 523.25, 554.37, 587.33, 622.25, 659.26, 698.46, //fifth octave + 739.99, 783.99, 830.61, 880.00, 932.33, 987.77, + 1046.5, 1108.7, 1174.7, 1244.5, 1318.5, 1396.9, //sixth octave + 1480.0, 1568.0, 1661.2, 1760.0, 1864.7, 1975.5, + 2093.0, 2217.5, 2349.3, 2489.0, 2637.0, 2793.8, //seventh octave + 2960.0, 3136.0, 3322.4, 3520.0, 3729.3, 3951.1, + }; + +// Note numbers within octave for notes A - G (white keys on piano) + +int notes[7] = { 10, 12, 1, 3, 5, 6, 8 }; //C is first note = 1 + +// Allowable note modifiers for notes A - G + +int flats[7] = { -1, -1, 0, -1, -1, 0, -1 }; //not C or F +int sharps[7] = {1, 0, 1, 1, 0, 1, 1 }; //not B or E + +// Play a melody from music data written in MML format. +// +// Parameters: +// m - pointer to string containing music data +// options (default 0) - support for different dialects of MML +// bit 0 (1) - standard octave numbering +// 0: octaves range from 0 to 6, middle C starts octave 3 +// 1: octaves range from 1 to 7, middle C starts octave 4 +// bit 1 (2) - stickyShift +// 0: < and > act on next note only +// 1: < and > act on all following notes +// bit 2 (4) - longDots +// 0: standard dot extensions (i.e. 150%, 175%, 187.5%) +// 1: GW-BASIC dot extensions (i.e. 150%, 225%, 337.5%) +// Returns: 0 if no error in input, otherwise position of offending character + +int PwmSound::play(char* m, int options) { + bool run = true, kbdPoll = true; + char c, c1; + int n, n1, n2; + + bool stdOctNum = (options & 1) ? true : false; //options bits + bool stickyShift = (options & 2) ? true : false; + bool longDots = (options & 4) ? true : false; + + _octave = 4; //set defaults + _shift = 0; + _tempo = 120; + _length = 4; + _1dot = 1.5; + _2dots = (longDots == true) ? 2.25 : 1.75; + _3dots = (longDots == true) ? 3.375 : 1.875; + _style = 7; + _dutyCycle = 0.5; + _mp = m; + _haveNext = false; + //pc.putc('['); + while (run) { + if (kbdPoll && pc.readable()) { + pc.getc(); + break; + } + c = _getChar(); //read next char in input stream + //pc.putc(c); + switch (c) { + case 'A': //specify note by letter (and modifier) + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + if (stdOctNum) { + n = ((_octave - 1) * 12) + notes[c - 'A']; + } else { + n = (_octave * 12) + notes[c - 'A']; + } + c1 = _nextChar(); //optional modifier + if (c1 == '-' || c1 == '+' || c1 == '#') { + c1 = _getChar(); + n += (c1 == '-') ? flats[c - 'A'] : sharps[c - 'A']; + } + n += _shift * 12; + if (!stickyShift) { + _shift = 0; + } + n1 = _getNumber(); //optional length number + n2 = _getDots(); //optional dots + if (n1 == 0) { + _note(n, _length, n2); + } else { + _note(n, n1, n2); + } + break; + + case 'K': + kbdPoll = false; + break; + + case 'L': //set note length + n = _getNumber(); + if (n >= 1 && n <= 64) { + _length = n; + } + break; + + case 'M': //set music style (proportion of note length played) + switch (_getChar() ) { + case 'L': //legato + _style = 8; + break; + + case 'N': //normal + _style = 7; + break; + + case 'S': //staccato + _style = 6; + break; + } + break; + + case 'N': //specify note by number + n = _getNumber(); + n += _shift * 12; //not really sure about this + if (!stickyShift) { + _shift = 0; + } + _note(n, _length); + break; + + case 'O': //set octave + n = _getNumber(); + if (stdOctNum) { + if (n >= 1 && n <= 7) { + _octave = n; + } + } else { + if (n >= 0 && n <= 6) { + _octave = n; + } + } + break; + + case 'P': //pause or rest + case 'R': + n1 = _getNumber(); //optional length number + n2 = _getDots(); //optional dots + if (n1 == 0) { + _note(0, _length, n2); + } else { + _note(0, n1, n2); + } + break; + + case 'Q': //set timbre + n = _getNumber(); + if (n >= 1 && n <= 4) { + _dutyCycle = n / 8.0; + } + break; + + case 'T': //set tempo + n = _getNumber(); + if ( n>= 32 && n <= 255) { + _tempo = n; + } + break; + + case '<': //move down an octave + _shift--; + break; + + case '>': //move up an octave + _shift++; + break; + + case ':': //comment to end of line + case '#': + while (_getChar() != '\n') ; + break; + + case ' ': //skip over white space and line endings + case '\t': + case '\r': + case '\n': + break; + + case '\0': //end of string + run = false; + break; + + default: //abort on invalid characters + _pin = 0.0; + return(int (_mp - m) ); //return position of error + } + } //end of while + //pc.putc(']'); + _pin = 0.0; + return 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 +// dots - length extension (0-3, default 0) +// Returns: nothing + +void PwmSound::_note(int number, int length, int dots) { + float duration, play, rest; + + if (number < 1 || number > 84) { //convert bad note to a rest + number = 0; + } + + duration = 240.0 / (_tempo * length); + if (dots == 1) { + duration *= _1dot; + } else if (dots == 2) { + duration *= _2dots; + } else if (dots == 3) { + duration *= _3dots; + } + play = duration * _style / 8.0; + rest = duration * (8 - _style) / 8.0; + + if (number > 0) { + _pin.period(1.0 / notePitches[number]); + _pin = _dutyCycle; + } + wait(play); + _pin = 0.0; + wait(rest); +} + +// Read next character in input string +// +// Parameters: none +// Returns: next character + +char PwmSound::_getChar(void) { + if (_haveNext) { + _haveNext = false; + return _nextCh; + } else { + return *_mp++; + } +} + +// Examine next character in input string without consuming it +// +// Parameters: none +// Returns: next character + +char PwmSound::_nextChar(void) { + if (!_haveNext) { + _nextCh = *_mp++; + _haveNext = true; + } + return _nextCh; +} + +// Read a variable length number from input string +// +// Parameters: none +// Returns: number + +int PwmSound::_getNumber(void) { + int n = 0; + + while (isdigit(_nextChar()) ) { + n *= 10; + n += _getChar() - '0'; + } + return n; +} + +// Read variable number of dots from input string +// +// Parameters: none +// Returns: number of dots + +int PwmSound::_getDots(void) { + int n = 0; + + while (_nextChar() == '.') { + _getChar(); + n++; + } + return n; +} + +// END of play.cpp