Here is my code (with serial, not a keyboard as in the video):
// Parser for algorithmic music (serial version)
// This version:
// * no limit on number of digits for constants
// * No error checking
// * No precedence (of operators) checking
// * Each individual operation MUST BE between parentheses
// * Algos must end with the character 'E'
// by 23N! 2012.09.15
const byte MAX_CHAR=50; // max number of characters in formula
char pile[MAX_CHAR],sortie[MAX_CHAR]; // stacks for conversion to RPN (shunting-yard algorithm)
unsigned long pileRPN[MAX_CHAR],t; // stack for RPN computation / time variable
byte p,s; // counters for pile and sortie
boolean firstChar,multipleDigit; // for reinitialization when new algo is received / multiple digit detection
void setup() {
// This works on an arduino MEGA.
// Just modify the registers and pins for other boards.
// A prescaler of 8 with a 8 or 9 bits fast PWM might me more appropriate but lower sampling rate.
// In the current situation, long algos might cause problems/crashes.
// If sound seems too slow, just increase the increment of t.
// PWM out pin
DDRB = (1 << PB5); // Pin 11 on MEGA
// fast-PWM 10-bit / compare match on OCR1A / output on OC1A=PB5 / prescaler=1
TCCR1A = (1 << COM1A1)|(1<<WGM10)|(1<<WGM11);
TCCR1B = (1 << CS10)|(1<<WGM12);
// Initial values
t=0;
p=0;
s=0;
firstChar=true;
multipleDigit=false;
// Serial
Serial.begin(57600);
Serial.println("Ready!");
}
ISR(TIMER1_OVF_vect) // Interrupt Service Routine (sampling rate of sound)
{
// computation of the algo based on its RPN representation
p=0;
for (byte i=0;i<s;i++) { // Loop on the RPN algo as an array of char (=sortie)
switch(sortie[i]) {
case 't': // variable=> just add to pile
pileRPN[p++]=t;
multipleDigit=false; // not a digit
break;
case '+': // perform ADDITION, de-pile
p--; //de-pile
pileRPN[p-1]+=pileRPN[p]; // perform operation
multipleDigit=false; // not a digit
break;
case '|': // perform OR, de-pile
p--;
pileRPN[p-1]|=pileRPN[p];
multipleDigit=false;
break;
case '&': // perform AND, de-pile
p--;
pileRPN[p-1]&=pileRPN[p];
multipleDigit=false;
break;
case '^': // perform XOR, de-pile
p--;
pileRPN[p-1]^=pileRPN[p];
multipleDigit=false;
break;
case '-': // perform SUBSTRACTION, de-pile
p--;
pileRPN[p-1]-=pileRPN[p];
multipleDigit=false;
break;
case '*': // perform MULTIPLICATION, de-pile
p--;
pileRPN[p-1]*=pileRPN[p];
multipleDigit=false;
break;
case '/': // perform DIVISION, de-pile
p--;
pileRPN[p-1]/=pileRPN[p];
multipleDigit=false;
break;
case '>': // perform BIT SHIFT RIGHT, de-pile
p--;
pileRPN[p-1]=pileRPN[p-1]>>pileRPN[p];
multipleDigit=false;
break;
case '<': // perform BIT SHIFT LEFT, de-pile
p--;
pileRPN[p-1]=pileRPN[p-1]<<pileRPN[p];
multipleDigit=false;
break;
case '%': // perform MODULO, de-pile
p--;
pileRPN[p-1]=pileRPN[p-1]%pileRPN[p];
multipleDigit=false;
break;
default: // digit, add to pile except if multiple digits (in this case process the digits)
if (multipleDigit) {
pileRPN[p-1]*=10;
pileRPN[p-1]+=sortie[i]-48;
}
else {
pileRPN[p++]=sortie[i]-48;
multipleDigit=true;
}
break;
}
}
OCR1A=pileRPN[0]; // PWM output (the final result is the first element of the stack)
t++; // variable inc.
}
void loop() {
// read the serial input and parse it (don't forget to type "E" at the end of the algo)
// example (copy-paste in serial monitor): ((t>5)*t)E (">" is actually ">>" not "greater than")
// each individual operation MUST BE between parentheses.
while (Serial.available() > 0) {
if(firstChar) { // Disable interrupt and initialize variables
TIMSK1 = (0 << TOIE1);
firstChar=false;
p=0;
s=0;
}
char inChar = Serial.read(); // current char
char symbol=inChar; // meta-char
if ((inChar>=48)&(inChar<58)) symbol='D'; // Digit
if ((inChar!='(')&(inChar!=')')&(inChar!='t')&(inChar!='E')&(symbol!='D')) symbol='O'; // Operator
// Shunting-yard algorithm
switch(symbol) {
case 't':
case 'D':
sortie[s++]=inChar; // if variable or digit, add to sortie
break;
case 'O':
case '(':
pile[p++]=inChar; // if operator or left parenthesis, add to pile
break;
case ')':
sortie[s++]=pile[p-1]; // if right parenthesis, transfer from pile to sortie, remove left parenthesis
// In our case there is always a single operation between parentheses. No need to check if there is
// a left parenthesis in the pile, we can just move the index p back and overwrite the left parenthesis.
p-=2;
break;
case 'E': // End char => send the new algo to the interrupt for sound generation.
// Not necessary to have an end char, end can be detected by matching parentheses.
// But an End char makes things clearer and easier.
Serial.println("Uploaded!");
firstChar=true;
t=0;
p=0;
TIMSK1 = (1 << TOIE1); // enable interrupt
break;
}
}
}
If the algorithm is entered in RPN then it's even easier as there is almost nothing in the loop.
In the case of a keyboard and an LCD screen, then it's getting difficult to type anything if the algo is a bit long (the program rarely enters the loop and then misread the keyboard most of the time). What I do in this case is using an arduino for the interface/parsing (just a loop, no interrupt) and an attiny for sound (computation of the algo). Those two are connected through serial.