parent
15e35c5682
commit
aa3fa1c917
@ -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 <num could
|
||||
represent jump back to label num. Although I prefer the notation
|
||||
<ABC>3 to represent playing the notes ABC in succession 3 times.
|
||||
This notation could be nested, for example, <ABC<DEF>2>3.
|
||||
Perhaps <ABC> denotes playing ABC indefinitely? No I prefer
|
||||
a simple repeat. Use ABC: for an infinite repeat.
|
||||
|
||||
<ABC]1DE]2FG> 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;
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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 <num could
|
||||
represent jump back to label num. Although I prefer the notation
|
||||
<ABC>3 to represent playing the notes ABC in succession 3 times.
|
||||
This notation could be nested, for example, <ABC<DEF>2>3.
|
||||
Perhaps <ABC> denotes playing ABC indefinitely? No I prefer
|
||||
a simple repeat. Use ABC: for an infinite repeat.
|
||||
|
||||
<ABC]1DE]2FG> 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;
|
||||
}
|
||||
@ -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
|
||||
@ -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 <num could
|
||||
represent jump back to label num. Although I prefer the notation
|
||||
<ABC>3 to represent playing the notes ABC in succession 3 times.
|
||||
This notation could be nested, for example, <ABC<DEF>2>3.
|
||||
Perhaps <ABC> denotes playing ABC indefinitely? No I prefer
|
||||
a simple repeat. Use ABC: for an infinite repeat.
|
||||
|
||||
<ABC]1DE]2FG> 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;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
/** @file ticker.c
|
||||
@author M. P. Hayes, UCECE
|
||||
@date 2 April 2007
|
||||
@brief
|
||||
*/
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
Loading…
Reference in new issue