#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

typedef struct {
  unsigned char code;
  char *mnem;
  int duration;
  unsigned char X;
} Phone;

static void msleep(long msec) {
  struct timespec ts;
  ts.tv_sec = msec / 1000;
  ts.tv_nsec = (msec % 1000) * 1000000;
  nanosleep(&ts, NULL);
}

static inline void send(int port, unsigned char c) {
  write(port, &c, 1);
}

static inline void sends(int port, char *c) {
  write(port, c, strlen(c));
}
  
static char *ProgName = "sj";

// (this code allows SpeakJet mnemonics with or without the '\' so that
//  it can also be used with Speakalator Web at:
//
//      https://7800.8bitdev.org/webapps/speakalator.html
// )

// This table was extracted from Page 16 of:
//      https://people.ece.cornell.edu/land/courses/ece4760/Speech/speakjetusermanual.pdf

// Note that my calculation of linux sleep duration doesn't take into account any speed
// changes or repeats etc.  It's a crude hack, might be improved on later.

// MSA Sound Allophone Component List:

//  Code    Phoneme          Sample Words        Msec.      Phoneme Type
// ------ ------------ ----------------------- -------- ---------------------

Phone phone[] = {
  {   0, "", 0, },

  // pauses
  //   p0 p1 p2 p3 p4 p5 p6                 // Pauses of various durations, these will cause the volume to ramp down, wait a specified
                                            // amount of time and then ramp back up. 1, 2 & 3, ramp the volume while the format
                                            // frequencies are being changed. 4, 5 & 6 wait for silence before changing the format
                                            // frequencies. 
  {   0, "\\P0", 0, },
  {   1, "\\P1", 100, },
  {   2, "\\P2", 200, },
  {   3, "\\P3", 700, },
  {   4, "\\P4", 300, },
  {   5, "\\P5", 60, },
  {   6, "\\P6", 90, },
  
  //   delay=
  {   30, "\\DELAY=", 0, }, // PARAM=X      // This command specifies the number of 10ms intervals to delay before continuing
                                            // on to the next code. The Delay range is from 0 to 255. 

  // ( I think the tones may use one of the other mechanisms described in the document. )
  
  // music oct1/oct2/oct3
  //   B A G F E D C
  //     A G F   D C sharp
  //   B A G   E D   flat

  /*
; SCP Mode Commands
; The following subroutines execute SCP Mode commands that allow direct
; control over ther SpeakJet using the serial connection. See the SpeakJet
; datasheet p 6 for more information on SCP Mode.

              SpeakJet’s Subset of the SCP Commands
            The following table lists the SCP commands.

Command                Function                     Description

\                        Begin
                     Escape Sequence
                                                    The Escape character is the first character of a
                                                    two character Escape Sequence.
                                                    The Escape character is followed with a single digit,
                                                    which represents the Serial Node of the SpeakJet
                                                    to communicate with. SCP Mode is entered when the
                                                    received Serial Node is either 0, or matches the
                                                    SpeakJet's Node value. If the received Serial Node
                                                    is not 0 and does not match the SpeakJet's Node
                                                    value then SCP Mode is turned off.
  sends(port, "\\0");

0 to 9                   Numbers.                   As digits are received, they are stored into the SCP’s input buffer.
A to F

H                        Store
                      Memory Type
                                                    Stores a value into the 8-Bit Memory Type control register.
The two types of SpeakJet memory can be set with the
following memory types:
  sends(port, "0H");  (Write will be to a register)
             or
  sends(port, "32H"); (Write will be to the Internal EEPROM)

J                        Store
                      Memory Address
                                                    Stores a value into the 8-Bit Memory Address control register.
Example: To set the memory address to "126".
  sends(port, "\\0");  (SCM mode)
  sends(port, "126J"); (The memory address is now 126)

N                       Store
                        Memory
                                                    Stores value into the Memory specifies by the control registers.
Example: To write the hex (01) to the first location of the Internal EEPROM.
  sends(port, "\\0");  (SCM mode)
  sends(port, "0J");   (The memory address is now 0)
  sends(port, "32H");  (Write will be to the Internal EEPROM)
  sends(port, "01N");  (Writ 0x01 (binary 1) at memory address 0)

R                       Clear
                        Buffer
                                                    Clears the contents of the SpeakJet’s 64 byte Input Buffer
                                                    and the SCP’s 16 byte Input Buffer.
sj_clear_buffer ; 'R' Clear Buffer

  sends(port, "\\");      
  sends(port, "0");       
  sends(port, "R");       
  sends(port, "X");       

S                       Stop
                     Enunciating
                                                    Sets the SpeakJet's Wait flag to “1” which will cause
                                                    the MSA module to ignore any data currently in
                                                    the SpeakJet’s 64 byte input buffer.
sj_stop_voice ; 'S' Stop Enunciating

  sends(port, "\\");      
  sends(port, "0");       
  sends(port, "S");       
  sends(port, "X");       

T                       Start
                     Enunciating
                                                    Sets the SpeakJet's Wait flag to “0” which will cause
                                                    the MSA module to enunciate any data currently
                                                    in the SpeakJet’s 64 byte input buffer.
sj_start_voice ; 'T' Start Enunciating

  sends(port, "\\");      
  sends(port, "0");       
  sends(port, "T");       
  sends(port, "X");       

V                                                   Acknowledge Causes the SpeakJet to enunciate the word "Ready"
sj_verify ; 'V' Enunciate "Ready" to verify connection

  sends(port, "\\");      
  sends(port, "0");       
  sends(port, "V");       
  sends(port, "X");       


W                    Hard Reset                     Resets the SpeakJet in the same way that cycling the Reset Line does.
sj_reset ; 'W' Exit SCP Mode and reset SpeakJet

  sends(port, "\\");      
  sends(port, "0");       
  sends(port, "W");       


X                      Exit                         Exits SCP Mode.   



   */
  
  /*
    send 22 (pitch) then:

    Octave 1 (Low)   C=45,  D=51,  E=57,  F=60,  G=67,  A=76,  B=85  C#/Db=48,  D#/Eb=54,  F#/Gb=64,  G#/Ab=72,  A#/Bb=80
    Octave 2 (Mid)   C=90,  D=102, E=114, F=120, G=134, A=152, B=170 C#/Db=96,  D#/Eb=108, F#/Gb=128, G#/Ab=144, A#/Bb=160
    Octave 3 (High)  C=180, D=204, E=228, F=240, G=254*

    then 200 or 210 or 220.

SpeakJet 3-Octave Pitch Array (Hex)
Note     Octave 1 (Low)    Octave 2   (Mid)    Octave 3 (High)
C          0x2D (45)         0x5A (90)           0xB4 (180)
C# / Db    0x30 (48)         0x60 (96)           0xC0 (192)
D          0x33 (51)         0x66 (102)          0xCC (204)
D# / Eb    0x36 (54)         0x6C (108)          0xD8 (216)
E          0x39 (57)         0x72 (114)          0xE4 (228)
F          0x3C (60)         0x78 (120)          0xF0 (240)
F# / Gb    0x40 (64)         0x80 (128)          0xFA (250)
G          0x43 (67)         0x86 (134)          0xFE (254)
G# / Ab    0x48 (72)         0x90 (144)               —
A          0x4C (76)         0x98 (152)               —
A# / Bb    0x50 (80)         0xA0 (160)               —
B          0x55 (85)         0xAA (170)               —

#define pitch_code   22
#define O1C_pitch    33
#define O2C_pitch    65
#define O3C_pitch    131
#define O1Cs_pitch   35
#define O2Cs_pitch   69
#define O3Cs_pitch   139
#define O1D_pitch    37
#define O2D_pitch    73
#define O3D_pitch    147
#define O1Ds_pitch   39
#define O2Ds_pitch   78
#define O3Ds_pitch   156
#define O1E_pitch    41
#define O2E_pitch    82
#define O3E_pitch    165
#define O1F_pitch    44
#define O2F_pitch    87
#define O3F_pitch    175
#define O1Fs_pitch   46
#define O2Fs_pitch   93
#define O3Fs_pitch   185
#define O1G_pitch    49
#define O2G_pitch    98
#define O3G_pitch    196
#define O1Gs_pitch   52
#define O2Gs_pitch   104
#define O3Gs_pitch   208
#define O1A_pitch    55
#define O2A_pitch    110
#define O3A_pitch    220
#define O1As_pitch   58
#define O2As_pitch   117
#define O3As_pitch   233
#define O1B_pitch    62
#define O2B_pitch    123
#define O3B_pitch    247

   */
  
  // modifiers
  
  {   7, "\\FAST", 0, },                    // Plays the next phoneme at 1/2 the time it normally would play.
  {   8, "\\SLOW", 0, },                    // Plays the next phoneme at 1 and 1/2 the time it normally would play.
  
  {  14, "\\STRESS", 0, },                  // Plays the next phoneme with a small amount of stress in the voice.
  {  15, "\\RELAX", 0, },                   // Plays the next phoneme with a small amount of relaxation in the voice.
  
  {   16, "\\WAIT", 0, },                   // This command will stop the voicing and wait for a start command. The Start command
                                            // can be issued by either sending the SCP start command or by changing the state of one
                                            // of the input lines that has been previously set to do a Start. 

  // controls
  
  {   20, "\\VOLUME=", 0, }, // PARAM=X     // This command sets the master volume level. A value will need to be sent after the
                                            // volume command that specifies the desired volume. Volume levels can range from 0 to
                                            // 127. The default is 96. 
  
  {   21, "\\SPEED=", 0, }, // PARAM=X      // This command sets the play speed. A value will need to be sent after the speed
                                            // command that specifies the desired speed. Speeds can range from 0 to 127. The
                                            // default is 114.
  
  {   22, "\\PITCH=", 0, }, // PARAM=X      // This command sets the Vocalization Pitch in Hertz. A value will need to be sent after
                                            // the pitch command that specifies the desired pitch. The vocalization pitch is what makes
                                            // a voice sound High pitched or Low pitched. For singing, the pitch has a range of 3 full
                                            // octaves (32Hz to 240hz). The Vocalization Pitch works only on sounds that are voiced.
                                            // Pitches can range from 0 to 255. The default is 88. Note that anything under 30 starts
                                            // to sound like clicks instead of a voice. Also Note that a value of 0 = 0 Hz and thusly, will
                                            // not actually vocalize
  
  {   23, "\\BEND=", 0, }, // PARAM=X       // This command sets the frequency Bend. A value will need to be sent after the Bend
                                            // command that specifies the desired Bend. The frequency Bend adjusts the output
                                            // frequencies of the oscillators. This will change the voicing from a deep-hollow sounding
                                            // voice to a High-metallic sounding voice. Bends can range from 0 to 15. The default is 5.
  
  {   24, "\\PORTCTR=", 0, }, // PARAM=X    // This command sets the Port Control Value. A value will need to be sent after the
                                            // PortCtr command that specifies the desired function of the output lines. The Output line
                                            // control bits are binaurally encoded where a 1 indicates that the output function is chip
                                            // controlled and a 0 indicates that the output function is user controlled. Bit 0 corresponds
                                            // to OUT0, etc… PortCtr values can range from 0 to 7. The default is 7.
  
  {   25, "\\PORT=", 0, }, // PARAM=X       // This command sets the Port Output Value. A value will need to be sent after the Port
                                            // command that specifies the desired state of the output lines. When the Output line
                                            // control bits are set to 0, the corresponding port bit is represented on the output line. Bit
                                            // 0 corresponds to OUT0, etc… Port values can range from 0 to 7. The default is 0.
  
  {   26, "\\REPEAT=", 0, }, // PARAM=X     // This command sets a number of times to Repeat the next code. A value will need to be
                                            // sent after the Repeat command that specifies the number of times to repeat the next
                                            // command.
                                            // The Repeat range is from 0 to 255.

  {   28, "\\CALL=", 0, }, // PARAM=X       // This command specifies which EEPROM phrase to play then to return from.
                                            // This can be nested 3 deep maximum.
  /*
0 hello
1 goodbye
2 ready
3 speakjet
4 star-trek style computer beeps and bloops
5 (a countdown) "10 9 8 7 6 5 4 3 2 1"
6 whoop whoop whoop alerts
7 "Biddie-biddie-biddie" (TWiki from 'Buck Rogers in the 25th Century' - bdbdbd, what's up Buck?)
8 robot
9 activated
   */
  
  {   29, "\\GOTO=", 0, }, // PARAM=X       // This command specifies which EEPROM phrase to play.

  {   31, "\\RESET", 0, }, // PARAM=X       // This command resets the Volume, Speed, Pitch and Bend to the default values

  { 128, "\\IY", /* See, Even, Feed */ 70, /* Voiced Long Vowel */ },
  { 130, "\\EY", /* Hair, Gate, Beige */ 70, /* Voiced Long Vowel */ },
  { 137, "\\OW", /* Comb, Over, Hold */ 70, /* Voiced Long Vowel */ },
  { 139, "\\UW", /* Food, June */ 70, /* Voiced Long Vowel */ },
  
  { 129, "\\IH", /* Sit, Fix, Pin */ 70, /* Voiced Short Vowel */ },
  { 131, "\\EH", /* Met, Check, Red */ 70, /* Voiced Short Vowel */ },
  { 132, "\\AY", /* Hat, Fast, Fan */ 70, /* Voiced Short Vowel */ },
  { 133, "\\AX", /* Cotten */ 70, /* Voiced Short Vowel */ },
  { 134, "\\UX", /* Luck, Up, Uncle */ 70, /* Voiced Short Vowel */ },
  { 135, "\\OH", /* Hot, Clock, Fox */ 70, /* Voiced Short Vowel */ },
  { 136, "\\AW", /* Father, Fall */ 70, /* Voiced Short Vowel */ },
  { 138, "\\UH", /* Book, Could, Should */ 70, /* Voiced Short Vowel */ },

  { 140, "\\MM", /* Milk, Famous */ 70, /* Voiced Nasal */ },
  { 141, "\\NE", /* Nip, Danger, Thin */ 70, /* Voiced Nasal */ },
  { 142, "\\NO", /* No, Snow, On */ 70, /* Voiced Nasal */ },
  { 143, "\\NGE", /* Think, Ping */ 70, /* Voiced Nasal */ },
  { 144, "\\NGO", /* Hung, Song */ 70, /* Voiced Nasal */ },

  { 145, "\\LE", /* Lake, Alarm, Lapel */ 70, /* Voiced Resonate */ },
  { 146, "\\LO", /* Clock, Plus, Hello */ 70, /* Voiced Resonate */ },
  { 147, "\\WW", /* Wool, Sweat */ 70, /* Voiced Resonate */ },
  { 148, "\\RR", /* Ray, Brain, Over */ 70, /* Voiced Resonate */ },

  { 149, "\\IYRR", /* Clear, Hear, Year */ 200, /* Voiced R Color Vowel */ },
  { 150, "\\EYRR", /* Hair, Stair, Repair */ 200, /* Voiced R Color Vowel */ },
  { 151, "\\AXRR", /* Fir, Bird, Burn */ 190, /* Voiced R Color Vowel */ },
  { 152, "\\AWRR", /* Part, Farm, Yarn */ 200, /* Voiced R Color Vowel */ },
  { 153, "\\OWRR", /* Corn, Four, Your */ 185, /* Voiced R Color Vowel */ },

  { 154, "\\EYIY", /* Gate, Ate, Ray */ 165, /* Voiced Diphthong */ },
  { 155, "\\OHIY", /* Mice, Fight, White */ 200, /* Voiced Diphthong */ },
  { 156, "\\OWIY", /* Boy, Toy, Voice */ 225, /* Voiced Diphthong */ },
  { 157, "\\OHIH", /* Sky, Five, I */ 185, /* Voiced Diphthong */ },
  { 158, "\\IYEH", /* Yes, Yarn, Million */ 170, /* Voiced Diphthong */ },
  { 159, "\\EHLL", /* Saddle, Angle, Spell */ 140, /* Voiced Diphthong */ },
        { 159, "\\EHLE", /* Saddle, Angle, Spell */ 140, /* Voiced Diphthong */ }, // alias used in speakalator
  { 160, "\\IYUW", /* Cute, Few */ 180, /* Voiced Diphthong */ },
  { 161, "\\AXUW", /* Brown, Clown, Thousand */ 170, /* Voiced Diphthong */ },
  { 162, "\\IHWW", /* Two, New, Zoo */ 170, /* Voiced Diphthong */ },
  { 163, "\\AYWW", /* Our, Ouch, Owl */ 200, /* Voiced Diphthong */ },
  { 164, "\\OWWW", /* Go, Hello, Snow */ 131, /* Voiced Diphthong */ },

  { 166, "\\VV", /* Vest, Even */ 70, /* Voiced Fictive */ },
  { 167, "\\ZZ", /* Zoo, Zap */ 70, /* Voiced Fictive */ },
  { 168, "\\ZH", /* Azure, Treasure */ 70, /* Voiced Fictive */ },
  { 169, "\\DH", /* There, That, This */ 70, /* Voiced Fictive */ },

  { 170, "\\BE", /* Bear, Bird, Beed */ 45, /* Voiced Stop */ },
  { 171, "\\BO", /* Bone, Book Brown */ 45, /* Voiced Stop */ },
  { 172, "\\EB", /* Cab, Crib, Web */ 10, /* Voiced Stop */ },
  { 173, "\\OB", /* Bob, Sub, Tub */ 10, /* Voiced Stop */ },
  { 174, "\\DE", /* Deep, Date, Divide */ 45, /* Voiced Stop */ },
  { 175, "\\DO", /* Do, Dust, Dog */ 45, /* Voiced Stop */ },
  { 176, "\\ED", /* Could, Bird */ 10, /* Voiced Stop */ },
  { 177, "\\OD", /* Bud, Food */ 10, /* Voiced Stop */ },
  { 178, "\\GE", /* Get, Gate, Guest */ 55, /* Voiced Stop */ },
  { 179, "\\GO", /* Got, Glue, Goo */ 55, /* Voiced Stop */ },
  { 180, "\\EG", /* Peg, Wig */ 55, /* Voiced Stop */ },
  { 181, "\\OG", /* Dog, Peg */ 55, /* Voiced Stop */ },

  { 165, "\\JH", /* Dodge, Jet, Savage */ 70, /* Voiced Affricate */ },
  { 182, "\\CH", /* Church, Feature, March */ 70, /* Voiceless Affricate */ },
  
  { 183, "\\HE", /* Help, Hand, Hair */ 70, /* Voiceless Fricative */ },
  { 184, "\\HO", /* Hoe, Hot, Hug */ 70, /* Voiceless Fricative */ },
  { 185, "\\WH", /* Who, Whale, White */ 70, /* Voiceless Fricative */ },
  { 186, "\\FF", /* Food, Effort, Off */ 70, /* Voiceless Fricative */ },
  { 187, "\\SE", /* See, Vest, Plus */ 40, /* Voiceless Fricative */ },
  { 188, "\\SO", /* So, Sweat */ 40, /* Voiceless Fricative */ },
  { 189, "\\SH", /* Ship, Fiction, Leash */ 50, /* Voiceless Fricative */ },
  { 190, "\\TH", /* Thin, month */ 40, /* Voiceless Fricative */ },
  
  { 191, "\\TT", /* Part, Little, Sit */ 50, /* Voiceless Stop */ },
  { 192, "\\TU", /* To, Talk, Ten */ 70, /* Voiceless Stop */ },
  { 193, "\\TS", /* Parts, Costs, Robots */ 170, /* Voiceless Stop */ },
  { 194, "\\KE", /* Can't, Clown, Key */ 55, /* Voiceless Stop */ },
  { 195, "\\KO", /* Comb, Quick, Fox */ 55, /* Voiceless Stop */ },
  { 196, "\\EK", /* Speak, Task */ 55, /* Voiceless Stop */ },
  { 197, "\\OK", /* Book, Took, October */ 45, /* Voiceless Stop */ },
  { 198, "\\PE", /* People, Computer */ 99, /* Voiceless Stop */ },
  { 199, "\\PO", /* Paw, Copy */ 99, /* Voiceless Stop */ },
  
  { 200, "\\R0", /* */ 80, /* Robot */ },
  { 201, "\\R1", /* */ 80, /* Robot */ },
  { 202, "\\R2", /* */ 80, /* Robot */ },
  { 203, "\\R3", /* */ 80, /* Robot */ },
  { 204, "\\R4", /* */ 80, /* Robot */ },
  { 205, "\\R5", /* */ 80, /* Robot */ },
  { 206, "\\R6", /* */ 80, /* Robot */ },
  { 207, "\\R7", /* */ 80, /* Robot */ },
  { 208, "\\R8", /* */ 80, /* Robot */ },
  { 209, "\\R9", /* */ 80, /* Robot */ },
  
  { 210, "\\A0", /* */ 300, /* Alarm */ },
  { 211, "\\A1", /* */ 101, /* Alarm */ },
  { 212, "\\A2", /* */ 102, /* Alarm */ },
  { 213, "\\A3", /* */ 540, /* Alarm */ },
  { 214, "\\A4", /* */ 530, /* Alarm */ },
  { 215, "\\A5", /* */ 500, /* Alarm */ },
  { 216, "\\A6", /* */ 135, /* Alarm */ },
  { 217, "\\A7", /* */ 600, /* Alarm */ },
  { 218, "\\A8", /* */ 300, /* Alarm */ },
  { 219, "\\A9", /* */ 250, /* Alarm */ },
  
  { 220, "\\B0", /* */ 200, /* Beeps */ },
  { 221, "\\B1", /* */ 270, /* Beeps */ },
  { 222, "\\B2", /* */ 280, /* Beeps */ },
  { 223, "\\B3", /* */ 260, /* Beeps */ },
  { 224, "\\B4", /* */ 300, /* Beeps */ },
  { 225, "\\B5", /* */ 100, /* Beeps */ },
  { 226, "\\B6", /* */ 104, /* Beeps */ },
  { 227, "\\B7", /* */ 100, /* Beeps */ },
  { 228, "\\B8", /* */ 270, /* Beeps */ },
  { 229, "\\B9", /* */ 262, /* Beeps */ },
  
  { 230, "\\C0", /* */ 160, /* Biological */ },
  { 231, "\\C1", /* */ 300, /* Biological */ },
  { 232, "\\C2", /* */ 182, /* Biological */ },
  { 233, "\\C3", /* */ 120, /* Biological */ },
  { 234, "\\C4", /* */ 175, /* Biological */ },
  { 235, "\\C5", /* */ 350, /* Biological */ },
  { 236, "\\C6", /* */ 160, /* Biological */ },
  { 237, "\\C7", /* */ 260, /* Biological */ },
  { 238, "\\C8", /* */ 95, /* Biological */ },
  { 239, "\\C9", /* */ 75, /* Biological */ },
  
  { 240, "\\D0", /* 0 */ 95, /* DTMF */ },
  { 241, "\\D1", /* 1 */ 95, /* DTMF */ },
  { 242, "\\D2", /* 2 */ 95, /* DTMF */ },
  { 243, "\\D3", /* 3 */ 95, /* DTMF */ },
  { 244, "\\D4", /* 4 */ 95, /* DTMF */ },
  { 245, "\\D5", /* 5 */ 95, /* DTMF */ },
  { 246, "\\D6", /* 6 */ 95, /* DTMF */ },
  { 247, "\\D7", /* 7 */ 95, /* DTMF */ },
  { 248, "\\D8", /* 8 */ 95, /* DTMF */ },
  { 249, "\\D9", /* 9 */ 95, /* DTMF */ },
  { 250, "\\D10", /* * */ 95, /* DTMF */ },
  { 251, "\\D11", /* # */ 95, /* DTMF */ },
  
  { 252, "\\M0", /* Sonar, Ping */ 125, /* Miscellaneous */ },
  { 253, "\\M1", /* Pistol, Shot */ 250, /* Miscellaneous */ },
  { 254, "\\M2", /* WOW */ 530, /*  Miscellaneous */ },

  {  255, "\\END", 0, },                    // End of Phrase.
};

// Simple Mnemonic to Hex mapping (Subset for example)
int get_entry(const char *mnemonic) {
  int i, c;
  char *s = strchr(mnemonic, '=');
  if (s) {
    c = *++s;
    *s = '\0';
  }
  for (i = 0; i < sizeof(phone)/sizeof(phone[0]); i++) {
    if ((strcasecmp(mnemonic, phone[i].mnem)   == 0  /* MN*/) ||
        (strcasecmp(mnemonic, phone[i].mnem+1) == 0) /*\MN*/) {
      if (s) {
        *s = c;
        phone[i].X = atoi(s);
      }
      return i;
    }
  }
  return 0; // Unknown (an empty entry, won't fail)
}

const unsigned char scale[3][12] = {
  {45, 48, 51, 54, 57, 60, 64, 67, 72, 76, 80, 85},    // Octave 1
  {90, 96, 102, 108, 114, 120, 128, 134, 144, 152, 160, 170}, // Octave 2
  {180, 192, 204, 216, 228, 240, 250, 254, 0, 0, 0, 0} // Octave 3 (Partial)
};

static inline void play(int port, int octave, int note) {
  unsigned char pitch = scale[octave][note];
  if (pitch == 0) return; // Skip invalid high notes

  // a whole bunch of trial and error here trying to get musical notes
  // to work.  All notes come out the same.  There's something I'm not
  // getting about pitch control.  It works OK for speech.
  
  //send(port, 31);
  //msleep(50);
  //send(port,0);
  send(port, 22);    // Set Pitch Command
  send(port, pitch); // Send the value from the array
  //msleep(50);              // Short delay to ensure register updates
  send(port, 132/*240*//*200*/);   // Play standard tone   pitch only works on voiced sounds??
  msleep(100);            // Note duration
}

int main(int argc, char **argv) {
  char sounds[1024];
  char waits[1024];
  char dev[1024];
  char *word = "";
  int print = 0;
  
  strcpy(dev, "/dev/ttyUSB0");
  ProgName = argv[0];
  
  // parameter decoding is a quick hack.  Can be improved later.
  if (argc == 1) {
    fprintf(stderr, "sj: [-q] Speakjet mnemonics to be converted to binary and sent to the AtariVox+ on %s\n\n", dev);
    exit(0);
  }

  while (argv[1][0] == '-') {
    if (strcmp(argv[1], "-p") == 0 && argv[2] != NULL) {
      word = argv[2];
      argc -= 2; argv += 2;
      print = 1;
    } else if (strcmp(argv[1], "-d") == 0 && argv[2] != NULL) {
      strcpy(dev, argv[2]);
      argc -= 2; argv += 2;
#ifdef NEVER
    } else if (strcmp(argv[1], "-q") == 0) {
      fprintf(stderr, "// Embed this in your program to use with the speak() routine.\n");
      fprintf(stdout, "const char *utterance = \"");
      for (int i = 2; i < argc; i++) {
        int idx = get_entry(argv[i]);
        unsigned char duration = phone[idx].duration, code = phone[idx].code;
        fprintf(stdout, "\\x%02x", code);
        if (phone[idx].mnem[strlen(phone[idx].mnem)-1] == '=') {
          fprintf(stdout, "\\x%02x", phone[idx].X & 255);
        }
      }
      fprintf(stdout, "\";\n");
      exit(0);
#endif
    } else {
      fprintf(stderr, "%s: unknown option %s\n\n", ProgName, argv[1]);
      exit(1);
    }
  }
  
  int serial_port;
  
  if (!print) {
    if (strncmp(dev, "/dev/", 5) != 0) {
      fprintf(stderr, "%s: option to -d should be in /dev - %s doesn't look like a device\n\n", ProgName, dev);
      exit(1);
    }
    serial_port = open(dev, O_RDWR);
    if (serial_port < 0) {
      fprintf(stderr, "Could not open %s - %s\n", dev, strerror(errno));
      //return 1;
      print = 1;
    }
  }
  
  // 2. Configure Port (19200 Baud, 8N1)
  struct termios tty;
  if (!print) {
    tcgetattr(serial_port, &tty);
    cfsetispeed(&tty, B19200);
    cfsetospeed(&tty, B19200);
    tty.c_cflag &= ~PARENB; // No parity
    tty.c_cflag &= ~CSTOPB; // 1 stop bit
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8; // 8 data bits
    tty.c_cflag |= (CLOCAL | CREAD);
    tcsetattr(serial_port, TCSANOW, &tty);
  } else {
    fprintf(stderr, "%s: ", word);
  }
  
  for (int i = 1; i < argc; i++) {
    int idx;
    unsigned char duration, code, X;

    if (argv[i][strlen(argv[i])-1] == ',') argv[i][strlen(argv[i])-1] = '\0'; // strip trailing comma from entries 

    if ((('0' <= argv[i][0]) && (argv[i][0] <= '9')) || (strlen(argv[i]) == 1)) {
      if (argv[i][0] == '0' && (argv[i][1] == 'x' || argv[i][1] == 'X')) { // can give numeric codes in range 0x00:0xff
        int hex=0, rc;
        rc = sscanf(argv[i], "%x", &hex);
        code = hex & 255;
      } else if ('1' <= (argv[i][0]) && (argv[i][0]) <= '9') { // can give numeric codes in range 0:255
        code = atoi(argv[i]) & 255;
      } else if (strlen(argv[i]) == 1) {
        code = argv[i][0];
      }
      for (idx = 0; idx < sizeof(phone)/sizeof(phone[0]); idx++) {
        if (phone[idx].code == code) break;
      }
      if (idx == sizeof(phone)/sizeof(phone[0])) {
        idx = 0;
        // use code verbatim
        duration = 100; X = 0;
      } else {
        duration = phone[idx].duration;
        code = phone[idx].code;
        X = phone[idx].X & 255;
      }
    } else {
      idx = get_entry(argv[i]);
      duration = phone[idx].duration;
      code = phone[idx].code;
      X = phone[idx].X & 255;
    }
    
    
    if (strchr(phone[idx].mnem, '=')) {
      fprintf(stderr, "%s%0d ", phone[idx].mnem, X);
      if (!print) fprintf(stderr, "(%d:%d) ", code, duration);
      if (!print) send(serial_port, code);
      if (!print) msleep(5);
      if (!print) send(serial_port, X);
    } else {
      fprintf(stderr, "%s ", phone[idx].mnem);
      if (!print) fprintf(stderr, "(%d:%d) ", code, duration);
      if (!print) send(serial_port, code);
    }
    if (!print) msleep(duration);
  }
  // some examples send an 'end' after every word.  Not sure it's needed.
  // send(serial_port, 255);
  
  if (!print) fprintf(stderr, "\n%0d bytes sent to AtariVox+\n", argc); else fprintf(stderr, "\n");

  if (!print) close(serial_port);
  exit(0);
  return 1;
}
