Syntax-Directed Translation of ABC Music Notation

(from Nathan's discussion section on 2/8/10)

ABC Notation

ABC is an ASCII notation for music. Notes are represented by letters that indicate their pitches (e.g., 'C' is middle C; 'c' is high C) and optional length modifiers. A note without a length modifier has the default length. The length modifiers include the following (P stands for a pitch character):

PN

the default length * N

P/N

the default length / N

P/

the default length / 2

(3:NPPP

three notes, each of length N/3 times the default (the pitches can be different)

PL1>PL2

The first note is 1.5 times the length indicated by the (optional) length modifier L1; the second note is 0.5 times the length indicated by the length modifier L2.

A sequence of notes is divided by the '|' character into measures, which are groups with the same number of beats. We didn't enforce equal length of measures in our exercise in section.

A sample tune in ABC notation—“Row, Row, Row Your Boat”:

C C C/>D/ E | E/>D/ E/>F/ G2 |
c/3c/3c/3 (3:1GGG (3:1EEE (3:1CCC | G/>F/ E/>D/ C2

Translating ABC Notation into Sound

Suppose we have a function play(double frequency, double length) which plays a single note. We can use syntax-directed translation to read a song in ABC notation and play it.

The Bison grammar in the file playabc.y contains rules for translating ABC text into sound. The grammar uses two types of semantic values for symbols: notes and numbers. The note type is defined in the prologue section:

typedef struct {
  double frequency;
  double length;
} note_t;

The %union declaration tells Bison what types may be used for semantic values. In the C code that Bison generates, semantic values are implemented as instances of a union type declared with this body:

%union {
  note_t note;
  double num;
}

The types of semantic values for particular symbols are specified by %type declarations:

%type <note> note pitch
%type <num>  length num digit

The production rules construct note objects, update their lengths as modifiers are recognized, and play them when the length information is complete:

group : note    { play($1.frequency, $1.length); }
      | broken
      | triplet
      ;

note : pitch        { $$ = $1; }
     | pitch length { $$.frequency = $1.frequency; $$.length = $2; }
     ;

pitch : 'C' { $$.frequency = 261.6; $$.length = default_length; }
      | /* ... */
      | 'c' { $$.frequency = 523.2; $$.length = default_length; }
      ;

The play function is implemented with the beep command. Source code for beep is available here; whether it works on non-Linux systems I don't know. You can get it on Ubuntu with the command "sudo apt-get install beep". It works only on a local machine, not over ssh.