/*
Votrax SC-01 Controller
by Ant, like the bug
Feb 2024
An Arduino sketch for operating the Votrax SC-01 Speech
Synthesizer chip first released in the early 1980s.
SC-01 Technical Documentation: http://redcedar.com/sc01/sc01.pdf
Phoneme dictionary: https://downloads.reactivemicro.com/Apple%20II%20Items/Hardware/SC-01-A/Datasheet/SC-01%20Dictionary.pdf
*/
/*
TODO
• serial output to shift register instead of individual output
pins for each phoneme bit.
• keyboard input for new phoneme sequences
• eurorack integration:
- 1v/oct vc input for pitch sequencing MCX rate
- use gate signal instead of A/R high to trigger next phoneme
- x/y and button interface for realtime phoneme generation + performance
*/
/*
After hours of work on this script, during which I was
continually reminded how at C++ I am, I found this video by
Scott Savage, detailing his library and kit which do most of
what I want this sketch to eventually do.
https://www.youtube.com/watch?v=ER0t3IYygDw
*/
//Input must be comprised of valid Votrax phoneme symbols.
//Don't forget to define the length of the input!
const int inputLen = 11;
const String input[inputLen] = {"K", "AH1", "UH3", "R", "PA0", "H", "AE1", "EH3", "M", "ER", "S"};
const bool repeat = false;
const bool repeatDelay = 1000;
// const int i1 = 5; add these back into outputPins[] in setup() when ready
// const int i2 = 6;
const int stb = 2;
const int p0 = 3;
const int p1 = 4;
const int p2 = 5;
const int p3 = 6;
const int p4 = 7;
const int p5 = 8;
const int ar = 9;
const int arsim = 10; //simulates A/R pulse from SC-01 for debug
//Valid phoneme symbols
const String symbols[] = {
"EH3", "EH2", "EH1", "PA0", "DT", "A2", "A1", "ZH", "AH2", "I3", "I2", "I1", "M", "N", "B", "V",
"CH", "SH", "Z", "AW1", "NG", "AH1", "NG", "AH1", "OO1", "OO", "L", "K", "J", "H", "G", "F", "D", "S",
"A", "AY", "Y1", "UH3", "AH", "P", "O", "I", "U", "Y", "T", "R", "E", "W", "AE", "AE1",
"AW2", "UH2", "UH1", "UH", "O2", "O1", "IU", "U1", "THV", "TH", "ER", "EH", "E1", "AW", "PA1", "STOP"
};
void setup() {
Serial.begin(9600);
Serial.println("Initializing pins...");
int outputPins[9] = {stb,p0,p1,p2,p3,p4,p5};
for (int i=0;i<9;i++){
int pin = outputPins[i];
String pinLabel = String(pin);
pinMode(pin,OUTPUT);
Serial.println("Pin " + pinLabel + " set to OUTPUT");
}
int inputPins[] = {ar};
for (int i=0;i<1;i++){
int pin = inputPins[i];
String pinLabel = String(pin);
pinMode(pin,INPUT);
Serial.println("Pin " + pinLabel + " set to INPUT");
}
Serial.println("Pins initialized. \n");
}
//Returns true if an input symbol is included in symbols[].
bool checkSymbol(String symbol){
for (int i=0; i<64; i++){
if(symbol == symbols[i]){
Serial.println("Symbol (" + symbol + ") is VALID.");
return true;
}
}
Serial.println("Symbol (" + symbol + ") is NOT VALID.");
return false;
}
//Returns true if all input symbols are in the symbols[] array.
bool inputIsValid(String thisInput[], int thisInputLen){
Serial.println("Validating input...");
int validCount = 0;
for(int i=0; i<inputLen; i++){
if (checkSymbol(input[i])==true){
validCount++;
}
}
if(validCount == inputLen){
Serial.println("Input is VALID. \n");
return true;
}
else{
Serial.println("Input is NOT VALID.");
return false;
}
}
//Returns the index of a given phoneme symbol.
int symbolIndex(String element){
for(int i=0; i<64; i++){
if(element==symbols[i]){
int index = i;
Serial.println("Symbol (" + element + ") is at index " + i +".");
return index;
}
}
}
//Triggers latch function on SC-01.
int strobe(int hold){
digitalWrite(stb,HIGH);
String holdTime = String(hold);
Serial.println("Strobe pin: HIGH. Hold time: " + holdTime);
delay(hold);
digitalWrite(stb,LOW);
Serial.println("Strobe pin: LOW.");
Serial.println();
delay(5);
digitalWrite(arsim,LOW); //simulated here for debug
return 0;
}
//Main Loop
void loop() {
//If input is valid...
if(inputIsValid(input,inputLen)){
Serial.println("Ready to convert to binary. \n");
//Find the index of each phoneme...
for (int i=0; i<inputLen; i++){
int index = symbolIndex(input[i]);
//Convert the index to binary...
int bits[8] = {};
for (int j=0;j<8;j++){
int data = bitRead(index,j);
bits[j]= data;
}
int orderedBits[8] = {bits[7],bits[6],bits[5],bits[4],bits[3],bits[2],bits[1],bits[0]};
for(int j=0; j<8; j++){
Serial.print(orderedBits[j]);
}
Serial.println();
//Assign the 6 least significant bits to p0-p5...
int outputPins[6] = {p0,p1,p2,p3,p4,p5};
for (int j=0; j<8; j++){
if(orderedBits[j+2]==1){
digitalWrite(outputPins[j], HIGH);
}
if(orderedBits[j+2]==0){
digitalWrite(outputPins[j],LOW);
}
}
Serial.println("Bits assigned and ready to flash.");
Serial.println();
//Wait for A/R Pulse...
while(digitalRead(ar)==LOW){
Serial.println("Waiting for A/R pulse...");
delay(250);
digitalWrite(arsim,HIGH); //simulated here for debug
}
Serial.println("A/R pulse detected!");
//Flash the chip...
strobe(250);
}
//If repeat is false, end the program.
if(repeat == false){
delay(500);
Serial.println("Repeat is OFF.");
Serial.println("\n PROGRAM END.");
delay(1000);
exit(0);
}
//If repeat is true, repeat the program.
else{
Serial.println("Repeat is ON.");
String rptDlyTime = String(repeatDelay);
Serial.println("Repeating input; delay = " + rptDlyTime);
Serial.println();
}
}
//If the input is not valid, end the program.
else{
delay(1000);
Serial.println("\n PROGRAM END.");
delay(1000);
exit(0);
}
}