Sunday, September 1, 2024

Arduino Teensy 4.1 MIDI Step Sequencer

 I had been working on a Teensy based Synthesizer project, but the one I was designing was too complex to manage the code. Luckily a friend of mine was also working on one, so I am using his.

But now, how do you play it, well you can plug a keyboard in and play it that way, or you can program a sequence in to a keyboard sequencer etc.

But I was sure there was a project for a small stand alone sequencer I could build.


And there was.  Simple DIY Electronic Music Projects has a ton of projects...including an Arduino based Step Sequencer.

I knew this was a terrific starting point.

Two areas for improvement...the MIDI channel was hard coded in, so if for whatever reason you changed your mind, you had to reprogram it.

Second the BPM rate was fixed...again, no way to change it without reprogrammming.

So I started with the base code and added my own... He (Kevin the author) wrote some well annotated code, so it was simple to add my code.

I added three things.

I added the ability to select a MIDI channel, I added the BPM rate selection...and I added a display so you could see your settings AND see the 'beat' as it went across the 8 notes.


I will list the parts of code that I added and explain them, then the whole code at the end.

First, display.

For the display I already had an Adafruit 8x16 matrix display on their LED Backpack.

I needed to be able to drive it and change the font...both were a bit of a challenge for me.

I edited both Adafruit_LEDBackpack.h and Adafruit_I2C.h from "&Wire" to "&Wire2" because I am using SCL2/SDA2 for I2C

#include <Adafruit_GFX.h>

#include "Adafruit_LEDBackpack.h" 

#include <Fonts/Picopixel.h>

Adafruit_8x16matrix matrix = Adafruit_8x16matrix();

 First two are needed to drive the hardware, third one changes the font to a smaller one (so everything fits on screen)

Fourth line defines the matrix as a display device (vs a tft or something else)


Next the code to assign the MIDI channel

int midiChannel;

int midird;

float bpmmath;

The first line I changed...the default code sets a fixed midi channel there.

next I needed some variables to track the midi setting and bpm setting


Now some setup to allow the user to set the midi channel during boot up (from my friend who made the synth)

unsigned long start_millis;  // use this variable to take a snapshot of

                             //    the current number of milliseconds

                             //    since the Teensy last booted


#define HOW_LONG_TO_WAIT_IN_MILLISECONDS  5000  // use this constant to specify how long

                                                //    you want to look/wait for something

                                                //    at startup: this defines 5-seconds

Next bit of code is added to voice setup so during the first 5 sec after boot you can select the midi channel.

 void setup() {

 //added code to drive matrix LED and the midi channel select code  

  matrix.begin(0x70);  // pass in the address.  default for led backplane

 Wire2.begin(); //in order to use SCL2/SDA2 you need Wire2 and need to change the Adafruit libraries listed above 

  // next section allows a delay for user to set midi channel using MIDI/BPM pot

  start_millis = millis();   // take a snapshot of the current number of

                              //    milliseconds since the Teensy booted

  while ((millis() - start_millis) < HOW_LONG_TO_WAIT_IN_MILLISECONDS)

   {

      // whatever you put here will be done repeatedly

      //    until your defined time in milliseconds expires

  midird = analogRead(A14); //reads pot for midi...later same pot used for BPM

  midiChannel = map(midird, 0, 1023, 1, 16); //map() converts the analog pot to 16 choices

  delay(50);

  matrix.setFont(&Picopixel); //make font smaller

  matrix.setTextSize(1);

  matrix.setTextWrap(true);  // we dont want text to wrap so it scrolls nicely

  matrix.setTextColor(LED_ON);

  matrix.setRotation(1); //if hookup wires are on left then 1...if on right then 3

    matrix.clear();

    matrix.setCursor(1,5);

    matrix.print("M ");

    matrix.print(midiChannel);

    matrix.writeDisplay();

    delay(50);

 }


All the rest of the code is in the void loop

Next on the list (as we go down the code)...I had a problem with the synth sending midi messages back through the usbMIDI and eventually locking up the synth...this can be disabled on the synth, but it made more sense to simply stop the controller from receiving the return info (that it doesn't need or want to operate)


void loop() {

   while (usbMIDI.read()) {

}

It just reads and discards or ignore inbound messages (I am not sure exactly which)...either way it solved the problem.

Now the additional code to read the input and set the delay for the appropriate BPM (beats per minute)

  //so I modified code to allow a pot to give the delay instead of fixed...it is set to run 1000ms to 150ms delay

  int bpmread  = analogRead(A14); 

  int bpm = map(bpmread, 2, 1015, 1000, 150);

  float bpmmath = 1.0 / (bpm / 1000.0) * 60.0; //probably a better way to do this, but it is what I came up with

  int bpmshow = bpmmath; //gives us a BPM vs a delay to display

 delay(bpm); // this is a code change from fixed delay to adjustable

   //this dispays bpm on matrix display

  matrix.setFont(&Picopixel); //sets the smaller font

  matrix.setTextSize(1);

  matrix.setTextWrap(true);  // we dont want text to wrap so it scrolls nicely

  matrix.setTextColor(LED_ON);

  matrix.setRotation(1);

    matrix.clear();

    matrix.setCursor(3,5);

    matrix.print(bpmshow);


And finally some code to flash an appropriate LED when a note in the sequence is played (1-8)

//tracks where in the sequence it is for use on display later in the code...

  int x = (((playingNote + 1) * 2) - 2); //so the first note (A0) lights the first led etc

  int y = 7;  //I didn't need to put this here...but it made sense to keep it all in one place

 matrix.drawPixel(x, y, LED_ON);  //uses variables from above to plot a dot at the current step position

  matrix.writeDisplay();  // this writes ALL the above info to the matrix LED display

 }

 Thats all the modifications...the wiring should be straight forward...but I will summarize here (again on my Teensy4.1)

A0-A7   -10k pots for the notes in the sequence

A14       -10k pot for MIDI/BPM adjustment

TX1/RX1  -(if you add a actual 5pin DIN MIDI port)

SCL2/SDA2  -for I2C to drive LED display

The 10k pots have 3.3v on one side, GND on the other side and the center pin goes to the A0-7,A14 pins for input.  DO NOT USE 5V.

On my pots I have GND on the 'left' (viewed from the top) pin or the pin you turn counter clockwise toward...

...3.3v is on the 'right' pin or the pin you turn clockwise toward.   

This way CCW is down and CW is up.

I am using the USB port to power it, and pull 5v off for a MIDI connector and the LED matrix display.

 

 

No comments: