diff options
author | px4dev <px4@purgatory.org> | 2013-03-17 10:39:01 -0700 |
---|---|---|
committer | px4dev <px4@purgatory.org> | 2013-03-17 10:39:01 -0700 |
commit | 8d47b3c6d428fefc709c612b983fb8449c944a96 (patch) | |
tree | b82b211303299951b48a9fd5e1bdbb9bbf8fb7d4 | |
parent | 002fb1b4ea90f0cdb7709a3668fb8387b64fcc1e (diff) | |
parent | 8fe14fdfdb5e0fedb0fdf8e3405111ee0c265678 (diff) | |
download | px4-firmware-8d47b3c6d428fefc709c612b983fb8449c944a96.tar.gz px4-firmware-8d47b3c6d428fefc709c612b983fb8449c944a96.tar.bz2 px4-firmware-8d47b3c6d428fefc709c612b983fb8449c944a96.zip |
Merge pull request #220 from PX4/tone_alarm_PLAY
New tone_alarm driver that uses ANSI music / GWBasic PLAY strings
-rw-r--r-- | apps/commander/commander.c | 10 | ||||
-rw-r--r-- | apps/drivers/stm32/tone_alarm/tone_alarm.cpp | 934 |
2 files changed, 655 insertions, 289 deletions
diff --git a/apps/commander/commander.c b/apps/commander/commander.c index 68ab2c5b6..7c0a25f86 100644 --- a/apps/commander/commander.c +++ b/apps/commander/commander.c @@ -243,11 +243,11 @@ static int led_off(int led) } enum AUDIO_PATTERN { - AUDIO_PATTERN_ERROR = 1, - AUDIO_PATTERN_NOTIFY_POSITIVE = 2, - AUDIO_PATTERN_NOTIFY_NEUTRAL = 3, - AUDIO_PATTERN_NOTIFY_NEGATIVE = 4, - AUDIO_PATTERN_TETRIS = 5 + AUDIO_PATTERN_ERROR = 2, + AUDIO_PATTERN_NOTIFY_POSITIVE = 3, + AUDIO_PATTERN_NOTIFY_NEUTRAL = 4, + AUDIO_PATTERN_NOTIFY_NEGATIVE = 5, + AUDIO_PATTERN_NOTIFY_CHARGE = 6 }; int trigger_audio_alarm(uint8_t old_mode, uint8_t old_state, uint8_t new_mode, uint8_t new_state) diff --git a/apps/drivers/stm32/tone_alarm/tone_alarm.cpp b/apps/drivers/stm32/tone_alarm/tone_alarm.cpp index 6799a0a21..baa652d8a 100644 --- a/apps/drivers/stm32/tone_alarm/tone_alarm.cpp +++ b/apps/drivers/stm32/tone_alarm/tone_alarm.cpp @@ -35,27 +35,59 @@ * Driver for the PX4 audio alarm port, /dev/tone_alarm. * * The tone_alarm driver supports a set of predefined "alarm" - * patterns and one user-supplied pattern. Patterns are ordered by - * priority, with a higher-priority pattern interrupting any - * lower-priority pattern that might be playing. + * tunes and one user-supplied tune. * * The TONE_SET_ALARM ioctl can be used to select a predefined - * alarm pattern, from 1 - <TBD>. Selecting pattern zero silences + * alarm tune, from 1 - <TBD>. Selecting tune zero silences * the alarm. * - * To supply a custom pattern, write an array of 1 - <TBD> tone_note - * structures to /dev/tone_alarm. The custom pattern has a priority - * of zero. + * Tunes follow the syntax of the Microsoft GWBasic/QBasic PLAY + * statement, with some exceptions and extensions. + * + * From Wikibooks: + * + * PLAY "[string expression]" + * + * Used to play notes and a score ... The tones are indicated by letters A through G. + * Accidentals are indicated with a "+" or "#" (for sharp) or "-" (for flat) + * immediately after the note letter. See this example: + * + * PLAY "C C# C C#" + * + * Whitespaces are ignored inside the string expression. There are also codes that + * set the duration, octave and tempo. They are all case-insensitive. PLAY executes + * the commands or notes the order in which they appear in the string. Any indicators + * that change the properties are effective for the notes following that indicator. + * + * Ln Sets the duration (length) of the notes. The variable n does not indicate an actual duration + * amount but rather a note type; L1 - whole note, L2 - half note, L4 - quarter note, etc. + * (L8, L16, L32, L64, ...). By default, n = 4. + * For triplets and quintets, use L3, L6, L12, ... and L5, L10, L20, ... series respectively. + * The shorthand notation of length is also provided for a note. For example, "L4 CDE L8 FG L4 AB" + * can be shortened to "L4 CDE F8G8 AB". F and G play as eighth notes while others play as quarter notes. + * On Sets the current octave. Valid values for n are 0 through 6. An octave begins with C and ends with B. + * Remember that C- is equivalent to B. + * < > Changes the current octave respectively down or up one level. + * Nn Plays a specified note in the seven-octave range. Valid values are from 0 to 84. (0 is a pause.) + * Cannot use with sharp and flat. Cannot use with the shorthand notation neither. + * MN Stand for Music Normal. Note duration is 7/8ths of the length indicated by Ln. It is the default mode. + * ML Stand for Music Legato. Note duration is full length of that indicated by Ln. + * MS Stand for Music Staccato. Note duration is 3/4ths of the length indicated by Ln. + * Pn Causes a silence (pause) for the length of note indicated (same as Ln). + * Tn Sets the number of "L4"s per minute (tempo). Valid values are from 32 to 255. The default value is T120. + * . When placed after a note, it causes the duration of the note to be 3/2 of the set duration. + * This is how to get "dotted" notes. "L4 C#." would play C sharp as a dotted quarter note. + * It can be used for a pause as well. + * + * Extensions/variations: + * + * MB MF The MF command causes the tune to play once and then stop. The MB command causes the + * tune to repeat when it ends. * - * Patterns will normally play once and then silence (if a pattern - * was overridden it will not resume). A pattern may be made to - * repeat by inserting a note with the duration set to - * DURATION_REPEAT. This pattern will loop until either a - * higher-priority pattern is started or pattern zero is requested - * via the ioctl. */ #include <nuttx/config.h> +#include <debug.h> #include <drivers/device/device.h> #include <drivers/drv_tone_alarm.h> @@ -69,6 +101,8 @@ #include <errno.h> #include <stdio.h> #include <unistd.h> +#include <math.h> +#include <ctype.h> #include <arch/board/board.h> #include <drivers/drv_hrt.h> @@ -202,141 +236,233 @@ public: virtual ssize_t write(file *filp, const char *buffer, size_t len); private: - static const unsigned _max_pattern = 6; - static const unsigned _pattern_none = _max_pattern + 1; - static const unsigned _pattern_user = 0; - static const unsigned _max_pattern_len = 40; - static const unsigned _note_max = TONE_NOTE_MAX; - - static const tone_note _patterns[_max_pattern][_max_pattern_len]; - static const uint16_t _notes[_note_max]; - - unsigned _current_pattern; - unsigned _next_note; - - hrt_call _note_end; - tone_note _user_pattern[_max_pattern_len]; - + static const unsigned _tune_max = 1024; // be reasonable about user tunes + static const char * const _default_tunes[]; + static const unsigned _default_ntunes; + static const uint8_t _note_tab[]; + + const char *_user_tune; + + const char *_tune; // current tune string + const char *_next; // next note in the string + + unsigned _tempo; + unsigned _note_length; + enum { MODE_NORMAL, MODE_LEGATO, MODE_STACCATO} _note_mode; + unsigned _octave; + unsigned _silence_length; // if nonzero, silence before next note + bool _repeat; // if true, tune restarts at end + + hrt_call _note_call; // HRT callout for note completion + + // Convert a note value in the range C1 to B7 into a divisor for + // the configured timer's clock. + // + unsigned note_to_divisor(unsigned note); + + // Calculate the duration in microseconds of play and silence for a + // note given the current tempo, length and mode and the number of + // dots following in the play string. + // + unsigned note_duration(unsigned &silence, unsigned note_length, unsigned dots); + + // Calculate the duration in microseconds of a rest corresponding to + // a given note length. + // + unsigned rest_duration(unsigned rest_length, unsigned dots); + + // Start playing the note + // + void start_note(unsigned note); + + // Stop playing the current note and make the player 'safe' + // + void stop_note(); + + // Start playing the tune + // + void start_tune(const char *tune); + + // Parse the next note out of the string and play it + // + void next_note(); + + // Find the next character in the string, discard any whitespace and + // return the canonical (uppercase) version. + // + int next_char(); + + // Extract a number from the string, consuming all the digit characters. + // + unsigned next_number(); + + // Consume dot characters from the string, returning the number consumed. + // + unsigned next_dots(); + + // hrt_call trampoline for next_note + // static void next_trampoline(void *arg); - void next(); - bool check(tone_note *pattern); - void stop(); -}; - -/* predefined patterns for alarms 1-_max_pattern */ -const tone_note ToneAlarm::_patterns[_max_pattern][_max_pattern_len] = { - { - {TONE_NOTE_A7, 12}, - {TONE_NOTE_D8, 12}, - {TONE_NOTE_C8, 12}, - {TONE_NOTE_A7, 12}, - {TONE_NOTE_D8, 12}, - {TONE_NOTE_C8, 12}, - {TONE_NOTE_D8, 4}, - {TONE_NOTE_C8, 4}, - {TONE_NOTE_D8, 4}, - {TONE_NOTE_C8, 4}, - {TONE_NOTE_D8, 4}, - {TONE_NOTE_C8, 4}, - }, - {{TONE_NOTE_B6, 100}, {TONE_NOTE_B6, DURATION_REPEAT}}, - {{TONE_NOTE_C7, 100}}, - {{TONE_NOTE_D7, 100}}, - {{TONE_NOTE_E7, 100}}, - { - //This is tetris ;) - {TONE_NOTE_C6, 40}, - {TONE_NOTE_G5, 20}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_A5S, 40}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_G5, 20}, - {TONE_NOTE_F5, 40}, - {TONE_NOTE_F5, 20}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_C6, 40}, - {TONE_NOTE_A5S, 20}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_G5, 60}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_A5S, 40}, - {TONE_NOTE_C6, 40}, - {TONE_NOTE_G5S, 40}, - {TONE_NOTE_F5, 40}, - {TONE_NOTE_F5, 60}, - {TONE_NOTE_A5S, 40}, - {TONE_NOTE_C6S, 20}, - {TONE_NOTE_F6, 40}, - {TONE_NOTE_D6S, 20}, - {TONE_NOTE_C6S, 20}, - {TONE_NOTE_C6, 60}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_C6, 40}, - {TONE_NOTE_A5S, 20}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_G5, 40}, - {TONE_NOTE_G5, 20}, - {TONE_NOTE_G5S, 20}, - {TONE_NOTE_A5S, 40}, - {TONE_NOTE_C6, 40}, - {TONE_NOTE_G5S, 40}, - {TONE_NOTE_F5, 40}, - {TONE_NOTE_F5, 60}, - } }; -const uint16_t ToneAlarm::_notes[_note_max] = { - 63707, /* E4 */ - 60132, /* F4 */ - 56758, /* F#4/Gb4 */ - 53571, /* G4 */ - 50565, /* G#4/Ab4 */ - 47727, /* A4 */ - 45048, /* A#4/Bb4 */ - 42520, /* B4 */ - 40133, /* C5 */ - 37880, /* C#5/Db5 */ - 35755, /* D5 */ - 33748, /* D#5/Eb5 */ - 31853, /* E5 */ - 30066, /* F5 */ - 28378, /* F#5/Gb5 */ - 26786, /* G5 */ - 25282, /* G#5/Ab5 */ - 23863, /* A5 */ - 22524, /* A#5/Bb5 */ - 21260, /* B5 */ - 20066, /* C6 */ - 18940, /* C#6/Db6 */ - 17877, /* D6 */ - 16874, /* D#6/Eb6 */ - 15927, /* E6 */ - 15033, /* F6 */ - 14189, /* F#6/Gb6 */ - 13393, /* G6 */ - 12641, /* G#6/Ab6 */ - 11931, /* A6 */ - 11262, /* A#6/Bb6 */ - 10630, /* B6 */ - 10033, /* C7 */ - 9470, /* C#7/Db7 */ - 8938, /* D7 */ - 8437, /* D#7/Eb7 */ - 7963, /* E7 */ - 7516, /* F7 */ - 7094, /* F#7/Gb7 */ - 6696, /* G7 */ - 6320, /* G#7/Ab7 */ - 5965, /* A7 */ - 5631, /* A#7/Bb7 */ - 5315, /* B7 */ - 5016, /* C8 */ - 4735, /* C#8/Db8 */ - 4469, /* D8 */ - 4218 /* D#8/Eb8 */ +// predefined tune array +const char * const ToneAlarm::_default_tunes[] = { + "MFT240L8 O4aO5dc O4aO5dc O4aO5dc L16dcdcdcdc", // startup tune + "MBT200a8a8a8PaaaP", // ERROR tone + "MFT200e8a8a", // NotifyPositive tone + "MFT200e8e", // NotifyNeutral tone + "MFT200e8c8e8c8e8c8", // NotifyNegative tone + "MFT90O3C16.C32C16.C32C16.C32G16.E32G16.E32G16.E32C16.C32C16.C32C16.C32G16.E32G16.E32G16.E32C4", // charge! + "MFT60O3C32O2A32F16F16F32G32A32A+32O3C16C16C16O2A16", // dixie + "MFT90O2C16C16C16F8.A8C16C16C16F8.A4P16P8", // cucuracha + "MNT150L8O2GGABGBADGGABL4GL8F+", // yankee + "MFT200O3C4.O2A4.G4.F4.D8E8F8D4F8C2.O2G4.O3C4.O2A4.F4.D8E8F8G4A8G2P8", // daisy + "T200O2B4P8B16B16B4P8B16B16B8G+8E8G+8B8G+8B8O3E8" // william tell + "O2B8G+8E8G+8B8G+8B8O3E8O2B4P8B16B16B4P8B16" + "O2B16B4P8B16B16B4P8B16B16B8B16B16B8B8B8B16" + "O2B16B8B8B8B16B16B8B8B8B16B16B8B8B2B2B8P8" + "P4P4P8O1B16B16B8B16B16B8B16B16O2E8F+8G+8" + "O1B16B16B8B16B16O2E8G+16G+16F+8D+8O1B8B16" + "O1B16B8B16B16B8B16B16O2E8F+8G+8E16G+16B4" + "O2B16A16G+16F+16E8G+8E8O3B16B16B8B16B16B8" + "O3B16B16O4E8F+8G+8O3B16B16B8B16B16O4E8G+16" + "O4G+16F+8D+8O3B8B16B16B8B16B16B8B16B16O4E8" + "O4F+8G+8E16G+16B4B16A16G+16F+16E8G+8E8O3G+16" + "O3G+16G+8G+16G+16G+8G+16G+16G+8O4C+8O3G+8" + "O4C+8O3G+8O4C+8O3G+8F+8E8D+8C+8G+16G+16G+8" + "O3G+16G+16G+8G+16G+16G+8O4C+8O3G+8O4C+8O3G+8" + "O4C+8O3B8A+8B8A+8B8G+16G+16G+8G+16G+16G+8" + "O3G+16G+16G+8O4C+8O3G+8O4C+8O3G+8O4C+8O3G+8" + "O3F+8E8D+8C+8G+16G+16G+8G+16G+16G+8G+16G+16" + "O3G+8O4C+8O3G+8O4C+8O3G+8O4C+8O3B8A+8B8O2B16" + "O2B16B8F+16F+16F+8F+16F+16F+8G+8A8F+4A8G+8" + "O2E4G+8F+8F+8F+8O3F+16F+16F+8F+16F+16F+8" + "O3G+8A8F+4A8G+8E4G+8F+8O2B16B16B8O1B16B16" + "O1B8B16B16B8B16B16O2E8F+8G+8O1B16B16B8B16" + "O1B16O2E8G+16G+16F+8D+8O1B8B16B16B8B16B16" + "O1B8B16B16O2E8F+8G+8E16G+16B4B16A16G+16F+16" + "O2E8G+8E8O3B16B16B8B16B16B8B16B16O4E8F+8" + "O4G+8O3B16B16B8B16B16O4E8G+16G+16F+8D+8O3B8" + "O3B16B16B8B16B16B8B16B16O4E8F+8G+8E16G+16" + "O4B4B16A16G+16F+16E8G+8E8O3E64F64G64A64B64" + "O4C64D64E8E16E16E8E8G+4.F+8E8D+8E8C+8O3B16" + "O4C+16O3B16O4C+16O3B16O4C+16D+16E16O3A16" + "O3B16A16B16A16B16O4C+16D+16O3G+16A16G+16" + "O3A16G+16A16B16O4C+16O3F+16G+16F+16G+16F+16" + "O3G+16F+16G+16F+16G+16F+16D+16O2B16O3B16" + "O4C+16D+16E8D+8E8C+8O3B16O4C+16O3B16O4C+16" + "O3B16O4C+16D+16E16O3A16B16A16B16A16B16O4C+16" + "O4D+16O3G+16A16G+16A16G+16A16B16O4C+16O3F+16" + "O3G+16F+16G+16F+16A16F+16E16E8P8C+4C+16O2C16" + "O3C+16O2C16O3D+16C+16O2B16A16A16G+16E16C+16" + "O2C+16C+16C+16C+16E16D+16O1C16G+16G+16G+16" + "O1G+16G+16G+16O2C+16E16G+16O3C+16C+16C+16" + "O3C+16C+16O2C16O3C+16O2C16O3D+16C+16O2B16" + "O2A16A16G+16E16C+16C+16C+16C+16C+16E16D+16" + "O1C16G+16G+16G+16G+16G+16G+16O2C+16E16G+16" + "O3C+16E16D+16C+16D+16O2C16G+16G+16G+16O3G+16" + "O3E16C+16D+16O2C16G+16G+16G+16O3G+16E16C+16" + "O3D+16O2B16G+16G+16A+16G16D+16D+16G+16G16" + "O2G+16G16G+16A16G+16F+16E16O1B16A+16B16O2E16" + "O1B16O2F+16O1B16O2G+16E16D+16E16G+16E16A16" + "O2F+16B16O3G+16F+16E16D+16F+16E16C+16O2B16" + "O3C+16O2B16O3C+16D+16E16F+16G+16O2A16B16" + "O2A16B16O3C+16D+16E16F+16O2G+16A16G+16A16" + "O2C16O3C+16D+16E16O2F+16G+16F+16G+16F+16" + "O2G+16F+16G+16F+16G+16F+16D+16O1B16C16O2C+16" + "O2D+16E16O1B16A+16B16O2E16O1B16O2F+16O1B16" + "O2G+16E16D+16E16G+16E16A16F+16B16O3G+16F+16" + "O3E16D+16F+16E16C+16O2B16O3C+16O2B16O3C+16" + "O3D+16E16F+16G+16O2A16B16A16B16O3C+16D+16" + "O3E16F+16O2G+16A16G+16A16B16O3C+16D+16E16" + "O2F+16O3C+16O2C16O3C+16D+16C+16O2A16F+16" + "O2E16O3E16F+16G+16A16B16O4C+16D+16E8E16E16" + "O4E8E8G+4.F8E8D+8E8C+8O3B16O4C+16O3B16O4C+16" + "O3B16O4C+16D+16E16O3A16B16A16B16A16B16O4C+16" + "O4D+16O3G+16A16G+16A16G+16A16B16O4C+16O3F+16" + "O3G+16F+16G+16F+16G+16F+16G+16F+16G+16F+16" + "O3D+16O2B16O3B16O4C+16D+16E8E16E16E8E8G+4." + "O4F+8E8D+8E8C+8O3B16O4C+16O3B16O4C+16O3B16" + "O4C+16D+16E16O3A16B16A16B16A16B16O4C+16D+16" + "O3G+16A16G+16A16G+16A16B16O4C+16O3F+16G+16" + "O3F+16G+16F+16A16G+16F+16E8O2B8O3E8G+16G+16" + "O3G+8G+16G+16G+8G+16G+16G+8O4C+8O3G+8O4C+8" + "O3G+8O4C+8O3G+8F+8E8D+8C+8G+16G+16G+8G+16" + "O3G+16G+8G+16G+16G+8O4C+8O3G+8O4C+8O3G+8" + "O4C+8O3B8A+8B8A+8B8G+16G+16G+8G+16G+16G+8" + "O3G+16G+16G+8O4C+8O3G+8O4C+8O3G+8O4C+8O3G+8" + "O3F+8E8D+8C+8G+16G+16G+8G+16G+16G+8G+16G+16" + "O3G+8O4C+8O3G+8O4C+8O3G+8O4C+8O3B8A+8B8A+8" + "O3B8O2F+16F+16F+8F+16F+16F+8G+8A8F+4A8G+8" + "O2E4G+8F+8B8O1B8O2F+16F+16F+8F+16F+16F+8" + "O2G+8A8F+4A8G+8E4G+8F+8B16B16B8O1B16B16B8" + "O1B16B16B8B16B16O2E8F+8G+8O1B16B16B8B16B16" + "O2E8G+16G+16F+8D+8O1B8B16B16B8B16B16B8B16" + "O1B16O2E8F+8G+8E16G+16B4B16A16G+16F+16E8" + "O1B8O2E8O3B16B16B8B16B16B8B16B16O4E8F+8G+8" + "O3B16B16B8B16B16O4E8G+16G+16F+8D+8O3B8B16" + "O3B16B8B16B16B8B16B16O4E8F+8G+8O3E16G+16" + "O3B4B16A16G+16F+16E16F+16G+16A16G+16A16B16" + "O4C+16O3B16O4C+16D+16E16D+16E16F+16G+16A16" + "O3B16O4A16O3B16O4A16O3B16O4A16O3B16O4A16" + "O3B16O4A16O3B16O4A16O3B16O4A16O3B16E16F+16" + "O3G+16A16G+16A16B16O4C+16O3B16O4C+16D+16" + "O4E16D+16E16F+16G+16A16O3B16O4A16O3B16O4A16" + "O3B16O4A16O3B16O4A16O3B16O4A16O3B16O4A16" + "O3B16O4A16O3B16P16G+16O4G+16O3G+16P16D+16" + "O4D+16O3D+16P16E16O4E16O3E16P16A16O4A16O3A16" + "P16O3G+16O4G+16O3G+16P16D+16O4D+16O3D+16" + "P16O3E16O4E16O3E16P16A16O4A16O3A16O4G16O3G16" + "O4G16O3G16O4G16O3G16O4G16O3G16O4G8E8C8E8" + "O4G+16O3G+16O4G+16O3G+16O4G+16O3G+16O4G+16" + "O3G+16O4G+8E8O3B8O4E8G+16O3G+16O4G+16O3G+16" + "O4G+16O3G+16O4G+16O3G+16O4G+8F8C+8F8A+16" + "O3A+16O4A+16O3A+16O4A+16O3A+16O4A+16O3A+16" + "O4A+8G8E8G8B8P16A+16P16A16P16G+16P16F+16" + "P16O4E16P16D+16P16C+16P16O3B16P16A+16P16" + "O3A16P16G+16P16F+16P16E16P16D+16P16F+16E16" + "O3F+16G+16A16G+16A16B16O4C+16O3B16O4C+16" + "O4D+16E16D+16E16F+16G+16A16O3B16O4A16O3B16" + "O4A16O3B16O4A16O3B16O4A16O3B16O4A16O3B16" + "O4A16O3B16O4A16O3B16E16F+16G+16A16G+16A16" + "O3B16O4C+16O3B16O4C+16D+16E16D+16E16F+16" + "O4G+16A16O3B16O4A16O3B16O4A16O3B16O4A16O3B16" + "O4A16O3B16O4A16O3B16O4A16O3B16O4A16O3B16" + "P16O3G+16O4G+16O3G+16P16D+16O4D+16O3D+16" + "P16O3E16O4E16O3E16P16A16O4A16O3A16P16G+16" + "O4G+16O3G+16P16D+16O4D+16O3D+16P16E16O4E16" + "O3E16P16A16O4A16O3A16O4G16O3G16O4G16O3G16" + "O4G16O3G16O4G16O3G16O4G8E8C8E8G+16O3G+16" + "O4G+16O3G+16O4G+16O3G+16O4G+16O3G+16O4G+8" + "O4E8O3B8O4E8G+16O3G+16O4G+16O3G+16O4G+16" + "O3G+16O4G+16O3G+16O4G+8F8C+8F8A+16O3A+16" + "O4A+16O3A+16O4A+16O3A+16O4A+16O3A+16O4A+8" + "O4G8E8G8B8P16A+16P16A16P16G+16P16F+16P16" + "O4E16P16D+16P16C+16P16O3B16P16A+16P16A16" + "P16O3G+16P16F+16P16E16P16D+16P16F16E16D+16" + "O3E16D+16E8B16B16B8B16B16B8B16B16O4E8F+8" + "O4G+8O3B16B16B8B16B16B8B16B16O4G+8A8B8P8" + "O4E8F+8G+8P8O3G+8A8B8P8P2O2B16C16O3C+16D16" + "O3D+16E16F16F+16G16G+16A16A+16B16C16O4C+16" + "O4D+16E16D+16F+16D+16E16D+16F+16D+16E16D+16" + "O4F+16D+16E16D+16F+16D+16E16D+16F+16D+16" + "O4E16D+16F+16D+16E16D+16F+16D+16E16D+16F+16" + "O4D+16E8E16O3E16O4E16O3E16O4E16O3E16O4E8" + "O3B16O2B16O3B16O2B16O3B16O2B16O3B8G+16O2G+16" + "O3G+16O2G+16O3G+16O2G+16O3G8E16O2E16O3E16" + "O2E16O3E16O2E16O3E8E16E16E8E8E8O2B16B16B8" + "O2B8B8G+16G+16G+8G+8G+8E16E16E8E8E8O1B8O2E8" + "O1B8O2G+8E8B8G+8O3E8O2B8O3E8O2B8O3G+8E8B8" + "O3G+8O4E4P8E16E16E8E8E8E8E4P8E16E4P8O2E16" + "O2E2P64", }; +const unsigned ToneAlarm::_default_ntunes = sizeof(_default_tunes) / sizeof(_default_tunes[0]); + +// semitone offsets from C for the characters 'A'-'G' +const uint8_t ToneAlarm::_note_tab[] = {9, 11, 0, 2, 4, 5, 7}; + /* * Driver 'main' command. */ @@ -345,8 +471,9 @@ extern "C" __EXPORT int tone_alarm_main(int argc, char *argv[]); ToneAlarm::ToneAlarm() : CDev("tone_alarm", "/dev/tone_alarm"), - _current_pattern(_pattern_none), - _next_note(0) + _user_tune(nullptr), + _tune(nullptr), + _next(nullptr) { // enable debug() calls //_debug_enabled = true; @@ -386,13 +513,8 @@ ToneAlarm::init() /* toggle the CC output each time the count passes 1 */ TONE_rCCR = 1; - /* - * Configure the timebase to free-run at half max frequency. - * XXX this should be more flexible in order to get a better - * frequency range, but for the F4 with the APB1 timers based - * at 42MHz, this gets us down to ~320Hz or so. - */ - rPSC = 1; + /* default the timer to a prescale value of 1; playing notes will change this */ + rPSC = 0; /* make sure the timer is running */ rCR1 = GTIM_CR1_CEN; @@ -401,196 +523,413 @@ ToneAlarm::init() return OK; } -int -ToneAlarm::ioctl(file *filp, int cmd, unsigned long arg) +unsigned +ToneAlarm::note_to_divisor(unsigned note) { - int result = 0; - unsigned new_pattern = arg; + // compute the frequency first (Hz) + float freq = 880.0f * expf(logf(2.0f) * ((int)note - 46) / 12.0f); - debug("ioctl %i %u", cmd, new_pattern); + float period = 0.5f / freq; -// irqstate_t flags = irqsave(); + // and the divisor, rounded to the nearest integer + unsigned divisor = (period * TONE_ALARM_CLOCK) + 0.5f; - /* decide whether to increase the alarm level to cmd or leave it alone */ - switch (cmd) { - case TONE_SET_ALARM: - debug("TONE_SET_ALARM %u", arg); - - if (new_pattern == 0) { - /* cancel any current alarm */ - _current_pattern = _pattern_none; - next(); - - } else if (new_pattern > _max_pattern) { - - /* not a legal alarm value */ - result = -ERANGE; - - } else if ((_current_pattern == _pattern_none) || (new_pattern > _current_pattern)) { - - /* higher priority than the current alarm */ - _current_pattern = new_pattern; - _next_note = 0; + return divisor; +} - /* and start playing it */ - next(); +unsigned +ToneAlarm::note_duration(unsigned &silence, unsigned note_length, unsigned dots) +{ + unsigned whole_note_period = (60 * 1000000 * 4) / _tempo; - } else { - /* current pattern is higher priority than the new pattern, ignore */ - } + if (note_length == 0) + note_length = 1; + unsigned note_period = whole_note_period / note_length; + switch (_note_mode) { + case MODE_NORMAL: + silence = note_period / 8; + break; + case MODE_STACCATO: + silence = note_period / 4; break; - default: - result = -ENOTTY; + case MODE_LEGATO: + silence = 0; break; } + note_period -= silence; -// irqrestore(flags); - - /* give it to the superclass if we didn't like it */ - if (result == -ENOTTY) - result = CDev::ioctl(filp, cmd, arg); + unsigned dot_extension = note_period / 2; + while (dots--) { + note_period += dot_extension; + dot_extension /= 2; + } - return result; + return note_period; } -int -ToneAlarm::write(file *filp, const char *buffer, size_t len) +unsigned +ToneAlarm::rest_duration(unsigned rest_length, unsigned dots) { - irqstate_t flags; - - /* sanity-check the size of the write */ - if (len > (_max_pattern_len * sizeof(tone_note))) - return -EFBIG; + unsigned whole_note_period = (60 * 1000000 * 4) / _tempo; - if ((len % sizeof(tone_note)) || (len == 0)) - return -EIO; + if (rest_length == 0) + rest_length = 1; - if (!check((tone_note *)buffer)) - return -EIO; + unsigned rest_period = whole_note_period / rest_length; - flags = irqsave(); + unsigned dot_extension = rest_period / 2; + while (dots--) { + rest_period += dot_extension; + dot_extension /= 2; + } - /* if we aren't playing an alarm tone */ - if (_current_pattern <= _pattern_user) { + return rest_period; +} - /* reset the tone state to play the new user pattern */ - _current_pattern = _pattern_user; - _next_note = 0; +void +ToneAlarm::start_note(unsigned note) +{ + // compute the divisor + unsigned divisor = note_to_divisor(note); - /* copy in the new pattern */ - memset(_user_pattern, 0, sizeof(_user_pattern)); - memcpy(_user_pattern, buffer, len); + // pick the lowest prescaler value that we can use + // (note that the effective prescale value is 1 greater) + unsigned prescale = divisor / 65536; - /* and start it */ - debug("starting user pattern"); - next(); - } + // calculate the timer period for the selected prescaler value + unsigned period = (divisor / (prescale + 1)) - 1; - irqrestore(flags); + rPSC = prescale; // load new prescaler + rARR = period; // load new toggle period + rEGR = GTIM_EGR_UG; // force a reload of the period + rCCER |= TONE_CCER; // enable the output - return len; } void -ToneAlarm::next_trampoline(void *arg) +ToneAlarm::stop_note() { - ToneAlarm *ta = (ToneAlarm *)arg; + /* stop the current note */ + rCCER &= ~TONE_CCER; - ta->next(); + /* + * Make sure the GPIO is not driving the speaker. + * + * XXX this presumes PX4FMU and the onboard speaker driver FET. + */ + stm32_gpiowrite(GPIO_TONE_ALARM, 0); } void -ToneAlarm::next(void) +ToneAlarm::start_tune(const char *tune) { - const tone_note *np; + // kill any current playback + hrt_cancel(&_note_call); + + // record the tune + _tune = tune; + _next = tune; + + // initialise player state + _tempo = 120; + _note_length = 4; + _note_mode = MODE_NORMAL; + _octave = 4; + _silence_length = 0; + _repeat = false; // otherwise command-line tunes repeat forever... + + // schedule a callback to start playing + hrt_call_after(&_note_call, 0, (hrt_callout)next_trampoline, this); +} - /* stop the current note */ - rCCER &= ~TONE_CCER; +void +ToneAlarm::next_note() +{ + // do we have an inter-note gap to wait for? + if (_silence_length > 0) { + stop_note(); + hrt_call_after(&_note_call, (hrt_abstime)_silence_length, (hrt_callout)next_trampoline, this); + _silence_length = 0; + return; + } - /* if we are no longer playing a pattern, we have nothing else to do here */ - if (_current_pattern == _pattern_none) { - stop(); + // make sure we still have a tune - may be removed by the write / ioctl handler + if ((_next == nullptr) || (_tune == nullptr)) { + stop_note(); return; } - ASSERT(_next_note < _note_max); + // parse characters out of the string until we have resolved a note + unsigned note = 0; + unsigned note_length = _note_length; + unsigned duration; + + while (note == 0) { + // we always need at least one character from the string + int c = next_char(); + if (c == 0) + goto tune_end; + _next++; + + switch (c) { + case 'L': // select note length + _note_length = next_number(); + if (_note_length < 1) + goto tune_error; + break; + + case 'O': // select octave + _octave = next_number(); + if (_octave > 6) + _octave = 6; + break; - /* find the note to play */ - if (_current_pattern == _pattern_user) { - np = &_user_pattern[_next_note]; + case '<': // decrease octave + if (_octave > 0) + _octave--; + break; - } else { - np = &_patterns[_current_pattern - 1][_next_note]; - } + case '>': // increase octave + if (_octave < 6) + _octave++; + break; + + case 'M': // select inter-note gap + c = next_char(); + if (c == 0) + goto tune_error; + _next++; + switch (c) { + case 'N': + _note_mode = MODE_NORMAL; + break; + case 'L': + _note_mode = MODE_LEGATO; + break; + case 'S': + _note_mode = MODE_STACCATO; + break; + case 'F': + _repeat = false; + break; + case 'B': + _repeat = true; + break; + default: + goto tune_error; + } + break; - /* work out which note is next */ - _next_note++; + case 'P': // pause for a note length + stop_note(); + hrt_call_after(&_note_call, + (hrt_abstime)rest_duration(next_number(), next_dots()), + (hrt_callout)next_trampoline, + this); + return; + + case 'T': { // change tempo + unsigned nt = next_number(); + + if ((nt >= 32) && (nt <= 255)) { + _tempo = nt; + } else { + goto tune_error; + } + break; + } - if (_next_note >= _note_max) { - /* hit the end of the pattern, stop */ - _current_pattern = _pattern_none; + case 'N': // play an arbitrary note + note = next_number(); + if (note > 84) + goto tune_error; + if (note == 0) { + // this is a rest - pause for the current note length + hrt_call_after(&_note_call, + (hrt_abstime)rest_duration(_note_length, next_dots()), + (hrt_callout)next_trampoline, + this); + return; + } + break; - } else if (np[1].duration == DURATION_END) { - /* hit the end of the pattern, stop */ - _current_pattern = _pattern_none; + case 'A'...'G': // play a note in the current octave + note = _note_tab[c - 'A'] + (_octave * 12) + 1; + c = next_char(); + switch (c) { + case '#': // up a semitone + case '+': + if (note < 84) + note++; + _next++; + break; + case '-': // down a semitone + if (note > 1) + note--; + _next++; + break; + default: + // 0 / no next char here is OK + break; + } + // shorthand length notation + note_length = next_number(); + if (note_length == 0) + note_length = _note_length; + break; - } else if (np[1].duration == DURATION_REPEAT) { - /* next note is a repeat, rewind in preparation */ - _next_note = 0; + default: + goto tune_error; + } } - /* set the timer to play the note, if required */ - if (np->pitch <= TONE_NOTE_SILENCE) { + // compute the duration of the note and the following silence (if any) + duration = note_duration(_silence_length, note_length, next_dots()); + + // start playing the note + start_note(note); + + // and arrange a callback when the note should stop + hrt_call_after(&_note_call, (hrt_abstime)duration, (hrt_callout)next_trampoline, this); + return; - /* set reload based on the pitch */ - rARR = _notes[np->pitch]; + // tune looks bad (unexpected EOF, bad character, etc.) +tune_error: + lowsyslog("tune error\n"); + _repeat = false; // don't loop on error - /* force an update, reloads the counter and all registers */ - rEGR = GTIM_EGR_UG; + // stop (and potentially restart) the tune +tune_end: + stop_note(); + if (_repeat) + start_tune(_tune); + return; +} - /* enable the output */ - rCCER |= TONE_CCER; +int +ToneAlarm::next_char() +{ + while (isspace(*_next)) { + _next++; } + return toupper(*_next); +} - /* arrange a callback when the note/rest is done */ - hrt_call_after(&_note_end, (hrt_abstime)10000 * np->duration, (hrt_callout)next_trampoline, this); +unsigned +ToneAlarm::next_number() +{ + unsigned number = 0; + int c; + + for (;;) { + c = next_char(); + if (!isdigit(c)) + return number; + _next++; + number = (number * 10) + (c - '0'); + } } -bool -ToneAlarm::check(tone_note *pattern) +unsigned +ToneAlarm::next_dots() { - for (unsigned i = 0; i < _note_max; i++) { + unsigned dots = 0; - /* first note must not be repeat or end */ - if ((i == 0) && - ((pattern[i].duration == DURATION_END) || (pattern[i].duration == DURATION_REPEAT))) - return false; + while (next_char() == '.') { + _next++; + dots++; + } + return dots; +} - if (pattern[i].duration == DURATION_END) - break; +void +ToneAlarm::next_trampoline(void *arg) +{ + ToneAlarm *ta = (ToneAlarm *)arg; - /* pitch must be legal */ - if (pattern[i].pitch >= _note_max) - return false; + ta->next_note(); +} + + +int +ToneAlarm::ioctl(file *filp, int cmd, unsigned long arg) +{ + int result = OK; + + debug("ioctl %i %u", cmd, arg); + +// irqstate_t flags = irqsave(); + + /* decide whether to increase the alarm level to cmd or leave it alone */ + switch (cmd) { + case TONE_SET_ALARM: + debug("TONE_SET_ALARM %u", arg); + + if (arg <= _default_ntunes) { + if (arg == 0) { + // stop the tune + _tune = nullptr; + _next = nullptr; + } else { + // play the selected tune + start_tune(_default_tunes[arg - 1]); + } + } else { + result = -EINVAL; + } + + break; + + default: + result = -ENOTTY; + break; } - return true; +// irqrestore(flags); + + /* give it to the superclass if we didn't like it */ + if (result == -ENOTTY) + result = CDev::ioctl(filp, cmd, arg); + + return result; } -void -ToneAlarm::stop() +int +ToneAlarm::write(file *filp, const char *buffer, size_t len) { - /* stop the current note */ - rCCER &= ~TONE_CCER; + // sanity-check the buffer for length and nul-termination + if (len > _tune_max) + return -EFBIG; - /* - * Make sure the GPIO is not driving the speaker. - * - * XXX this presumes PX4FMU and the onboard speaker driver FET. - */ - stm32_gpiowrite(GPIO_TONE_ALARM, 0); + // if we have an existing user tune, free it + if (_user_tune != nullptr) { + + // if we are playing the user tune, stop + if (_tune == _user_tune) { + _tune = nullptr; + _next = nullptr; + } + + // free the old user tune + free((void *)_user_tune); + _user_tune = nullptr; + } + + // if the new tune is empty, we're done + if (buffer[0] == '\0') + return OK; + + // allocate a copy of the new tune + _user_tune = strndup(buffer, len); + if (_user_tune == nullptr) + return -ENOMEM; + + // and play it + start_tune(_user_tune); + + return len; } /** @@ -602,7 +941,7 @@ namespace ToneAlarm *g_dev; int -play_pattern(unsigned pattern) +play_tune(unsigned tune) { int fd, ret; @@ -611,7 +950,8 @@ play_pattern(unsigned pattern) if (fd < 0) err(1, "/dev/tone_alarm"); - ret = ioctl(fd, TONE_SET_ALARM, pattern); + ret = ioctl(fd, TONE_SET_ALARM, tune); + close(fd); if (ret != 0) err(1, "TONE_SET_ALARM"); @@ -619,12 +959,30 @@ play_pattern(unsigned pattern) exit(0); } +int +play_string(const char *str) +{ + int fd, ret; + + fd = open("/dev/tone_alarm", O_WRONLY); + + if (fd < 0) + err(1, "/dev/tone_alarm"); + + ret = write(fd, str, strlen(str) + 1); + close(fd); + + if (ret < 0) + err(1, "play tune"); + exit(0); +} + } // namespace int tone_alarm_main(int argc, char *argv[]) { - unsigned pattern; + unsigned tune; /* start the driver lazily */ if (g_dev == nullptr) { @@ -640,13 +998,21 @@ tone_alarm_main(int argc, char *argv[]) } if ((argc > 1) && !strcmp(argv[1], "start")) - play_pattern(1); + play_tune(1); if ((argc > 1) && !strcmp(argv[1], "stop")) - play_pattern(0); + play_tune(0); + + if ((tune = strtol(argv[1], nullptr, 10)) != 0) + play_tune(tune); - if ((pattern = strtol(argv[1], nullptr, 10)) != 0) - play_pattern(pattern); + /* if it looks like a PLAY string... */ + if (strlen(argv[1]) > 2) { + const char *str = argv[1]; + if (str[0] == 'M') { + play_string(str); + } + } errx(1, "unrecognised command, try 'start', 'stop' or an alarm number"); } |