Mittwoch, 9. März 2016

Arduino Beatstept Extension

-1. TLDR 

A project using the Arduino to extend the number of trigger outputs of the Arturia Beatstep pro as well as adding a randomizing clock generator for use in an Eurorack modular synth.

0. Overview

I've purchased the Arturia Beatstep pro some time ago and I thought that it worked real wonders for my Eurorack setup. But having more than eight different drum instruments, leaves you with a few more trigger outputs to wish for. Since I think that I'm not the only one with this problem, some others might profit from the stuff I made in the workshop in the last few days. While I was at it, I also added a little extra: There are four outputs for random clock signals and three knobs to adjust the randomness of these outputs.

1. Explanation of what I wanted

Let's begin with the basics. I wanted a few extra trigger outputs for the Beatstep pro. Since it only provides eight analog trigger outputs but sends all triggers to the midi out, it is possible to send the midi output to an Arduino that generates +5v trigger signals. The Arduino provides 13 digital output pins, so why not up the ante and generate a few extra outputs. After summing up what I needed, I came up with this output usage:

Digital Pin 0: TX -> Reserved for MIDI output
Digital Pin 1: RX -> Needed for MIDI input
Digital Pin 2-13: Trigger 2-12

The schematic looks like this:

So why the extra 4 triggers? The Beatstep Pro also transmits MIDI clock signals. These could be used to add some extra features to this otherwise rather boring project. So two outputs should generate clocked trigges and the other two generate random trigger signals.

There is only one thing that's missing. There is no protection circuit for the inputs. This means that you could endanger the arduino by sending modular synth level signals into these outputs. I left it out in order to make the circuit a bit simpler.

2. Adding pots and switches

I've decided to add a few pots and switches in order to control the density and randomness of the created trigger events. One of the switches can also be used to store the last bar of randomly generated triggers.

This is also one of the simples things to do with an arduino. If you don't understand this part of the schematics, I recommend you to check out these basic tutorials:

https://www.arduino.cc/en/tutorial/potentiometer
and
https://www.arduino.cc/en/Tutorial/Button

Again, just for the sake of completeness, here's the corresponding part of my schematics:

3. Adding the MIDI Input


I also wanted the Arduino to receive MIDI input from the beatstep. Therefor I've added a simple midi Input via a 6N138 Optocoupler. Schematics for this are available from almost any Arduino tutorial page about MIDI. Just for completeness I'll post a picture of what it looked like in my circuit diagram:

4. Adding LEDs to the trigger outputs. 

If you are interested, here's the complete schematic:




5. Writing the code:

In order to keep things clean and easy to understand, I separated the code into six tabs in the Arduino IDE. Here I'll go through each of them independently. Some I'll explain a little more, others less. Don't forget that you can write me an email if you have some questions about the code!
Also check out the code on GitHub: https://github.com/IvoFrancx/BeatstepExtension

5.1 Main tab

The code starts with some MIDI handling. As you can see in the following code bit, I've added the standard MIDI libraries, created the default midi instance and set the handles in the setup() function.

There are three floats that I define for the knobs and two booleans for the switches.

in the setup() I first set the pins 2 to 14 to output mode. The next part are the MIDI handles. First the the MIDI.begin() function is called. After that there are handles for NoteOn, NoteOff and the clock (including start, stop and continue).

After this, the random seed is randomized. For some detailed information on why this is important, refer to this page. Also a small startupAnimation is run through the pins so that there is some funky blinkenlichten when the unit is powered up. You can see the code for the animation in one of the next parts.

At last the loop() function. There are only two things here. First the MIDI.read() function that reads midi messages from the serial port (RX). And the readVoltages() function I defined to store the position of the knobs and switches to the corresponding variables (k1, k2, k3, s1, s2).

#include <MIDI.h>
#include <midi_Defs.h>
#include <midi_Message.h>
#include <midi_Namespace.h>
#include <midi_Settings.h>

MIDI_CREATE_DEFAULT_INSTANCE();

float k1;  //KNOB 1
float k2;  //KNOB 2
float k3;  //KNOB 3
boolean s1;  //SWITCH 1
boolean s2;  //SWITCH 2


////////////////////////////////////////////////////////////////////////////SETUP

void setup() {
  for (int i=2; i<14; i++) {
    pinMode(i, OUTPUT);
  }
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandleClock(handleClock);
  MIDI.setHandleStart(handleStart);
  MIDI.setHandleStop(handleStop);
  MIDI.setHandleContinue(handleContinue);
  randomSeed(analogRead(A7));
  startupAnimation();
}

////////////////////////////////////////////////////////////////////////////LOOP
void loop() {
  MIDI.read();
  readVoltages();
}

5.2 ClockHandling


This next part of the Code is a little more complicated. Here's the part where all the clock processing happens. It's ist not so crucial for the trigger outs, but for the randomized clock outs.
What happens here, is that the code basically increments the variable clck by 1 each time a midi clock signal is received. Thanks to the MIDI library and the MIDI.setHandleClock(handleClock) called in the setup(), void handleClock is called for every clock tick. After 96 ticks the the clock is reset to zero. The pulseDeterminator() calculates the probability for a hit according to the positions of the potentiometers and the current position in the bar. Lastly the read- and writeRhythmArray() function stores the last hits into an Array, or reads from it.


////////////////////////////////////////////////////////////////////////////CLOCK
int clck;
float ckDiv = 1;
boolean rhythmArray[96];

void handleClock() {
    
  if (clck == 96) clck = 0;    ///// Reset after one bar
  
  ClockOuts(clck);

  /////RANDOM OUT 1:
  if      ( s1) readRhythmArray ();
  else if (!s1) writeRhythmArray();
  
  clck++;                      //// increment the global Midi Clock (96 ticks)
}

boolean pulseDeterminator() {
  /* 
      k1 determines the global probability of a hit
      k2 determines the probability in relation to the distance of the last quarter
      if k2 is    0 -> Only hit on quarter notes will be played
      if k2 is 1023 -> all hits have the same probabiltiy
  */
  float randHit = random(0,1024);
  setCkDiv(k3);
  if (randHit <= k1 && 
       int(((clck%int(24.0*ckDiv))*(k2/1024.0))/ckDiv) <= 
       random(0,12*ckDiv)) return true;
  else return false;
}

void readRhythmArray() {
  if (rhythmArray[clck]) digitalWrite(10,HIGH);
  else if (!rhythmArray[clck]) digitalWrite(10,LOW);
}

void writeRhythmArray() {
  if (pulseDeterminator()) {
    digitalWrite(10,HIGH);
    rhythmArray[clck] = true;
  }
  else {
    digitalWrite(10,LOW);
    rhythmArray[clck] = false;
  }
}

///// Clock Divider (At Knob 3)
void setCkDiv(float knob) {
  if (knob < 200) ckDiv = 4;
  else if (knob >= 200 && knob < 400)  ckDiv = 2;
  else if (knob >= 400 && knob < 600) ckDiv = 1;
  else if (knob >= 600 && knob < 800) ckDiv = 0.5;
  else if (knob >= 800) ckDiv = 0.25;
}

5.3 ClockOuts

This third tab is just the definition of the other three clock outs. I decidecd to have one out with steady quarter ticks (for that four on the floor goodness). Another one with ticks that correlate to the setting of potentiometer 3 (all the way counter clockwise is 1/32 notes, all the way clockwise is one tick per bar). The third output just randomly ticks every other 16th note.


void ClockOuts(int clck) {

  ////////////Output 12 || Quarter ticks
  if (clck % 24 == 0) digitalWrite(13, HIGH);    //Set LED HIGH on 1/4 notes
  else if (clck % 24 == 3)digitalWrite(13, LOW); //Set LED LOW after 3/32 notes.

  ////////////Output 11 || Clicks with Clock Divider
  if (clck%int(24.0*ckDiv) == 0) digitalWrite(12, HIGH);    // same as before   
  else if (clck%int(24.0*ckDiv) == 3)digitalWrite(12, LOW); // only with clock division

  ////////////Output 10 || Random ticks (16ths)
  int rndm = (int)random(0,2);
  if (clck % 6 == 0) {
    if (rndm == 1) digitalWrite(11,HIGH);
  }
  else if (clck % 6 == 3) {
    if (rndm == 0) digitalWrite(11,LOW);
  }
}

5.4 readVoltages

Here the voltages are read. The function is called in the loop() part of this sketch. Not much to explain here. Pretty basic stuff...


void readVoltages() {
  k1 = analogRead(A0);  // Knob 1 (General frequency of occurrence)
  k2 = analogRead(A1);  // Knob 2 (Frequency of occurrence near Clock Div setting)
  k3 = analogRead(A2);  // Knob 3 (Clock divider setting (1/2, 1/4, 1/16, 1/32)
  if      (analogRead(A3)>= 512) s1 = true;   //Switch 1 (Store/Read last Bar)
  else if (analogRead(A3)<  512) s1 = false;
  if      (analogRead(A4)>= 512) s2 = true;   //Switch 2 (Not used yet)
  else if (analogRead(A4)<  512) s2 = false;
}

5.5 Midi handles

The functions corresponding to the handles that were defined in the setup() are called in this part of the sketch. So for example, if I call MIDI.setHandleNoteOn(myHandleNoteOn) in the setup, the function myHandleNoteOn is called once MIDI.read() receives a note on signal.
I've replicated the input to the output just to be shure that the incoming midi is sent back out to the output. Usually this should be done automatically unless the function MIDI.turnThruOff() is called in the setup. The Beatstep pro sends it's midi values on channel 10. You can change this through the Midi Control Center software, but I just left it the way it was.


////////////////////////////////////////////////////////////////////////////NOTE ON
void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) {
  MIDI.sendNoteOn(inNote, inVelocity, inChannel); //to repllicate the input (thru)
  if (inVelocity > 0 && inChannel == 10) {
    if      (inNote == 44) digitalWrite(2 , HIGH);
    else if (inNote == 45) digitalWrite(3 , HIGH);
    else if (inNote == 46) digitalWrite(4 , HIGH);
    else if (inNote == 47) digitalWrite(5 , HIGH);
    else if (inNote == 48) digitalWrite(6 , HIGH); 
    else if (inNote == 49) digitalWrite(7 , HIGH);
    else if (inNote == 50) digitalWrite(8 , HIGH);
    else if (inNote == 51) digitalWrite(9 , HIGH);
  }
}

////////////////////////////////////////////////////////////////////////////NOTE OFF
void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) {
  MIDI.sendNoteOff(inNote, inVelocity, inChannel);  //to repllicate the input (thru)
  if (inChannel == 10) { 
    if      (inNote == 44) digitalWrite(2 , LOW);
    else if (inNote == 45) digitalWrite(3 , LOW);
    else if (inNote == 46) digitalWrite(4 , LOW);
    else if (inNote == 47) digitalWrite(5 , LOW);
    else if (inNote == 48) digitalWrite(6 , LOW); 
    else if (inNote == 49) digitalWrite(7 , LOW);
    else if (inNote == 50) digitalWrite(8 , LOW);
    else if (inNote == 51) digitalWrite(9 , LOW);
  }
}

/////////////////////////////////////////////////////////////////////////START & STOP
boolean playFlag;

void handleStart() {
  playFlag = true;
  clck = 0;
}

void handleStop() {
  playFlag = false;
  for (int i=2; i<14; i++) {
    digitalWrite(i,LOW);
  }
}

void handleContinue() {
  playFlag = true;
}

5.6 Startup Animation

There's nothing wrong with a little bit of fun with LEDs. So lets poppen the korken an spitzen the sparken. It's time for mittengrabben, relaxen and watchen das blinkenlichten:


int animationDelay = 30;

void startupAnimation() {
    for (int rotationDelay=animationDelay;rotationDelay>0;rotationDelay--) {
    for (int i=2;i<13;i+=2) {
      digitalWrite(i,HIGH);
      digitalWrite(15-i,HIGH);
      delay(rotationDelay);
      digitalWrite(i,LOW);
      digitalWrite(15-i,LOW);
    }
  }
}

Once again, check out the code on GitHub: https://github.com/IvoFrancx/BeatstepExtension

6. Finishing up the panels

I used a milling cutter to get the holes just right. I think I'll make a new panel from one piece instead of two pieces next to each other. As soon as I get the aluminum panels I've ordered, I'll make a video of the milling process.
This is the back side of the unit. I'm really glad that this is hidden away when using the module. It got quite the mess during experimenting...