Subject: Re: Votrax SC-01 Datasheet
Date: 1997/08/01
Author: Al Kossow <aek@goonsquad.spies.com.spies.com>
From article <01bc9d89$3bf4a360$0200a8c0@Mike>, by "Mike Coates"
<mike@dissfulfils.co.uk>:
>
Does anybody have a datasheet or other information on this speech synthesizer
chip (as used in Qbert, Gorf etc) or know what games also use one?
>
They were common in the voice output boards in older (S-100) home computers. Try a web search for "SC-01" or "Federal Screw Works" which was the company that made them. Also try over in the CP/M or alt.folklore.computers groups, or comp.speech I’d be interested in a copy too.. There was space for an SC-01 on the Sega speech boards, and I’d like to document that part in my Sega X/Y programming description.
And just so this isn’t completely content free, this was something I turned up for programming the part in C.
Al
------------------------------------------------
The file VOICE.C contains a major subroutine "talk" which enables the user to operate a Votrax SC-01 speech synthesizer chip phonetically. That is, the subroutine takes as right argument a character string made up of the Votrax standard phoneme descriptors. For example the statement talk("T,EH,N,PA1,"); (the trailing , is mandatory) will say the word ten. Note that without the final PA1 the synthesizer will say NNNNNN.... if you use a latched scheme like I do.
Some other subroutines to make life easier are included. They are [1] saynum(num) will pronounce a number (num is unsigned integer) [2] sayday(dayow) will say the day of the week (dayow is char 0-6) [3] saymonth(month) will say the month (month is char 0-11).
No doubt this could be sped up in some way, but it simplifies code. Especially if you want to write an interactive program to check out various phoneme combinations. The routine "pronounce" assumes that you have the chip set up as a LIST device when IOBYTE is set for UC3: . I.E. setting STAT LIST:=UC3: on my micro gets the Votrax ready for LIST output. This routine will most likely need changing on your machine, although this route is convenient for interfacing less tolerant languages as STAT can do all the work.
Easiest method of use in BDS-C is to compile VOICE.C separately and CLINK it to whatever routines you write to use the synthesizer.
/* written by Mark Zachmann
56 Rutgers St. #4
Rochester, N.Y. 14607
*/
/* Note: I'm sure that some of these may be improved
and would appreciate any feedback */
/* A set of routines to allow a user to program in BDS C words and
phrases using a VOTRAX SC-01 chip synthesizer. The main
functions is "talk" which takes a character string argument that looks
like phonemes. The subroutine "pronounce" does the port I/O and will
need to be be individually modified, most likely. Read the comments
above it */
/* written by Mark Zachmann Sept. 5 1981 Version 0.2 */
/* main talking functions... what a slow way to do things */
/* it reads a character string corresponding to the phoneme
descriptions in the VOTRAX phoneme chart (page 3 of SC-01 data
sheet)
and decodes them into numbers (type char, though), then says them.
Note that because it takes a while to talk I say each phoneme
seperately, and thus, while saying one phoneme, the next
can be decoded from the character string input */
talk(line)
char *line;
{ while(0!=*line)
{ pronounce(phoneme(line)); /* transmit to chip */
while(','!=*line++); /* increment counter to start
of next phoneme */
}
}
/* Subroutine to take a character string and return a number (type
char) corresponding to the first phoneme found in the string. Returns
0xFF if no match (although partial matches will have undefined
results, e.g. O5 may be the same as O1) */
phoneme(line)
char *line;
{ switch(*line++) {
case 'A': switch(*line)
{ case '1': return(5); /* A1 */
case '2': return(6); /* A2 */
case 'Y': return(0x21); /* AY */
case 'E': { if (','==*++line) return(0x2e); /* AE */
return(0x2f); /* AE1 */
}
case 'H': { if (','==*++line) return(0x24); /* AH */
if('1'==*line) return(0x15); /* AH1 */
return(8); /* AH2 */
}
case 'W': { if(','==*++line) return(0x3d); /* AW */
if('1'==*line) return(0x13); /* AW1 */
return(0x30); /* AW2 */
}
}
case 'B': return(0xe); /* B */
case 'C': return(0x10); /* CH */
case 'D': { if(','==*line) return(0x1e); /* D */
return(0x4); /* DT */
}
case 'E': switch(*line)
{ case ',': return(0x2c); /* E */
case 'R': return(0x3a); /* ER */
case '1': return(0x3c); /* E1 */
case 'H': switch(*++line) {
case ',': return(0x3b); /* EH */
case '1': return(2); /* EH1 */
case '2': return(1); /* EH2 */
default: return(0); /* EH3 */
}
}
case 'F': return(0x1d); /* F */
case 'G': return(0x1c); /* G */
case 'H': return(0x1b); /* H */
case 'I': switch(*line)
{ case ',': return(0x27); /* I */
case 'U': return(0x36); /* IU */
case '1': return(0x0b); /* I1 */
case '2': return(0x0a); /* I2 */
default: return(9); /* I3 */
}
case 'J': return(0x1a); /* J */
case 'K': return(0x19); /* K */
case 'L': return(0x18); /* L */
case 'M': return(0x0c); /* M */
case 'N': if(','==*line) return(0x0d); /* N */
return(0x14); /* NG */
case 'O': switch(*line)
{ case ',': return(0x26); /* O */
case '1': return(0x35); /* O1 */
case '2': return(0x34); /* O2 */
case 'O': { if(','==*++line) return(0x17); /* OO */
return(0x16); /* OO1 */
}
}
case 'P': { if(','==*line) return(0x25); /* P */
if('0'==*++line) return(3); /* PA0 */
return(0x3e); /* PA1 */
}
case 'R': return(0x2b); /* R */
case 'S': { if(','==*line) return(0x1f); /* S */
if('H'==*line) return(0x11); /* SH */
return(0x3f); /* STOP */
}
case 'T': { if(','==*line) return(0x2a); /* T */
if(','==*++line) return(0x39); /* TH */
return(0x38); /* THV */
}
case 'U': switch(*line)
{ case ',': return(0x28); /* U */
case '1': return(0x37); /* U1 */
default: switch(*++line)
{ case ',': return(0x33); /* UH */
case '1': return(0x32); /* UH1 */
case '2': return(0x31); /* UH2 */
default: return(0x23); /* UH3 */
}
}
case 'V': return(0x0f); /* V */
case 'W': return(0x2d); /* W */
case 'Y': { if(','==*line) return(0x29); /* Y */
return(0x22); /* Y1 */
}
case 'Z': { if(','==*line) return(0x12); /* Z */
return(7); /* ZH */
}
default : return(0xff); /* error !! */
}
}
/* this is the pronunciation subroutine. It sends a single phoneme to
the Votrax synthesis chip. I have implemented mine by way of the
iobyte. When the list device is set to UC3: my BIOS sends
output to the synthesizer. Thus all handshaking is external
to this program. !! You will need to modify either this or your BIOS. I
suggest the BIOS as then BASIC and etc. stupid compilers can easily
interface to the chip */
pronounce(c)
char(c);
{ char iobyt;
iobyt=bdos(7); /* read iobyte */
bdos(8,0xc0); /* set list to UC3: */
bios(5,(c|0x80)); /* send character, I set my pitch to 80h always */
bdos(8,iobyt); /* reset IOBYTE to whatever it was */
}
/* program to say a number argument
assumes unsigned input, and calls itself recusively. Not terribly fast */
saynum(numb)
unsigned numb;
{ char c,d,some;
unsigned j;
some=0; /* nothing said yet */
/* first process the 1000's */
if(0!=(j=numb/1000))
{ saynum(j); /* Recur to do thousands */
talk("TH,AH1,W,Z,I1,N,D,PA1,"); /* thousand */
numb%=1000; /* continue with left over */
some=1 ; /* flag it as having said something */
}
/* now the 100's */
if(0!=(j=numb/100))
{ saynum(j); /* recurring */
talk("H,H,UH,N,D,R,I3,D,PA1,"); /* hundred */
numb%=100; /* continue again */
some=1 ; /* flag it */
}
/* now separate the 10's off (if > 20) */
if((numb>=20)&&0!=(j=numb%10)) /* recur again if not simple */ {
saynum(numb-j); /* first do the tens case */
numb=j; /* now the remainder */
some=1;
}
/* main non-recursive portion */
c=numb; /* turn into a char for speed */
d=(c>=20)? c/10 : c%10 ; /* the kernel is here */
switch(d)
{ case 1 : { if(c==1) talk("W,UH1,I3,N,"); /* 1 */ else
talk("E1,L,EH1,V,I1,N,"); /* 11 */
break;
}
case 2 : { if(c==20) talk("T,PA0,W,EH2,N,"); /* 20 */ else
if(c==12) talk("T,PA0,W,EH2,UH2,L,V,"); /* 12 */ else
talk("T,PA0,U,W,"); /* 2 */
break;
}
case 3 : { if(c==3) talk("TH,R,E1,Y,"); /* 3 */
else talk("TH,UH2,ER,"); /* 13,31 */
break ;
}
case 4 : { if(c==4) talk("F,O,ER,"); /* 4 */
else talk("F,O1,R,"); /* 14,41 */
break;
}
case 5 : { if(c==5) talk("F,AH1,Y1,V,"); /* 5 */
else talk("F,I1,F,"); /* 15,51 */
break;
}
case 6 : talk("S,I,K,S,"); break; /* 6's */
case 7 : talk("S,EH,V,I3,N,"); break; /* 7's */
case 8 : talk("A1,Y,T,"); break ; /* 8's */
case 9 : talk("N,AH2,Y,N,"); break ; /* 9's */
case 0 : { if(c==10) talk("T,EH,N,"); /* 10 */
else if(!some) talk("Z,E1,R,O1,W,"); /* 0 */
break ;
}
}
if(c>=20) talk("STOP,T,E1,PA0,"); /* ty */
else if(c>12) talk("STOP,T,E1,E1,N,PA0,"); /* teen */ else talk("PA1,");
/* done */
}
/* */
/* says the day of the week, 0 is sunday 6 is saturday */
sayday(dayow)
char dayow;
{ switch(dayow) {
case 0: talk("S,UH,N,N,"); break;
case 1: talk("M,UH,N,"); break;
case 2: talk("T,PA0,U,EH3,Z,STOP,"); break;
case 3: talk("W,EH,N,Z,"); break;
case 4: talk("TH,U1,ER,Z,"); break;
case 5: talk("F,R,AH1,Y,"); break;
case 6: talk("S,AE1,EH3,T,ER,"); break;
default: talk("H,W,UH,T,"); break; /* what? */
}
talk("D,A1,AY,Y1,STOP,STOP,"); /* day */
}
/* */
/* says the month desired */
saymonth(month) /* month 0-January...11-December */
char(month);
{ switch(month) {
case 0: talk("D,J,I3,AE1,UH2,N,Y,IU,U1,W,AE1,EH3,R,Y,"); break;
case 1: talk("F,EH1,B,Y,IU,U1,W,AE1,EH3,R,Y,"); break; case 2:
talk("M,AH2,ER,T,CH,"); break;
case 3: talk("A1,AY,P,R,OO1,L,"); break;
case 4: talk("M,A,Y,"); break;
case 5: talk("D,J,U1,U,N,"); break;
case 6: talk("D,J,U1,UH3,L,AR1,E1,"); break;
case 7: talk("AW,G,UH3,S,T,"); break;
case 8: talk("S,EH1,P,STOP,T,EH1,EH2,M,B,R,"); break; case 9:
talk("AH1,K,STOP,T,O,W,B,R,"); break;
case 10: talk("N,O1,W,PA0,V,EH,M,B,R,"); break;
case 11: talk("D,I2,Y,S,EH,M,B,R,"); break;
default: talk("H,W,UH,T,"); break;
}
}