Arduino 64 step Chiptune Sequencer

This is what I've been working on for the past few months:

It is a standalone 64-step chiptune-esque sequencer!

See this video for more info on features:

Tell me what you think!!!
Or if you have any questions, feel free to ask!
;D

Nice!

Wow very cool :). Care to share a bit more about how it works / which parts you used?

Would be educational ;).

Sure, I can give more info.

Three shift registers control each 8*8 matrix, one for each of green red and rows. These are driven like normal shift registers with the registers of each matrix daisychained, so there are two latch pins, one data pin and clock. The pushbuttons and pots are hooked up as they normally are. The speaker is driven through a transistor and is controlled by two digital pins, one for the drum noises and one for the tones. In all the audio is only 2bit.

On to software:
The main part of the program is a while loop that loops for however long one step in the pattern is. Inside this loop everything is constantly updated. Pots are continually read and according variables set, buttons the same. For the drum sounds, a switch determines which sound should be playing, and runs a function with the corresponding parameters. That is, there is a function that takes parameters that decide how fast the pitch of the sound will change, what frequency it starts and ends at, and the amount of randomness to the tone. So, for the kick sound it is a low frequency, quickly decreasing tone with no noise. The snare is an unchanging base pitch with a lot of randomness to simulate noise. The melody section of the code is similar: judging by the array storing the notes, it plays a frequency, multiplied by 2^x according to which octave t is. The displaying portion of the code takes variables such as one for the position in the sequence and the arrays for the 64 steps and figures out which columns should be lit based on which row is currently displayed.

Opps, I forgot:
How it is used:
Some functions are shown in the video, but here are some more features:
First button is like a select button. By moving the cursor with the 3rd and 4th pots, you can place a green dot, a note in the sequence. The third button (second button is broken) is the instrument button. By holding then releasing it, like the pattern button in the video, you can switch between displaying the drum track or the melody track. While showing the melody track, tapping the button (not holding it) shows the next octave. In drum mode, there are 8 sounds, one for each row (more could be added easily):
kick--snare--hihatclosed--hihatopen--rising tone--falling tone-- rising noise--falling noise
In melody display mode, each of the first 7 rows is a note in a Cmajor scale. The last row makes a note sharp if a dot is placed. (E and B stay the same)

Thanks sciguy for the information ;). Are the sounds generated with the Arduino? How did you do that?

Great project. Do you plan to share the code?

-br

http://entropymouse.com

I might share the code if people want.

It's not really optimized yet, still pretty kludgey.

And yes all the sound is generated on the arduino. I used the tone library, one tone object for the drums, one for the melody. The melody part is pretty straightforward, but the drums took a bit of tinkering.
I explained how some of the sound works in my previous post.

If you plan to write an howto or sorf of, I am going to create one. It's totally cool. Have to start to try the tone library... :slight_smile:

Right now I'm working on making little snippets of songs. When I have enough I'll compile them to one video. (audio only though)

As for a howto, the hardware is pretty simple, besides a custom pcb, it is just pots and buttons hooked up normally and a speaker and a transistor.

Here's part of the code: (declarations and setup)

#include <Bounce.h>

#include <Tone.h>

Tone drum;
Tone lead;

Bounce button1 = Bounce( 8,7 );
Bounce button2 = Bounce( 9,7 );
Bounce button3 = Bounce( 10,7 );
Bounce button4 = Bounce( 11,7 );


const int clockPin = 2;
const int dataPin = 3;
const int latch1 = 4;
const int latch2 = 5;

#define REST -1
#define BASS 1
#define SNARE 2
#define HHATC 3
#define HHATO 4
#define RISE 5
#define PEW 6
#define NOISERISE 7
#define NOISEPEW 8

int beat[4][16] = {
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
  ,
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
  ,
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
  ,
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }

};


int music[4][16] = {
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
  ,
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
  ,
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
  ,
  {
    1, REST, REST, REST, 
    REST, REST, REST, REST, 
    REST, REST, REST, REST,
    REST, REST, REST, REST
  }
};

const int notes[] = {

  NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, 
  NOTE_E3, NOTE_E3,  NOTE_F3, NOTE_FS3, 
  NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, 
  NOTE_B3, NOTE_B3,
};

int octaves[4][16] = {
  {
    1, 0, 0, 0, 
    0, 0, 0, 0, 
    0, 0, 0, 0,
    0, 0, 0, 0
  }
  ,
  {
    1, 0, 0, 0, 
    0, 0, 0, 0, 
    0, 0, 0, 0,
    0, 0, 0, 0
  }
  ,
  {
    1, 0, 0, 0, 
    0, 0, 0, 0, 
    0, 0, 0, 0,
    0, 0, 0, 0
  }
  ,
  {
    1, 0, 0, 0, 
    0, 0, 0, 0, 
    0, 0, 0, 0,
    0, 0, 0, 0
  }

};

boolean sharps[4][16] = {
  {
    true, false, false, false,
    false, false, false, false,
    false, false, false, false,
    false, false, false, false
  }
  ,
  {
    true, false, false, false,
    false, false, false, false,
    false, false, false, false,
    false, false, false, false
  }
  ,
  {
    true, false, false, false,
    false, false, false, false,
    false, false, false, false,
    false, false, false, false
  }
  ,
  {
    true, false, false, false,
    false, false, false, false,
    false, false, false, false,
    false, false, false, false
  }
};




int position;  // position in sequence.  0-15
int pattern;  //number of the pattern currently playing
int row=0;
long held;
long held2;


boolean trackMode = true;  //true: drum track    false: lead track
int octaveDisp = 0;  //which octave is displayed

boolean playMode = false;  //false: loope displayed pattern      true:  goes thru all 4 patterns.
int patternDisp = 0;

void setup()  {
  drum.begin(18);
  lead.begin(19);

  position = 0;

  for(int i = 2; i<=5; i++)   {
    pinMode(i, OUTPUT);
  }
  Serial.begin(9600);
  button1.write(HIGH);
  button2.write(HIGH);
  button3.write(HIGH);
  button4.write(HIGH);
  for(int i =0; i<=3; i++)  {
    beat[i][0] = REST;
    music[i][0] = REST;
    octaves[i][0] = 0;
    sharps[i][0] = false;
  }
}

Second part:

void loop()  {
  int cursorX;
  int cursorY;
  int fade;
  int tempo = 125; //time in ms between each step in the sequence
  unsigned long startTime = millis();
  while(millis() - startTime <= tempo)  {


    tempo = map(analogRead(0), 0, 1023, 50, 500);
    fade = analogRead(1);
    cursorX = constrain(map(analogRead(2), 0, 1023, -1, 16), 0, 15);
    cursorY = constrain(map(analogRead(3), 0, 1023, -1, 8), 0, 7);




    if( button1.update() == true && button1.read() == LOW)  {
      if(trackMode == true)  {
        if(cursorY+1 != beat[patternDisp][cursorX])  {
          beat[patternDisp][cursorX] = cursorY+1;
        }
        else {
          beat[patternDisp][cursorX] = REST;
        }
      }
      else if(trackMode == false)  {
        if(cursorY < 7)  {
          if(cursorY != music[patternDisp][cursorX])  {
            music[patternDisp][cursorX] = cursorY;
            octaves[patternDisp][cursorX] = octaveDisp;
          }
          else {
            music[patternDisp][cursorX] = REST;
          }
        }
        if(cursorY == 7 && sharps[patternDisp][cursorX] != true)  {
          sharps[patternDisp][cursorX] = true;
        }
        else  {
          sharps[patternDisp][cursorX] = false;
        }
      }
    }

    if( button3.update() == true && button3.read() == HIGH)  {
      if(held <= 500)  {
        if(trackMode == false)  {

          if(octaveDisp == 2)  {
            octaveDisp = 0;
          }
          else  {
            octaveDisp++;
          }
        }
      }
      else if(held > 500)  {
        trackMode = !trackMode;
      }
    }
    held = button3.duration();

    if( button4.update() == true && button4.read() == HIGH)  {
      if(held2 <= 500)  {

        if(patternDisp == 3)  {
          patternDisp = 0;
        }
        else  {
          patternDisp++;
        }
      }
      else if(held2 > 500)  {
        playMode = !playMode;
      }
    }
    held2 = button4.duration();


    /*
    DrumStep Values for each sound!
     ShiftScaler*   NoiseAmt  StartFreq  EndFreq  Duration**
     Bass          -3          0            400     100        0
     Snare          0          650          150     150        100
     HHatC          0          300          900     900        20
     HHatO          0          300          900     900        110
     Rise          10          0            200    1200        0
     Pew           -5          0           1200     500        0
     NoiseRise      9          600          200    1000        0
     NoisePew      -9          600         1000     200        0
     
     * positive: rising tone   negative: falling tone
     **  0 for no duration limit.
     
     */

    if(fade <= 682)  {
      switch(beat[pattern][position])  {
      case BASS:
        drumStep(startTime, -3, 0, 400, 100, 0);
        break;
      case SNARE:
        drumStep(startTime, 0, 650, 150, 150, 100);
        break;
      case HHATC:
        drumStep(startTime, 0, 300, 900, 900, 20);
        break;
      case HHATO:
        drumStep(startTime, 0, 300, 900, 900, 110);
        break;
      case RISE:
        drumStep(startTime, 10, 0, 200, 1200, 0);
        break;
      case PEW:
        drumStep(startTime, -5, 0, 1200, 500, 0);
        break;
      case NOISERISE:
        drumStep(startTime, 9, 600, 200, 1000, 0);
        break;
      case NOISEPEW:
        drumStep(startTime, -9, 600, 1000, 200, 0);
        break;

      default:
        drum.stop();
        break;
      }
    }
    else {
      drum.stop();
    }


    [glow]if(fade >= 341)  {
      if(music[pattern][position] != REST)  {
        if((position == 0 || notes[2*music[pattern][position]+sharps[pattern][position]]*(1<<octaves[pattern][position])) != (notes[2*music[pattern][position-1]+sharps[pattern][position]]*(1<<octaves[pattern][position-1])))  {
          lead.play(notes[2*music[pattern][position]+sharps[pattern][position]]*(1<<octaves[pattern][position]));
        }
      }
      else  {
        lead.stop();
      }
    }[/glow]
    else  {
      lead.stop();
    }



    //row byte generator
    row++;
    if(row==8)  {
      row=0;
    }
    byte rowByte = 1 << row;
    //red byte generator
    byte redCol1 =0;
    byte redCol2 =0;
    if(pattern == patternDisp)  {
      if(position <= 7)  {
        redCol1 = B10000000 >> position;
        redCol2 = B00000000;
      }
      else if(position >= 8)  {
        redCol2 = B10000000 >> (position - 8);
        redCol1 = B00000000;
      }
    }


    if(row == cursorY)  {
      if(cursorX <= 7)  {
        redCol1 = redCol1 | (B10000000 >> cursorX);
      }
      else if(cursorX >= 8)  {
        redCol2 = redCol2 | (B10000000 >> (cursorX - 8));
      }
    }


    byte greenCol1 = 0;
    byte greenCol2 = 0;
    if(trackMode == true)  {

      for(int i = 0; i <=7; i++)  {
        if(row+1 == beat[patternDisp][i])  {
          greenCol1 = greenCol1 | (B10000000 >> i);
        }

      }


      for(int i = 0; i <=7; i++)  {
        if(row+1 == beat[patternDisp][i+8])  {
          greenCol2 = greenCol2 | (B10000000 >> i);
        }
      }

    }
    else  {

      for(int i = 0; i <=7; i++)  {
        if(row == music[patternDisp][i] && octaves[patternDisp][i] == octaveDisp)  {
          greenCol1 = greenCol1 | (B10000000 >> i);
        }
        if(row == 7 && sharps[patternDisp][i] == true && octaves[patternDisp][i] == octaveDisp)  {
          greenCol1 = greenCol1 | (B10000000 >> i);
        }

      }


      for(int i = 0; i <=7; i++)  {
        if(row == music[patternDisp][i+8] && octaves[patternDisp][i+8] == octaveDisp)  {
          greenCol2 = greenCol2 | (B10000000 >> i);
        }
        if(row == 7 && sharps[patternDisp][i+8] == true && octaves[patternDisp][i+8] == octaveDisp)  {
          greenCol2 = greenCol2 | (B10000000 >> i);
        }
      }
    }

    digitalWrite(latch1, LOW);
    shiftSwap(rowByte, MSBFIRST);
    shiftSwap(greenCol1, LSBFIRST);
    shiftSwap(redCol1, LSBFIRST);
    digitalWrite(latch1, HIGH);
    digitalWrite(latch2, LOW);
    shiftSwap(rowByte, MSBFIRST);
    shiftSwap(greenCol2, LSBFIRST);
    shiftSwap(redCol2, LSBFIRST);
    digitalWrite(latch2, HIGH);

  }



  if(position < 15)  {
    position++;
  }
  else  {
    if(playMode == true)  {
      if(pattern < 3)  {
        pattern++;
      }
      else  {
        pattern = 0;
      }

    }
    else  {
      pattern = patternDisp;
    }
    position = 0;
  }
}



//==================Sounds Steps=======================

void drumStep(unsigned long startTime, int shiftScaler, int noiseAmt, int startFreq, int endFreq, int duration)  {
  int freq = startFreq + (shiftScaler*(millis() - startTime));
  if(shiftScaler < 0)   {
    if(freq>=endFreq && (millis() - startTime <= duration || duration == 0))  {
      drum.play(freq + random(0, noiseAmt));
    }
    else  {
      drum.stop();
    }
  }  
  else  {
    if(freq<=endFreq && (millis() - startTime <= duration || duration == 0))  {
      drum.play(freq + random(0, noiseAmt));
    }
    else  {
      drum.stop();
    }
  }
}





//============================Misc Functions=========================

void shiftSwap(byte value, int byteOrder)  {
  byte newByte = ((value << 4) & 0xf0) | ((value >> 4) & 0x0f);
  shiftOut(dataPin, clockPin, byteOrder, newByte);
}

Something tells me the highlighted part and many other parts could be optimized...
:o

That is so ridiculously bad ass. I have no words. :smiley:

Now give it midi output!

Midi was one of my ideas to expand this, so maybe sometime!
Other ideas:
Touchscreen (translation: Superbad-a$$)
External clock signal for tempo (so I could use more than one and they're synced)
Custom drum sounds customizable in real-time
Attack, release, adjustable velocity (does anyone know if there is a serial controlled envelope controller IC? Just wondering)

There is this awesome avr SID:
http://www.swinkels.tvtom.pl/swinsid/

Can you show how did you connect the speaker to the arduino?

-------Collector------5v ---///-----DigitalPin
_ / /
(_|--Base-------------------------*---///----DigitalPin
^ \
| --Emitter---------(Speaker)----------Gnd
|
2n2222 transistor

Resistors are 330ohm