/* Arduino Synth v 0.1 This is code for a sound synthesizer built with an Arduino. Features 4 LEDs indicate 1 of 16 programs (voices) indicated by 4 LED's with binary encoding Plays scales or tunes determined by arrays. Cheap, fun, and you get to program it yourself. So far there's not too much implemented to change the timbre of sounds and it outputs square waves. There is a "phase control" though that changes the duty cycle of the square wave which does a little something in this direction. There are 3 pins resevered for a future R2R ladder for D-A conversion. Even a two-bit digital sine wave sounds much mellower than a square wave, so this seems like promising future work. */ #include <math.h> #define outpin 4 // hook up this pin to audio out in series with a 1 uf cap and 1k resistor. #define outpin1 5 // pins reserved for future 4 bit R2R #define outpin2 6 #define outpin3 7 // analog inputs, these number are for the analog pin inputs A0, A1 etc // use 5 - 50k pots. Looking at the pot shaft, with pot leads facing up, hook up left leads to +5V, // middle terminals to A0, A1 etc, and right terminals to ground #define pitchPot 0 #define modPot 1 #define speedPot 2 #define phasePot 3 #define jukePot 4 #define stopPin 8 // this is the "break" switch - to break out of long loops - enables retriggering scales, tunes etc // one side of single pole pushbutton switch to pin 8, other side to ground #define LEDbit0 10 // LED's for program selection indication - hook up LED with long lead #define LEDbit1 11 // facing the pin and a 1k series resistor to ground #define LEDbit2 12 #define LEDbit3 13 int randomWalkLowRange; // for random walk function int randomWalkHighRange; int val, runlevel; volatile int encoder0PinA = 2; // pins for A & B channel of the encoder volatile int encoder0PinB = 3; // hook up encoder middle terminal to ground // hook up A & B channels (end leads of encoder) to pins 2 & 3 with 1 uf (105) caps to ground volatile unsigned int encoder0Pos = 0; int n = LOW; int ptime, range, strt, inc; int k, x, dur, freq, t; int i, j; int sens, adder; float ps, fpitch; // variable for pow pitchShift routine float noteval; //note values float A = 14080; float AS = 14917.2; float B = 15804.3; float C = 16744; float CS = 17739.7; float D = 18794.5; float DS = 19912.1; float E = 21096.2; float F = 22350.6; float FS = 23679.6; float G = 25087.7; float GS = 26579.5; float A2 = 28160; float A2S = 29834.5; float B2 = 31608.5; float C2 = 33488.1; float C2S = 35479.4; float D2 = 37589.1; float D2S = 39824.3; float E2 = 42192.3; float F2 = 44701.2; float F2S = 47359.3; float G2 = 50175.4; float G2S = 53159; float A3 = 56320; //rhythm values int wh = 1024; int h = 512; int dq = 448; int q = 256; int qt = 170; int de = 192; int e = 128; int et = 85; int dsx = 96; int sx = 64; int thx = 32; float majScale[] = { A, B, CS, D, E, FS, GS, A2, B2, C2S, D2, E2, F2S, G2S, A3}; float minScale[] = { A, B, C, D, E, F, GS, A2, B2, C2, D2, E2, F2, G2S, A3}; float creme[] = { // make sure this array and the following one have same number of entries - bottom array is time values A, CS, D, CS, D, E, CS, D, CS, B, A}; float cremeDur[] = { q, q, qt, qt, qt, q, q, qt, qt, qt, q}; void setup() { pinMode(outpin, OUTPUT); pinMode(9, OUTPUT); //R2R pins to output pinMode(8, OUTPUT); pinMode(7, OUTPUT); pinMode(6, OUTPUT); Serial.begin(9600); pinMode(LEDbit0, OUTPUT); pinMode(LEDbit1, OUTPUT); pinMode(LEDbit2, OUTPUT); pinMode(LEDbit3, OUTPUT); pinMode(stopPin, INPUT); digitalWrite(stopPin, HIGH); // turn on pullups pinMode(encoder0PinA, INPUT); digitalWrite(encoder0PinA, HIGH); // turn on pullups pinMode(encoder0PinB, INPUT); digitalWrite(encoder0PinB, HIGH); // turn on pullups attachInterrupt(0, doEncoder, CHANGE); // encoder pin on interrupt 0 - pin 2 Serial.println("start"); pinMode(outpin, INPUT); // turn off audio out } void loop(){ runlevel = encoder0Pos; switch (runlevel){ case 0: case 1: doJoker1(); break; case 2: doScale2(); break; case 3: doArp3(); break; case 4: doCreme4(); break; case 5: doCreme5(); break; case 6: doScale6(); break; } } //******************************************************* void doJoker1(){ sens = analogRead(pitchPot); //sens =500; // hardwire for testing adder = max((sens/5),1); for (x=sens; x<=(sens + (analogRead(speedPot) * 5)) ; x+=analogRead(jukePot)){ if ( digitalRead(stopPin) == 0){ break; } noteval = x; // transpose scale up 12 tones - pow function generates transpostion dur = 100; freqout((int)noteval, sens/47); // delay(10); } delay(analogRead(modPot)); } // endJoker1 **************************************************** /*void arp2(){ sens = analogRead(pitchPot); range = abs(512 - sens) * 4.0; strt = sens * 1; inc=abs(512 - sens); if (inc == 0){ inc = 1; } for (x=strt; ( x > (strt / 4)) && (x < (inc * 30)); x* = (1 + ((sens - 512) / 1023.00))){ noteval = x; // transpose scale up 12 tones - pow function generates transpostion dur = 100; freqout((int)noteval, inc); // if ((abs(sens - analogRead(0))) > 2){ break;} } delay(analogRead(modPot); } */ // end *************************************************** void doScale2(){ ps = ((float)analogRead(jukePot)) * 24.0 / 1023.0; // choose new transpose interval every loop for(x= 0; x<=15; x++){ if ( digitalRead(stopPin) == 0){ break; } noteval = (majScale[x] / (float)(analogRead(pitchPot))) * pow(2,ps); // transpose scale up 12 tones - pow function generates transpostion dur = analogRead(speedPot); freqout((int)noteval, dur); delay(analogRead(modPot)); } } /****************************************************/ void doArp3(){ Serial.println("arp"); fpitch = (float)analogRead(pitchPot) / 64; Serial.print("fpitch = "); Serial.println(fpitch, DEC); if (fpitch == 0){ fpitch = 1; } strt = pow(2, fpitch); Serial.print("strt = "); Serial.println(strt, DEC); for(x=strt; x<= strt * 4; strt = (strt * ( 1 + ((float)analogRead(jukePot)/1023)))){ if ( digitalRead(stopPin) == 0){ break; } dur = analogRead(speedPot); freqout(strt, dur); delay(analogRead(modPot)); } } /****************************************************/ void doCreme4(){ randomWalkLowRange = 0; randomWalkHighRange = 10; i = randomWalk(analogRead(speedPot) / 32); noteval = creme[i] / ((float)analogRead(pitchPot)); dur = ((cremeDur[i] * (float)analogRead(jukePot)) / 64.0); freqout(noteval, dur); delay(analogRead(modPot)); } /****************************************************/ void doCreme5(){ int LowRange = 0; int HighRange = 10; for(x= 0; x<=HighRange; x++){ if ( digitalRead(stopPin) == 0){ break; } noteval = creme[x] * 2 / ((float)analogRead(pitchPot)); dur = ((cremeDur[x] * (float)analogRead(speedPot)) / 64.0); freqout(noteval, dur); delay(analogRead(modPot)); } } /****************************************************/ void doScale6(){ int LowRange = 0; int HighRange = 14; for(x= 0; x<=HighRange; x++){ if ( digitalRead(stopPin) == 0){ break; } noteval = minScale[x] * 2 / ((float)analogRead(pitchPot)); dur = (4 * analogRead(speedPot)); freqout(noteval, dur); delay(analogRead(modPot)); } } int randomWalk(int moveSize){ static int place; // variable to store value in random walk - declared static so that it stores // values in between function calls, but no other functions can mess with its value place = place + (random(-moveSize, moveSize + 1)); if (place < randomWalkLowRange){ // check lower and upper limits place = place + (randomWalkLowRange - place); // reflect number back in positive direction } else if(place > randomWalkHighRange){ place = place - (place - randomWalkHighRange); // reflect number back in negative direction } return place; } void freqout(int freq, int t) { //calculate 1/2 period in us unsigned int cycles; long i, hperiod; long highPeriod, lowPeriod; int phase; t = max(t,1); // check to prevent 0 time hperiod = (500000 / freq) - 7; // subtract 7 us to make up for digitalWrite overhead - determined empirically phase = analogRead(phasePot); highPeriod = hperiod * phase / 1023; lowPeriod = ((hperiod * (1023 - phase)) / 1023) - 1; // - 1 to make up for fractional microsecond in digitaWrite overhead // calculate cycles cycles = ((long)freq * (long)t) / 1000; // calculate cycles if (highPeriod <= 0 || lowPeriod <= 0){ pinMode(outpin, INPUT); return; } pinMode(outpin, OUTPUT); // turn on output pin for (i=0; i<= cycles; i++){ // play note for t ms digitalWrite(outpin, HIGH); delayMicroseconds(highPeriod); digitalWrite(outpin, LOW); delayMicroseconds(lowPeriod); } pinMode(outpin, INPUT); // shut off pin to avoid noise from other operations } void doEncoder(){ if (digitalRead(encoder0PinA) == HIGH) { if (digitalRead(encoder0PinB) == LOW) { encoder0Pos = (encoder0Pos - 1 ) % 16; } else { encoder0Pos = (encoder0Pos + 1 ) % 16; } } else { if (digitalRead(encoder0PinB) == LOW) { encoder0Pos = (encoder0Pos + 1 ) % 16; } else { encoder0Pos = (encoder0Pos - 1 ) % 16; } } PORTB = PORTB & (B000011); // clear out LED bits PORTB = PORTB | (encoder0Pos << 2); // light up channel LED's // Serial.print (encoder0Pos, DEC); // Serial.print ("/"); }