Class to play tones, various sounds and music in MML format using a PWM channel.
play.cpp@1:67056b9df9ff, 2014-05-06 (annotated)
- Committer:
- paulg
- Date:
- Tue May 06 13:16:28 2014 +0000
- Revision:
- 1:67056b9df9ff
Added play() function to play music written in Music Macro Language (MML) format. Removed tune() as no longer required.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
paulg | 1:67056b9df9ff | 1 | /****************************************************************************** |
paulg | 1:67056b9df9ff | 2 | * File: play.cpp |
paulg | 1:67056b9df9ff | 3 | * Author: Paul Griffith |
paulg | 1:67056b9df9ff | 4 | * Created: 28 Mar 2014 |
paulg | 1:67056b9df9ff | 5 | * Last Edit: see below |
paulg | 1:67056b9df9ff | 6 | * Version: see below |
paulg | 1:67056b9df9ff | 7 | * |
paulg | 1:67056b9df9ff | 8 | * Description: |
paulg | 1:67056b9df9ff | 9 | * Play melody from music data written in Music Macro Language (MML) format. |
paulg | 1:67056b9df9ff | 10 | * Part of PwmSound class. |
paulg | 1:67056b9df9ff | 11 | * |
paulg | 1:67056b9df9ff | 12 | * Copyright (c) 2014 Paul Griffith, MIT License |
paulg | 1:67056b9df9ff | 13 | * |
paulg | 1:67056b9df9ff | 14 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
paulg | 1:67056b9df9ff | 15 | * of this software and associated documentation files (the "Software"), to |
paulg | 1:67056b9df9ff | 16 | * deal in the Software without restriction, including without limitation the |
paulg | 1:67056b9df9ff | 17 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
paulg | 1:67056b9df9ff | 18 | * sell copies of the Software, and to permit persons to whom the Software is |
paulg | 1:67056b9df9ff | 19 | * furnished to do so, subject to the following conditions: |
paulg | 1:67056b9df9ff | 20 | * |
paulg | 1:67056b9df9ff | 21 | * The above copyright notice and this permission notice shall be included in |
paulg | 1:67056b9df9ff | 22 | * all copies or substantial portions of the Software. |
paulg | 1:67056b9df9ff | 23 | * |
paulg | 1:67056b9df9ff | 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
paulg | 1:67056b9df9ff | 25 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
paulg | 1:67056b9df9ff | 26 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
paulg | 1:67056b9df9ff | 27 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
paulg | 1:67056b9df9ff | 28 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
paulg | 1:67056b9df9ff | 29 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
paulg | 1:67056b9df9ff | 30 | * IN THE SOFTWARE. |
paulg | 1:67056b9df9ff | 31 | * |
paulg | 1:67056b9df9ff | 32 | * Modifications: |
paulg | 1:67056b9df9ff | 33 | * Ver Date By Details |
paulg | 1:67056b9df9ff | 34 | * 0.00 28Mar14 PG File created. |
paulg | 1:67056b9df9ff | 35 | * 1.00 06May14 PG Initial release. |
paulg | 1:67056b9df9ff | 36 | * |
paulg | 1:67056b9df9ff | 37 | ******************************************************************************/ |
paulg | 1:67056b9df9ff | 38 | /* |
paulg | 1:67056b9df9ff | 39 | * Music Macro Language (MML) is a music description language used in sequencing |
paulg | 1:67056b9df9ff | 40 | * music on computer and video game systems. For further details refer to the |
paulg | 1:67056b9df9ff | 41 | * Wikipedia article of the same name. |
paulg | 1:67056b9df9ff | 42 | * |
paulg | 1:67056b9df9ff | 43 | * There are many dialects of MML. The one used here is essentially that of |
paulg | 1:67056b9df9ff | 44 | * the PLAY statement from Microsoft GW-BASIC. The original Microsoft |
paulg | 1:67056b9df9ff | 45 | * documentation is available online by following the References from the |
paulg | 1:67056b9df9ff | 46 | * Wikipedia GW-BASIC article. The MML data format is described below: |
paulg | 1:67056b9df9ff | 47 | * |
paulg | 1:67056b9df9ff | 48 | * A-G Play note using the letter names of the scale. The letter may be |
paulg | 1:67056b9df9ff | 49 | * followed by either a # or + for a sharp, or a - for a flat. Any note |
paulg | 1:67056b9df9ff | 50 | * letter followed by a #, + or - must refer to a black key on a piano. |
paulg | 1:67056b9df9ff | 51 | * |
paulg | 1:67056b9df9ff | 52 | * K Keyboard. Stops play() polling the PC keyboard during playback. |
paulg | 1:67056b9df9ff | 53 | * If K is not found, any PC keystroke will stop playback. |
paulg | 1:67056b9df9ff | 54 | * |
paulg | 1:67056b9df9ff | 55 | * Ln Sets the length of each note. n is a decimal number between 1 and 64 |
paulg | 1:67056b9df9ff | 56 | * L1 is a whole note (semi-breve), L4 is a quarter note (crotchet) and |
paulg | 1:67056b9df9ff | 57 | * so on. The L value persists until the next L command is encountered. |
paulg | 1:67056b9df9ff | 58 | * |
paulg | 1:67056b9df9ff | 59 | * A length number may also follow a note letter name to change the length |
paulg | 1:67056b9df9ff | 60 | * for that note only. For example, D8 is equivalent to L8D. |
paulg | 1:67056b9df9ff | 61 | * |
paulg | 1:67056b9df9ff | 62 | * ML Music Legato. The note is played the for the full length of its |
paulg | 1:67056b9df9ff | 63 | * specified time. There is no rest between notes. |
paulg | 1:67056b9df9ff | 64 | * |
paulg | 1:67056b9df9ff | 65 | * MN Music Normal. The note is played for 7/8 of its specified time, and |
paulg | 1:67056b9df9ff | 66 | * 1/8 of the specified time is a rest between notes. This is the default. |
paulg | 1:67056b9df9ff | 67 | * |
paulg | 1:67056b9df9ff | 68 | * MS Music Staccato. The note is played for 3/4 of its specified time, and |
paulg | 1:67056b9df9ff | 69 | * 1/4 of the specified time is a rest between notes. |
paulg | 1:67056b9df9ff | 70 | * |
paulg | 1:67056b9df9ff | 71 | * Nn Play note. n is a decimal number between 0 and 84. n = 1 is the lowest |
paulg | 1:67056b9df9ff | 72 | * note and n = 84 is the highest note. n set to 0 indicates a rest. N can |
paulg | 1:67056b9df9ff | 73 | * be used as an alternative to defining a note by an octave and a letter. |
paulg | 1:67056b9df9ff | 74 | * For example, N37 = middle C. |
paulg | 1:67056b9df9ff | 75 | * |
paulg | 1:67056b9df9ff | 76 | * On Sets the current octave. n is a decimal number between 0 and 6. The |
paulg | 1:67056b9df9ff | 77 | * default octave is 4. Middle C starts octave 3, i.e. O3C = middle C. |
paulg | 1:67056b9df9ff | 78 | * |
paulg | 1:67056b9df9ff | 79 | * Note: The above convention differs from standard piano octave numbering |
paulg | 1:67056b9df9ff | 80 | * where middle C starts octave 4, i.e. O4C = middle C. |
paulg | 1:67056b9df9ff | 81 | * |
paulg | 1:67056b9df9ff | 82 | * Pn Pause, or rest, for a length defined by n. P works in the same way as |
paulg | 1:67056b9df9ff | 83 | * the L command above. For example, P2 = a half rest. |
paulg | 1:67056b9df9ff | 84 | * |
paulg | 1:67056b9df9ff | 85 | * Qn Sets the tonal quality (timbre). n is a decimal number between 1 and 4. |
paulg | 1:67056b9df9ff | 86 | * It sets the PWM duty cycle to n / 8, (i.e. 12.5%, 25%, 37.5% or 50%). |
paulg | 1:67056b9df9ff | 87 | * The default is 4 (50% duty cycle). |
paulg | 1:67056b9df9ff | 88 | * |
paulg | 1:67056b9df9ff | 89 | * Rn Rest, for a length defined by n. Alternative form of the P command. |
paulg | 1:67056b9df9ff | 90 | * |
paulg | 1:67056b9df9ff | 91 | * Tn Sets the tempo (the pace at which the music plays) in beats per minute. |
paulg | 1:67056b9df9ff | 92 | * n is a decimal number between 32 and 255. The default tempo is 120. One |
paulg | 1:67056b9df9ff | 93 | * beat corresponds to a quarter note (L4). |
paulg | 1:67056b9df9ff | 94 | * |
paulg | 1:67056b9df9ff | 95 | * . Dot. A dot can follow a letter note or a pause. It extends the duration |
paulg | 1:67056b9df9ff | 96 | * of the note or pause by half (to 150%). More than one dot may be used. |
paulg | 1:67056b9df9ff | 97 | * |
paulg | 1:67056b9df9ff | 98 | * Note: The Microsoft documentation states that multiple dots extend the |
paulg | 1:67056b9df9ff | 99 | * duration as follows: |
paulg | 1:67056b9df9ff | 100 | * 2 dots = 1.5 ^ 2 = 225%, 3 dots = 1.5 ^ 3 = 337.5%. |
paulg | 1:67056b9df9ff | 101 | * This differs from standard musical notation where multiple dots |
paulg | 1:67056b9df9ff | 102 | * provide successively smaller extensions as follows: |
paulg | 1:67056b9df9ff | 103 | * 2 dots = 1 + 1/2 + 1/4 = 175%. |
paulg | 1:67056b9df9ff | 104 | * 3 dots = 1 + 1/2 + 1/4 + 1/8 = 187.5%. |
paulg | 1:67056b9df9ff | 105 | * |
paulg | 1:67056b9df9ff | 106 | * The standard notation extensions are used here. |
paulg | 1:67056b9df9ff | 107 | * |
paulg | 1:67056b9df9ff | 108 | * > Play the following note in the next higher octave. For example, |
paulg | 1:67056b9df9ff | 109 | * O3C >D E is equivalent to O3C O4D O3E. |
paulg | 1:67056b9df9ff | 110 | * |
paulg | 1:67056b9df9ff | 111 | * < Play the following note in the next lower octave. For example, |
paulg | 1:67056b9df9ff | 112 | * O3C <D E is equivalent to O3C O2D O3E. |
paulg | 1:67056b9df9ff | 113 | * |
paulg | 1:67056b9df9ff | 114 | * Note: Some dialects of MML appear to treat < and > as persistent |
paulg | 1:67056b9df9ff | 115 | * commands that affect all following notes. |
paulg | 1:67056b9df9ff | 116 | * |
paulg | 1:67056b9df9ff | 117 | * Note: The Microsoft documentation does not say whether or not < and > |
paulg | 1:67056b9df9ff | 118 | * should act on notes specified by number, such as N37. In this |
paulg | 1:67056b9df9ff | 119 | * implementation it does, so >N37 = N49. |
paulg | 1:67056b9df9ff | 120 | * |
paulg | 1:67056b9df9ff | 121 | * : # Treat remainder of line as a comment. Note: line must end with '\n'. |
paulg | 1:67056b9df9ff | 122 | * |
paulg | 1:67056b9df9ff | 123 | * Note: Some dialects of MML use # to start a comment. We accept either. |
paulg | 1:67056b9df9ff | 124 | * |
paulg | 1:67056b9df9ff | 125 | * White space and line endings (CR, LF) are ignored (except in comments). |
paulg | 1:67056b9df9ff | 126 | * |
paulg | 1:67056b9df9ff | 127 | * Note: The play() function has a second parameter which supports some MML |
paulg | 1:67056b9df9ff | 128 | * dialect alternatives. Refer to the comments below for details. |
paulg | 1:67056b9df9ff | 129 | */ |
paulg | 1:67056b9df9ff | 130 | |
paulg | 1:67056b9df9ff | 131 | #include "mbed.h" |
paulg | 1:67056b9df9ff | 132 | #include "PwmSound.h" |
paulg | 1:67056b9df9ff | 133 | #include "ctype.h" |
paulg | 1:67056b9df9ff | 134 | |
paulg | 1:67056b9df9ff | 135 | extern Serial pc; //for debug, comment out of not needed |
paulg | 1:67056b9df9ff | 136 | |
paulg | 1:67056b9df9ff | 137 | // Standard note pitches in Hz |
paulg | 1:67056b9df9ff | 138 | // From Wikipedia: http://en.wikipedia.org/wiki/Scientific_pitch_notation |
paulg | 1:67056b9df9ff | 139 | // First entry is a dummy, real note numbers start at 1 |
paulg | 1:67056b9df9ff | 140 | // Seven octaves, twelve notes per octave |
paulg | 1:67056b9df9ff | 141 | // C, C#, D, D#, E, F, F#, G, G#, A, A#, B |
paulg | 1:67056b9df9ff | 142 | // Middle C (261.63Hz) is element 37 |
paulg | 1:67056b9df9ff | 143 | |
paulg | 1:67056b9df9ff | 144 | float notePitches[1+84] = { |
paulg | 1:67056b9df9ff | 145 | 1.0, //dummy |
paulg | 1:67056b9df9ff | 146 | 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, //first octave |
paulg | 1:67056b9df9ff | 147 | 46.249, 48.999, 51.913, 55.000, 58.270, 61.735, |
paulg | 1:67056b9df9ff | 148 | 65.406, 69.296, 73.416, 77.782, 82.407, 87.307, //second octave |
paulg | 1:67056b9df9ff | 149 | 92.499, 97.999, 103.83, 110.00, 116.54, 123.47, |
paulg | 1:67056b9df9ff | 150 | 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, //third octave |
paulg | 1:67056b9df9ff | 151 | 185.00, 196.00, 207.65, 220.00, 233.08, 246.94, |
paulg | 1:67056b9df9ff | 152 | 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, //fourth octave |
paulg | 1:67056b9df9ff | 153 | 369.99, 392.00, 415.30, 440.00, 466.16, 493.88, |
paulg | 1:67056b9df9ff | 154 | 523.25, 554.37, 587.33, 622.25, 659.26, 698.46, //fifth octave |
paulg | 1:67056b9df9ff | 155 | 739.99, 783.99, 830.61, 880.00, 932.33, 987.77, |
paulg | 1:67056b9df9ff | 156 | 1046.5, 1108.7, 1174.7, 1244.5, 1318.5, 1396.9, //sixth octave |
paulg | 1:67056b9df9ff | 157 | 1480.0, 1568.0, 1661.2, 1760.0, 1864.7, 1975.5, |
paulg | 1:67056b9df9ff | 158 | 2093.0, 2217.5, 2349.3, 2489.0, 2637.0, 2793.8, //seventh octave |
paulg | 1:67056b9df9ff | 159 | 2960.0, 3136.0, 3322.4, 3520.0, 3729.3, 3951.1, |
paulg | 1:67056b9df9ff | 160 | }; |
paulg | 1:67056b9df9ff | 161 | |
paulg | 1:67056b9df9ff | 162 | // Note numbers within octave for notes A - G (white keys on piano) |
paulg | 1:67056b9df9ff | 163 | |
paulg | 1:67056b9df9ff | 164 | int notes[7] = { 10, 12, 1, 3, 5, 6, 8 }; //C is first note = 1 |
paulg | 1:67056b9df9ff | 165 | |
paulg | 1:67056b9df9ff | 166 | // Allowable note modifiers for notes A - G |
paulg | 1:67056b9df9ff | 167 | |
paulg | 1:67056b9df9ff | 168 | int flats[7] = { -1, -1, 0, -1, -1, 0, -1 }; //not C or F |
paulg | 1:67056b9df9ff | 169 | int sharps[7] = {1, 0, 1, 1, 0, 1, 1 }; //not B or E |
paulg | 1:67056b9df9ff | 170 | |
paulg | 1:67056b9df9ff | 171 | // Play a melody from music data written in MML format. |
paulg | 1:67056b9df9ff | 172 | // |
paulg | 1:67056b9df9ff | 173 | // Parameters: |
paulg | 1:67056b9df9ff | 174 | // m - pointer to string containing music data |
paulg | 1:67056b9df9ff | 175 | // options (default 0) - support for different dialects of MML |
paulg | 1:67056b9df9ff | 176 | // bit 0 (1) - standard octave numbering |
paulg | 1:67056b9df9ff | 177 | // 0: octaves range from 0 to 6, middle C starts octave 3 |
paulg | 1:67056b9df9ff | 178 | // 1: octaves range from 1 to 7, middle C starts octave 4 |
paulg | 1:67056b9df9ff | 179 | // bit 1 (2) - stickyShift |
paulg | 1:67056b9df9ff | 180 | // 0: < and > act on next note only |
paulg | 1:67056b9df9ff | 181 | // 1: < and > act on all following notes |
paulg | 1:67056b9df9ff | 182 | // bit 2 (4) - longDots |
paulg | 1:67056b9df9ff | 183 | // 0: standard dot extensions (i.e. 150%, 175%, 187.5%) |
paulg | 1:67056b9df9ff | 184 | // 1: GW-BASIC dot extensions (i.e. 150%, 225%, 337.5%) |
paulg | 1:67056b9df9ff | 185 | // Returns: 0 if no error in input, otherwise position of offending character |
paulg | 1:67056b9df9ff | 186 | |
paulg | 1:67056b9df9ff | 187 | int PwmSound::play(char* m, int options) { |
paulg | 1:67056b9df9ff | 188 | bool run = true, kbdPoll = true; |
paulg | 1:67056b9df9ff | 189 | char c, c1; |
paulg | 1:67056b9df9ff | 190 | int n, n1, n2; |
paulg | 1:67056b9df9ff | 191 | |
paulg | 1:67056b9df9ff | 192 | bool stdOctNum = (options & 1) ? true : false; //options bits |
paulg | 1:67056b9df9ff | 193 | bool stickyShift = (options & 2) ? true : false; |
paulg | 1:67056b9df9ff | 194 | bool longDots = (options & 4) ? true : false; |
paulg | 1:67056b9df9ff | 195 | |
paulg | 1:67056b9df9ff | 196 | _octave = 4; //set defaults |
paulg | 1:67056b9df9ff | 197 | _shift = 0; |
paulg | 1:67056b9df9ff | 198 | _tempo = 120; |
paulg | 1:67056b9df9ff | 199 | _length = 4; |
paulg | 1:67056b9df9ff | 200 | _1dot = 1.5; |
paulg | 1:67056b9df9ff | 201 | _2dots = (longDots == true) ? 2.25 : 1.75; |
paulg | 1:67056b9df9ff | 202 | _3dots = (longDots == true) ? 3.375 : 1.875; |
paulg | 1:67056b9df9ff | 203 | _style = 7; |
paulg | 1:67056b9df9ff | 204 | _dutyCycle = 0.5; |
paulg | 1:67056b9df9ff | 205 | _mp = m; |
paulg | 1:67056b9df9ff | 206 | _haveNext = false; |
paulg | 1:67056b9df9ff | 207 | //pc.putc('['); |
paulg | 1:67056b9df9ff | 208 | while (run) { |
paulg | 1:67056b9df9ff | 209 | if (kbdPoll && pc.readable()) { |
paulg | 1:67056b9df9ff | 210 | pc.getc(); |
paulg | 1:67056b9df9ff | 211 | break; |
paulg | 1:67056b9df9ff | 212 | } |
paulg | 1:67056b9df9ff | 213 | c = _getChar(); //read next char in input stream |
paulg | 1:67056b9df9ff | 214 | //pc.putc(c); |
paulg | 1:67056b9df9ff | 215 | switch (c) { |
paulg | 1:67056b9df9ff | 216 | case 'A': //specify note by letter (and modifier) |
paulg | 1:67056b9df9ff | 217 | case 'B': |
paulg | 1:67056b9df9ff | 218 | case 'C': |
paulg | 1:67056b9df9ff | 219 | case 'D': |
paulg | 1:67056b9df9ff | 220 | case 'E': |
paulg | 1:67056b9df9ff | 221 | case 'F': |
paulg | 1:67056b9df9ff | 222 | case 'G': |
paulg | 1:67056b9df9ff | 223 | if (stdOctNum) { |
paulg | 1:67056b9df9ff | 224 | n = ((_octave - 1) * 12) + notes[c - 'A']; |
paulg | 1:67056b9df9ff | 225 | } else { |
paulg | 1:67056b9df9ff | 226 | n = (_octave * 12) + notes[c - 'A']; |
paulg | 1:67056b9df9ff | 227 | } |
paulg | 1:67056b9df9ff | 228 | c1 = _nextChar(); //optional modifier |
paulg | 1:67056b9df9ff | 229 | if (c1 == '-' || c1 == '+' || c1 == '#') { |
paulg | 1:67056b9df9ff | 230 | c1 = _getChar(); |
paulg | 1:67056b9df9ff | 231 | n += (c1 == '-') ? flats[c - 'A'] : sharps[c - 'A']; |
paulg | 1:67056b9df9ff | 232 | } |
paulg | 1:67056b9df9ff | 233 | n += _shift * 12; |
paulg | 1:67056b9df9ff | 234 | if (!stickyShift) { |
paulg | 1:67056b9df9ff | 235 | _shift = 0; |
paulg | 1:67056b9df9ff | 236 | } |
paulg | 1:67056b9df9ff | 237 | n1 = _getNumber(); //optional length number |
paulg | 1:67056b9df9ff | 238 | n2 = _getDots(); //optional dots |
paulg | 1:67056b9df9ff | 239 | if (n1 == 0) { |
paulg | 1:67056b9df9ff | 240 | _note(n, _length, n2); |
paulg | 1:67056b9df9ff | 241 | } else { |
paulg | 1:67056b9df9ff | 242 | _note(n, n1, n2); |
paulg | 1:67056b9df9ff | 243 | } |
paulg | 1:67056b9df9ff | 244 | break; |
paulg | 1:67056b9df9ff | 245 | |
paulg | 1:67056b9df9ff | 246 | case 'K': |
paulg | 1:67056b9df9ff | 247 | kbdPoll = false; |
paulg | 1:67056b9df9ff | 248 | break; |
paulg | 1:67056b9df9ff | 249 | |
paulg | 1:67056b9df9ff | 250 | case 'L': //set note length |
paulg | 1:67056b9df9ff | 251 | n = _getNumber(); |
paulg | 1:67056b9df9ff | 252 | if (n >= 1 && n <= 64) { |
paulg | 1:67056b9df9ff | 253 | _length = n; |
paulg | 1:67056b9df9ff | 254 | } |
paulg | 1:67056b9df9ff | 255 | break; |
paulg | 1:67056b9df9ff | 256 | |
paulg | 1:67056b9df9ff | 257 | case 'M': //set music style (proportion of note length played) |
paulg | 1:67056b9df9ff | 258 | switch (_getChar() ) { |
paulg | 1:67056b9df9ff | 259 | case 'L': //legato |
paulg | 1:67056b9df9ff | 260 | _style = 8; |
paulg | 1:67056b9df9ff | 261 | break; |
paulg | 1:67056b9df9ff | 262 | |
paulg | 1:67056b9df9ff | 263 | case 'N': //normal |
paulg | 1:67056b9df9ff | 264 | _style = 7; |
paulg | 1:67056b9df9ff | 265 | break; |
paulg | 1:67056b9df9ff | 266 | |
paulg | 1:67056b9df9ff | 267 | case 'S': //staccato |
paulg | 1:67056b9df9ff | 268 | _style = 6; |
paulg | 1:67056b9df9ff | 269 | break; |
paulg | 1:67056b9df9ff | 270 | } |
paulg | 1:67056b9df9ff | 271 | break; |
paulg | 1:67056b9df9ff | 272 | |
paulg | 1:67056b9df9ff | 273 | case 'N': //specify note by number |
paulg | 1:67056b9df9ff | 274 | n = _getNumber(); |
paulg | 1:67056b9df9ff | 275 | n += _shift * 12; //not really sure about this |
paulg | 1:67056b9df9ff | 276 | if (!stickyShift) { |
paulg | 1:67056b9df9ff | 277 | _shift = 0; |
paulg | 1:67056b9df9ff | 278 | } |
paulg | 1:67056b9df9ff | 279 | _note(n, _length); |
paulg | 1:67056b9df9ff | 280 | break; |
paulg | 1:67056b9df9ff | 281 | |
paulg | 1:67056b9df9ff | 282 | case 'O': //set octave |
paulg | 1:67056b9df9ff | 283 | n = _getNumber(); |
paulg | 1:67056b9df9ff | 284 | if (stdOctNum) { |
paulg | 1:67056b9df9ff | 285 | if (n >= 1 && n <= 7) { |
paulg | 1:67056b9df9ff | 286 | _octave = n; |
paulg | 1:67056b9df9ff | 287 | } |
paulg | 1:67056b9df9ff | 288 | } else { |
paulg | 1:67056b9df9ff | 289 | if (n >= 0 && n <= 6) { |
paulg | 1:67056b9df9ff | 290 | _octave = n; |
paulg | 1:67056b9df9ff | 291 | } |
paulg | 1:67056b9df9ff | 292 | } |
paulg | 1:67056b9df9ff | 293 | break; |
paulg | 1:67056b9df9ff | 294 | |
paulg | 1:67056b9df9ff | 295 | case 'P': //pause or rest |
paulg | 1:67056b9df9ff | 296 | case 'R': |
paulg | 1:67056b9df9ff | 297 | n1 = _getNumber(); //optional length number |
paulg | 1:67056b9df9ff | 298 | n2 = _getDots(); //optional dots |
paulg | 1:67056b9df9ff | 299 | if (n1 == 0) { |
paulg | 1:67056b9df9ff | 300 | _note(0, _length, n2); |
paulg | 1:67056b9df9ff | 301 | } else { |
paulg | 1:67056b9df9ff | 302 | _note(0, n1, n2); |
paulg | 1:67056b9df9ff | 303 | } |
paulg | 1:67056b9df9ff | 304 | break; |
paulg | 1:67056b9df9ff | 305 | |
paulg | 1:67056b9df9ff | 306 | case 'Q': //set timbre |
paulg | 1:67056b9df9ff | 307 | n = _getNumber(); |
paulg | 1:67056b9df9ff | 308 | if (n >= 1 && n <= 4) { |
paulg | 1:67056b9df9ff | 309 | _dutyCycle = n / 8.0; |
paulg | 1:67056b9df9ff | 310 | } |
paulg | 1:67056b9df9ff | 311 | break; |
paulg | 1:67056b9df9ff | 312 | |
paulg | 1:67056b9df9ff | 313 | case 'T': //set tempo |
paulg | 1:67056b9df9ff | 314 | n = _getNumber(); |
paulg | 1:67056b9df9ff | 315 | if ( n>= 32 && n <= 255) { |
paulg | 1:67056b9df9ff | 316 | _tempo = n; |
paulg | 1:67056b9df9ff | 317 | } |
paulg | 1:67056b9df9ff | 318 | break; |
paulg | 1:67056b9df9ff | 319 | |
paulg | 1:67056b9df9ff | 320 | case '<': //move down an octave |
paulg | 1:67056b9df9ff | 321 | _shift--; |
paulg | 1:67056b9df9ff | 322 | break; |
paulg | 1:67056b9df9ff | 323 | |
paulg | 1:67056b9df9ff | 324 | case '>': //move up an octave |
paulg | 1:67056b9df9ff | 325 | _shift++; |
paulg | 1:67056b9df9ff | 326 | break; |
paulg | 1:67056b9df9ff | 327 | |
paulg | 1:67056b9df9ff | 328 | case ':': //comment to end of line |
paulg | 1:67056b9df9ff | 329 | case '#': |
paulg | 1:67056b9df9ff | 330 | while (_getChar() != '\n') ; |
paulg | 1:67056b9df9ff | 331 | break; |
paulg | 1:67056b9df9ff | 332 | |
paulg | 1:67056b9df9ff | 333 | case ' ': //skip over white space and line endings |
paulg | 1:67056b9df9ff | 334 | case '\t': |
paulg | 1:67056b9df9ff | 335 | case '\r': |
paulg | 1:67056b9df9ff | 336 | case '\n': |
paulg | 1:67056b9df9ff | 337 | break; |
paulg | 1:67056b9df9ff | 338 | |
paulg | 1:67056b9df9ff | 339 | case '\0': //end of string |
paulg | 1:67056b9df9ff | 340 | run = false; |
paulg | 1:67056b9df9ff | 341 | break; |
paulg | 1:67056b9df9ff | 342 | |
paulg | 1:67056b9df9ff | 343 | default: //abort on invalid characters |
paulg | 1:67056b9df9ff | 344 | _pin = 0.0; |
paulg | 1:67056b9df9ff | 345 | return(int (_mp - m) ); //return position of error |
paulg | 1:67056b9df9ff | 346 | } |
paulg | 1:67056b9df9ff | 347 | } //end of while |
paulg | 1:67056b9df9ff | 348 | //pc.putc(']'); |
paulg | 1:67056b9df9ff | 349 | _pin = 0.0; |
paulg | 1:67056b9df9ff | 350 | return 0; |
paulg | 1:67056b9df9ff | 351 | } |
paulg | 1:67056b9df9ff | 352 | |
paulg | 1:67056b9df9ff | 353 | // Play a musical note on output pin |
paulg | 1:67056b9df9ff | 354 | // |
paulg | 1:67056b9df9ff | 355 | // Parameters: |
paulg | 1:67056b9df9ff | 356 | // number - 0 = rest, notes from 1 to 84, middle C (262Hz) = 37 |
paulg | 1:67056b9df9ff | 357 | // length - duration of note (1-64): 1 = whole note (semibreve) |
paulg | 1:67056b9df9ff | 358 | // 2 = half note (minim) |
paulg | 1:67056b9df9ff | 359 | // 4 = quarter note (crotchet) = 1 beat |
paulg | 1:67056b9df9ff | 360 | // 8 = eighth note (quaver) |
paulg | 1:67056b9df9ff | 361 | // etc |
paulg | 1:67056b9df9ff | 362 | // dots - length extension (0-3, default 0) |
paulg | 1:67056b9df9ff | 363 | // Returns: nothing |
paulg | 1:67056b9df9ff | 364 | |
paulg | 1:67056b9df9ff | 365 | void PwmSound::_note(int number, int length, int dots) { |
paulg | 1:67056b9df9ff | 366 | float duration, play, rest; |
paulg | 1:67056b9df9ff | 367 | |
paulg | 1:67056b9df9ff | 368 | if (number < 1 || number > 84) { //convert bad note to a rest |
paulg | 1:67056b9df9ff | 369 | number = 0; |
paulg | 1:67056b9df9ff | 370 | } |
paulg | 1:67056b9df9ff | 371 | |
paulg | 1:67056b9df9ff | 372 | duration = 240.0 / (_tempo * length); |
paulg | 1:67056b9df9ff | 373 | if (dots == 1) { |
paulg | 1:67056b9df9ff | 374 | duration *= _1dot; |
paulg | 1:67056b9df9ff | 375 | } else if (dots == 2) { |
paulg | 1:67056b9df9ff | 376 | duration *= _2dots; |
paulg | 1:67056b9df9ff | 377 | } else if (dots == 3) { |
paulg | 1:67056b9df9ff | 378 | duration *= _3dots; |
paulg | 1:67056b9df9ff | 379 | } |
paulg | 1:67056b9df9ff | 380 | play = duration * _style / 8.0; |
paulg | 1:67056b9df9ff | 381 | rest = duration * (8 - _style) / 8.0; |
paulg | 1:67056b9df9ff | 382 | |
paulg | 1:67056b9df9ff | 383 | if (number > 0) { |
paulg | 1:67056b9df9ff | 384 | _pin.period(1.0 / notePitches[number]); |
paulg | 1:67056b9df9ff | 385 | _pin = _dutyCycle; |
paulg | 1:67056b9df9ff | 386 | } |
paulg | 1:67056b9df9ff | 387 | wait(play); |
paulg | 1:67056b9df9ff | 388 | _pin = 0.0; |
paulg | 1:67056b9df9ff | 389 | wait(rest); |
paulg | 1:67056b9df9ff | 390 | } |
paulg | 1:67056b9df9ff | 391 | |
paulg | 1:67056b9df9ff | 392 | // Read next character in input string |
paulg | 1:67056b9df9ff | 393 | // |
paulg | 1:67056b9df9ff | 394 | // Parameters: none |
paulg | 1:67056b9df9ff | 395 | // Returns: next character |
paulg | 1:67056b9df9ff | 396 | |
paulg | 1:67056b9df9ff | 397 | char PwmSound::_getChar(void) { |
paulg | 1:67056b9df9ff | 398 | if (_haveNext) { |
paulg | 1:67056b9df9ff | 399 | _haveNext = false; |
paulg | 1:67056b9df9ff | 400 | return _nextCh; |
paulg | 1:67056b9df9ff | 401 | } else { |
paulg | 1:67056b9df9ff | 402 | return *_mp++; |
paulg | 1:67056b9df9ff | 403 | } |
paulg | 1:67056b9df9ff | 404 | } |
paulg | 1:67056b9df9ff | 405 | |
paulg | 1:67056b9df9ff | 406 | // Examine next character in input string without consuming it |
paulg | 1:67056b9df9ff | 407 | // |
paulg | 1:67056b9df9ff | 408 | // Parameters: none |
paulg | 1:67056b9df9ff | 409 | // Returns: next character |
paulg | 1:67056b9df9ff | 410 | |
paulg | 1:67056b9df9ff | 411 | char PwmSound::_nextChar(void) { |
paulg | 1:67056b9df9ff | 412 | if (!_haveNext) { |
paulg | 1:67056b9df9ff | 413 | _nextCh = *_mp++; |
paulg | 1:67056b9df9ff | 414 | _haveNext = true; |
paulg | 1:67056b9df9ff | 415 | } |
paulg | 1:67056b9df9ff | 416 | return _nextCh; |
paulg | 1:67056b9df9ff | 417 | } |
paulg | 1:67056b9df9ff | 418 | |
paulg | 1:67056b9df9ff | 419 | // Read a variable length number from input string |
paulg | 1:67056b9df9ff | 420 | // |
paulg | 1:67056b9df9ff | 421 | // Parameters: none |
paulg | 1:67056b9df9ff | 422 | // Returns: number |
paulg | 1:67056b9df9ff | 423 | |
paulg | 1:67056b9df9ff | 424 | int PwmSound::_getNumber(void) { |
paulg | 1:67056b9df9ff | 425 | int n = 0; |
paulg | 1:67056b9df9ff | 426 | |
paulg | 1:67056b9df9ff | 427 | while (isdigit(_nextChar()) ) { |
paulg | 1:67056b9df9ff | 428 | n *= 10; |
paulg | 1:67056b9df9ff | 429 | n += _getChar() - '0'; |
paulg | 1:67056b9df9ff | 430 | } |
paulg | 1:67056b9df9ff | 431 | return n; |
paulg | 1:67056b9df9ff | 432 | } |
paulg | 1:67056b9df9ff | 433 | |
paulg | 1:67056b9df9ff | 434 | // Read variable number of dots from input string |
paulg | 1:67056b9df9ff | 435 | // |
paulg | 1:67056b9df9ff | 436 | // Parameters: none |
paulg | 1:67056b9df9ff | 437 | // Returns: number of dots |
paulg | 1:67056b9df9ff | 438 | |
paulg | 1:67056b9df9ff | 439 | int PwmSound::_getDots(void) { |
paulg | 1:67056b9df9ff | 440 | int n = 0; |
paulg | 1:67056b9df9ff | 441 | |
paulg | 1:67056b9df9ff | 442 | while (_nextChar() == '.') { |
paulg | 1:67056b9df9ff | 443 | _getChar(); |
paulg | 1:67056b9df9ff | 444 | n++; |
paulg | 1:67056b9df9ff | 445 | } |
paulg | 1:67056b9df9ff | 446 | return n; |
paulg | 1:67056b9df9ff | 447 | } |
paulg | 1:67056b9df9ff | 448 | |
paulg | 1:67056b9df9ff | 449 | // END of play.cpp |