/*
SC-01 Controller v0.3
Ant "like the bug" D'Angelo
February 2024
A sketch for controlling an SC-01 Speech Synthesizer chip.
Documentation for the SC-01 is here: http://redcedar.com/sc01/sc01.pdf
TODO:
- implement vowel grid mode
- implement dynamic prefs
*/
//Default preferences
const int dataMode = 2; // 1 = parallel; 2 = serial
const int phonemeMode = 1; // 1 = Saved phrase; 2 = Random phrase; 3 = Vowel grid
const int advanceMode = 1; // 1 = advance on ar pin high; 2 = advance on gate pin high
const bool repeat = false; // only loops once if false
const bool repeatDelay = 1000; // delay between loops
//Saved phrase
const int mem1Len = 3;
const String mem1Symbols[mem1Len] = {"K", "AH1", "R"};
const int mem1Inflections[mem1Len] = {3,2,1};
//Pins
const int stb = 2; // To SC-01 Strobe pin
const int ar = 3; // To SC-01 A/R pin
//SC-01 data pins (parallel dataMode only)
const int i1 = 4;
const int i2 = 5;
const int p0 = 6;
const int p1 = 7;
const int p2 = 8;
const int p3 = 9;
const int p4 = 10;
const int p5 = 11;
// 74HC595 pins (serial dataMode only)
const int datapin = 4;
const int clockpin = 5;
const int latchpin = 6;
const int arSim = 13; // debug only
// Interface pins
const int rdm = A0; // No connection; noise source for randomSeed()
const int gridX = A2; // +5v Eurorack CV
const int gridY = A3; // +5v Eurorack CV
const int gate = 7; // +5v Eurorack Gate
const int phonemeModeSelect = 8;
const int advanceModeSelect = 9;
//Valid phoneme symbols
const String validSymbols[64] = {
"EH3", "EH2", "EH1", "PA0", "DT", "A2", "A1", "ZH",
"AH2", "I3", "I2", "I1", "M", "N", "B", "V",
"CH", "SH", "Z", "AW1", "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"
};
// //Vowel grid
// String vowelGrid[8][5]={
// {"E","EH","AE","UH","OO1"},
// {"E1","EH1","AE1","UH1","R"},
// {"Y","EH2","AH","UH2","ER"},
// {"Y1","EH3","AH1","UH3","L"},
// {"I","A","AH2","O","IU"},
// {"I1","A1","AW","O1","U"},
// {"I2","A2","AW1","O2","U1"},
// {"I3","AY","AW2","OO","W"}
// };
//Return true if AR pulse is detected
bool arListen(int mode){
if(mode==1){
return digitalRead(ar)==HIGH ? true : false;
}
if(mode==2){
return digitalRead(gate)==HIGH ? true : false;
}
}
//Trigger SC-01 latch
void strobe(){
digitalWrite(stb,HIGH);
Serial.println("Strobe HIGH");
delay(250);
digitalWrite(stb,LOW);
Serial.println("Strobe LOW");
}
//Write a given byte to parallel data pins
void sendParallel(byte output){
int dataPins[8] = {i1,i2,p0,p1,p2,p3,p4,p5};
for (int i=7; i>=0; i--){
if (bitRead(output,i) == 1){
digitalWrite(dataPins[i],HIGH);
}
if (bitRead(output,i) == 0){
digitalWrite(dataPins[i],LOW);
}
}
Serial.println("Pins written, ready to flash!");
}
//Write a given byte to shift register
void sendSerial(byte data){
shiftOut(datapin, clockpin, MSBFIRST, data);
digitalWrite(latchpin,HIGH);
delay(1000);
digitalWrite(latchpin,LOW);
}
// class preferences{
// public:
// int phonemeMode = phonemeMode; // 1 = Saved phrase; 2 = Random phrase; 3 = Vowel grid
// int advanceMode = advanceMode; // 1 = advance on ar pin high; 2 = advance on gate pin high
// bool repeat = repeat; // only loops once if false
// bool repeatDelay = repeatDelay; // delay between loops
// void set(){
// phonemeMode = analogRead(phonemeModeSelect)>500 ? 1:2;
// advanceMode = analogRead(advanceModeSelect)>500 ? 1:2;
// }
// };
struct phrase{
int len;
String symbols[16];
int inflections[16];
};
class Phoneme{
public:
String symbol = "PA0";
int inflection = 0;
// Return index of given phoneme symbol if found in validSymbols[], or default to PA0 if symbol is not found.
int symbolIndex(){
for (int i=0; i<64; i++){
if(symbol==validSymbols[i]){
delay(10);
Serial.println("Symbol " + symbol + " is at index " + i +".");
delay(10);
return i;
}
}
Serial.println("Symbol " + symbol + " is at invalid; defaulting to PA0 at index 3");
return 3;
}
//Return inflection if inflection is 0 to 3; otherwise, default to 0
int checkInflection(){
if(inflection >= 0 && inflection<4){
Serial.println("Inflection value is " + String(inflection) + ".");
return inflection;
}
else{
Serial.println(F("Inflection value is INVALID. Defaulting to 0"));
return 0;
}
}
//Returns an 8-bit value where
// - the first two bits represent inflection
// - the last 6 bits represent the phoneme
byte encode(int phon, int inf){
byte newByte = inf << 6;
newByte = newByte | phon;
Serial.println(F("Encoded output: "));
for (int i=7; i>=0;i--){
Serial.print(bitRead(newByte,i));
}
Serial.println();
return newByte;
}
};
//SETUP=========================================================
void setup(){
Serial.begin(9600);
Serial.println(F("PROGRAM START."));
Serial.println();
Serial.println(F("Initializing pins..."));
delay(500);
int inputPins[7] = {ar,rdm,gate,gridX,gridY,phonemeModeSelect,advanceModeSelect};
for (int i=0;i<7;i++){
pinMode(inputPins[i],INPUT);
}
pinMode(stb,OUTPUT);
if(dataMode == 1){
Serial.println(F("Data mode: Parallel"));
int dataPins[8] = {i1,i2,p0,p1,p2,p3,p4,p5};
for (int i=0;i<8;i++){
pinMode(dataPins[i],OUTPUT);
}
}
if(dataMode == 2){
Serial.println(F("Data mode: Serial"));
int dataPins[3]={datapin,clockpin,latchpin};
for(int i=0; i<3; i++){
pinMode(dataPins[i],OUTPUT);
}
}
pinMode(arSim,OUTPUT); // debug only
Serial.println(F("Pins initialized. \n"));
}
//LOOP===========================================================
void loop(){
Serial.println(F("MAIN LOOP START:"));
digitalWrite(arSim,LOW); // debug only
int pMode = digitalRead(phonemeModeSelect)==HIGH ? 2:1;
int aMode = digitalRead(advanceModeSelect)==HIGH ? 2:1;
phrase Phrase;
if (pMode == 1){
//Populate Phrase with data from memory
Serial.println(F("Phoneme Mode: Memory"));
Phrase.len = mem1Len;
for(int i=0;i<Phrase.len;i++){
Phrase.symbols[i]=mem1Symbols[i];
Phrase.inflections[i]=mem1Inflections[i];
Serial.println("Phoneme " + String(i) + ": " + String(Phrase.symbols[i]) + ", " + String(Phrase.inflections[i]));
}
Serial.println();
}
if (pMode == 2){
randomSeed(analogRead(rdm));
//Generate a random phrase
Serial.println(F("Phoneme Mode: Random"));
int minLen = 2;
int maxLen = 8;
Phrase.len = random(minLen,maxLen);
Serial.println("Random length: " + String(Phrase.len));
for (int i=0;i<Phrase.len;i++){
Phrase.symbols[i] = validSymbols[random(0,63)];
Phrase.inflections[i] = 0;
Serial.println("Random phoneme " + String(i+1) + ": " + String(Phrase.symbols[i]) + ", " + String(Phrase.inflections[i]));
}
Serial.println();
}
// if (phonemeMode == 3){
// Serial.println("Phoneme mode: Vowel Grid");
// Phrase.len = 1;
// int x = map(analogRead(gridX),0,1023,0,4);
// int y = map(analogRead(gridY),0,1023,0,7);
// Phrase.symbols[0]=vowelGrid[x][y];
// Phrase.inflections[0]=0;
// }
for (int i=0; i<Phrase.len; i++){
Serial.println("Writing phoneme " + String(i) + "...");
Phoneme p;
p.symbol = Phrase.symbols[i];
p.inflection = Phrase.inflections[i];
int a = p.symbolIndex();
int b = p.checkInflection();
if(dataMode == 1){
sendParallel(p.encode(a,b));
}
if(dataMode == 2){
sendSerial(p.encode(a,b));
}
while(!arListen(aMode)){
delay(1);
Serial.println(F("Waiting for AR pulse..."));
}
Serial.println(F("AR pulse detected!"));
strobe();
// digitalWrite(arSim,LOW); // debug only
Serial.println(F("Ready for next phoneme. \n"));
}
if(!repeat){
Serial.println(F("END PROGRAM."));
delay(500);
exit(0);
}
delay(repeatDelay);
};