r0d0t's stuff

Musicomatic : the random jazz machine

Here is the arduino souce code !

It sould fit an atmega8, but the tone() function bugs on it so you’ll have to use an atmega168 or 328.

 ///////////////////////////////////////
 //////////////////THE//////////////////
 //////////////MUSICOMATIC//////////////
 ////////////// BY R0D0T  //////////////
 //////// http://r0d0t.tumblr.com //////
 ///////////////////////////////////////
 // You should get a look at the video /
 // first to understand the code ///////
 http://r0d0t.tumblr.com/post/45022928402
 ///////////////////////////////////////

#define selfShutdownPin 4 //keep this pin HIGH, when you put it LOW it will cut the power
//see the schematics in the video to better understand this

//DRUMS
#include <Servo.h> //include the servo library
byte servoPins[3] = { //store the servo pins in an array to be able to mix them
  6,7,8};
#define lowAngle 90 //the angle the servos need to reach to hit the wood
#define highAngle 80 //the angle the servos need to reach to be able to hit again ;)
Servo servo0; //the 3 servos objects
Servo servo1;
Servo servo2;

//TEMPO
//the fastets note duration will be the sixteenth note for the melody
//so everything will be timed based on the sixteenth
byte tempo; //time between two sixteenth
#define tempoMin 100 //fastest tempo
#define tempoMax 150 //slowest tempo

//NOTES
#define tonePin 0 //buzzer pin
//frequencies in hertz of 2 octaves. 0 is used for muted notes
unsigned int noteFreq[25] = {
  0,262,277,294,311,330,349,370,392,415,440,466,494,
  523,554,587,622,659,698,740,784,830,880,932,988};
byte scale[6] = { //index of the pentatonic scale in noteFreq[]
  0,3,5,6,7,10};
byte scaleOffset; //apply an offset to each note of the scale to change the pitch
#define scaleOffsetMin 1
#define scaleOffsetMax 15
#define pauseBetweenNotes 0.8 //to separate the notes

//CHORUS RHYTHMS : define some rhythms to pick that sound good
#define numberOfRhythms 7
//array containing the duration of the notes for the rhythms.
//Zeros doesn't matter, it's because the arrays need to have the same size
byte rhythms[numberOfRhythms][4] = {
  {4,0,0,0},
  {1,3,0,0},
  {8,0,0,0},
  {6,2,0,0},
  {3,1,0,0},
  {1,1,2,0},
  {2,1,1,0}
};

//SCORES : store the notes to be played, and theyre duration
byte drums[3][16]; //angles for each servo
byte chorus[16]; //index of the notes in noteFreq[]
byte chorusDuration[16]; //duration of the chorus's notes in ms
byte impro[16];  //index of the notes in noteFreq[]
byte improDuration[16]; //duration of the impro's notes in ms

//SETUP
void setup() {
//SELF SHUTDOWN
  pinMode(selfShutdownPin, OUTPUT);
  digitalWrite(selfShutdownPin, HIGH); //Self shutdown if LOW

//RANDOMIZE EVERYTHING !
  randomSeed(analogRead(A0)); //pick a first random seed
  //but doing this, you will often have the same seed (between 0..1024)
  //so let get a bigger, and really random seed....
  unsigned long seed;
  for(int i=0; i<random(0,100); i++){
    seed += analogRead(A0);
    seed += analogRead(A1);
    seed += analogRead(A2);
    seed += analogRead(A3);
    seed += analogRead(A4);
  }
  randomSeed(seed); //that's a good huge seed !
  
  //randomize tempo and scale offset once per music
  tempo = random(tempoMin,tempoMax); //no comment here...
  scaleOffset = random(scaleOffsetMin,scaleOffsetMax); //pick a random scale offset
  for(byte i=0; i<6; i++){ //then apply the offset to each note of the scale
    scale[i] += scaleOffset;
  }
  //this way the pitch of each note will increase depending on the offset

  //randomize servos
  //you shouldn't read the following lines, it's crap
  //its randomly attributes pins (from the servo's pin array) to the servos
  //but avoids to pick the same twice
  //I should find better way to do that, but it works...
  byte servoRandom[3];
  servoRandom[0] = random(0,3);
  do{
    servoRandom[1] = random(0,3);
  } 
  while (servoRandom[1]==servoRandom[0]);
  do{
    servoRandom[2] = random(0,3);
  } 
  while ((servoRandom[2]==servoRandom[0]) || (servoRandom[2]==servoRandom[1]));
  //Attach the pins to the servos
  servo0.attach(servoPins[servoRandom[0]]); 
  servo1.attach(servoPins[servoRandom[1]]); 
  servo2.attach(servoPins[servoRandom[2]]);
  //then make them take them default angle
  servo0.write(lowAngle);
  servo1.write(lowAngle);
  servo2.write(lowAngle);

  //play the scale with the good tempo
  //initially it was done for debugging purposes, but it sounds good
  for (byte i=0; i<sizeof(scale); i++){
    tone(tonePin, noteFreq[scale[i]], tempo*0.8);
    delay(tempo);
  }
  
  //wait a little before beginning the tune...
  delay(500);

//PLAY THE MUSIC !!!
//See below to understand what each function does...
  RandomizeDrums();
  PlayIntro(); //introduces drums one by one
  for(byte thisTheme=0; thisTheme<3; thisTheme++){ //play 3 "themes"
    RandomizeChorus(); //change the chorus for each theme
    for(byte i=0; i<4; i++){ //a theme is composed of :
      PlayChorus(); //the chorus, the same for the whole theme
      RandomizeImpro(); //an impro randomized each time
      PlayImpro();  //play the impro
    }
    PlayInterThemes(); //between themes and at the end of the song, play drums alone
  }

//once this marvelous music reaches it's end, make the servo take their default position
  servo0.write(lowAngle);
  servo1.write(lowAngle);
  servo2.write(lowAngle);
  delay(2*tempo); //let them time to reach their position
  servo0.detach();
  servo1.detach();
  servo2.detach();

//and then shut down !
  digitalWrite(selfShutdownPin, LOW);
}

//LOOP
void loop() { //The loop is useless as the code runs once
}

void PlayIntro(){ //introduces the drums one by one
  for(byte i=0; i<16; i++){
    servo2.write(drums[2][i]); //the faster one first
    delay(tempo);
  }
  for(byte i=0; i<16; i++){
    servo1.write(drums[1][i]); //the a slower one
    servo2.write(drums[2][i]);
    delay(tempo);
  }
  for(byte i=0; i<16; i++){
    servo0.write(drums[0][i]); //and finaly the slowest
    servo1.write(drums[1][i]);
    servo2.write(drums[2][i]);
    delay(tempo);
  }
}

void PlayInterThemes(){ //drums between main themes and at the end of the song
  for(byte i=0; i<16; i++){
    servo2.write(drums[2][i]);
    if(i>8){
      servo1.write(drums[1][i]);
    }
    delay(tempo);
  }
}

void RandomizeDrums(){
  for(byte thisDrum=0 ; thisDrum<3 ; thisDrum++){
    for(byte i=0; i<16; i++){ //clear the array containing the drums's servos angles
      drums[thisDrum][i] = lowAngle;
    }
  }
  for(byte i=0; i<16; i++){
    if((i%4)==3){ //the fastest drum
      drums[2][i] = highAngle;
      drums[2][i-1] = highAngle;
      //the servo needs time to reach it's position,
      //so you need to move it on both i and i-1
    }
    if((i%8)==2){ //the medium one
      drums[1][i] = highAngle;
      drums[1][i-1] = highAngle;
    }
    if((i%16)==15){//the slowest
      drums[0][i] = highAngle;
      drums[0][i-1] = highAngle;
    }
    for(byte i=2; i<16; i+=2){
      if(0 == random(0,12)){//and add some random hits to the slowest one
        drums[0][i] = highAngle;
        drums[0][i-1] = highAngle;
      }
    }
  }
}

void RandomizeChorus(){
  for(byte i=0; i<16; i++){
    chorus[i] = 0; //clear the chorus array
  }
  
  byte i = 0; //start from the beginning of the chorus
  while(i<16){ //then, while the end isn't reached
    byte thisRhythm = random(0, numberOfRhythms); //pick a random rhythm
    byte thisNote = 0;
    
    while(rhythms[thisRhythm][thisNote] !=0){ //while the end of the rhythms isn't reached
                                           //(because the rhythms arrays ends with zeroes)
      chorus[i] = scale[random(0,6)]; //pick a random note in the scale
      
      if(i>0){//if the current note is not the first one...
        if(random(0,48)==0) { //...randomly...
          chorus[i] = 0; //...mute it.
        }
      }
      
      chorusDuration[i] = rhythms[thisRhythm][thisNote]; //set the note duration based one the rhythm
      thisNote++; //go to the next note
      i+=chorusDuration[i]; //go forward by the note's duration
      if(i>=16) //if the end is reached...
        break; //...go to the next step !
    }
  }
}

void PlayChorus(){ //play the chorus we just generated !
  for(byte i=0; i<16; i++){ //for each instant
    if(chorus[i] > 0){ //if there is a note
      if(chorus[i] != chorus [i-1]){ //and this note is note the same than the previous
        tone(tonePin, noteFreq[chorus[i]], chorusDuration[i]*tempo*0.8); //play it
      }
    }
    servo0.write(drums[0][i]); //simply move each drum's servo
    servo1.write(drums[1][i]);
    servo2.write(drums[2][i]);
    delay(tempo); //wait until the next note !
  }
}

void RandomizeImpro(){ //same as RandomizeChorus(), but simpler
  for(byte i=0; i<16; i++){
    impro[i] = 0; //clear the array
  }
  byte i = 0;
  while(i<16){
    do{
      impro[i] = scale[random(0,6)];
    } 
    while (impro[i] == impro[i-1]); //avoid two notes of the same pitch
    improDuration[i] = random(1, 4);
    i+=improDuration[i];
  }
}

void PlayImpro(){ //the same as PlayChorus()... they should have been merged
  for(byte i=0; i<16; i++){
    if(impro[i] > 0){
      if(impro[i] != impro [i-1]){
        tone(tonePin, noteFreq[impro[i]], improDuration[i]*tempo);
      }
    }
    if(i==15){
      noTone(tonePin);
    }
    servo0.write(drums[0][i]);
    servo1.write(drums[1][i]);
    servo2.write(drums[2][i]);
    delay(tempo);
  }
}
Mar 10, 2013 @ 15:04