/*********************************************************** * Arduino Theremin/Synth Code * * Developed by Max Pierson * * Version Rev13 20 June 2009 * * Released under the WTFPL license, although I would * * appreciate Attribution and Share-Alike * * See blog.wingedvictorydesign.com for the latest version. * ************************************************************/ #include "math.h" //necessary for wah effect #define PULSEPIN 2 //the I/O pin of the Ping unit #define SPEAKERPIN 3 //audio playback on pin 3. #define HEARTPIN 11 byte heartrate = 0; //used for the heartbeat effect /*********************Analalog input variables ******************************/ int analog1 = 255; //volume byte analog2 = 255; //waveform select unsigned int analog3 = 0; float analog3buffer = 1; //wah effect byte analog4; //decay unsigned int decay = 255; //Used to create an logarithmic decay of amplitude int analog0offset = 375; int analog1offset = 375; int analog2offset = 564; int analog3offset = 450; int analog4offset = 369; /* offsets that are subtracted from the analogReads to zero them out. These will be * dynamically set in setup () */ /**************************Ultrasound Variables***************************/ unsigned long pulselength = 2000; //stores the incoming values from the Ping unit int period = 3822; /* loaded into the timer1 OCR1A buffer to control the time between array steps, and * thus the frequency of the output. */ int periodold = 3822; // stores the prior value of period. used to cue amplitude decay, etc. /****************************Playback arrays and variables*************************/ #define SAMPLESIZE 32 char sine[] = {0,49,90,117,127,117,90,49,0,-49,-90,-117,-127,-117,-90,-49,0,49,90,117,127,117,90,49,0,-49,-90,-117,-127,-117,-90,-49}; //a simple sine wave with 32 samples char overtones[] = {0,107,118,127,118,92,52,-8,17,46,-6,-36,-56,-67,-70,-80,0,80,70,67,56,36,6,-46,-17,8,-52,-92,-118,-127,-118,-107}; //the same wave with the overtones (and undertone) baked in byte playbuffer[SAMPLESIZE]; //buffer used to store the composite output from the sampled tone and effects. volatile byte waveindex = 0; //index variable for position in waveform array Sine[] long playbufferelement = 0; void setup() { pinMode(SPEAKERPIN, OUTPUT); //Set speaker pin to output pinMode(HEARTPIN, OUTPUT); //Red LED on pin 11 configuredaregister(); /* jump over to the configure_da_register tab, and do our manual register setup * (timer1 and timer2 config). */ analogReference(EXTERNAL); delay(500); //give the photoresistors a moment to reach equilibrium analog1offset = analogRead(1) + 15; analog2offset = analogRead(2) + 15; analog3offset = analogRead(3) + 15; analog4offset = analogRead(4) + 15; //initial read of the photoresistors, used to offset the values during play }//end setup() void loop() { heartrate--; analogWrite(HEARTPIN, heartrate); /* increments the 'heartbeat' LED on pin 11 downward. Every 256 times through the main loop * it will overflow to (underflow?) to 255. */ periodold = (period + periodold) / 2; //average new values with old ones for better stability. period = getfrequency(); //jump over to the getfrequency tab to measure the ultrasound distance and convert it to an appropriate range. /************************** Read the analog photoresistor inputs ***************************/ analog1 = map(constrain(analogRead(1), analog1offset, 900), analog1offset, 900, 255, 0); analog2 = map(constrain(analogRead(2), analog2offset, 900), analog2offset, 900, 255, 0); analog3 = map(constrain(analogRead(3), analog3offset, 900), analog3offset, 900, 0, 500); analog4 = map(constrain(analogRead(4), analog4offset, 900), analog4offset, 900, 255, 230); /* take the analog inputs and scale them to a byte datatype. Note that there's some dead space at the top * and bottom of the sensor values-- so that the sensor stays at "off" or goes to "100%" at the end of travel. */ /******************************* Apply volume and effects **********************************/ if (abs(periodold - period) > 25) { decay = 255; } else { decay = ((decay * analog4) / 255); } //if a new tone is produced, reset decay to full, otherwise increment it downward. analog3buffer = float(analog3) / 200; //convert sensor 3 to a floating point number in the range of pi/16 to pi/4 for (byte i = 0; i < SAMPLESIZE ; i++) { playbufferelement = (analog2*sine[i] + (255-analog2)*overtones[i]) / 255; /* waveform selection-- as analog2 ranges from 0-255 it changes the tone from * a pure sine wave to a more complex wavetype. */ playbufferelement = playbufferelement*cos(analog3buffer*i); /* Wah effect-- creates a variable comb filter. Note that as cos(0) = 1, when the input * from the sensor is 0, the waveform is unchanged */ playbufferelement = playbufferelement * analog1 * decay / 65025; //volume and decay. playbuffer[i] = playbufferelement + 128; //bias the values back to 0-255 for PWM output. } }//end loop() ISR(TIMER1_COMPA_vect) { /* timer1 ISR. Every time it is called it sets the speaker pin * to the next value in playbuffer[]. frequency modulation is done by changing * the timing between successive calls of this function, e.g. for a 1KHz tone, * set the timing so that it runs through playbuffer[] 1000 times a second. */ if (waveindex > SAMPLESIZE - 1) { //reset waveindex if it has reached the end of the array waveindex = 0; } OCR2B = playbuffer[waveindex]; /* Set timer 2 PWM duty cycle to next value in array. OCR2B is the register * corresponding to duty cycle for PWM pin 3. */ waveindex++; //increment array index OCR1A = period; //set timer 1 compare match to new value of period. } //end Timer1 ISR void configuredaregister() { /************************** PWM audio configuration ****************************/ // Configures PWM on pins 3 and 11 to run at maximum speed, rather than the default // 500Hz, which is useless for audio output. cli(); //disable interrupts while registers are configured bitSet(TCCR2A, WGM20); bitSet(TCCR2A, WGM21); //set Timer2 to fast PWM mode (doubles PWM frequency) bitSet(TCCR2A, COM2B1); /* set pin COM2B (pin3) to toggle on when timer2 overflows, off when a match * is made (non-inverting mode). */ bitSet(TCCR2B, CS20); bitClear(TCCR2B, CS21); bitClear(TCCR2B, CS22); /* set prescaler to /1 (no prescaling). The timer will overflow every * 62.5nS * 256ticks = 16uS, giving a PWM frequency of 62,500Hz, I think. */ sei(); //enable interrupts now that registers have been set /************************* Timer 1 interrupt configuration *************************/ cli(); //disable interrupts while registers are configured bitClear(TCCR1A, COM1A1); bitClear(TCCR1A, COM1A1); bitClear(TCCR1A, COM1A1); bitClear(TCCR1A, COM1A1); /* Normal port operation, pins disconnected from timer operation (breaking pwm). * Should be set this way by default, anyway. */ bitClear(TCCR1A, WGM10); bitClear(TCCR1A, WGM11); bitSet(TCCR1B, WGM12); bitClear(TCCR1B, WGM13); /* Mode 4, CTC with TOP set by register OCR1A. Allows us to set variable timing for * the interrupt by writing new values to OCR1A. */ bitSet(TCCR1B, CS10); bitClear(TCCR1B, CS11); bitClear(TCCR1B, CS12); /* set the clock prescaler to /1 (no prescaling. The timer will increment every 62.5nS. * Timer 1 is a 16-bit timer, so the maximum value is 65536, * Giving us a theoretical range of 62.5nS-4.096mS. There are 24 samples, so the * theoretical frequency range is 667KHz - 10.1Hz, which neatly covers the audio * spectrum of 20KHz-20Hz. Theoretical, because I wouldn't recommend actually calling * the Timer1 interrupt every .62.5nS :) I could have also used the /8 prescaler, * but this gives better precision. */ bitClear(TCCR1C, FOC1A); bitClear(TCCR1C, FOC1B); /* Disable Force Output Compare for Channels A and B, whatever that is. * Should be set this way by default anyway. */ OCR1A = 160; /* Sets Output Compare Register A at 160, so initially a match will be generated * every 62.5nS * 8 * 160 = 80uS, for a 1/(80uS*48) = 260Hz tone. */ bitClear(TIMSK1, ICIE1); //disable input capture interrupt bitClear(TIMSK1, OCIE1B); //disable Output Compare B Match Interrupt bitSet(TIMSK1, OCIE1A); //enable Output Compare A Match Interrupt bitClear(TIMSK1, TOIE1); //disable Overflow Interrupt Enable sei(); //enable interrupts now that registers have been set return; } /************************** Ultrasound measurement ***************************/ unsigned int getfrequency() { pinMode(PULSEPIN, OUTPUT); digitalWrite(PULSEPIN, LOW); delayMicroseconds(2); digitalWrite(PULSEPIN, HIGH); delayMicroseconds(5); digitalWrite(PULSEPIN, LOW); pinMode(PULSEPIN, INPUT); //send a pulse to the Ping sensor to start measurement, and set pulsepin to input while ((PIND & B100) != B100) { //while pulsepin is low, do nothing } pulselength = micros(); //record the time of the beginning of the pulse while ((PIND & B100) != 0) { //if (micros() - pulselength > 5500) { //break; //give up if no match is found within range. //} } //while the pulsepin is high, do nothing. pulselength = micros() - pulselength; /* measure the length of the pulse by subtracting the beginning from the end. * I'm using this algorithm instead of pulseIn() because it is unaffected by the * number of times the timer1 ISR is called */ if (pulselength > 160 && pulselength < 5000) { /* throw away measurements that are outside usable range-- both too short-- * the sensor becomes quite noisy under 2in, and too long, about 3'*/ period = (((pulselength - 160) * 103) / 35) + 955; /* takes the incoming 160-5000 sensor range and maps it to a period range of * 955 to 15289 (deep C to Soprano C). */ return period; } else { return periodold; } } //end getfrequency()