Add extra modules

main
Michael Hayes 15 years ago
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…
Cancel
Save