Wednesday, April 4, 2012

Arduino Midi to Trigger interface with random cv output



Today I completed V1.0 of my Arduino Midi - Trigger interface. Its nothing groundbreaking but a very useful module to give you up to 9 trigger outputs, a random cv output, clock input and midi control over all inputs. The inspiration for mine was watching some demo's of the amazing drum dokta module from Din Sync. I picked one of these up and they sound killer but I needed a useful and easy way to trigger the sounds. The module is basically 2 parts. One is a clock divider inspired somewaht by the 4ms module, the other is a simple midi implementation. As it stands, triggers 1-6 as well as the random cv trigger are controlled by midi notes 60-66. This is easily editable in the code and more triggers can be added. Below is a diagram of the connections and the code follows. Hopefully someone will find this useful and help me to improve it!




One other important thing to note, this program uses the Arduino Midi library which can be found here.
Code below:

//////////////////////////////////////////////////////////
#include "MIDI.h"

// Modular utility controller
// Clock divider, random CV generator, Midi to trigger
// Rev 01. 4-2012 Travis Thatcher recompas@gmail.com

// midi values
#define OFF 1
#define ON 2
#define WAIT 3

boolean DEBUG = false; // to turn off all debug print messages

int resetPin = A0; // analog input used for reset button
int resetValue = 0;
int resetStart = 0;
boolean resetState = false;

int pwmPin = 5; // pin to output cv
int sequence[16]; // array to hold sequnce for pwm out
int counter=0; // counter for pwm sequence array

int triggerPins[] = {2, 4, 10, 11, 12, 13}; // trigger pins
int triggers[] = {0, 0, 0, 0, 0, 0}; // hold trigger counts
int triggerDivs[] = {4, 8, 1, 2, 16, 4}; // these set up the clock divisions for eachoutput
int triggerTimes[] = {0, 0, 0, 0, 0, 0}; // hold times for triggers
int triggerStates[] = {LOW, LOW, LOW, LOW, LOW, LOW};
int triggerTime = 1;

int currentTime=0;

byte incomingByte;
byte velocity;
int action = 2; //1 =note off ; 2=note on ; 3= wait


void setup() {

MIDI.begin(2); // i need to use channel 2 since i use the midi thru to send data do my doepfer midi to cv on channel 1
randomSeed(analogRead(0)); // lets get random
// setup output pins
pinMode(pwmPin, OUTPUT);
pinMode(2, OUTPUT);
pinMode(4, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
resetStart = millis();
shuffle();
if(DEBUG){
Serial.println("hello!");
}
attachInterrupt(1, trigger, RISING);


}

void loop() {
if (MIDI.read()) { // Is there a MIDI message incoming ?
switch(MIDI.getType()) { // Get the type of the message we caught
case NoteOn: { // If it is a note on
playNote(MIDI.getData1());
break;
}

default:
break;
}
}

//Handle reset processing
resetValue = analogRead(resetPin);
currentTime = millis();

if(resetValue >= 200){ // arbitrary, just making sure the button is pushed. anything greater than 100 would probably work

if(resetState == false){
resetState = true;
shuffle();
resetBeat();
resetStart = currentTime;
if(DEBUG){
Serial.println("resetting");
}
}
else if(currentTime - resetStart>=200){
resetState = false;
}
}

// check out the trigger states
for(int i=0; i<6; i++){
if(triggerStates[i]==HIGH){
if(currentTime - triggerTimes[i] > triggerTime){
if(DEBUG){
Serial.println("turning off!");
}
digitalWrite(triggerPins[i], LOW);
triggerStates[i]=LOW;
}
}
}

}

void shuffle(){
for(int i=0; i<16; i++){
sequence[i]=random(0,255);
if(DEBUG){
Serial.println(sequence[i], DEC);
}
}

// reset beats

for(int j=0; j<6; j++){
triggers[j]=0;
digitalWrite(triggerPins[j], LOW);
triggerStates[j]=LOW;
}
trigger();

}

void resetBeat(){
counter=0;
}

void trigger(){
noInterrupts();
if(DEBUG){
Serial.println("triggering");
}

analogWrite(pwmPin, sequence[counter]);
counter++;
if(counter == 16){
counter = 0;
}

//trigger outputs
for(int i=0; i<6; i++){
triggers[i]++;
if(triggerDivs[i]==triggers[i]){
digitalWrite(triggerPins[i], HIGH);
triggerStates[i] = HIGH;
triggerTimes[i] = currentTime;
triggers[i]=0;
}else{

}
interrupts();
}
}

void playNote(byte note){
noInterrupts();
if(DEBUG){
Serial.println(note);
}

// funkee random bits
if(note-60==6){
analogWrite(pwmPin, sequence[counter]);
counter++;
if(counter == 16){
counter = 0;
}
}


// triggers!
for(int i=0; i<6; i++){

if(note-60==i){
digitalWrite(triggerPins[i], HIGH);
triggerStates[i] = HIGH;
triggerTimes[i] = currentTime;
triggers[i]=0;
action = 2;
}else{

}

interrupts();

}
}

9 comments:

Max said...

Great, I also want trigger I/O for Arduino! What is the circuit for your trigger outputs? Do you need a resistor or anything? Ground?

Unknown said...

I just took the output straight from the digital pins. It should be a 5v signal. I suppose it would be best to buffer each trigger signal - and I'd do that for a more robust module if I wind up making another one.

Max said...

Do you have any issues without the buffer?

Unknown said...

Not that I've found. Can't really break anything without it. If you end up having problems, just add some buffers.

Max said...

I will try! Thanks!

apcnt said...

I'm also looking for a useful way to trigger my Simmons and other stuff. Does your method enable to adjust trigger length via midi? As i found that triggering drummachines with a regular audio output while altering the length of the sample can give very nice results... Any idea how to accomplish this?

apcnt said...

I'm also looking for a useful way to trigger my Simmons and other stuff. Does your method enable to adjust trigger length via midi? As i found that triggering drummachines with a regular audio output while altering the length of the sample can give very nice results... Any idea how to accomplish this?

Unknown said...

The code doesn't currently support changing the trigger length via midi, however it wouldn't be too hard to implement a basic trigger length knob or midi cc. Personally I'd read in an analog signal from a pot and use that to set the gate length variable, triggerTime.

manvsmachine said...

The code needs a few modifications to work on the current arduino ide.

//////////////////////////////////////////////////////////
#include
using namespace midi;
MIDI_CREATE_DEFAULT_INSTANCE();
// Modular utility controller
// Clock divider, random CV generator, Midi to trigger
// Rev 01. 4-2012 Travis Thatcher recompas@gmail.com

// midi values
#define OFF 1
#define ON 2
#define WAIT 3

boolean DEBUG = false; // to turn off all debug print messages

int resetPin = A0; // analog input used for reset button
int resetValue = 0;
int resetStart = 0;
boolean resetState = false;

int pwmPin = 5; // pin to output cv
int sequence[16]; // array to hold sequnce for pwm out
int counter=0; // counter for pwm sequence array

int triggerPins[] = {2, 4, 10, 11, 12, 13}; // trigger pins
int triggers[] = {0, 0, 0, 0, 0, 0}; // hold trigger counts
int triggerDivs[] = {4, 8, 1, 2, 16, 4}; // these set up the clock divisions for eachoutput
int triggerTimes[] = {0, 0, 0, 0, 0, 0}; // hold times for triggers
int triggerStates[] = {LOW, LOW, LOW, LOW, LOW, LOW};
int triggerTime = 1;

int currentTime=0;

byte incomingByte;
byte velocity;
int action = 2; //1 =note off ; 2=note on ; 3= wait


void setup()
{

MIDI.begin(2); // i need to use channel 2 since i use the midi thru to send data do my doepfer midi to cv on channel 1
randomSeed(analogRead(0)); // lets get random
// setup output pins
pinMode(pwmPin, OUTPUT);
pinMode(2, OUTPUT);
pinMode(4, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
resetStart = millis();
shuffle();
if(DEBUG){
Serial.println("hello!");
}
attachInterrupt(1, trigger, RISING);


}

void loop()
{
if (MIDI.read()) { // Is there a MIDI message incoming ?
switch(MIDI.getType()) // Get the type of the message we caught
{
case NoteOn:
{ // If it is a note on
playNote(MIDI.getData1());
break;
}

default:
break;
}
}

//Handle reset processing
resetValue = analogRead(resetPin);
currentTime = millis();

if(resetValue >= 200){ // arbitrary, just making sure the button is pushed. anything greater than 100 would probably work

if(resetState == false){
resetState = true;
shuffle();
resetBeat();
resetStart = currentTime;
if(DEBUG){
Serial.println("resetting");
}
}
else if(currentTime - resetStart>=200){
resetState = false;
}
}

// check out the trigger states
for(int i=0; i<6; i++){
if(triggerStates[i]==HIGH){
if(currentTime - triggerTimes[i] > triggerTime){
if(DEBUG){
Serial.println("turning off!");
}
digitalWrite(triggerPins[i], LOW);
triggerStates[i]=LOW;
}
}
}

}

void shuffle(){
for(int i=0; i<16; i++){
sequence[i]=random(0,255);
if(DEBUG){
Serial.println(sequence[i], DEC);
}
}

// reset beats

for(int j=0; j<6; j++){
triggers[j]=0;
digitalWrite(triggerPins[j], LOW);
triggerStates[j]=LOW;
}
trigger();

}

void resetBeat(){
counter=0;
}

void trigger(){
noInterrupts();
if(DEBUG){
Serial.println("triggering");
}

analogWrite(pwmPin, sequence[counter]);
counter++;
if(counter == 16){
counter = 0;
}

//trigger outputs
for(int i=0; i<6; i++){
triggers[i]++;
if(triggerDivs[i]==triggers[i]){
digitalWrite(triggerPins[i], HIGH);
triggerStates[i] = HIGH;
triggerTimes[i] = currentTime;
triggers[i]=0;
}else{

}
interrupts();
}
}

void playNote(byte note){
noInterrupts();
if(DEBUG){
Serial.println(note);
}

// funkee random bits
if(note-60==6){
analogWrite(pwmPin, sequence[counter]);
counter++;
if(counter == 16){
counter = 0;
}
}


// triggers!
for(int i=0; i<6; i++){

if(note-60==i){
digitalWrite(triggerPins[i], HIGH);
triggerStates[i] = HIGH;
triggerTimes[i] = currentTime;
triggers[i]=0;
action = 2;
}else{

}

interrupts();

}
}