From aa3fa1c91759941b0e4369dc8742642ccb511928 Mon Sep 17 00:00:00 2001 From: Michael Hayes Date: Tue, 30 Aug 2011 03:01:17 +0000 Subject: [PATCH] Add extra modules --- extra/mmelody.c | 303 +++++++++++++++++++++++++++++++ extra/mmelody.h | 66 +++++++ extra/piezo.c | 18 ++ extra/piezo.h | 44 +++++ extra/piezo_beep.c | 47 +++++ extra/piezo_beep.h | 39 ++++ extra/squeaker.c | 436 +++++++++++++++++++++++++++++++++++++++++++++ extra/squeaker.h | 123 +++++++++++++ extra/squeaker2.c | 435 ++++++++++++++++++++++++++++++++++++++++++++ extra/ticker.c | 6 + extra/ticker.h | 45 +++++ extra/tweeter.c | 104 +++++++++++ extra/tweeter.h | 79 ++++++++ 13 files changed, 1745 insertions(+) create mode 100644 extra/mmelody.c create mode 100644 extra/mmelody.h create mode 100644 extra/piezo.c create mode 100644 extra/piezo.h create mode 100644 extra/piezo_beep.c create mode 100644 extra/piezo_beep.h create mode 100644 extra/squeaker.c create mode 100644 extra/squeaker.h create mode 100644 extra/squeaker2.c create mode 100644 extra/ticker.c create mode 100644 extra/ticker.h create mode 100644 extra/tweeter.c create mode 100644 extra/tweeter.h diff --git a/extra/mmelody.c b/extra/mmelody.c new file mode 100644 index 0000000..247554e --- /dev/null +++ b/extra/mmelody.c @@ -0,0 +1,303 @@ +/** @file mmelody.c + @author M. P. Hayes, UCECE + @date 20 April 2007 + @brief Play simple melodies. +*/ + +#define MMELODY_TRANSPARENT 1 + +#include "mmelody.h" + + +/* By default notes are in the scale C4 -> C5. + Note the first 12 frets on a six string guitar covers 3 octaves + from the pitch E2 (82.41 Hz) to the pitch E5 (659.26 Hz). + + Tunes are specified using the notation C4C5F4G4C4 but for brevity + this can be simplified to C4C5F4GC where the previously specified + octave number persists. A problem with this notation is the + verbosity when we have something like B2C3B2C3. Most melodies will + only require 2 and 3 at the most octaves. The ABC music notation + uses CDEFGABcdefgabc'd'e'f'g'a'b'c' to denote 3 octaves from C2 to + C5? This uses numbers to indicate note duration, for example, C2 + denotes a C of twice the standard duration. + + For emphasis of first beat in bar, perhaps use ^ to indicate + louder, for example, C^. Similarly, to make a note quieter it + could have a v suffix. Alternatively, | bar markers could be + inserted and perhaps time signatures. + + By default notes are quarter-notes. It is probably best to define + speed in terms of quarter-notes (beats per minute) rather than bars + per minute since this requires bars to be defined. Two bars in 4/4 + could be represented by C^DEDC^BAB whereas two bars in 3/4 could be + represented by C^DEC^DE. + + So how should we denote note duration? We need to distinguish + between 2 identical quarter-notes (A A) played in succession and a + half-note since this sounds different. + + We could use AA to indicate two A quarter-notes. Alternatively, we + could use AA to indicate an A half-note. However, for a whole note + we would need to indicate this with AAAA. With the latter scheme + we could separate two indentical quarter notes with a comma, for + example, A,A. + + Rests are easy. Each space represents one rest of quarter-note + duration. Two spaces represent a half-note rest. Alternatively, + we could represent this with " /". + + From a sequencing point of view it is simpler if every symbol + represents a quarter-note rather than having variable length notes + since this alters the sequencing timing. This favours the approach + of using AA to denote a half-note. + + If I implement a simple attack/decay response then it would be + easier to use A/ for a half-note since we would interpret the / as + to keep playing the previous note without sounding it again. + Alternatively, when each new note is sounded there could be a short + delay. + + >num could indicate jump forward to label num while 3 to represent playing the notes ABC in succession 3 times. + This notation could be nested, for example, 2>3. + Perhaps denotes playing ABC indefinitely? No I prefer + a simple repeat. Use ABC: for an infinite repeat. + + represents ABCDEABCFG where ]n denotes alternate + endings. + +*/ + +enum {MMELODY_SCALE_SIZE = 12}; + + +static void +mmelody_ticker_set (mmelody_t mmelody) +{ + uint16_t speed; + + speed = mmelody->speed * mmelody->note_fraction; + + TICKER_INIT (&mmelody->ticker, mmelody->poll_rate * 60 * 4 / speed); +} + + +static void +mmelody_note_play (mmelody_t mmelody, mmelody_note_t note) +{ + mmelody->play_callback (mmelody->play_callback_data, note, + mmelody->volume); +} + + +/* Specify the default note length in fractions of a measure (bar). + A value of 4 is the default which makes each note a quarter note. */ +static void +mmelody_note_fraction_set (mmelody_t mmelody, uint8_t note_fraction) +{ + mmelody->note_fraction = note_fraction; + mmelody_ticker_set (mmelody); +} + + +static mmelody_note_t +mmelody_char_to_note (uint8_t ch) +{ + /* A = 9, B = 11, C = 0, D = 2, E = 4, F = 5, G = 7 */ + static const mmelody_note_t const lookup[] = {9, 11, 0, 2, 4, 5, 7}; + + return lookup[ch - 'A']; +} + + +/* Scan next part of melody until a note or end of melody is found. */ +static const char * +mmelody_scan (mmelody_t mmelody, const char *str) +{ + while (1) + { + uint8_t num; + char cmd; + char modifier; + bool have_hash; + bool have_num; + mmelody_note_t note; + + /* Play rest at end of melody. */ + if (! *str) + { + mmelody_note_play (mmelody, 0); + return str; + } + + cmd = *str++; + + have_hash = *str == '#'; + if (have_hash) + str++; + + modifier = 0; + if (*str == '+' || *str == '-') + modifier = *str++; + + have_num = 0; + num = 0; + while (*str >= '0' && *str <= '9') + { + have_num = 1; + num = num * 10 + *str++ - '0'; + } + + switch (cmd) + { + /* Repeat sequence from start. */ + case ':': + str = mmelody->start; + continue; + + /* Define start of loop. */ + case '<': + /* We could implement a small stack to allow nested + loops. */ + mmelody->loop_start = str; + mmelody->loop_count = 0; + continue; + + /* Loop. */ + case '>': + mmelody->loop_count++; + if (!num) + num = 2; + + if (mmelody->loop_count < num) + { + /* Jump to start of loop. If no start of loop symbol, + jump to start. */ + str = mmelody->loop_start; + if (!str) + str = mmelody->start; + } + continue; + + /* Alternate endings. */ + case '[': + if (mmelody->loop_count == num - 1) + continue; + + /* Skip to next alternate ending, the end of loop, or end of + melody. */ + while (*str && *str != '[' && *str != '>') + str++; + continue; + + /* Play rest. */ + case ' ': + mmelody_note_play (mmelody, 0); + return str; + break; + + case '*': + if (num) + mmelody_note_fraction_set (mmelody, num); + continue; + + case '@': + if (num) + mmelody_speed_set (mmelody, num); + continue; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + note = mmelody_char_to_note (cmd); + + if (have_hash) + note++; + + if (have_num) + mmelody->octave = num; + + if (modifier == '+') + note += MMELODY_SCALE_SIZE; + + if (modifier == '-') + note -= MMELODY_SCALE_SIZE; + + /* Convert note to MIDI note number. */ + note += (mmelody->octave + 1) * MMELODY_SCALE_SIZE; + + mmelody_note_play (mmelody, note); + return str; + break; + + /* Continue with previous note. */ + case '/': + default: + return str; + break; + } + } + return str; +} + + +void +mmelody_play (mmelody_t mmelody, const char *str) +{ + mmelody->cur = mmelody->start = str; + mmelody->loop_start = 0; + mmelody->loop_count = 0; + mmelody->octave = MMELODY_OCTAVE_DEFAULT; + mmelody_note_fraction_set (mmelody, 4); +} + + +/* Set (base) speed in beats per minute (BPM). */ +void +mmelody_speed_set (mmelody_t mmelody, mmelody_speed_t speed) +{ + mmelody->speed = speed; + mmelody_ticker_set (mmelody); +} + + +/* Set volume as percentage of maximum. */ +void +mmelody_volume_set (mmelody_t mmelody, mmelody_volume_t volume) +{ + mmelody->volume = volume; +} + + +void +mmelody_update (mmelody_t mmelody) +{ + if (TICKER_UPDATE (&mmelody->ticker)) + { + if (mmelody->cur) + mmelody->cur = mmelody_scan (mmelody, mmelody->cur); + } +} + + +mmelody_t +mmelody_init (mmelody_obj_t *mmelody, + uint16_t poll_rate, + mmelody_callback_t play_callback, + void *play_callback_data) +{ + mmelody->poll_rate = poll_rate; + mmelody->play_callback = play_callback; + mmelody->play_callback_data = play_callback_data; + mmelody->volume = 100; + mmelody->note_fraction = 1; + mmelody_speed_set (mmelody, MMELODY_SPEED_DEFAULT); + mmelody_play (mmelody, 0); + return mmelody; +} diff --git a/extra/mmelody.h b/extra/mmelody.h new file mode 100644 index 0000000..237b651 --- /dev/null +++ b/extra/mmelody.h @@ -0,0 +1,66 @@ +/** @file mmelody.h + @author M. P. Hayes, UCECE + @date 20 April 2007 + @brief Play simple melodies. +*/ +#ifndef MMELODY_H +#define MMELODY_H + +#include "system.h" +#include "ticker.h" + +typedef uint8_t mmelody_speed_t; +typedef uint8_t mmelody_scale_t; +typedef uint8_t mmelody_note_t; +typedef uint8_t mmelody_volume_t; + + +enum {MMELODY_OCTAVE_DEFAULT = 4}; +enum {MMELODY_SPEED_DEFAULT = 200}; + + +typedef struct +{ + ticker_t ticker; + /* Pointer to current position in string. */ + const char *cur; + /* Pointer to start of string. */ + const char *start; + const char *loop_start; + int8_t loop_count; + uint8_t note_fraction; + mmelody_speed_t speed; + mmelody_volume_t volume; + uint8_t octave; + void (* play_callback) (void *data, uint8_t note, uint8_t volume); + void *play_callback_data; + uint16_t poll_rate; +} mmelody_private_t; + +typedef mmelody_private_t mmelody_obj_t; +typedef mmelody_obj_t *mmelody_t; + + +typedef void (* mmelody_callback_t) (void *data, uint8_t note, uint8_t volume); + +extern mmelody_t +mmelody_init (mmelody_obj_t *dev, + uint16_t poll_rate, + mmelody_callback_t play_callback, + void *play_callback_data); + +extern void +mmelody_play (mmelody_t mmelody, const char *str); + +extern void +mmelody_update (mmelody_t mmelody); + +/* Set (base) speed in beats per minute (BPM). */ +extern void +mmelody_speed_set (mmelody_t mmelody, mmelody_speed_t speed); + +/* Set volume as percentage of maximum. */ +extern void +mmelody_volume_set (mmelody_t mmelody, mmelody_volume_t volume); + +#endif diff --git a/extra/piezo.c b/extra/piezo.c new file mode 100644 index 0000000..61df6bf --- /dev/null +++ b/extra/piezo.c @@ -0,0 +1,18 @@ +/** @file piezo.c + @author M. P. Hayes, UCECE + @date 12 March 2003 + @brief +*/ + +#include "piezo.h" + + +/* Create a new PIEZO device. */ +piezo_t +piezo_init (const piezo_cfg_t *cfg) +{ + /* Configure pio as output. */ + pio_config_set (cfg->pio, PIO_OUTPUT_LOW); + + return cfg; +} diff --git a/extra/piezo.h b/extra/piezo.h new file mode 100644 index 0000000..2727af5 --- /dev/null +++ b/extra/piezo.h @@ -0,0 +1,44 @@ +/** @file piezo.h + @author M. P. Hayes, UCECE + @date 12 March 2003 + @brief +*/ + +#ifndef PIEZO_H +#define PIEZO_H + +#include "config.h" +#include "delay.h" +#include "pio.h" + + +#define PIEZO_CFG(PIO) {PIO} + +typedef struct +{ + pio_t pio; +} piezo_cfg_t; + + +typedef const piezo_cfg_t piezo_obj_t; + +typedef piezo_obj_t *piezo_t; + + +/* CFG is points to configuration data specified by PIEZO_CFG to + define the pio the PIEZO is connected to. The returned handle is + passed to the other piezo_xxx routines to denote the PIEZO to + operate on. */ +extern piezo_t +piezo_init (const piezo_cfg_t *cfg); + + +/* Perhaps we only need a toggle routine. This would + be faster and save memory. */ +static inline void +piezo_set (piezo_t piezo, uint8_t val) +{ + pio_output_set (piezo->pio, val); +} + +#endif diff --git a/extra/piezo_beep.c b/extra/piezo_beep.c new file mode 100644 index 0000000..0aae1ec --- /dev/null +++ b/extra/piezo_beep.c @@ -0,0 +1,47 @@ +/** @file piezo_beep.c + @author M. P. Hayes, UCECE + @date 12 April 2007 + @brief Piezo beeping routines (blocking). +*/ + +#include "piezo_beep.h" +#include "pio.h" + + +/** Generate a beep, */ +void piezo_beep (piezo_t piezo, uint16_t duration_ms) +{ + uint16_t i; + + for (i = 0; i < duration_ms * PIEZO_BEEP_FREQ_KHZ * 2; i++) + { + pio_output_toggle (piezo->pio); + DELAY_US (500 / PIEZO_BEEP_FREQ_KHZ); + } +} + + +/** Generate a short beep, */ +void piezo_beep_short (piezo_t piezo) +{ + uint16_t i; + + for (i = 0; i < PIEZO_BEEP_SHORT_PERIOD_MS * PIEZO_BEEP_FREQ_KHZ * 2; i++) + { + pio_output_toggle (piezo->pio); + DELAY_US (500 / PIEZO_BEEP_FREQ_KHZ); + } +} + + +/** Generate a long beep, */ +void piezo_beep_long (piezo_t piezo) +{ + uint16_t i; + + for (i = 0; i < PIEZO_BEEP_LONG_PERIOD_MS * PIEZO_BEEP_FREQ_KHZ * 2; i++) + { + pio_output_toggle (piezo->pio); + DELAY_US (500 / PIEZO_BEEP_FREQ_KHZ); + } +} diff --git a/extra/piezo_beep.h b/extra/piezo_beep.h new file mode 100644 index 0000000..625ebeb --- /dev/null +++ b/extra/piezo_beep.h @@ -0,0 +1,39 @@ +/** @file piezo_beep.h + @author M. P. Hayes, UCECE + @date 12 April 2007 + @brief Piezo beeping routines. Note these block. +*/ + +#ifndef PIEZO_BEEP_H +#define PIEZO_BEEP_H + +#include "config.h" +#include "piezo.h" + + +/* Time in milliseconds for a short beep. */ +#ifndef PIEZO_BEEP_SHORT_PERIOD_MS +#define PIEZO_BEEP_SHORT_PERIOD_MS 30 +#endif + +/* Time in milliseconds for a long beep. */ +#ifndef PIEZO_BEEP_LONG_PERIOD_MS +#define PIEZO_BEEP_LONG_PERIOD_MS 200 +#endif + +/* Beep frequency in kHz. */ +#ifndef PIEZO_BEEP_FREQ_KHZ +#define PIEZO_BEEP_FREQ_KHZ 2 +#endif + + +extern void +piezo_beep (piezo_t dev, uint16_t duration_ms); + +extern void +piezo_beep_short (piezo_t dev); + +extern void +piezo_beep_long (piezo_t dev); + +#endif diff --git a/extra/squeaker.c b/extra/squeaker.c new file mode 100644 index 0000000..2a912ea --- /dev/null +++ b/extra/squeaker.c @@ -0,0 +1,436 @@ +/** @file squeaker.c + @author M. P. Hayes, UCECE + @date 14 April 2007 + @brief Play simple tunes with PWM. +*/ + +#define SQUEAKER_TRANSPARENT 1 + +#include "squeaker.h" + + +#define SQUEAKER_HOLDOFF_TIME 50e-3 +enum {SQUEAKER_PRESCALER = 256}; + + +/* By default notes are in the scale C4 -> C5. + Note the first 12 frets on a six string guitar covers 3 octaves + from the pitch E2 (82.41 Hz) to the pitch E5 (659.26 Hz). + + Tunes are specified using the notation C4C5F4G4C4 but for brevity + this can be simplified to C4C5F4GC where the previously specified + octave number persists. A problem with this notation is the + verbosity when we have something like B2C3B2C3. Most melodies will + only require 2 and 3 at the most octaves. The ABC music notation + uses CDEFGABcdefgabc'd'e'f'g'a'b'c' to denote 3 octaves from C2 to + C5? This uses numbers to indicate note duration, for example, C2 + denotes a C of twice the standard duration. + + For emphasis of first beat in bar, perhaps use ^ to indicate + louder, for example, C^. Similarly, to make a note quieter it + could have a v suffix. Alternatively, | bar markers could be + inserted and perhaps time signatures. + + By default notes are quarter-notes. It is probably best to define + speed in terms of quarter-notes (beats per minute) rather than bars + per minute since this requires bars to be defined. Two bars in 4/4 + could be represented by C^DEDC^BAB whereas two bars in 3/4 could be + represented by C^DEC^DE. + + So how should we denote note duration? We need to distinguish + between 2 identical quarter-notes (A A) played in succession and a + half-note since this sounds different. + + We could use AA to indicate two A quarter-notes. Alternatively, we + could use AA to indicate an A half-note. However, for a whole note + we would need to indicate this with AAAA. With the latter scheme + we could separate two indentical quarter notes with a comma, for + example, A,A. + + Rests are easy. Each space represents one rest of quarter-note + duration. Two spaces represent a half-note rest. Alternatively, + we could represent this with " /". + + From a sequencing point of view it is simpler if every symbol + represents a quarter-note rather than having variable length notes + since this alters the sequencing timing. This favours the approach + of using AA to denote a half-note. + + If I implement a simple attack/decay response then it would be + easier to use A/ for a half-note since we would interpret the / as + to keep playing the previous note without sounding it again. + Alternatively, when each new note is sounded there could be a short + delay. + + >num could indicate jump forward to label num while 3 to represent playing the notes ABC in succession 3 times. + This notation could be nested, for example, 2>3. + Perhaps denotes playing ABC indefinitely? No I prefer + a simple repeat. Use ABC: for an infinite repeat. + + represents ABCDEABCFG where ]n denotes alternate + endings. + +*/ + + + +static void +squeaker_ticker_set (squeaker_t squeaker) +{ + uint16_t speed; + + speed = squeaker->speed * squeaker->note_fraction; + +#if 0 + /* The division is performed first to reduce the chance of integer + overflow. This results in poorer accuracy when the poll_rate + is low. In practice, the poll_rate cannot be too low otherwise + the generated notes will be inaccurate. */ + TICKER_INIT (&squeaker->ticker, (squeaker->poll_rate / speed) * 60); +#else + TICKER_INIT (&squeaker->ticker, (squeaker->poll_rate / SQUEAKER_PRESCALER) + * 60 / speed); +#endif +} + + +/* Specify the default note length in fractions of a measure (bar). + A value of 4 is the default which makes each note a quarter note. */ +static void +squeaker_note_fraction_set (squeaker_t squeaker, uint8_t note_fraction) +{ + squeaker->note_fraction = note_fraction; + squeaker_ticker_set (squeaker); +} + + +enum {SQUEAKER_SCALE_SIZE = 12}; + +void +squeaker_note_set (squeaker_t squeaker, + squeaker_period_t period, + squeaker_period_t duty) +{ + squeaker->note_period = period; + squeaker->note_duty = duty; + squeaker->note_holdoff = squeaker->holdoff; +} + + +static void +squeaker_rest_play (squeaker_t squeaker) +{ + squeaker_note_set (squeaker, 0, 0); +} + + +static void +squeaker_note_play (squeaker_t squeaker, squeaker_note_t note) +{ + squeaker_period_t period; + squeaker_period_t duty; + uint8_t index; + uint8_t octave; + +#if 0 + /* See if we can play this note. */ + if (note < SQUEAKER_NOTE_MIN) + return; +#endif + + note -= SQUEAKER_NOTE_MIN; + octave = note / SQUEAKER_SCALE_SIZE; + index = note - octave * SQUEAKER_SCALE_SIZE; + + period = squeaker->scale_table[index]; + + while (octave-- > 0) + period >>= 1; + + duty = (period * squeaker->volume) >> 8; + + squeaker_note_set (squeaker, period, duty); + +#if 0 + printf ("note = %d, octave = %d, period = %d\n", + note, index / SQUEAKER_SCALE_SIZE, period); +#endif +} + + +static squeaker_note_t +squeaker_char_to_note (uint8_t ch) +{ + squeaker_note_t note; + /* A = 9, B = 11, C = 0, D = 2, E = 4, F = 5, G = 7 */ + static const squeaker_note_t const lookup[] = {9, 11, 0, 2, 4, 5, 7}; + + return lookup[ch - 'A']; + +#if 0 + + switch (ch) + { + default: + case 'A': + case 'B': + note = (ch - 'A') * 2 + 9; + break; + + case 'C': + case 'D': + case 'E': + note = (ch - 'C') * 2; + break; + + case 'F': + case 'G': + note = (ch - 'F') * 2 + 5; + break; + } +#endif + +#if 0 + switch (ch) + { + case 'A': + note = 9; break; + case 'B': + note = 11; break; + break; + case 'C': + note = 0; break; + case 'D': + note = 2; break; + case 'E': + note = 4; break; + case 'F': + note = 5; break; + case 'G': + note = 7; break; + default: + note = 0; + break; + } + +#endif + + return note; +} + + +/* Scan next part of melody until a note or end of melody is found. */ +static const char * +squeaker_scan (squeaker_t squeaker, const char *str) +{ + while (1) + { + uint8_t num; + char cmd; + char modifier; + bool have_hash; + bool have_num; + squeaker_note_t note; + + /* Play rest at end of melody. */ + if (! *str) + { + squeaker_rest_play (squeaker); + return str; + } + + cmd = *str++; + + have_hash = *str == '#'; + if (have_hash) + str++; + + modifier = 0; + if (*str == '+' || *str == '-') + modifier = *str++; + + have_num = 0; + num = 0; + while (*str >= '0' && *str <= '9') + { + have_num = 1; + num = num * 10 + *str++ - '0'; + } + + switch (cmd) + { + /* Repeat sequence from start. */ + case ':': + str = squeaker->start; + continue; + + /* Define start of loop. */ + case '<': + /* We could implement a small stack to allow nested + loops. */ + squeaker->loop_start = str; + squeaker->loop_count = 0; + continue; + + /* Loop. */ + case '>': + squeaker->loop_count++; + if (!num) + num = 2; + + if (squeaker->loop_count < num) + { + /* Jump to start of loop. If no start of loop symbol, + jump to start. */ + str = squeaker->loop_start; + if (!str) + str = squeaker->start; + } + continue; + + /* Alternate endings. */ + case '[': + if (squeaker->loop_count == num - 1) + continue; + + /* Skip to next alternate ending, the end of loop, or end of + melody. */ + while (*str && *str != '[' && *str != '>') + str++; + continue; + + /* Play rest. */ + case ' ': + squeaker_rest_play (squeaker); + return str; + break; + + case '*': + if (num) + squeaker_note_fraction_set (squeaker, num); + continue; + + case '@': + if (num) + squeaker_speed_set (squeaker, num); + continue; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + note = squeaker_char_to_note (cmd); + + if (have_hash) + note++; + + if (have_num) + squeaker->octave = num; + + if (modifier == '+') + note += SQUEAKER_SCALE_SIZE; + + if (modifier == '-') + note -= SQUEAKER_SCALE_SIZE; + + /* Convert note to MIDI note number. */ + note += (squeaker->octave + 1) * SQUEAKER_SCALE_SIZE; + + squeaker_note_play (squeaker, note); + return str; + break; + + /* Continue with previous note. */ + case '/': + default: + return str; + break; + } + } + return str; +} + + +void +squeaker_play (squeaker_t squeaker, const char *str) +{ + squeaker->cur = squeaker->start = str; + squeaker->loop_start = 0; + squeaker->loop_count = 0; + squeaker->octave = SQUEAKER_OCTAVE_DEFAULT; + squeaker_note_fraction_set (squeaker, 4); +} + + +/* Set (base) speed in beats per minute (BPM). */ +void +squeaker_speed_set (squeaker_t squeaker, squeaker_speed_t speed) +{ + squeaker->speed = speed; + squeaker_ticker_set (squeaker); +} + + +/* Set volume as percentage of maximum. */ +void +squeaker_volume_set (squeaker_t squeaker, squeaker_volume_t volume) +{ + squeaker->volume = volume; +} + + +int8_t +squeaker_update (squeaker_t squeaker) +{ + /* Rather than a 16 bit ticker it is faster to use + an 8-bit prescaler and an 8-bit ticker. */ + + squeaker->prescaler++; + if (! squeaker->prescaler) + { + if (TICKER_UPDATE (&squeaker->ticker)) + { + if (squeaker->cur) + squeaker->cur = squeaker_scan (squeaker, squeaker->cur); + } + + /* We could halve the note duty after some interval to + simulate note decay. The decay interval could be related + to the desired sustain. This approach is a bit brutal + especially when the duty is small. */ + + if (squeaker->note_holdoff) + squeaker->note_holdoff--; + } + + if (++squeaker->note_clock >= squeaker->note_period) + squeaker->note_clock = 0; + + return squeaker->note_clock < squeaker->note_duty + && ! squeaker->note_holdoff; +} + + +squeaker_t +squeaker_init (squeaker_obj_t *squeaker, + uint16_t poll_rate, + squeaker_scale_t *scale_table) +{ + squeaker->poll_rate = poll_rate; + squeaker->scale_table = scale_table; + squeaker->note_period = 0; + squeaker->note_duty = 0; + squeaker->volume = 127; + squeaker->prescaler = 0; + squeaker->holdoff = squeaker->poll_rate + / (uint16_t)(SQUEAKER_PRESCALER / SQUEAKER_HOLDOFF_TIME); + + squeaker_play (squeaker, 0); + squeaker_speed_set (squeaker, SQUEAKER_SPEED_DEFAULT); + return squeaker; +} diff --git a/extra/squeaker.h b/extra/squeaker.h new file mode 100644 index 0000000..bcb75a1 --- /dev/null +++ b/extra/squeaker.h @@ -0,0 +1,123 @@ +/** @file squeaker.h + @author M. P. Hayes, UCECE + @date 14 April 2007 + @brief Play simple tunes with PWM. +*/ +#ifndef SQUEAKER_H +#define SQUEAKER_H + +#include "system.h" +#include "ticker.h" + +typedef uint8_t squeaker_speed_t; +typedef uint8_t squeaker_scale_t; +typedef uint8_t squeaker_note_t; +typedef uint8_t squeaker_duration_t; +typedef uint8_t squeaker_period_t; +typedef uint8_t squeaker_volume_t; + + +enum {SQUEAKER_OCTAVE_DEFAULT = 4}; +enum {SQUEAKER_SPEED_DEFAULT = 200}; + + +/* Could calculate scale divisors at run time. 2^(1/2) is approx + 1.0594631. A reasonable rational approximation is 267/252 = + 1.0595238. Let's save memory and provide a macro to compute the + divisors. */ + +#define SQUEAKER_DIVISOR(POLL_RATE, FREQ) (POLL_RATE / FREQ + 0.5) + + +#if 0 +enum {SQUEAKER_NOTE_MIN = 60}; +/* Define divisors for chromatic scale C4 -> C5. For better accuracy + this should be defined for the lowest frequency scale, however, + this may need 16 bits per note. */ +#define SQUEAKER_SCALE_TABLE(POLL_RATE) \ + {SQUEAKER_DIVISOR (POLL_RATE, 261.6256), \ + SQUEAKER_DIVISOR (POLL_RATE, 277.1826), \ + SQUEAKER_DIVISOR (POLL_RATE, 293.6648), \ + SQUEAKER_DIVISOR (POLL_RATE, 311.1270), \ + SQUEAKER_DIVISOR (POLL_RATE, 329.6276), \ + SQUEAKER_DIVISOR (POLL_RATE, 349.2282), \ + SQUEAKER_DIVISOR (POLL_RATE, 369.9944), \ + SQUEAKER_DIVISOR (POLL_RATE, 391.9954), \ + SQUEAKER_DIVISOR (POLL_RATE, 415.3047), \ + SQUEAKER_DIVISOR (POLL_RATE, 440.0000), \ + SQUEAKER_DIVISOR (POLL_RATE, 466.1638), \ + SQUEAKER_DIVISOR (POLL_RATE, 493.8833)} +#else +enum {SQUEAKER_NOTE_MIN = 40}; +/* Define divisors for chromatic scale E2 -> D#3. For better accuracy + this should be defined for the lowest frequency scale, however, + this may need 16 bits per note. */ +#define SQUEAKER_SCALE_TABLE(POLL_RATE) \ + {SQUEAKER_DIVISOR (POLL_RATE, 82.41), \ + SQUEAKER_DIVISOR (POLL_RATE, 87.31), \ + SQUEAKER_DIVISOR (POLL_RATE, 92.50), \ + SQUEAKER_DIVISOR (POLL_RATE, 98.00), \ + SQUEAKER_DIVISOR (POLL_RATE, 103.83), \ + SQUEAKER_DIVISOR (POLL_RATE, 110.0), \ + SQUEAKER_DIVISOR (POLL_RATE, 116.54), \ + SQUEAKER_DIVISOR (POLL_RATE, 123.47), \ + SQUEAKER_DIVISOR (POLL_RATE, 130.81), \ + SQUEAKER_DIVISOR (POLL_RATE, 138.59), \ + SQUEAKER_DIVISOR (POLL_RATE, 146.83), \ + SQUEAKER_DIVISOR (POLL_RATE, 155.56)} +#endif + +typedef struct +{ + uint8_t note_clock; + uint8_t note_period; + uint8_t note_duty; + uint8_t note_holdoff; + /* Pointer to start of string. */ + const char *start; + /* Pointer to current position in string. */ + const char *cur; + uint8_t holdoff; + uint16_t poll_rate; + const char *loop_start; + int8_t loop_count; + uint8_t prescaler; + uint8_t note_fraction; + squeaker_speed_t speed; + squeaker_volume_t volume; + ticker8_t ticker; + squeaker_scale_t *scale_table; + uint8_t octave; +} squeaker_private_t; + + +typedef squeaker_private_t squeaker_obj_t; +typedef squeaker_obj_t *squeaker_t; + + +/* The scale table is usually defined with: + + static squeaker_scale_t scale_table[] = SQUEAKER_SCALE_TABLE (LOOP_POLL_RATE); +*/ + + +extern squeaker_t +squeaker_init (squeaker_obj_t *dev, + uint16_t poll_rate, + squeaker_scale_t *scale_table); + +extern void +squeaker_play (squeaker_t squeaker, const char *str); + +extern int8_t +squeaker_update (squeaker_t squeaker); + +/* Set (base) speed in beats per minute (BPM). */ +extern void +squeaker_speed_set (squeaker_t squeaker, squeaker_speed_t speed); + +/* Set volume as percentage of maximum. */ +extern void +squeaker_volume_set (squeaker_t squeaker, squeaker_volume_t volume); + +#endif diff --git a/extra/squeaker2.c b/extra/squeaker2.c new file mode 100644 index 0000000..60e9cf4 --- /dev/null +++ b/extra/squeaker2.c @@ -0,0 +1,435 @@ +/** @file squeaker.c + @author M. P. Hayes, UCECE + @date 14 April 2007 + @brief Play simple tunes with PWM. +*/ + +#define SQUEAKER_TRANSPARENT 1 + +#include "squeaker.h" + + +#define SQUEAKER_HOLDOFF_TIME 50e-3 +enum {SQUEAKER_PRESCALER = 256}; + + +/* By default notes are in the scale C4 -> C5. + Note the first 12 frets on a six string guitar covers 3 octaves + from the pitch E2 (82.41 Hz) to the pitch E5 (659.26 Hz). + + Tunes are specified using the notation C4C5F4G4C4 but for brevity + this can be simplified to C4C5F4GC where the previously specified + octave number persists. A problem with this notation is the + verbosity when we have something like B2C3B2C3. Most melodies will + only require 2 and 3 at the most octaves. The ABC music notation + uses CDEFGABcdefgabc'd'e'f'g'a'b'c' to denote 3 octaves from C2 to + C5? This uses numbers to indicate note duration, for example, C2 + denotes a C of twice the standard duration. + + For emphasis of first beat in bar, perhaps use ^ to indicate + louder, for example, C^. Similarly, to make a note quieter it + could have a v suffix. Alternatively, | bar markers could be + inserted and perhaps time signatures. + + By default notes are quarter-notes. It is probably best to define + speed in terms of quarter-notes (beats per minute) rather than bars + per minute since this requires bars to be defined. Two bars in 4/4 + could be represented by C^DEDC^BAB whereas two bars in 3/4 could be + represented by C^DEC^DE. + + So how should we denote note duration? We need to distinguish + between 2 identical quarter-notes (A A) played in succession and a + half-note since this sounds different. + + We could use AA to indicate two A quarter-notes. Alternatively, we + could use AA to indicate an A half-note. However, for a whole note + we would need to indicate this with AAAA. With the latter scheme + we could separate two indentical quarter notes with a comma, for + example, A,A. + + Rests are easy. Each space represents one rest of quarter-note + duration. Two spaces represent a half-note rest. Alternatively, + we could represent this with " /". + + From a sequencing point of view it is simpler if every symbol + represents a quarter-note rather than having variable length notes + since this alters the sequencing timing. This favours the approach + of using AA to denote a half-note. + + If I implement a simple attack/decay response then it would be + easier to use A/ for a half-note since we would interpret the / as + to keep playing the previous note without sounding it again. + Alternatively, when each new note is sounded there could be a short + delay. + + >num could indicate jump forward to label num while 3 to represent playing the notes ABC in succession 3 times. + This notation could be nested, for example, 2>3. + Perhaps denotes playing ABC indefinitely? No I prefer + a simple repeat. Use ABC: for an infinite repeat. + + represents ABCDEABCFG where ]n denotes alternate + endings. + +*/ + + + +static void +squeaker_ticker_set (squeaker_t *squeaker) +{ + uint16_t speed; + + speed = squeaker->speed * squeaker->note_fraction; + +#if 0 + /* The division is performed first to reduce the chance of integer + overflow. This results in poorer accuracy when the poll_rate + is low. In practice, the poll_rate cannot be too low otherwise + the generated notes will be inaccurate. */ + TICKER_INIT (&squeaker->ticker, (squeaker->poll_rate / speed) * 60); +#else + TICKER_INIT (&squeaker->ticker, (squeaker->poll_rate / SQUEAKER_PRESCALER) + * 60 / speed); +#endif +} + + +static void +squeaker_note_fraction_set (squeaker_t *squeaker, uint8_t note_fraction) +{ + squeaker->note_fraction = note_fraction; + squeaker_ticker_set (squeaker); +} + + +enum {SQUEAKER_SCALE_SIZE = 12}; + +void +squeaker_note_set (squeaker_t *squeaker, + squeaker_period_t period, + squeaker_period_t duty) +{ + squeaker->note_period = period; + squeaker->note_duty = duty; + squeaker->note_holdoff = squeaker->holdoff; +} + + +static void +squeaker_rest_play (squeaker_t *squeaker) +{ + squeaker_note_set (squeaker, 0, 0); +} + + +static void +squeaker_note_play (squeaker_t *squeaker, squeaker_note_t note) +{ + squeaker_period_t period; + squeaker_period_t duty; + uint8_t index; + uint8_t octave; + +#if 0 + /* See if we can play this note. */ + if (note < SQUEAKER_NOTE_MIN) + return; +#endif + + note -= SQUEAKER_NOTE_MIN; + octave = note / SQUEAKER_SCALE_SIZE; + index = note - octave * SQUEAKER_SCALE_SIZE; + + period = squeaker->scale_table[index]; + + while (octave-- > 0) + period /= 2; + + duty = period * squeaker->volume / 200; + + squeaker_note_set (squeaker, period, duty); + +#if 0 + printf ("note = %d, octave = %d, period = %d\n", + note, index / SQUEAKER_SCALE_SIZE, period); +#endif + +} + + +static squeaker_note_t +squeaker_char_to_note (uint8_t ch) +{ + squeaker_note_t note; + /* A = 9, B = 11, C = 0, D = 2, E = 4, F = 5, G = 7 */ + static const squeaker_note_t const lookup[] = {9, 11, 0, 2, 4, 5, 7}; + + return lookup[ch - 'A']; + +#if 0 + + switch (ch) + { + default: + case 'A': + case 'B': + note = (ch - 'A') * 2 + 9; + break; + + case 'C': + case 'D': + case 'E': + note = (ch - 'C') * 2; + break; + + case 'F': + case 'G': + note = (ch - 'F') * 2 + 5; + break; + } +#endif + +#if 0 + switch (ch) + { + case 'A': + note = 9; break; + case 'B': + note = 11; break; + break; + case 'C': + note = 0; break; + case 'D': + note = 2; break; + case 'E': + note = 4; break; + case 'F': + note = 5; break; + case 'G': + note = 7; break; + default: + note = 0; + break; + } + +#endif + + return note; +} + + +/* Scan next part of melody until a note or end of melody is found. */ +static const char * +squeaker_scan (squeaker_t *squeaker, const char *str) +{ + while (1) + { + uint8_t num; + char cmd; + char modifier; + bool have_hash; + bool have_num; + squeaker_note_t note; + + /* Play rest at end of melody. */ + if (! *str) + { + squeaker_rest_play (squeaker); + return str; + } + + cmd = *str++; + + have_hash = *str == '#'; + if (have_hash) + str++; + + modifier = 0; + if (*str == '+' || *str == '-') + modifier = *str++; + + have_num = 0; + num = 0; + while (*str >= '0' && *str <= '9') + { + have_num = 1; + num = num * 10 + *str++ - '0'; + } + + switch (cmd) + { + /* Repeat sequence from start. */ + case ':': + str = squeaker->start; + continue; + + /* Define start of loop. */ + case '<': + /* We could implement a small stack to allow nested + loops. */ + squeaker->loop_start = str; + squeaker->loop_count = 0; + continue; + + /* Loop. */ + case '>': + squeaker->loop_count++; + if (!num) + num = 2; + + if (squeaker->loop_count < num) + { + /* Jump to start of loop. If no start of loop symbol, + jump to start. */ + str = squeaker->loop_start; + if (!str) + str = squeaker->start; + } + continue; + + /* Alternate endings. */ + case '[': + if (squeaker->loop_count == num - 1) + continue; + + /* Skip to next alternate ending, the end of loop, or end of + melody. */ + while (*str && *str != '[' && *str != '>') + str++; + continue; + + /* Play rest. */ + case ' ': + squeaker_rest_play (squeaker); + return str; + break; + + case '*': + if (num) + squeaker_note_fraction_set (squeaker, num); + continue; + + case '@': + if (num) + squeaker_speed_set (squeaker, num); + continue; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + note = squeaker_char_to_note (cmd); + + if (have_hash) + note++; + + if (have_num) + squeaker->octave = num; + + if (modifier == '+') + note += SQUEAKER_SCALE_SIZE; + + if (modifier == '-') + note -= SQUEAKER_SCALE_SIZE; + + /* Convert note to MIDI note number. */ + note += (squeaker->octave + 1) * SQUEAKER_SCALE_SIZE; + + squeaker_note_play (squeaker, note); + return str; + break; + + /* Continue with previous note. */ + case '/': + default: + return str; + break; + } + } + return str; +} + + +void +squeaker_play (squeaker_t *squeaker, const char *str) +{ + squeaker->cur = squeaker->start = str; + squeaker->loop_start = 0; + squeaker->loop_count = 0; + squeaker->octave = SQUEAKER_OCTAVE_DEFAULT; + squeaker_note_fraction_set (squeaker, 1); +} + + +/* Set (base) speed in beats per minute (BPM). */ +void +squeaker_speed_set (squeaker_t *squeaker, squeaker_speed_t speed) +{ + squeaker->speed = speed; + squeaker_ticker_set (squeaker); +} + + +/* Set volume as percentage of maximum. */ +void +squeaker_volume_set (squeaker_t *squeaker, squeaker_volume_t volume) +{ + squeaker->volume = volume; +} + + +int8_t +squeaker_update (squeaker_t *squeaker) +{ + /* Rather than a 16 bit ticker it is faster to use + an 8-bit prescaler and an 8-bit ticker. */ + + squeaker->prescaler++; + if (! squeaker->prescaler) + { + if (TICKER_UPDATE (&squeaker->ticker)) + { + if (squeaker->cur) + squeaker->cur = squeaker_scan (squeaker, squeaker->cur); + } + + /* We could halve the note duty after some interval to + simulate note decay. The decay interval could be related + to the desired sustain. This approach is a bit brutal + especially when the duty is small. */ + + if (squeaker->note_holdoff) + squeaker->note_holdoff--; + } + + if (++squeaker->note_clock >= squeaker->note_period) + squeaker->note_clock = 0; + + return squeaker->note_clock < squeaker->note_duty + && ! squeaker->note_holdoff; +} + + +squeaker_t +squeaker_init (squeaker_obj_t *squeaker, + uint16_t poll_rate, + squeaker_scale_t *scale_table) +{ + squeaker->poll_rate = poll_rate; + squeaker->scale_table = scale_table; + squeaker->note_period = 0; + squeaker->note_duty = 0; + squeaker->volume = 100; + squeaker->prescaler = 0; + squeaker->holdoff = squeaker->poll_rate + / (uint16_t)(SQUEAKER_PRESCALER / SQUEAKER_HOLDOFF_TIME); + + squeaker_play (squeaker, 0); + squeaker_speed_set (squeaker, SQUEAKER_SPEED_DEFAULT); + return squeaker; +} diff --git a/extra/ticker.c b/extra/ticker.c new file mode 100644 index 0000000..25c741e --- /dev/null +++ b/extra/ticker.c @@ -0,0 +1,6 @@ +/** @file ticker.c + @author M. P. Hayes, UCECE + @date 2 April 2007 + @brief +*/ + diff --git a/extra/ticker.h b/extra/ticker.h new file mode 100644 index 0000000..ea0fb17 --- /dev/null +++ b/extra/ticker.h @@ -0,0 +1,45 @@ +/** @file ticker.h + @author M. P. Hayes, UCECE + @date 2 April 2007 + @brief +*/ +#ifndef TICKER_H +#define TICKER_H + +#include "system.h" + +typedef struct +{ + uint16_t period; + uint16_t clock; +} ticker_t; + + +typedef struct +{ + uint16_t period; + uint16_t clock; +} ticker16_t; + + +typedef struct +{ + uint8_t period; + uint8_t clock; +} ticker8_t; + + +#define TICKER_INIT(DEV, PERIOD) \ + (DEV)->period = (PERIOD); \ + (DEV)->clock = (DEV)->period; + + +/* Return non-zero when the ticker rolls over otherwise return 0. */ +#define TICKER_UPDATE(DEV) \ + (--(DEV)->clock ? 0 : ((DEV)->clock = (DEV)->period)) + + +#define TICKER_START(DEV) \ + (DEV)->clock = (DEV)->period; + +#endif diff --git a/extra/tweeter.c b/extra/tweeter.c new file mode 100644 index 0000000..fa0f240 --- /dev/null +++ b/extra/tweeter.c @@ -0,0 +1,104 @@ +/** @file tweeter.c + @author M. P. Hayes, UCECE + @date 20 April 2007 + @brief Generate PWM for a piezo tweeter. +*/ + +#define TWEETER_TRANSPARENT 1 + +#include "tweeter.h" + + +#define TWEETER_HOLDOFF_TIME 50e-3 + +enum {TWEETER_SCALE_SIZE = 12}; + + +void +tweeter_note_set (tweeter_t tweeter, + tweeter_period_t period, + tweeter_period_t duty) +{ + tweeter->note_period = period; + tweeter->note_duty = duty; + + /* To distinguish between two quarter notes and a half note of the + same pitch we have a short hold off period at the start of each + note. */ + tweeter->note_holdoff = tweeter->poll_rate + / (uint16_t)(1 / TWEETER_HOLDOFF_TIME); +} + + +/* The note and velocity are specified as per the MIDI standard except + a note value of 0 indicates a rest (note off). The velocity has a + maximum value of 127 and gives an indication of the note + volume. */ +void +tweeter_note_play (tweeter_t tweeter, tweeter_note_t note, uint8_t velocity) +{ + tweeter_period_t period; + tweeter_period_t duty; + uint8_t index; + uint8_t octave; + + if (note == 0) + { + /* Play a rest. */ + tweeter_note_set (tweeter, 0, 0); + return; + } + + /* See if we can play this note. */ + if (note < TWEETER_NOTE_MIN) + return; + + note -= TWEETER_NOTE_MIN; + octave = note / TWEETER_SCALE_SIZE; + index = note - octave * TWEETER_SCALE_SIZE; + + period = tweeter->scale_table[index]; + + while (octave-- > 0) + period >>= 1; + + duty = (period * velocity) >> 8; + + tweeter_note_set (tweeter, period, duty); + +#if 0 + printf ("note = %d, octave = %d, period = %d\n", + note, index / TWEETER_SCALE_SIZE, period); +#endif + +} + + +int8_t +tweeter_update (tweeter_t tweeter) +{ + if (tweeter->note_holdoff) + { + tweeter->note_holdoff--; + return 0; + } + + if (++tweeter->note_clock >= tweeter->note_period) + tweeter->note_clock = 0; + + return tweeter->note_clock < tweeter->note_duty; +} + + +tweeter_t +tweeter_init (tweeter_obj_t *tweeter, + uint16_t poll_rate, + tweeter_scale_t *scale_table) +{ + tweeter->poll_rate = poll_rate; + tweeter->scale_table = scale_table; + tweeter->note_period = 0; + tweeter->note_duty = 0; + + return tweeter; +} diff --git a/extra/tweeter.h b/extra/tweeter.h new file mode 100644 index 0000000..243dc6f --- /dev/null +++ b/extra/tweeter.h @@ -0,0 +1,79 @@ +/** @file tweeter.h + @author M. P. Hayes, UCECE + @date 20 April 2007 + @brief Generate PWM for a piezo tweeter. +*/ +#ifndef TWEETER_H +#define TWEETER_H + +#include "system.h" +#include "ticker.h" + +typedef uint8_t tweeter_note_t; +typedef uint8_t tweeter_duration_t; +typedef uint8_t tweeter_period_t; +typedef uint8_t tweeter_velocity_t; +typedef uint8_t tweeter_scale_t; + +/* Could calculate scale divisors at run time. 2^(1/2) is approx + 1.0594631. A reasonable rational approximation is 267/252 = + 1.0595238. Let's save memory and provide a macro to compute the + divisors. */ + +#define TWEETER_DIVISOR(POLL_RATE, FREQ) (POLL_RATE / FREQ + 0.5) + + +enum {TWEETER_NOTE_MIN = 40}; +/* Define divisors for chromatic scale E2 -> D#3. For better accuracy + this should be defined for the lowest frequency scale, however, + this may need 16 bits per note. */ +#define TWEETER_SCALE_TABLE(POLL_RATE) \ + {TWEETER_DIVISOR (POLL_RATE, 82.41), \ + TWEETER_DIVISOR (POLL_RATE, 87.31), \ + TWEETER_DIVISOR (POLL_RATE, 92.50), \ + TWEETER_DIVISOR (POLL_RATE, 98.00), \ + TWEETER_DIVISOR (POLL_RATE, 103.83), \ + TWEETER_DIVISOR (POLL_RATE, 110.0), \ + TWEETER_DIVISOR (POLL_RATE, 116.54), \ + TWEETER_DIVISOR (POLL_RATE, 123.47), \ + TWEETER_DIVISOR (POLL_RATE, 130.81), \ + TWEETER_DIVISOR (POLL_RATE, 138.59), \ + TWEETER_DIVISOR (POLL_RATE, 146.83), \ + TWEETER_DIVISOR (POLL_RATE, 155.56)} + + +typedef struct +{ + uint8_t note_clock; + uint8_t note_period; + uint8_t note_duty; + uint16_t note_holdoff; + uint16_t poll_rate; + tweeter_scale_t *scale_table; +} tweeter_private_t; + + +typedef tweeter_private_t tweeter_obj_t; +typedef tweeter_obj_t *tweeter_t; + +/* The scale table is usually defined with: + + static tweeter_scale_t scale_table[] = TWEETER_SCALE_TABLE (LOOP_POLL_RATE); +*/ + +extern int8_t +tweeter_update (tweeter_t tweeter); + +/* The note and velocity are specified as per the MIDI standard except + a note value of 0 indicates a rest (note off). The velocity has a + maximum value of 127 and gives an indication of the note + volume. */ +extern void +tweeter_note_play (tweeter_t tweeter, tweeter_note_t note, uint8_t velocity); + +extern tweeter_t +tweeter_init (tweeter_obj_t *dev, + uint16_t poll_rate, + tweeter_scale_t *scale_table); + +#endif