aboutsummaryrefslogtreecommitdiffstats
path: root/spo256
diff options
context:
space:
mode:
Diffstat (limited to 'spo256')
-rw-r--r--spo256/Makefile.in34
-rw-r--r--spo256/README24
-rw-r--r--spo256/chars.c339
-rw-r--r--spo256/client.c40
-rw-r--r--spo256/currency.c43
-rw-r--r--spo256/getidx.c98
-rw-r--r--spo256/getspo.c68
-rw-r--r--spo256/number.c132
-rw-r--r--spo256/speak.146
-rw-r--r--spo256/speak.839
-rw-r--r--spo256/speak.c86
-rw-r--r--spo256/speak.conf.772
-rw-r--r--spo256/speak.h48
-rw-r--r--spo256/speak.init54
-rw-r--r--spo256/spo.c161
-rw-r--r--spo256/spo256.898
-rw-r--r--spo256/word.c38
-rw-r--r--spo256/words.c34
18 files changed, 1454 insertions, 0 deletions
diff --git a/spo256/Makefile.in b/spo256/Makefile.in
new file mode 100644
index 0000000..6e21ea4
--- /dev/null
+++ b/spo256/Makefile.in
@@ -0,0 +1,34 @@
+#
+# Template to build spo256-AL2 text-to-speech server.
+# $Id: Makefile.in 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+# Copyright (c) 1997 by Tycho Softworks.
+#
+
+OBJS = speak.o spo.o getspo.o words.o chars.o getidx.o client.o \
+ number.o currency.o
+PROGS = spo256
+START = 10
+RUNLVLS = 3
+
+all: $(PROGS)
+
+install: $(PROGS)
+ install -s -g bin $(PROGS) $(SBINDIR)
+ ln -sf $(SBINDIR)/spo256 $(BINDIR)/speak
+ install -g bin speak.conf $(SYSCONFDIR)
+ ../sdk/bin/instinit speak $(START) $(RUNLVLS)
+ ../sdk/bin/instsvc services speak 800/tcp \# SPO256-AL2 Text-to-Speech service
+ ../sdk/bin/instman $(MANDIR) 1 speak
+ ../sdk/bin/instman $(MANDIR) 7 speak.conf
+ ../sdk/bin/instman $(MANDIR) 8 spo256
+
+clean:
+ -rm $(PROGS)
+ -rm $(OBJS)
+
+.c.o:
+ $(CC) $(CFLAGS) $(OPTIMIZE) -I../sdk -o $@ -c $<
+
+spo256: $(OBJS)
+ $(CC) $(CFLAGS) -L../sdk/lib $(LIBS) -o $@ $(OBJS) $(LIBS)
+
diff --git a/spo256/README b/spo256/README
new file mode 100644
index 0000000..29c2fae
--- /dev/null
+++ b/spo256/README
@@ -0,0 +1,24 @@
+This particular SPO256 server is able to translate common usages for numbers
+and perform word substitution. For example, the number 123.45 is pronounced
+as "one hundred twenty three point four five". Internet names and addresses
+are also pronounced correctly to a large extent. The support of different
+usages and idioms in this server is not yet as complete as the WorldVU SPO
+server, but this is being worked on.
+
+The SPO server was rewritten in release 0.3 with a character state parser
+to allow better parsing for and pacing of spoken text. The 0.3 state
+parser now recognizes embedded server command sequences as well as text
+that is to be spoken. Embedded server commands are proceeded by the
+<ESC> character, and appear in <>'s, using a format much like mark-up tags.
+For example, word "spelling" can be turned on or off with <ESC><spell> and
+<ESC></spell>. The current tags supported by the SPO256 server are found
+in the spo256 man page. More tags will be added as they appear feasable to
+create.
+
+This SPO server differs from the original WorldVU server in that it can
+also be used in a client role to pipe standard output to the server. One
+can enter "cat filename | spo256 localhost" (or "cat filename | speak
+localhost") to redirect output to the spo server running on the local
+machine, for example.
+
+
diff --git a/spo256/chars.c b/spo256/chars.c
new file mode 100644
index 0000000..e4b2a2a
--- /dev/null
+++ b/spo256/chars.c
@@ -0,0 +1,339 @@
+/*
+ * Character state parser for text-to-speech server.
+ * $Id: chars.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ */
+
+#include <other/string.h>
+#include <other/config.h>
+#include <std/files.h>
+#include <std/process.h>
+#include "speak.h"
+
+enum
+{
+ CH_IGNORE,
+ CH_PERIOD,
+ CH_QUOTE,
+ CH_ESCAPE,
+ CH_SPACE,
+ CH_ALPHA,
+ CH_NUMERIC,
+ CH_PUNCT,
+};
+
+static uchar map[256];
+static uchar word[256];
+static int cpos = 0;
+static bool init = FALSE;
+static void (*state)(uchar ch);
+static bool quote;
+
+static void s_hex(uchar ch);
+static void s_num(uchar ch);
+static void s_cmd(uchar ch);
+static void s_leadcmd(uchar ch);
+static void s_abbrev(uchar ch);
+static void s_point(uchar ch);
+static void s_word(uchar ch);
+static void s_idle(uchar ch);
+
+static void s_hex(uchar ch)
+{
+ switch(ch)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a':
+ case 'A':
+ case 'b':
+ case 'B':
+ case 'c':
+ case 'C':
+ case 'd':
+ case 'D':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ word[cpos++] = ch;
+ break;
+ default:
+ word[cpos] = 0;
+ number(word);
+ state = s_idle;
+ s_idle(ch);
+ }
+}
+
+static void s_num(uchar ch)
+{
+ switch(map[ch])
+ {
+ case CH_NUMERIC:
+ word[cpos++] = ch;
+ break;
+ default:
+ switch(ch)
+ {
+ case 'e':
+ case '^':
+ word[cpos++] = ch;
+ number(word);
+ cpos = 0;
+ spo_word("raised");
+ spo_word("to");
+ spo_word("the");
+ spo_word("power");
+ break;
+ case ':':
+ case '/':
+ case '.':
+ word[cpos++] = ch;
+ break;
+ case '%':
+ word[cpos] = 0;
+ number(word);
+ state = s_idle;
+ spo_word("percent");
+ break;
+ case 'x':
+ case 'X':
+ if(cpos == 1)
+ {
+ cpos = 0;
+ state = s_hex;
+ break;
+ }
+ default:
+ word[cpos] = 0;
+ number(word);
+ state = s_idle;
+ s_idle(ch);
+ }
+ }
+}
+
+static void s_cmd(uchar ch)
+{
+ if(ch == '>')
+ {
+ word[cpos] = 0;
+ spo_cmd((char *)word);
+ state = s_idle;
+ }
+ else
+ word[cpos++] = ch;
+}
+
+static void s_leadcmd(uchar ch)
+{
+ if(ch == '<')
+ state = s_cmd;
+ else
+ {
+ state = s_idle;
+ s_idle(ch);
+ }
+}
+
+static void s_abbrev(uchar ch)
+{
+ switch(map[ch])
+ {
+ case CH_SPACE:
+ abbrev((char *)word);
+ state = s_idle;
+ break;
+ default:
+ spo_word((char *)word);
+ cpos = 0;
+ spo_word("dot");
+ state = s_word;
+ s_word(ch);
+ }
+}
+
+static void s_point(uchar ch)
+{
+ switch(map[ch])
+ {
+ case CH_ALPHA:
+ state = s_word;
+ case CH_PERIOD:
+ spo_word("dot");
+ break;
+ case CH_NUMERIC:
+ spo_word("point");
+ state = s_num;
+ break;
+ default:
+ state = s_idle;
+ break;
+ }
+}
+
+static void s_word(uchar ch)
+{
+ switch(map[ch])
+ {
+ case CH_PERIOD:
+ if(!cpos)
+ state = s_point;
+ else
+ state = s_abbrev;
+ word[cpos++] = '.';
+ word[cpos] = 0;
+ break;
+ case CH_PUNCT:
+ case CH_ALPHA:
+ case CH_NUMERIC:
+ if(cpos < 255)
+ word[cpos++] = ch;
+ break;
+ case CH_SPACE:
+ word[cpos] = 0;
+ spo_word((char *)word);
+ state = s_idle;
+ break;
+ default:
+ switch(ch)
+ {
+ case '\'':
+ word[cpos++] = ch;
+ break;
+ default:
+ word[cpos] = 0;
+ spo_word((char *)word);
+ spo_pause(P_WORD);
+ state = s_idle;
+ s_idle(ch);
+ }
+ }
+}
+
+static void s_idle(uchar ch)
+{
+ cpos = 0;
+ switch(map[ch])
+ {
+ case CH_NUMERIC:
+ state = s_num;
+ s_num(ch);
+ break;
+ case CH_ALPHA:
+ state = s_word;
+ s_word(ch);
+ break;
+ case CH_ESCAPE:
+ state = s_leadcmd;
+ break;
+ case CH_SPACE:
+ spo_pause(P_WORD);
+ break;
+ case CH_QUOTE:
+ if(quote)
+ {
+ spo_word("end");
+ quote = FALSE;
+ }
+ else
+ quote = TRUE;
+ spo_word("quote");
+ break;
+ default:
+ switch(ch)
+ {
+ case '$':
+ word[0] = '$';
+ cpos = 1;
+ state = s_num;
+ break;
+ case '*':
+ spo_word("times");
+ break;
+ case '/':
+ spo_word("divided");
+ spo_word("by");
+ break;
+ case '+':
+ spo_word("plus");
+ break;
+ case '-':
+ spo_word("minus");
+ break;
+ case '&':
+ spo_word("and");
+ break;
+ case '|':
+ spo_word("or");
+ break;
+ case '~':
+ spo_word("not");
+ break;
+ case ')':
+ spo_word("end");
+ case '(':
+ spo_word("parenthesis");
+ break;
+ case '=':
+ spo_word("equals");
+ break;
+ case '<':
+ spo_word("less");
+ spo_word("than");
+ break;
+ case '>':
+ spo_word("greater");
+ spo_word("than");
+ break;
+ default:
+ spo_pause(P_WORD);
+ }
+ }
+}
+
+void chars(FILE *fp)
+{
+ int i, ch;
+
+ quote = FALSE;
+ state = s_idle;
+
+ if(!init)
+ {
+ for(i = 0; i < 256; ++i)
+ map[i] = CH_IGNORE;
+
+ map[27] = CH_ESCAPE;
+
+ for(i = 33; i < 127; ++i)
+ map[i] = CH_PUNCT;
+
+ map[9] = map[13] = map[10] = map[32] = CH_SPACE;
+ for(i = 48; i < 58; ++i)
+ map[i] = CH_NUMERIC;
+
+ for(i = 0; i < 26; ++i)
+ {
+ map[i + 65] = CH_ALPHA;
+ map[i + 97] = CH_ALPHA;
+ }
+ map['.'] = CH_PERIOD;
+ map['\"'] = CH_QUOTE;
+ }
+
+ while(EOF != (ch = fgetc(fp)))
+ (*state)(ch);
+
+ (*state)(0);
+}
diff --git a/spo256/client.c b/spo256/client.c
new file mode 100644
index 0000000..b43696f
--- /dev/null
+++ b/spo256/client.c
@@ -0,0 +1,40 @@
+/*
+ * Client operating mode.
+ * $Id: client.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ *
+ * Abstract:
+ * The client mode of operation will connect to SPO256 server and
+ * redirect standard output to the server. This allows the speak
+ * (spo256) image to be used to terminate pipes.
+ */
+
+#include <std/process.h>
+#include <other/string.h>
+#include <std/files.h>
+#include <other/config.h>
+#include <net/socket.h>
+#include <net/stream.h>
+#include "speak.h"
+
+void client(char *hostname, ushort port)
+{
+ STREAM fp = opentcp(hostname, port);
+ char buf[1024];
+
+ if(!fp)
+ fatal(EX_UNAVAILABLE, "spo256: %s: unkown host\n", hostname);
+
+ gettcp(buf, sizeof(buf) - 1, fp); /* get banner line */
+ while(!feof(stdin))
+ {
+ fgets(buf, sizeof(buf) - 1, stdin);
+ if(feof(stdin))
+ break;
+
+ puttcp(buf, fp);
+ }
+ closetcp(fp);
+ exit(0);
+}
diff --git a/spo256/currency.c b/spo256/currency.c
new file mode 100644
index 0000000..b79c036
--- /dev/null
+++ b/spo256/currency.c
@@ -0,0 +1,43 @@
+/*
+ * Currency rules, for USD.
+ * $Id: currency.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ */
+
+#include <other/string.h>
+#include <other/config.h>
+#include "speak.h"
+
+void currency(char *str)
+{
+ char *cents;
+ ++str;
+
+ cents = strchr(str, '.');
+ if(cents)
+ *(cents++) = 0;
+
+ if(atol(str) != 0)
+ {
+ number(str);
+ if(atol(str) > 1)
+ spo_word("dollars");
+ else
+ spo_word("dollar");
+ }
+
+ if(cents)
+ {
+ if(atol(str) > 0)
+ spo_word("and");
+
+ number(cents);
+ if(atoi(cents) > 1)
+ spo_word("cent");
+ else
+ spo_word("cents");
+ }
+}
+
+
diff --git a/spo256/getidx.c b/spo256/getidx.c
new file mode 100644
index 0000000..70a730c
--- /dev/null
+++ b/spo256/getidx.c
@@ -0,0 +1,98 @@
+/*
+ * Build word and abbreviation index in memory from .conf.
+ * $Id: getidx.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ */
+
+#include <other/string.h>
+#include <other/config.h>
+#include <std/files.h>
+#include <std/process.h>
+#include <other/memory.h>
+#include "speak.h"
+
+MEMPOOL *mem;
+IDX **widx, **aidx;
+
+static int key(uchar *str)
+{
+ unsigned int k = 0;
+ int len = strlen((char *)str);
+
+ while(*str)
+ {
+ k = (k * 7) | (*str & 0x1f);
+ ++str;
+ }
+
+ k |= len;
+
+ return (k % KEYSIZE);
+}
+
+void getidx(CONFIG *cfg)
+{
+ int i;
+ char *p, *q;
+ IDX *new;
+
+ mem = mempool(16384, 16);
+
+ widx = (IDX **)memreq(mem, sizeof(IDX *) * KEYSIZE);
+ aidx = (IDX **)memreq(mem, sizeof(IDX *) * KEYSIZE);
+
+
+ for(i = 0; i < KEYSIZE; ++i)
+ widx[i] = aidx[i] = NULL;
+
+ seek_config(cfg, "words");
+ while(NULL != (p = read_config(cfg)))
+ {
+ q = strchr(p, '=');
+ if(!q)
+ continue;
+
+ *(q++) = 0;
+ p = strtrim(p, __SPACES);
+ q = strtrim(q, __SPACES);
+ i = key((uchar *)p);
+ new = memlreq(mem, sizeof(IDX));
+ new->word_link = widx[i];
+ widx[i] = new;
+ new->word_key = strreq(mem, p);
+ new->word_spo = strreq(mem, q);
+ }
+ seek_config(cfg, "abbrev");
+ while(NULL != (p = read_config(cfg)))
+ {
+ q = strchr(p, '=');
+ if(!q)
+ continue;
+
+ *(q++) = 0;
+ p = strtrim(p, __SPACES);
+ q = strtrim(q, __SPACES);
+ i = key((uchar *)p);
+ new = memlreq(mem, sizeof(IDX));
+ new->word_link = aidx[i];
+ aidx[i] = new;
+ new->word_key = strreq(mem, p);
+ new->word_spo = strreq(mem, q);
+ }
+}
+
+char *find(IDX **table, char *str)
+{
+ IDX *next = table[key((uchar *)str)];
+
+
+ while(next)
+ {
+ if(!stricmp(str, next->word_key))
+ return next->word_spo;
+
+ next = next->word_link;
+ }
+ return NULL;
+}
diff --git a/spo256/getspo.c b/spo256/getspo.c
new file mode 100644
index 0000000..5ff64d7
--- /dev/null
+++ b/spo256/getspo.c
@@ -0,0 +1,68 @@
+/*
+ * Parse .conf file interface spec for spo communications.
+ * $Id: getspo.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ */
+
+#include <other/string.h>
+#include <other/config.h>
+#include <std/files.h>
+#include <proc/process.h>
+#include <dev/tty.h>
+
+char *mask = NULL;
+
+int getspo(CONFIG *cfg)
+{
+ int spo = -1;
+ char *p;
+ stty_t stty;
+
+ if(!seek_config(cfg, "interface"))
+ fatal(EX_CONFIG, "spo256: [interface]: missing from speak.conf\n");
+
+ while(NULL != read_config(cfg))
+ {
+ if(NULL != (p = get_config(cfg, "bind")))
+ {
+ mask = strdup(p);
+ continue;
+ }
+
+ if(NULL != (p = get_config(cfg, "device")))
+ {
+ spo = open(p, O_RDWR);
+ if(spo < 0)
+ fatal(EX_UNAVAILABLE, "spo256: %s: cannot access\n", p);
+ stty = getstty(spo);
+ interactive(stty, FALSE);
+ setflowctrl(stty, FC_HARD);
+ continue;
+ }
+
+ if(NULL != (p = get_config(cfg, "speed")))
+ {
+ if(spo < 0)
+ fatal(EX_CONFIG, "spo256: set speed for unspecified device\n");
+ setspeed(stty, atol(p));
+ continue;
+ }
+
+ if(NULL != (p = get_config(cfg, "parity")))
+ {
+ if(spo < 0)
+ fatal(EX_CONFIG, "spo256: set format for unspecified device\n");
+ if(!stricmp(p, "even"))
+ setformat(stty, "7e1");
+ if(!stricmp(p, "odd"))
+ setformat(stty, "7o1");
+ if(!stricmp(p, "none"))
+ setformat(stty, "8n1");
+ continue;
+ }
+ }
+ putstty(spo, stty, TRUE);
+ return spo;
+}
+
diff --git a/spo256/number.c b/spo256/number.c
new file mode 100644
index 0000000..16c662a
--- /dev/null
+++ b/spo256/number.c
@@ -0,0 +1,132 @@
+/*
+ * Number base pronounciation rules and special cases.
+ * $Id: number.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ */
+
+#include <other/string.h>
+#include <other/config.h>
+#include "speak.h"
+
+static char *nteen[] = {
+ "ten", "eleven", "twelve", "thirteen", "fourteen",
+ "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};
+
+static char *n10[] = {
+ "zero", "ten", "twenty", "thirty", "fourty",
+ "fifty", "sixty", "seventy", "eighty", "ninety"};
+
+
+static void digits(long value)
+{
+ bool hundred = FALSE;
+ char buf[2];
+
+ if(value >= 100)
+ {
+ digits(value / 100);
+ spo_word("hundred");
+ value %= 100;
+ hundred = TRUE;
+ }
+
+ if(!value && hundred)
+ return;
+
+ if(value >= 20)
+ {
+ spo_word(n10[value / 10]);
+ value %= 10;
+ if(!value)
+ return;
+ }
+
+ if(value < 10)
+ {
+ buf[0] = value + '0';
+ buf[1] = 0;
+ spo_word(buf);
+ return;
+ }
+ spo_word(nteen[value - 10]);
+}
+
+void number(char *str)
+{
+ char *p;
+ long value = atol(str);
+
+ if(lit || spell)
+ {
+ spo_word(str);
+ return;
+ }
+
+ /* Check for w.x.y.z as "w dot x dot y dot z" */
+
+ if(ccount(str, ".") > 1)
+ {
+ while(NULL != (p = strchr(str, '.')))
+ {
+ *(p++) = 0;
+ number(str);
+ str = p;
+ }
+ number(str);
+ return;
+ }
+
+ if(NULL != (p = strchr(str, '.')))
+ {
+ *(p++) = 0;
+ number(str);
+ spo_word("point");
+ spo_word(p);
+ return;
+ }
+
+ if(NULL != (p = strchr(str, ':')))
+ {
+ digits(atol(str));
+ ++p;
+ if(atol(p) < 10)
+ if(atol(p))
+ write(spo, " O ", 2);
+
+ if(atol(p))
+ digits(atol(p));
+
+ if(NULL != (p = strchr(p, ':')))
+ {
+ spo_word("and");
+ digits(atol(++p));
+ spo_word("seconds");
+ }
+ else
+ {
+ p = str;
+ if(strchr(p, 'a') || strchr(p, 'A'))
+ write(spo, "A M ", 5);
+ if(strchr(p, 'p') || strchr(p, 'P'))
+ write(spo, "P M ", 5);
+ }
+ return;
+ }
+
+ if(value >= 1000000l)
+ {
+ digits(value / 1000000l);
+ spo_word("million");
+ value %= 1000000l;
+ }
+
+ if(value >= 1000)
+ {
+ digits(value / 1000);
+ spo_word("thousand");
+ value %= 1000;
+ }
+
+ digits(value);
+}
diff --git a/spo256/speak.1 b/spo256/speak.1
new file mode 100644
index 0000000..8ceac59
--- /dev/null
+++ b/spo256/speak.1
@@ -0,0 +1,46 @@
+.TH "speak" "1" "January 1997" "Speak 0.2" "Text-to-Speech"
+.PP
+.SH NAME
+.PP
+\fBspeak\fP - send output to text-to-speech service.
+.PP
+.SH SYNOPSIS
+.PP
+\fBspeak\fP [\fIhostname\fP]
+.PP
+.SH DESCRIPTION
+.PP
+The Text-to-speech package introduces a standard TCP/IP port service for
+attaching text-to-speech resources to your network. These resources can
+be used to monitor system status, provide verbal alarms, etc. This network
+service may be accessed by telnetting to a machine running a "speak" server,
+or by using the various application utilities, such as \fBspeak\fP.
+.PP
+
+The \fBspeak\fP program will capture and redirect all standard input to the
+network speech server, such as \fBspo256\fP, running either on the
+local machine ("\fIlocalhost\fP"), or to the server running on the host
+specified as a command line argument. \fBspeak\fP is normally used to
+terminate a pipe, such as in "\fIcat filename | speak\fP". The TCP service
+port used for the connection is specified in \fI/etc/services\fP.
+
+.PP
+While most of the text-to-speech programs are separately written, the
+\fBspeak\fP program is normally an alias for the actual server itself,
+such as the \fBspo256\fP program. This was intentional, as future
+versions of speak may drive hardware directly in an alternate usage
+(such as "\fIspeak /dev/ttyxx\fP"), as well as redirecting output to a named
+host.
+
+.PP
+.SH SEE ALSO
+.PP
+.BR say (1),
+.BR down (8),
+.BR spo256 (8),
+.BR services (7)
+.PP
+.SH AUTHOR
+.PP
+David Sugar (dyfet@tycho.com)
+.PP
diff --git a/spo256/speak.8 b/spo256/speak.8
new file mode 100644
index 0000000..aaeceef
--- /dev/null
+++ b/spo256/speak.8
@@ -0,0 +1,39 @@
+.TH "spo256" "8" "1997" "Text-to-Speech services" "Tycho Softworks"
+.PP
+.SH "NAME spo256 \- SPO256-AL2 Text-to-Speech Server"
+.PP
+.SH "SYNOPSIS"
+.PP
+\fBspo256\fP
+.br
+\fBspeak\fP [hostname]
+.PP
+.SH "DESCRIPTION"
+.PP
+.SH "FILES"
+.PP
+The spo256 server configuration may be found in \fB/etc/speak.conf\fP.
+.PP
+.SH "SEE ALSO"
+.PP
+.IP
+.IP o
+\fBdown(8)\fP: Shuts down the system.
+.IP
+.IP o
+\fBsay(1)\fP: Sends output to a "speak" server.
+.IP
+.IP o
+\fBservices(5)\fP: Installed TCP/IP network services.
+.IP
+.PP
+.SH "DIAGNOSTICS"
+.PP
+\fBspo256\fP will warn and fail to load if the "speak" services entry
+is missing, if the device cannot be opened, or if the speak.conf file
+is missing.
+.PP
+.SH "AUTHOR"
+.PP
+David Sugar (dyfet@tycho.com)
+.PP
diff --git a/spo256/speak.c b/spo256/speak.c
new file mode 100644
index 0000000..7de099f
--- /dev/null
+++ b/spo256/speak.c
@@ -0,0 +1,86 @@
+/*
+ * Base (main) for spo server.
+ * $Id: speak.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and reuse see product license.
+ *
+ * Abstract:
+ * Establish basic operating environment for SPO256-AL2 text-to-speech
+ * server. This module verifies system configuration, parses the
+ * config files for word lists that are stored in the quick-lookup
+ * internal hash word subsitution database, binds the server to a tcp
+ * network port, and accepts client requests.
+ */
+
+#include <proc/process.h>
+#include <std/string.h>
+#include <std/files.h>
+#include <other/config.h>
+#include <net/socket.h>
+#include <net/stream.h>
+#include "speak.h"
+
+int spo; /* device id of spo */
+FILE *io;
+
+void main(int argc, char **argv)
+{
+ int port = getservice("speak");
+ CONFIG *cfg;
+ SOCKET so;
+ char buf[1024];
+
+ if(!port)
+ fatal(EX_UNAVAILABLE, "spo256: speak: service not defined in /etc/services\n");
+
+ if(argc < 1 || argc > 2)
+ fatal(EX_USAGE, "use: spo256 [hostname]\n");
+
+ if(argc > 1)
+ client(argv[1], port);
+
+ if(!stricmp(pathfname(argv[0]), "speak"))
+ client("localhost", port);
+
+ if(NULL == (cfg = sys_config("speak")))
+ fatal(EX_CONFIG, "spo256: speak.conf: config file missing\n");
+
+ /* get device configuration */
+
+ spo = getspo(cfg);
+
+ getidx(cfg);
+ close_config(cfg);
+
+ /* bind server to socket */
+
+ so = tcpsocket(mask, port, 5);
+ if(so == INVALID_SOCKET)
+ fatal(EX_UNAVAILABLE, "spo256: %d: failed to bind port\n", port);
+
+ /* now, make ourselves a daemon */
+
+ pdetach(D_KEEPNONIO);
+
+ /* Main loop of server. The server accepts one connection in
+ turn from each requesting user application. The remaining
+ application requests are kept in backlog while the current
+ application is served. This assures non-overlapping speech.
+ */
+
+ for(;;)
+ {
+ io = accepttcp(so);
+ if(!io)
+ continue;
+
+ spo_init();
+ puttcp("SPO256-AL2 Text-to-Speech Server 1.0\r\n", io);
+
+ chars(io);
+
+ spo_pause(P_END);
+ closetcp(io);
+ }
+}
+
diff --git a/spo256/speak.conf.7 b/spo256/speak.conf.7
new file mode 100644
index 0000000..ccc3e57
--- /dev/null
+++ b/spo256/speak.conf.7
@@ -0,0 +1,72 @@
+.TH "speak.conf" "7" "January 1997" "Speak 0.2" "Text-to-Speech"
+.PP
+.SH NAME
+.PP
+\fBspeak.conf\fP - configuration file for text-to-speech services.
+.PP
+.SH SYNOPSIS
+.PP
+\fBspeak.conf\fP
+.PP
+.SH DESCRIPTION
+.PP
+The \fBspeak.conf\fP file is a text edit-able configuration file used by the
+text-to-speech server and various text-to-speech utilities.
+.PP
+\fBspeak.conf\fP is divided into sections, donated with "[]"'s. These
+sections include hardware parameters for the text-to-speech server used on the
+local machine, translation tables used for phonetic dictionaries by text-to-
+speech servers, and runtime default information for specific programs. On
+machines that do not run their own text-to-speech server, \fBspeak.conf\fP
+will still be used to specify runtime defaults for utility programs that are
+present.
+.PP
+.SH FILE FORMAT
+.PP
+The file consists of sections and parameters. A section begins with the
+name of the section in square brackets and continues until the next section
+begins. Sections contain parameters of the form \'name = value`'.
+.PP
+The file is line-based text - that is, each newline-terminated line
+represents either a comment, a section name, or a parameter. Parameters
+in the .conf file normally do not appear on multiple lines, except for
+several specific exceptions (lists and tables) that are not used in this
+package.
+.PP
+Section and parameter names are not case sensitive.
+.PP
+Only the first equal sign in a parameter is significant. Whitepsaces before
+or after the = are ignored. Leading and trailing whitespaces are ignored.
+Whitepaces within a parameter name are also ignored, while whitespaces
+within a parameter value are preserved.
+.PP
+Any line beginning with a semicolon or a pound sign is treated as a comment
+and is ignored.
+.PP
+Any line containing a list, in the form 'name = { v1 v2 v3 }' may be
+continued over multiple lines if the terminating '}' is not found in the
+current line.
+.PP
+Any line beginning with a plus is considered a repeat of the "name =" portion
+of the last parameter line. This is often used to build up hostname lists
+when multiple hostnames may be specified.
+.PP
+The parameter value may either be quoted or unquoted. Quoted parameters
+values may be used to specify lead or trailing spaces which would otherwise
+be ignored. Either matching single or double quotes may be used, and the
+quotes are not kept as part of the value returned to the application.
+.PP
+Parameter values may either be strings, numeric values, or boolean flags.
+When a boolean parameter is used, the characters "T" for 'true' and "F" for
+'false' may be used. The full word, "true" and "false", may also be
+spelled out. "Y" or "Yes" and "N" or "No" may also be used for "true" or
+"false".
+.PP
+.SH SEE ALSO
+.PP
+.BR speak (1)
+.PP
+.SH AUTHOR
+.PP
+David Sugar (dyfet@tycho.com)
+.PP
diff --git a/spo256/speak.h b/spo256/speak.h
new file mode 100644
index 0000000..07b9875
--- /dev/null
+++ b/spo256/speak.h
@@ -0,0 +1,48 @@
+/*
+ * Master header for spo server.
+ * $Id$
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions of distribution and use see license.
+ *
+ * Abstract:
+ * Define internal hash index system to quickly find text for
+ * substitutions. The spo-256 is driven by substitution tables
+ * which provide alternate spellings to adjust mis-pronounced
+ * speech.
+ */
+
+typedef enum
+{
+ P_END, /* Finish line off, send SPO-end mark */
+ P_LINE, /* Send nl because we have a empty line */
+ P_SENTANCE, /* Pause at end of sentance (nl) */
+ P_WORD /* Inter-word delay (spaces) */
+} PAUSE;
+
+#define KEYSIZE 791 /* Size of internal hash database keyrange */
+
+typedef struct _idx
+{
+ struct _idx *word_link; /* hash link chain */
+ char *word_key; /* initial word & hash key */
+ char *word_spo; /* substitute spelling */
+} IDX;
+
+extern int spo;
+extern IDX **widx, **aidx;
+extern char *mask;
+extern FILE *io;
+extern bool echo, spell, lit;
+
+void abbrev(char *str);
+int getspo(CONFIG *cfg);
+void getidx(CONFIG *cfg);
+char *find(IDX **table, char *str);
+void client(char *hostname, ushort port);
+
+void spo_init(void);
+void spo_cmd(char *str);
+void spo_word(char *str);
+void spo_pause(PAUSE);
+void spo_begquote(void);
+void spo_endquote(void);
diff --git a/spo256/speak.init b/spo256/speak.init
new file mode 100644
index 0000000..0c46b8a
--- /dev/null
+++ b/spo256/speak.init
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# modify FILESYSTEMS to activate and use vmon.
+FILESYSTEMS="/"
+
+mode=$1
+
+if [ $# = 0 ] ; then
+ mode='rc'
+fi
+
+if [ ! "$mode" = 'rc' ] ; then
+ # Source function library.
+ . /etc/rc.d/init.d/functions
+
+ # Source networking configuration.
+ . /etc/sysconfig/network
+
+ # Check that networking is up.
+ [ ${NETWORKING} = "no" ] && exit 0
+fi
+
+# See how we were called.
+case "$mode" in
+ rc)
+ echo "Starting SPO256-AL2 server"
+ spo256
+ if [ ! -z "$FILESYSTEMS" ] ; then
+ vmon $FILESYSTEMS
+ fi
+ ;;
+ start)
+ echo -n "Starting SPO256-AL2 speech services: "
+ daemon spo256
+ if [ ! -z "$FILESYSTEMS" ] ; then
+ daemon vmon $FILESYSTEMS
+ fi
+ echo
+ touch /var/lock/subsys/speak
+ ;;
+ stop)
+ echo -n "Shutting down SPO256-AL2 speech services: "
+ killproc spo256
+ if [ ! -z "$FILESYSTEMS" ] ; then
+ killproc vmon
+ fi
+ rm -f /var/lock/subsys/speak
+ echo ""
+ ;;
+ *)
+ echo "Usage: speak.init {start|stop}"
+ exit 1
+esac
+
diff --git a/spo256/spo.c b/spo256/spo.c
new file mode 100644
index 0000000..5c4f7ec
--- /dev/null
+++ b/spo256/spo.c
@@ -0,0 +1,161 @@
+/*
+ * SPO256 low level operations.
+ * $Id: spo.c 1.2 Mon, 24 Mar 1997 12:25:37 -0500 dyfet $
+ * Copyright (c) 1997 by Tycho Softworks.
+ * For conditions on distribution and reuse see product license.
+ *
+ * Abstract:
+ * Key SPO translation modules. This includes the word substution
+ * module, the numeric pronounciation system, pause control, and
+ * other low level spo services.
+ */
+
+#include <other/config.h>
+#include <other/string.h>
+#include "speak.h"
+
+/*
+ * Flags and operating mode properties that may now be effected by
+ * server commands.
+ */
+
+bool echo = FALSE; /* echo SPO output to user? */
+bool spell = FALSE; /* spell all words? */
+bool lit = FALSE; /* literally pronounce as is? */
+
+/*
+ * A new 'init' function is added to initialize operating state for
+ * each new connection. This assures that spo command sequences
+ * issued from a previous session are not left in effect.
+ */
+
+void spo_init(void)
+{
+ echo = FALSE; /* echo off by default */
+ spell = FALSE;
+ lit = FALSE;
+};
+
+/*
+ * Interpret server specific commands embedded in <ESC><cmd>
+ * sequences. The SPO has limited options to effect in this manner.
+ */
+
+void spo_cmd(char *str)
+{
+ if(!stricmp(str, "echo"))
+ {
+ echo = TRUE;
+ return;
+ }
+
+ if(!stricmp(str, "/echo"))
+ {
+ echo = FALSE;
+ return;
+ }
+
+ if(!stricmp(str, "spell"))
+ {
+ spell = TRUE;
+ return;
+ }
+
+ if(!stricmp(str, "/spell"))
+ {
+ spell = FALSE;
+ return;
+ }
+
+ if(!stricmp(str, "lit"))
+ {
+ lit = TRUE;
+ return;
+ }
+
+ if(!stricmp(str, "/lit"))
+ {
+ lit = FALSE;
+ return;
+ }
+};
+
+/*
+ * Pause spo output between words, numbers, and empty lines, so that
+ * words are properly announciated. This is typically accomplished with
+ * spaces and newline control.
+ */
+
+void spo_pause(PAUSE p)
+{
+ switch(p)
+ {
+ case P_WORD:
+ write(spo, " ", 1);
+ if(echo)
+ puttcp(" ", io);
+ break;
+ case P_SENTANCE:
+ write(spo, " \n", 2);
+ if(echo)
+ puttcp(" \n", io);
+ break;
+ case P_LINE:
+ write(spo, " \r\n", 3);
+ if(echo)
+ puttcp(" \r\n", io);
+ break;
+ case P_END:
+ write(spo, "\r\n", 2);
+ }
+}
+
+/* Announce quoted text. */
+
+void spo_begquote(void)
+{
+ spo_word("quote");
+}
+
+void spo_endquote(void)
+{
+ spo_word("end");
+ spo_begquote();
+}
+
+/*
+ * This performs low level lookup and pronounciation of words, either
+ * in the originally submitted spelling, or in the spo dictionary
+ * spelling.
+ */
+
+void spo_word(char *word)
+{
+ char *p = find(widx, word);
+ if(!p)
+ p = word;
+
+ if(lit)
+ p = word;
+
+ if(spell)
+ {
+ p = word;
+ while(*p)
+ {
+ write(spo, p, 1);
+ if(echo)
+ fputc(*p, io);
+ spo_pause(P_WORD);
+ ++p;
+ }
+ }
+ else
+ {
+ write(spo, p, strlen(p));
+ if(echo)
+ puttcp(p, io);
+ }
+ spo_pause(P_WORD);
+}
+
diff --git a/spo256/spo256.8 b/spo256/spo256.8
new file mode 100644
index 0000000..a0826ad
--- /dev/null
+++ b/spo256/spo256.8
@@ -0,0 +1,98 @@
+.TH "spo256" "8" "January 1997" "Speak 0.2" "Text-to-Speech"
+.PP
+.SH NAME
+.PP
+\fBspo256\fP - text-to-speech server for SPO256-AL2 chipset.
+.PP
+.SH SYNOPSIS
+.PP
+\fBspo256\fP
+.PP
+.SH DESCRIPTION
+.PP
+The \fBspo256\fP server provides a TCP/IP service interface for controlling
+a serial based SPO256-AL2 text-to-speech chip. The server binds to the
+"\fIspeak\fP" port defined in /etc/services and attaches to the serial
+tty device specified in the [\fIinterface\fP] section of \fBspeak.conf\fP.
+The server accepts a single TCP session from a client application at a time,
+streaming that client's text to the SPO device. The server corrects
+pronunciation of words, numbers, and common usages by performing in-stream
+text substitution before sending output to the SPO.
+.PP
+.SH INSTALLATION
+.PP
+The \fBspo256\fP server is meant to be started during system initialization
+(usually for run level 3). A \fBspeak.init\fP script is installed into
+/etc/rc.d, and this script will be linked to a numbered phase in the
+/etc/rc3.d directory. This final linkage is now part of the 'make install'
+supplied and should work for Linux, and perhaps other UNIX systems with
+similar rc.d directory and file layouts. The \fBspo256\fP service can
+normally start immediately after network services (\fIS10network\fP) have ran.
+.PP
+For BSD style systems, the speak.init script is installed as 'rc.speak'. For
+systems that have different naming schemes for the rc.d scripts, some
+additional work may be needed to correctly install speak.init. The
+\fBspo256\fP server may also be ran directly from the command line For
+testing, and the \fBspo256\fP command can simply be added to the
+\fIrc.local\fP file.
+.PP
+The make install will add an entry to \fB/etc/services\fP for the TCP speak
+port. By default, I normally use port 800. Since no effort has been made
+to register text-to-speech as a defined Internet port service, it is quite
+conceivable port 800 may at some point be defined for another service. If
+this happens, simply edit the /etc/services file as needed.
+.PP
+The [\fIinterface\fP] section of \fB/etc/speak.conf\fP will need to be
+edited before you start \fBspo256\fP for the first time. The entry for
+\fIdevice\fP should be given the correct tty serial port where your SPO
+board is attached. The speed I choose to use is 2400bps, which is the
+maximum speed the board is capable of. Valid values for parity include
+"odd", "even", and "none". Also verify board jumpers
+are set for the speed and data format specified in [\fIinterface\fP].
+.PP
+.SH "CORRECTIVE SPEECH"
+.PP
+The [\fIwords\fP] section of \fBspeak.conf\fP holds a list of text
+substitutions. These substitutions correct the inability of the SPO to
+correctly pronounce may words. Since the SPO provides no direct access
+to the low-level phonetic dictionary, these substitute spellings may
+appear odd. Sometimes, words are broken into multiple words with spaces,
+such as "online", which is sent to the SPO as "on lyne". Basically,
+through trial and error, one can build up a substitution table that works
+for the device.
+.PP
+The [\fIabbrev\fP] section simply provides the meaning behind commonly
+used abbreviations, so that they are spoken out correctly. Abbreviations
+are presumed to terminate with a '.', such as "Dr.", "Mr.", and are
+represented in a table separate from the text substitution table.
+.PP
+.SH SERVER COMMANDS
+.PP
+Server commands were introduced with the third release of the SPO256
+server. These commands are formed as "tags" which appear as part of the
+output stream sent to the server. These tags are preceeded by the <ESC>
+character, and are held in <>'s (much like HTML tags). Server commands
+that are supported by different speech servers may very widly to support
+features such as volume level, tone, inflection, etc. The limited set
+of commands supported by the SPO server are as follows:
+.PP
+The <\fIecho\fP> and <\fI/echo\fP> tags may be used to turn echo on and
+off. When echo is enabled, the output that is actually sent to the SPO
+device (including corrected spellings) is also echo'd back to the client
+that is connected to the server.
+.PP
+The <\fIspell\fP> and <\fI/spell\fP> tags may be used to turn spelling of
+words on and off. In spelling mode, each letter of each word is spoken.
+This should not be confused with <\fIlit\fP> and <\fI/lit\fP>, which are
+used to disable the word lookup function so that text as actually sent
+to the server is sent unprocessed to the SPO board.
+.PP
+.SH SEE ALSO
+.PP
+.BR speak.conf (7),
+.BR services (7)
+.PP
+.SH AUTHOR
+.PP
+David Sugar (dyfet@tycho.com)
+.PP
diff --git a/spo256/word.c b/spo256/word.c
new file mode 100644
index 0000000..2150837
--- /dev/null
+++ b/spo256/word.c
@@ -0,0 +1,38 @@
+/*
+
+ Word base pronounciation rules and special cases.
+*/
+
+#include <ctype.h>
+#include <std/types.h>
+#include <std/string.h>
+#include <std/config.h>
+#include "speak.h"
+
+void word(char *str)
+{
+ char *p;
+
+ /* Internet x@y as "x at y" */
+
+ if(NULL != (p = strchr(str, '@')))
+ {
+ *(p++) = 0;
+ word(str);
+ word("at");
+ word(p);
+ return;
+ }
+
+ /* Check for x.y.z as "x dot y dot z" */
+
+ while(NULL != (p = strchr(str, '.')))
+ {
+ *(p++) = 0;
+ spo_word(str);
+ spo_word("dot");
+ str = p;
+ }
+
+ spo_word(str);
+}
diff --git a/spo256/words.c b/spo256/words.c
new file mode 100644
index 0000000..19c8d57
--- /dev/null
+++ b/spo256/words.c
@@ -0,0 +1,34 @@
+/*
+
+ Each line of input from the client application is examined and split
+ into words, numbers, abbreviations, and special catagories for further
+ translation from free-form text to SPO spoken speech. Generally a
+ word is a collection of characters grouped by white-space markers
+ (nl, tab, and space). Special consideration is also given to quoting
+ properties. Additional rules may be applied once words have been
+ catagorized.
+*/
+
+#include <other/string.h>
+#include <other/config.h>
+#include <std/string.h>
+#include "speak.h"
+
+void abbrev(char *str)
+{
+ char *p;
+
+ if(!lit && !spell)
+ p = find(aidx, str);
+
+ if(p)
+ spo_word(p);
+ else
+ {
+ p = tail(str);
+ *(--p) = 0;
+ spo_word(str);
+ spo_pause(P_SENTANCE);
+ }
+}
+