/**************************************************************************** * drivers/audio/tone.c * * Copyright (C) 2016 Gregory Nutt. All rights reserved. * Author: Alan Carvalho de Assis * * This driver is based on Tone Alarm driver from PX4 project. It was * modified to become a NuttX driver and to use the Oneshot Timer API. * * The PX4 driver is here: * https://github.com/PX4/Firmware/blob/master/src/drivers/stm32/tone_alarm/tone_alarm.cpp * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_AUDIO_TONE /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Define tone modes */ #define MODE_NORMAL 1 #define MODE_LEGATO 2 #define MODE_STACCATO 3 /* Max tune string length*/ #define MAX_TUNE_LEN (1 * 256) /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the state of the upper half driver */ struct tone_upperhalf_s { uint8_t crefs; /* The number of times the device has been * opened */ #ifdef CONFIG_PWM_MULTICHAN uint8_t channel; /* Output channel that drives the tone. */ #endif volatile bool started; /* True: pulsed output is being generated */ sem_t exclsem; /* Supports mutual exclusion */ struct pwm_info_s tone; /* Pulsed output for Audio Tone */ struct pwm_lowerhalf_s *devtone; struct oneshot_lowerhalf_s *oneshot; }; /**************************************************************************** * Private Data ****************************************************************************/ /* Buffer to store the tune */ static char tune_buf[MAX_TUNE_LEN]; /* Semitone offsets from C for the characters 'A'-'G' */ static const uint8_t g_note_tab[] = { 9, 11, 0, 2, 4, 5, 7 }; /* Notes in Frequency */ static const uint16_t g_notes_freq[84] = { 0x0041, 0x0045, 0x0049, 0x004d, 0x0052, 0x0057, 0x005c, 0x0061, 0x0067, 0x006e, 0x0074, 0x007b, 0x0082, 0x008a, 0x0092, 0x009b, 0x00a4, 0x00ae, 0x00b8, 0x00c3, 0x00cf, 0x00dc, 0x00e9, 0x00f6, 0x0105, 0x0115, 0x0125, 0x0137, 0x0149, 0x015d, 0x0171, 0x0187, 0x019f, 0x01b8, 0x01d2, 0x01ed, 0x020b, 0x022a, 0x024b, 0x026e, 0x0293, 0x02ba, 0x02e3, 0x030f, 0x033e, 0x0370, 0x03a4, 0x03db, 0x0416, 0x0454, 0x0496, 0x04dc, 0x0526, 0x0574, 0x05c7, 0x061f, 0x067d, 0x06e0, 0x0748, 0x07b7, 0x082d, 0x08a9, 0x092d, 0x09b9, 0x0a4d, 0x0ae9, 0x0b8f, 0x0c3f, 0x0cfa, 0x0dc0, 0x0e91, 0x0f6f, 0x105a, 0x1152, 0x125a, 0x1372, 0x149a, 0x15d3, 0x171f, 0x187f, 0x19f4, 0x1b80, 0x1d22, 0x1ede }; /* Global variable used by the tone generator */ static const char *g_tune; static const char *g_next; static uint8_t g_tempo; static uint8_t g_note_mode; static uint32_t g_note_length; static uint32_t g_silence_length; static uint8_t g_octave; static bool g_repeat; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void oneshot_callback(FAR struct oneshot_lowerhalf_s *lower, FAR void *arg); static uint32_t note_duration(FAR uint32_t *silence, uint32_t note_length, uint32_t dots); static uint32_t rest_duration(uint32_t rest_length, uint32_t dots); static void start_note(FAR struct tone_upperhalf_s *upper, uint8_t note); static void stop_note(FAR struct tone_upperhalf_s *upper); static void start_tune(FAR struct tone_upperhalf_s *upper, const char *tune); static void next_note(FAR struct tone_upperhalf_s *upper); static int next_char(void); static uint8_t next_number(void); static uint8_t next_dots(void); static int tone_open(FAR struct file *filep); static int tone_close(FAR struct file *filep); static ssize_t tone_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t tone_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_toneops = { tone_open, /* open */ tone_close, /* close */ tone_read, /* read */ tone_write, /* write */ 0, /* seek */ 0 /* ioctl */ #ifndef CONFIG_DISABLE_POLL , 0 /* poll */ #endif #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS , 0 /* unlink */ #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: oneshot_callback ****************************************************************************/ static void oneshot_callback(FAR struct oneshot_lowerhalf_s *lower, FAR void *arg) { FAR struct tone_upperhalf_s *upper = (FAR struct tone_upperhalf_s *)arg; audinfo("Oneshot timer expired!\n"); /* Play the next note */ next_note(upper); } /**************************************************************************** * Name: note_duration * * Description: * This function calculates 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. * ****************************************************************************/ static uint32_t note_duration(FAR uint32_t *silence, uint32_t note_length, uint32_t dots) { uint32_t whole_note_period = (60 * 1000000 * 4) / g_tempo; uint32_t note_period; uint32_t dot_extension; if (note_length == 0) { note_length = 1; } note_period = whole_note_period / note_length; switch (g_note_mode) { case MODE_NORMAL: *silence = note_period / 8; break; case MODE_STACCATO: *silence = note_period / 4; break; case MODE_LEGATO: *silence = 0; break; default: auderr("Mode undefined!\n"); break; } note_period -= *silence; dot_extension = note_period / 2; while (dots--) { note_period += dot_extension; dot_extension /= 2; } return note_period; } /**************************************************************************** * Name: rest_duration * * Description: * This function calculates the duration in microseconds of a rest * corresponding to a given note length. * ****************************************************************************/ static uint32_t rest_duration(uint32_t rest_length, uint32_t dots) { uint32_t whole_note_period = (60 * 1000000 * 4) / g_tempo; uint32_t rest_period; uint32_t dot_extension; if (rest_length == 0) { rest_length = 1; } rest_period = whole_note_period / rest_length; dot_extension = rest_period / 2; while (dots--) { rest_period += dot_extension; dot_extension /= 2; } return rest_period; } /**************************************************************************** * Name: start_note ****************************************************************************/ static void start_note(FAR struct tone_upperhalf_s *upper, uint8_t note) { FAR struct pwm_lowerhalf_s *tone = upper->devtone; upper->tone.frequency = g_notes_freq[note - 1]; #ifdef CONFIG_PWM_MULTICHAN upper->tone.channels[0].channel = upper->channel; upper->tone.channels[0].duty = b16HALF; #else upper->tone.duty = b16HALF; #endif /* REVISIT: Should check the return value */ tone->ops->start(tone, &upper->tone); } /**************************************************************************** * Name: stop_note ****************************************************************************/ static void stop_note(FAR struct tone_upperhalf_s *upper) { FAR struct pwm_lowerhalf_s *tone = upper->devtone; tone->ops->stop(tone); } /**************************************************************************** * Name: start_tune * * Description: * This function starts playing the note. * ****************************************************************************/ static void start_tune(FAR struct tone_upperhalf_s *upper, const char *tune) { FAR struct timespec ts; /* Kill any current playback */ ONESHOT_CANCEL(upper->oneshot, &ts); /* Record the tune */ g_tune = tune; g_next = tune; /* Initialise player state */ g_tempo = 120; g_note_length = 4; g_note_mode = MODE_NORMAL; g_octave = 4; g_silence_length = 0; g_repeat = false; /* Schedule a callback to start playing */ ts.tv_sec = 1; ts.tv_nsec = 0; ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); } /**************************************************************************** * Name: next_note * * Description: * This function parses the next note out of the string and play it. * ****************************************************************************/ static void next_note(FAR struct tone_upperhalf_s *upper) { uint32_t note; uint32_t note_length; uint32_t duration; uint32_t sec; uint32_t nsec; FAR struct timespec ts; /* Do we have an inter-note gap to wait for? */ if (g_silence_length > 0) { stop_note(upper); duration = g_silence_length; /* Setup the time duration */ sec = duration / USEC_PER_SEC; nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; ts.tv_sec = (time_t) sec; ts.tv_nsec = (unsigned long)nsec; ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); g_silence_length = 0; return; } /* Make sure we still have a tune - may be removed by the write / ioctl * handler */ if ((g_next == NULL) || (g_tune == NULL)) { stop_note(upper); return; } /* Parse characters out of the string until we have resolved a note */ note = 0; note_length = g_note_length; while (note == 0) { /* We always need at least one character from the string */ int c = next_char(); if (c == 0) { goto tune_end; } g_next++; switch (c) { uint8_t nt; /* Select note length */ case 'L': g_note_length = next_number(); if (g_note_length < 1) { auderr("note length too short!\n"); goto tune_error; } break; /* Select octave */ case 'O': g_octave = next_number(); if (g_octave > 6) { g_octave = 6; } break; /* Decrease octave */ case '<': if (g_octave > 0) { g_octave--; } break; /* Increase octave */ case '>': if (g_octave < 6) { g_octave++; } break; /* Select inter-note gap */ case 'M': c = next_char(); if (c == 0) { auderr("no character after M!\n"); goto tune_error; } g_next++; switch (c) { case 'N': g_note_mode = MODE_NORMAL; break; case 'L': g_note_mode = MODE_LEGATO; break; case 'S': g_note_mode = MODE_STACCATO; break; case 'F': g_repeat = false; break; case 'B': g_repeat = true; break; default: auderr("unknown symbol: %c!\n", c); goto tune_error; break; } /* Pause for a note length */ case 'P': stop_note(upper); duration = rest_duration(next_number(), next_dots()); /* Setup the time duration */ sec = duration / USEC_PER_SEC; nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; ts.tv_sec = (time_t) sec; ts.tv_nsec = (unsigned long)nsec; ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); return; /* Change tempo */ case 'T': nt = next_number(); if ((nt >= 32) && (nt <= 255)) { g_tempo = nt; } else { auderr("T is out of range 32-255!\n"); goto tune_error; } break; /* Play an arbitrary note */ case 'N': note = next_number(); if (note > 84) { auderr("Note higher than 84!\n"); goto tune_error; } /* This is a rest - pause for the current note length */ if (note == 0) { duration = rest_duration(g_note_length, next_dots()); /* Setup the time duration */ sec = duration / USEC_PER_SEC; nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; ts.tv_sec = (time_t) sec; ts.tv_nsec = (unsigned long)nsec; ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); return; } break; /* Play a note in the current octave */ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': note = g_note_tab[c - 'A'] + (g_octave * 12) + 1; c = next_char(); switch (c) { /* Up a semitone */ case '#': case '+': if (note < 84) { note++; } g_next++; break; /* Down a semitone */ case '-': if (note > 1) { note--; } g_next++; break; /* No next char here is OK */ default: break; } /* Shorthand length notation */ note_length = next_number(); if (note_length == 0) { note_length = g_note_length; } break; default: goto tune_error; } } /* Compute the duration of the note and the following silence (if any) */ duration = note_duration(&g_silence_length, note_length, next_dots()); /* Start playing the note */ start_note(upper, note); /* Setup time duration */ sec = duration / USEC_PER_SEC; nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; ts.tv_sec = (time_t) sec; ts.tv_nsec = (unsigned long)nsec; /* And arrange a callback when the note should stop */ ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); return; /* Tune looks bad (unexpected EOF, bad character, etc.) */ tune_error: auderr("tune error\n"); /* Don't loop on error */ g_repeat = false; /* Stop (and potentially restart) the tune */ tune_end: stop_note(upper); if (g_repeat) { start_tune(upper, g_tune); } else { g_tune = NULL; } } /**************************************************************************** * Name: next_char * * Description: * This function find the next character in the string, discard any * whitespace and return the canonical (uppercase) version. * ****************************************************************************/ static int next_char(void) { while (isspace(*g_next)) { g_next++; } return toupper(*g_next); } /**************************************************************************** * Name: next_number * * Description: * This function extract a number from the string, consuming all the digit * characters. * ****************************************************************************/ static uint8_t next_number(void) { uint8_t number = 0; int c; for (;;) { c = next_char(); if (!isdigit(c)) { return number; } g_next++; number = (number * 10) + (c - '0'); } return number; } /**************************************************************************** * Name: next_dots * * Description: * This function consumes dot characters from the string, returning the * number consumed. * ****************************************************************************/ static uint8_t next_dots(void) { uint8_t dots = 0; while (next_char() == '.') { g_next++; dots++; } return dots; } /**************************************************************************** * Name: tone_open * * Description: * This function is called whenever the PWM device is opened. * ****************************************************************************/ static int tone_open(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct tone_upperhalf_s *upper = inode->i_private; uint8_t tmp; int ret; audinfo("crefs: %d\n", upper->crefs); /* Get exclusive access to the device structures */ ret = sem_wait(&upper->exclsem); if (ret < 0) { ret = -get_errno(); goto errout; } /* Increment the count of references to the device. If this the first time * that the driver has been opened for this device, then initialize the * device. */ tmp = upper->crefs + 1; if (tmp == 0) { /* More than 255 opens; uint8_t overflows to zero */ ret = -EMFILE; goto errout_with_sem; } /* Save the new open count on success */ upper->crefs = tmp; ret = OK; errout_with_sem: sem_post(&upper->exclsem); errout: return ret; } /**************************************************************************** * Name: tone_close * * Description: * This function is called when the PWM device is closed. * ****************************************************************************/ static int tone_close(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct tone_upperhalf_s *upper = inode->i_private; int ret; audinfo("crefs: %d\n", upper->crefs); /* Get exclusive access to the device structures */ ret = sem_wait(&upper->exclsem); if (ret < 0) { ret = -get_errno(); goto errout; } /* Decrement the references to the driver. If the reference count will * decrement to 0, then uninitialize the driver. */ if (upper->crefs > 1) { upper->crefs--; } sem_post(&upper->exclsem); ret = OK; errout: return ret; } /**************************************************************************** * Name: tone_read * * Description: * A dummy read method. This is provided only to satisfy the VFS layer. * ****************************************************************************/ static ssize_t tone_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { /* Return zero -- usually meaning end-of-file */ return 0; } /**************************************************************************** * Name: tone_write * * Description: * A dummy write method. This is provided only to satisfy the VFS layer. * ****************************************************************************/ static ssize_t tone_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct tone_upperhalf_s *upper = inode->i_private; /* We need to receive a string #RRGGBB = 7 bytes */ if (buffer == NULL) { /* Well... nothing to do */ return -EINVAL; } if (buflen >= MAX_TUNE_LEN) { /* Too big to it inside internal buffer (with extra NUL terminator) */ return -EINVAL; } /* Copy music to internal buffer */ memcpy(tune_buf, buffer, buflen); /* Failsafe NUL terminated string */ tune_buf[buflen] = '\0'; /* Let the music play */ start_tune(upper, tune_buf); return buflen; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: tone_register * * Description: * This function binds an instance of a "lower half" PWM driver with * the "upper half" Audio Tone device and registers that device so that can * be used by application code. * * * Input parameters: * path - The full path to the driver to be registers in the NuttX pseudo- * filesystem. The recommended convention is to name of PWM driver * as "/dev/tone0". * tone - A pointer to an instance of lower half PWM * drivers for the tone device. This instance will be bound to the Audio * tone driver and must persists as long as that driver persists. * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ int tone_register(FAR const char *path, FAR struct pwm_lowerhalf_s *tone, #ifdef CONFIG_PWM_MULTICHAN int channel, #endif FAR struct oneshot_lowerhalf_s *oneshot) { FAR struct tone_upperhalf_s *upper; DEBUGASSERT(path != NULL && tone != NULL); /* Allocate the upper-half data structure */ upper = (FAR struct tone_upperhalf_s *)kmm_zalloc(sizeof(struct tone_upperhalf_s)); if (!upper) { auderr("ERROR: Allocation failed\n"); return -ENOMEM; } /* Initialize the PWM device structure (it was already zeroed by * kmm_zalloc()). */ sem_init(&upper->exclsem, 0, 1); upper->devtone = tone; upper->oneshot = oneshot; #ifdef CONFIG_PWM_MULTICHAN upper->channel = (uint8_t)channel; #endif /* Register the PWM device */ audinfo("Registering %s\n", path); return register_driver(path, &g_toneops, 0666, upper); } #endif /* CONFIG_AUDIO_TONE */