Project showcase
Cthulhinho: A MIDI Controller

Cthulhinho: A MIDI Controller © GPL3+

Live performance oriented sequencer / arpeggiator for multiple synths control.

  • 2,497 views
  • 2 comments
  • 16 respects

Components and supplies

Ard nano
Arduino Nano R3
×1
128x64 I2C OLED display
×1
rotary encoder with push
with 5 pins: CLK, DT, SW, +, GND
×1
button
any momentary switch button will do, I used arcade ones
×6
09590 01
LED (generic)
×2
din 5 midi female jack
×2
two pole two way switch
×1
220 ohm resistor
×6
1N914 diode
×1
6N138 optocoupler
×1

About this project

Cthulhinho is an Arduino based MIDI controller for multiple devices primarily for live performance. The idea was to create a device that would keep my hands off synthesizers' keyobards since that takes too much time and effort and I'm not good with keys anyway, I much rather fiddle with knobs and faders and I also don't have eight hands. It's in a prototype stage (rat's nest of wires enclosed in a homemade wooden box), but fully functional!

Some monsters from the Deep do have more than two hands and this little beast, Cthulhinho (hence the name, and I also can't move past the Duda dude) can control up to 5 synthesizers, but the most common setup is one lead, one bass and one chords unit. Chords (and their respective arps) can be changed via large arcade type buttons, so you can't miss them in the heat of the battle. One can switch to song mode, where chords can be chained into parts and those parts into a whole song.

Chords can be edited via GUI or recorded from an external MIDI keyboard. There are two tracks reserved for CC messages which can be addressed to any MIDI channel. Synth units are attached to the MIDI out through some splitter (I use the banana split from 64px). In essence, you can offload the melody part of your performance to Cthulhinho and focus on live sound sculpting and manupulation, adjusting effects, etc., rather than messing with keyboards.

Future plans include an eurorack CV implementation, more arp patterns like meandering, more tactile user interface with less menu diving, but that also means bigger board and more knobs and I'm not that much into soldering, really. On current specs but not implemented yet: save&load to EEPROM, and some minor adjustments like 5-note chords, bass octave offset, improving features...

There's more details here: http://sinistersystems.com/cthulhinho/#gui

And a demo video (sorry about the messy table):

Code

working prototypeC/C++
It includes the standard midi library found here:
https://github.com/FortySevenEffects/arduino_midi_library.

And a modified display Ascii library here: https://github.com/greiman/SSD1306Ascii.
Where I modified some lines to achieve an inverted mode (selected gui items).
https://github.com/greiman/SSD1306Ascii/issues/29
You can download the modified library here:
http://sinistersystems.com/cthulhinho/SSD1306Ascii.zip
or modify the above one with my lines.
/* GUI


/--------------------\   /--------------------\
| SEQ arp chrds glob |   | seq ARP chrds glob |
|--------------------|   |--------------------| // [empty] = skip;
| pA:1112            |   |pos:12345678rR      | // r=rand(1,4); R=rand(1,8);  5-8 = 1-4 + oct; 
| pB:12331234        |   |vol:0123456789FrR   | // F=100%, r=rand(0,50); R=rand(50,100);
| pC:55665644        |   |gat:123456789FrRT   | // F=100%; r=rand(0,50); R=rand(50,100); T=tie;       _
|                    |   |oct:BA012rR         | // B=-2; A=-1; r=rand(-1,1); R=rand(-2,2);
| SEQ:AAABABCA.(stop)|   |cc1:0123456789ABCDEF|
|                    |   |cc2:0123456789ABCDEF|
\--------------------/   \--------------------/
key combo: switch mode (A-select chord, B-select pattern)

/--------------------\  /--------------------\ 
| seq arp CHRDS glob |  | seq arp chrds GLOB | 
|--------------------|  |--------------------| 
| c1: a# A# G# D#    |  | BPM:128  PITCH:  0 |
| c2: g# G# G  D#    |  | bss:14V ch:1 dly:0 | 
| c3: d# A  G# G#    |  | cc1:72 ch:2        |  
| c4: c# E  G# C     |  | cc2:74 ch:2    S/L |
| c5: e  E# B  F     |  | pads ch:3          |
| c6: c# C  G  D#    |  | rst:1 seq-m:8      |
\--------------------/  \--------------------/
                          bass: 
                            1,R,A = chpos (R-random, S-current_arp_value, 1234-chord_note[PITCH* independent]);   *:pitch in glob
                            4 = step length (steps required to change to next);
                            V (yes/no) = instead of midi notes, send: cc to transpose [C-note-pattern] volca bass [default:true, fixed until needed otherwise]
*/

#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
boolean alwmidi = true; // for debugging monitor: false

#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
#define I2C_ADDRESS 0x3C
SSD1306AsciiAvrI2c display;
unsigned long scrsm = 0;
boolean lit = true;

// led
#define sled 10

// buttons
int debounceMili = 100;
#define b1 5
boolean b1s;
boolean b1last;
#define b2 6
boolean b2s;
boolean b2last;
#define b3 7
boolean b3s;
boolean b3last;
#define b4 8
boolean b4s;
boolean b4last;
// shift
#define shB 12
boolean shs;
// escape
#define exB 11
boolean eXs;
boolean eXlast;
unsigned long lastExMili;
// shift + b4 debounce (mode change)


// rotary encoder
#define outputA 4
#define outputB 3
#define button  2
const char ttable[7][4] = {
  {0x0, 0x2, 0x4,  0x0}, {0x3, 0x0, 0x1, 0x40},
  {0x3, 0x2, 0x0,  0x0}, {0x3, 0x2, 0x1,  0x0},
  {0x6, 0x0, 0x4,  0x0}, {0x6, 0x5, 0x0, 0x80},
  {0x6, 0x5, 0x4,  0x0},
};
/*const char ttable[6][4] = {
  {0x3 , 0x2, 0x1,  0x0}, {0x83, 0x0, 0x1,  0x0},
  {0x43, 0x2, 0x0,  0x0}, {0x3 , 0x5, 0x4,  0x0},
  {0x3 , 0x3, 0x4, 0x40}, {0x3 , 0x5, 0x3, 0x80}
};
*/
volatile char rotState = 0;

//int aState;
//int aLastState;
int buttonState;
int buttonLastState;

// midi
boolean arpnote = false;
byte commandByte;
byte noteByte;
byte velocityByte;
byte noteON = 144;
byte velocity = 127;
byte gate = 5;
boolean tie = false;
byte noteOFF = 128;// on ch1
byte curcor = 0;
byte notepress = 0;
byte michord[4] = {0, 0, 0, 0};
//
//String notes[24] = {
//  "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
/* 48   49   50    51   52   53    54   55    56   57    58   59 */    
//  "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"};
/* 60   61   62    63   64   65    66   67    68   69    70   71 */  

String notes[12] = {
  "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"};
/* 0    1     2    3     4    5    6     7    8     9    10   11 */    

byte chords[6][4] = { // http://www.pianochord.org 
  {50, 62, 65, 69},
  {48, 60, 64, 67},
  {55, 62, 67, 71},
  {58, 62, 65, 70},
  {58, 61, 65, 70},
  {53, 60, 65, 69}
};
byte ccord[4] = {50, 62, 65, 69};
byte oldcc[4] = {50, 62, 65, 69};

const String arpn[6] = {"pos", "vol", "gat", "oct", "cc1", "cc2"};
byte arpd[6][16] = {
  /* poss */ { 1,  4,  1,  2,  3,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0}, /* 0:skip; 9:rnd(1,4); 10:rnd(1,8); 5-8:1-4+okt; */
  /* vols */ {10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0}, /* 0:skip; 10:F:full; 11:rnd(0,half); 12:rnd(half,full); 13:silent */
  /* gats */ { 5,  2, 10,  7, 13, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0}, /* 0:skip; 10:F:full; 11:rnd(0,half); 12:rnd(half,full); 13:tie */
  /* octs */ { 3,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0}, /* 0:skip; 1:-24; 2:-12; 3:0; 4:+12; 5:+24; 6:rnd(-12,12); 7:rnd(-24,24); */
  /* cc1s */ { 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0}, /* 0:0; 10:F:full; 11:rnd(0,half); 12:rnd(half,full);*/
  /* cc2s */ { 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0}  /* 0:0; 10:F:full; 11:rnd(0,half); 12:rnd(half,full);*/
};

byte seqd[4][16] = {
  /* pA  */ {2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 1-6 chords; 0:skip; */
  /* pB  */ {4, 5, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 1-6 chords; 0:skip; */
  /* pC  */ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 1-6 chords; 0:skip; */
  /* SEQ */ {3, 3, 3, 1, 3, 3, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0}, /* 1:A; 2:B; 3:C; 0:skip; */
};

String menu[4] = {"seq", "arp", "chrds", "glob"};
int menuRot = 6;
char topMenuId = 3; // menuRot / 2;
char sideMenuId = -1;
char paramMenuId = -1;
char paramValue;
boolean refreshGUI = true;
boolean inTopMenu = true;
boolean inSideMenu = false;
boolean inParamMenu = false;
char transpose = 0;

/* volca bass power on while [mem], select, [rec] to confirm */
/* (cc: 43,44,45 - volca bass osc123 detune) // https://www.reddit.com/r/volcas/comments/39758s/midi_in_on_the_volca_bass_not_working/ */
char glob[16] = {
/*  0 */    0, /* tempo bpm: 0 = 128 */
/*  1 */    0, /* global transpose */
/*  2 */    1, /* bass chord note position 1-6 [1234RA] */
/*  3 */    4, /* bass step length [ticks] 1-8 */
/*  4 */    1, /* bass is volca (if 1: send cc transpose instead of noteON; display 1 as "V", 0 as " ") */
/*  5 */    4, /* bass midi channel */
/*  6 */    0, /* bass change delay [ticks] 0-8 */
/*  7 */   72, /* cc1 address */
/*  8 */    3, /* cc1 midi channel */
/*  9 */   74, /* cc2 address */
/* 10 */    3, /* cc2 midi channel */
/* 11 */    2, /* pads midi channel [sends full chord] */
/* 12 */    1, /* reset on chord change */
/* 13 */   16, /* seq measure length */
/* 14 */    0, /* reserved */
/* 15 */    0, /* reserved */
};

byte chrd = 0;
boolean seqchr = false;
boolean issong = false;
char prevT = 0;
byte arp_note_step   = 0;
byte arp_note_volume = 0;
byte arp_note_gate   = 0;
byte arp_note_octave = 0;
unsigned long tick;
unsigned long tc0 = 0;
byte prevNote = 0;
byte bass_tick = 0;
byte bassPrevNote = 0;
unsigned long t2;
boolean mayreset = true;
unsigned int seqt = 0;
unsigned char seqp = 0;
unsigned char songp = 0;
char seqrun = -1;


// /////////////////////////////////////////////////////////////////////////////
void setup() {
// /////////////////////////////////////////////////////////////////////////////
  // diag
  if (!alwmidi) Serial.begin(9600);
  //Serial.print("deb");
  //pinMode(13, OUTPUT);
  
  //Serial.begin(31250);
  if (alwmidi) {
    MIDI.begin(MIDI_CHANNEL_OMNI);  // Listen to all incoming messages
    MIDI.setHandleNoteOn(noteOn);
    MIDI.setHandleNoteOff(noteOff);
  }
  
  // display
  display.begin(&Adafruit128x64, I2C_ADDRESS);
  display.setFont(Adafruit5x7);
  display.clear();
  
  // splash placeholder
  display.set2X();
  display.println("");
  display.println("/\\(O.o)/\\");
  display.println("  / | \\");
  delay(3000);
  display.clear();
  display.set1X();
  
  
  // rotary encoder
  pinMode (outputA, INPUT);
  pinMode (outputB, INPUT);
  pinMode (button,  INPUT_PULLUP);
  buttonLastState = digitalRead(button);
  
  // led
  pinMode (sled, OUTPUT);
  
  // buttons
  pinMode (b1, INPUT_PULLUP);
  pinMode (b2, INPUT_PULLUP);
  pinMode (b3, INPUT_PULLUP);
  pinMode (b4, INPUT_PULLUP);
  pinMode (shB, INPUT_PULLUP);
  pinMode (exB, INPUT_PULLUP);
  
  // midi tempo
  processTempo();
  
  // first chord
  setCCord(0);
}


// /////////////////////////////////////////////////////////////////////////////
void loop() {
// /////////////////////////////////////////////////////////////////////////////
  processControls();
  processTick();
  if (refreshGUI) {
    drawGUI();
    refreshGUI = false;
  }
  if (lit) {
    if (millis() > (scrsm + 30000)) { // 30 seconds screensaver
      display.clear();
      lit = false;
    }
  }
  //checkMIDI();
  if (alwmidi) MIDI.read();
}

// /////////////////////////////////////////////////////////////////////////////
void processTempo() {
// /////////////////////////////////////////////////////////////////////////////
  tick = 15000000.0 / (float) getRealTempo(); // (tick = 1/4 of beat, 120 bpm: 1 tick = (60,000,000 micros / 4) / 120 = 125,000 micros)
}

// /////////////////////////////////////////////////////////////////////////////
int getRealTempo() {
// /////////////////////////////////////////////////////////////////////////////
  int tmp;
  if (glob[0] <= 60) tmp = 128 + glob[0];
  else tmp = 128 + (glob[0] * 2);
  return tmp;
}

// /////////////////////////////////////////////////////////////////////////////
void processTick() {
// /////////////////////////////////////////////////////////////////////////////
  t2 = micros();
  //
  // gate and ties
  if (((t2 - tc0) >= (tick * ((gate * 0.1) * 10) / 10) - 1) && (arpnote == true)) { // * 0.1 * 10 / 10 ???? fix this (probably some cause, but fix!)
    arpnote = false;
    if (alwmidi) {
      if (tie == false) {
        MIDI.sendControlChange(123, 127, 1); // all notes off
      }
    }
    mayreset = true;
  }
  //
  // note params
  if (t2 - tc0 >= tick) {
    tc0 = t2;
    //
    if (seqrun >= 0) {
      seqt++;
      if (seqt >= glob[13]) {
        seqt = 0;
        advSeq();
      }
    }
    //
    // note 9:rnd(1,4); 10:rnd(1,8); 5-8:1-4+oct;
    char octp = 0;
    char arpv = arpd[0][arp_note_step];
    if (arpv == 10) arpv = random(1, 9);
    else if (arpv == 9) arpv = random(1, 5);
    if (arpv >= 5) {
      arpv -= 4;
      octp = 12;
    }
    int playNote = octp + ccord[(arpv - 1)];
    //
    // velocity 10:F:full; 11:rnd(0,half); 12:rnd(half,full);
    if (arpd[1][arp_note_volume] == 11) velocity = random(64);
    else if (arpd[1][arp_note_volume] == 12) velocity = random(64, 127);
    else if (arpd[1][arp_note_volume] == 13) velocity = 0;
    else velocity = (int)(12.7 * arpd[1][arp_note_volume]);
    //
    // octave 0:skip; 1:-24; 2:-12; 3:0; 4:+12; 5:+24; 6:rnd(-12,12); 7:rnd(-24,24);
    if (arpd[3][arp_note_octave] == 6) {
      playNote += 12 * random(-1, 2);
    } else if (arpd[3][arp_note_octave] == 7) {
      playNote += 12 * random(-2, 3);
    } else {
      playNote += (arpd[3][arp_note_octave] - 3) * 12;
    }
    //
    if (alwmidi) {
      if (tie == false) {
        MIDI.sendControlChange(123, 127, 1);
        MIDI.sendNoteOn(playNote, velocity, 1); // inNoteNumber,  inVelocity, inChannel
      } else {
        MIDI.sendNoteOn(playNote, velocity, 1); // inNoteNumber,  inVelocity, inChannel
        MIDI.sendNoteOn(prevNote, 0, 1);
      }
      
    }
    tie = false;
    arpnote = true;
    //
    trigBass(playNote);
    //
    // progress arp note 0:skip; 
    do {
      arp_note_step++; 
    } while (arpd[0][arp_note_step] == 0);
    if (arp_note_step >= 16) arp_note_step = 0;
    //
    // progress arp volume 0:skip;
    do {
      arp_note_volume++; 
    } while (arpd[1][arp_note_volume] == 0);
    if (arp_note_volume >= 16) arp_note_volume = 0;
    //
    // progress arp octave 0: skip
    do {
      arp_note_octave++; 
    } while (arpd[3][arp_note_octave] == 0);
    if (arp_note_octave >= 16) arp_note_octave = 0;
    //
    // progress arp gate 0:skip; 10:F:full; 11:rnd(0,half); 12:rnd(half,full); 13:tie
    do {
      arp_note_gate++; 
    } while (arpd[2][arp_note_gate] == 0);
    if (arp_note_gate >= 16) arp_note_gate = 0;
    if (arpd[2][arp_note_gate] == 11) gate = random(5);
    else if (arpd[2][arp_note_gate] == 12) gate = random(5, 10);
    else if (arpd[2][arp_note_gate] == 13) {
      gate = 10;
      tie = true;
    }
    else gate = arpd[2][arp_note_gate];
    //
    prevNote = playNote;
  }
  
}

// /////////////////////////////////////////////////////////////////////////////
void trigBass(byte pitch) {
// /////////////////////////////////////////////////////////////////////////////
  /*  2 - bass chord note position 1-6 [1234RA] */
  /*  3 - bass step length [ticks] 1-8 */
  /*  4 - bass is volca (if 1: send cc transpose instead of noteON; display 1 as "V", 0 as " ") */
  /*  5 - bass midi channel */
  /*  6 - bass change delay [ticks] 0-8 */
  
  if (bass_tick == glob[6]) {
    if (glob[2] <= 4) {
      pitch = ccord[(glob[2] - 1)];
    } else if (glob[2] == 5) {
      pitch = ccord[(random(1, 5) - 1)];
    } // else pitch = function call, current arp value (glob-6)
    //
    if (glob[4] == 1) { // bass = volca bass CC detune [1=-12 .. 12=-1, 64=0, 115=+1 .. 126=+12]
      /* (c,0,64)|(c#,1,115)|(d,2,116)|(d#,3,117)|(e,4,118)|(f,5,119)|(f#,6,5)|(g,7,6)|(g#,8,7)|(a,9,8)|(a#,10,9)|(h,11,12)*/
      byte detune = pitch % 12;
      byte bcc = 0;
      if (detune == 0) bcc = 64;
      else if (detune < 6) bcc = 114 + detune;
      else bcc = detune + 1;
      if (alwmidi) {
        MIDI.sendControlChange(43, bcc, glob[5]);
        MIDI.sendControlChange(44, bcc, glob[5]);
        MIDI.sendControlChange(45, bcc, glob[5]);
      }
    } else { // bass = note
      if (alwmidi) {
        MIDI.sendNoteOn(bassPrevNote, 0, glob[5]); // inNoteNumber, inVelocity, inChannel
        MIDI.sendNoteOn(pitch, 127, glob[5]); // inNoteNumber,  inVelocity, inChannel
      }
      bassPrevNote = pitch;
    }
  }
  if (bass_tick >= glob[3]) {
    bass_tick = -1;
  }
  bass_tick++;
}

// /////////////////////////////////////////////////////////////////////////////
void drawGUI() {
// /////////////////////////////////////////////////////////////////////////////
  lit = true;
  scrsm = millis();
  drawMENU();
  drawPanel();
}

void drawPanel() {
  display.setCursor(0, 2);
  switch (topMenuId) {
    case 0:
      drawSEQ();
      break;
    case 1:
      drawARP();
      break;
    case 2:
      drawCHRDS();
      break;
    default: /* 3 */
      drawGLOB();
  }
}

// /////////////////////////////////////////////////////////////////////////////
void drawCHRDS() {
// /////////////////////////////////////////////////////////////////////////////
  String n;
  for (char i = 0; i < 6; i++) {
    if (sideMenuId == i) display.invert();
    display.print(" c"); display.print((int)i); display.print(":");
    if (sideMenuId == i) display.invert();
    for (char j = 0; j < 4; j++) {
      if (sideMenuId == i && paramMenuId == j) display.invert();
      n = notes[(chords[i][j] % 12)];
      if (n.length() < 2) n = n + " ";
      display.print(" " + (String)(int)(chords[i][j] / 12) + n);
      if (sideMenuId == i && paramMenuId == j) display.invert();
    }
    display.println("     ");
  }
}

// /////////////////////////////////////////////////////////////////////////////
void drawARP() {
// /////////////////////////////////////////////////////////////////////////////
  for (char i = 0; i < 6; i++) {
    if (sideMenuId == i) display.invert();
    display.print(arpn[i] + ":");
    if (sideMenuId == i) display.invert();
    for (char j = 0; j < 16; j++) {
      if (sideMenuId == i && paramMenuId == j) display.invert();
      //
      if (arpd[i][j] == 0 && i < 4) {
        display.print(" ");
      } else if ((arpd[i][j] == 9 && i == 0) || (arpd[i][j] == 6 && i == 3) || (arpd[i][j] == 11 && !(i == 0 || i == 3))) {
        display.print("r");
      } else if (arpd[i][j] == 10 && (i == 1 || i == 2 || i >= 4)) {
        display.print("F");
      } else if ((arpd[i][j] == 10 && i == 0) || (arpd[i][j] == 7 && i == 3) || (arpd[i][j] == 12 && !(i == 0 || i == 3))) {
        display.print("R");
      } else if (arpd[i][j] == 13) {
        display.print("-");
      } else if (i == 3) {
        String ctrld = " <(0)>";
        display.print(ctrld.charAt(arpd[i][j]));
      } else {
        display.print((int)arpd[i][j]);
      }
      if (sideMenuId == i && paramMenuId == j) display.invert();
    }
    display.println("   ");
  }
}

// /////////////////////////////////////////////////////////////////////////////
void drawSEQ() {
// /////////////////////////////////////////////////////////////////////////////
  if (sideMenuId == 0) display.invert();
  display.print(" pA:");
  if (sideMenuId == 0) display.invert();
  drawSEQp(0); 
  display.println();
  //
  if (sideMenuId == 1) display.invert();
  display.print(" pB:");
  if (sideMenuId == 1) display.invert();
  drawSEQp(1); 
  display.println();
  //
  if (sideMenuId == 2) display.invert();
  display.print(" pC:");
  if (sideMenuId == 2) display.invert();
  drawSEQp(2); 
  display.println();
  //
  display.println("                    ");
  //
  if (sideMenuId == 3) display.invert();
  display.print("SEQ:");
  if (sideMenuId == 3) display.invert();
  drawSEQp(3); 
  display.println();
  //
  display.println("                    ");
  //
}
void drawSEQp(char sqpi) {
  for (char i = 0; i < 16; i++) {
    if (sideMenuId == sqpi && paramMenuId == i) display.invert();
    switch (seqd[sqpi][i]) {
      case -1:
        display.print(".");
        break;
      case 0:
        display.print(" ");
        break;
      default:
        if (sqpi == 3) {
          switch (seqd[sqpi][i]) {
            case 1:
              display.print("A");
              break;
            case 2:
              display.print("B");
              break;
            case 3:
              display.print("C");
              break;
            default:
              display.print(" ");
          }
        } else {
          display.print(seqd[sqpi][i]);
        }
    }
    if (sideMenuId == sqpi && paramMenuId == i) display.invert();
  }
}

// /////////////////////////////////////////////////////////////////////////////
void drawGLOB() {
// /////////////////////////////////////////////////////////////////////////////
  drawGlobCtrl(" BPM:", getRealTempo(), 0, true, true);
  drawGlobCtrl("  PITCH:", (int)glob[1], 1, true, false);
  display.println("           ");
  //
  drawGlobCtrl(" bss:", (int)glob[2], 2, false, false);
  drawGlobCtrl("", (int)glob[3], 3, false, false);
  drawGlobCtrl("", (int)glob[4], 4, false, false);
  drawGlobCtrl(" ch:", (int)glob[5], 5, false, false);
  drawGlobCtrl(" dly:", (int)glob[6], 6, false, false);
  display.println("           ");
  //
  drawGlobCtrl(" cc1:", (int)glob[7], 7, false, false);
  drawGlobCtrl(" ch:", (int)glob[8], 8, false, false);
  display.println("           ");
  //
  drawGlobCtrl(" cc2:", (int)glob[9], 9, false, false);
  drawGlobCtrl(" ch:", (int)glob[10], 10, false, false);
  display.println("    S/L    ");
  //
  drawGlobCtrl(" pads ch:", (int)glob[11], 11, false, false);
  display.println("              ");
  //
  drawGlobCtrl(" rst:", (int)glob[12], 12, false, false);
  drawGlobCtrl(" seq-m:", (int)glob[13], 13, false, false);
  display.println("           ");
  
}
void drawGlobCtrl(String tit, int val, int i, boolean pre, boolean neg) {
  display.print(tit);
  if (paramMenuId == i) display.invert();
  if (pre) {
    if (val >= 0) {
      if (val < 100) display.print(" ");
      if (val < 10) display.print(" ");
    }
  }
  if (pre && val < 0 && val > -10) display.print(" ");
  display.print(val);
  if (paramMenuId == i) display.invert();
}

// /////////////////////////////////////////////////////////////////////////////
void drawMENU() {
// /////////////////////////////////////////////////////////////////////////////
  display.setCursor(0,0);
  for (char i = 0; i <= 3; i++) {
    display.print(" ");
    if (topMenuId == i) display.invert();
    display.print(menu[i]);
    if (topMenuId == i) display.invert();
  }
  display.setCursor(0,1);
  if (topMenuId < 2)
    display.print("====o---o---o---o---  ");
  else
    display.print("====================  ");
}


// /////////////////////////////////////////////////////////////////////////////
void setCCord(unsigned char i) {
// /////////////////////////////////////////////////////////////////////////////
  curcor = i;
  for (byte j = 0; j < 4; j++) {
    ccord[j] = chords[i][j] + glob[1];
  }
  changePadsChord();
  for (byte j = 0; j < 4; j++) {
    oldcc[j] = ccord[j];
  }
  notepress = 0;
}

// /////////////////////////////////////////////////////////////////////////////
void changePadsChord() {
// /////////////////////////////////////////////////////////////////////////////
  for (byte i = 0; i < 4; i++) {
    if (alwmidi) {
      MIDI.sendNoteOn(oldcc[i], 0, glob[11]); // inNoteNumber,  inVelocity, inChannel
    }
  }
  for (byte i = 0; i < 4; i++) {
    if (alwmidi) {
      MIDI.sendNoteOn(ccord[i], 127, glob[11]); // inNoteNumber,  inVelocity, inChannel
    }
  }
}

// /////////////////////////////////////////////////////////////////////////////
void transpCCord() {
// /////////////////////////////////////////////////////////////////////////////
  if (transpose > prevT) {
    transNoteCh(true);
  } else if (transpose < prevT) {
    transNoteCh(false);
  }
  prevT = transpose;
}
void transNoteCh(boolean min) {
  for (char i = 0; i < abs(transpose - prevT); i++) {
    findChExtr(min);
  }
}
void findChExtr(boolean min) {
  isort(ccord);
  if (min) {
    ccord[0] = ccord[0] + 12;
  } else {
    ccord[3] = ccord[3] - 12;
  }
}
void isort(unsigned char *a) {
  for (int i = 1; i < 4; ++i) {
    unsigned char j = a[i];
    int k;
    for (k = i - 1; (k >= 0) && (j < a[k]); k--) {
      a[k + 1] = a[k];
    }
    a[k + 1] = j;
  }
}

// /////////////////////////////////////////////////////////////////////////////
void seqStop() {
// /////////////////////////////////////////////////////////////////////////////
  seqrun = -1;
  issong = false;
}

// /////////////////////////////////////////////////////////////////////////////
void seqStart(unsigned char i) {
// /////////////////////////////////////////////////////////////////////////////
  seqt = 0; // seq counter
  seqp = 0; // seq position
  seqrun = i; // which part to play/cycle
  setCCord(seqd[seqrun][seqp] - 1);
  if (!alwmidi) Serial.print(seqd[seqrun][seqp]);
}

// /////////////////////////////////////////////////////////////////////////////
void songStart() {
// /////////////////////////////////////////////////////////////////////////////
  issong = true;
  songp = 0;
  if (!alwmidi) Serial.println(seqd[3][songp] - 1);
  seqStart(seqd[3][songp] - 1);
}

// /////////////////////////////////////////////////////////////////////////////
void advSeq() {
// /////////////////////////////////////////////////////////////////////////////
  seqp++;
  if (seqd[seqrun][seqp] == 0 || seqp >= 16) {
    if (issong) {
      seqrun = advSong();
      if (!alwmidi) Serial.println();
    }
    seqp = 0;
  }
  setCCord(seqd[seqrun][seqp] - 1);
  if (!alwmidi) Serial.print(seqd[seqrun][seqp]);
}

// /////////////////////////////////////////////////////////////////////////////
char advSong() {
// /////////////////////////////////////////////////////////////////////////////
  seqp = 0;
  songp++;
  if (seqd[3][songp] == 0 || songp >= 16) {
    songp = 0;
  }
  return (seqd[3][songp] - 1);
}

// /////////////////////////////////////////////////////////////////////////////
void processControls() {
// /////////////////////////////////////////////////////////////////////////////
  // buttons
  int knof = 0;
  shs = !digitalRead(shB); // shift
  //
  b1s = !digitalRead(b1);
  if (b1s != b1last) {
    if (b1s == 1 && millis() > (lastExMili + debounceMili)) {
      if (!alwmidi) Serial.println("B1moment");
      knof = 1;
      lastExMili = millis();
    }
    b1last = b1s;
  }
  b2s = !digitalRead(b2);
  if (b2s != b2last) {
    if (b2s == 1 && millis() > (lastExMili + debounceMili)) {
      if (!alwmidi) Serial.println("B2moment");
      knof = 2;
      lastExMili = millis();
    }
    b2last = b2s;
  }
  b3s = !digitalRead(b3);
  if (b3s != b3last) {
    if (b3s == 1 && millis() > (lastExMili + debounceMili)) {
      if (!alwmidi) Serial.println("B3moment");
      knof = 3;
      lastExMili = millis();
    }
    b3last = b3s;
  }
  b4s = !digitalRead(b4);
  if (b4s != b4last) {
    if (b4s == 1 && millis() > (lastExMili + debounceMili)) {
      if (!alwmidi) Serial.println("B4moment");
      knof = 4;
      lastExMili = millis();
    }
    b4last = b4s;
  }
  //
  if (!shs) {
    if (!seqchr) {
      if (knof > 0) {
        setCCord(knof - 1);
      }
    } else {
      if (knof == 4) songStart();
      else if (knof > 0) {
        issong = false;
        seqStart(knof - 1);
      }
    }
  } else {
    if (!seqchr && knof == 1) {
      seqStop();
      setCCord(4);
    } else if (!seqchr && knof == 2) {
      seqStop();
      setCCord(5);
    } else if (knof == 4) {
      if (mayreset) {
        tc0 = t2 - tick;
        mayreset = false;
        if (alwmidi) {
          MIDI.sendControlChange(123, 127, 1); // (inControlNumber, inControlValue, inChannel)
          // send also all notes off on pads and bass
        }
        bass_tick = 0;
      }
    }
    else if (knof == 3) swapChSq();
  }
  //
  processRotary();
}

// /////////////////////////////////////////////////////////////////////////////
void swapChSq() {
// /////////////////////////////////////////////////////////////////////////////
  if (!alwmidi) Serial.println("swapSeq");
  seqchr = !seqchr;
  if (!seqchr) seqStop();
  digitalWrite(sled, seqchr);
}

// /////////////////////////////////////////////////////////////////////////////
void processRotary() {
// /////////////////////////////////////////////////////////////////////////////
  // exit button
  eXs = digitalRead(exB);
  if (eXs != eXlast) {
    if (eXs == 0 && millis() > (lastExMili + debounceMili)) {
      rotMenu(0);
      if (!alwmidi) Serial.print("esc");
      lastExMili = millis();
    }
    eXlast = eXs;
  }
  // rotary button push
  buttonState = digitalRead(button);
  if (buttonState != buttonLastState) {
    if (buttonState == 0) {
      rotMenu(0);
    }
    buttonLastState = buttonState;
  }
  // rotary rotate
  char res = rotProc();
  if (res) {
    if (shs) { // shift pressed
      transpose += (res == 0x40 ? -1 : 1);
      transpCCord();
    } else {
      rotMenu(res == 0x40 ? -1 : 1);
    }
  }
}
char rotProc() {
  char pinstate = (digitalRead(outputB) << 1) | digitalRead(outputA);
  rotState = ttable[rotState & 0xf][pinstate];
  return (rotState & 0xc0);
}

// /////////////////////////////////////////////////////////////////////////////
void rotMenu(int dir) {
// /////////////////////////////////////////////////////////////////////////////
  menuRot += dir;
  int max;
  
  // =================================================
  if (inTopMenu) {
  // =================================================
    //
    // rot: __________________________ cycle top menu;
    max = 3;
    if (menuRot < 0) menuRot = max;
    if (menuRot > max) menuRot = 0;
    topMenuId = menuRot;// / 2;
    //
    // press: _____________________ go into side menu;
    if (buttonState == 0) {
      //Serial.println("enter side");
      inTopMenu = false;
      if (topMenuId == 3) {
        inParamMenu = true;
        paramMenuId = 0;
      } else {
        inSideMenu = true;
        sideMenuId = 0;
      }
      menuRot = 0;
    }
    //
    //esc: _______________________________ reset tick;
    if (eXs == 0) {
      // reset tick TODO
      // tc0 = micros(); //??
    }
    //
  // =================================================
  } else if (inSideMenu) {
  // =================================================
    //
    //rot: __________________________ cycle side menu;
    if (topMenuId == 0)      max = 3;  // pA pB pC SEQ;
    else if (topMenuId == 1) max = 5;  // pos vol gat oct cc1 cc2;
    else if (topMenuId == 2) max = 5;  // c1 c2 c3 c4 c5 c6;
    else if (topMenuId == 3) max = 15; // bpm ptc bss1 bss2 bss3 bss_ch bss_dly cc1 cc1ch cc2 cc2ch s l pad_ch rst trnsp;
    //max = (max - 1) * 2;
    if (menuRot < 0) menuRot = max;
    if (menuRot > max) menuRot = 0;
    sideMenuId = menuRot;// / 2;
    //
    // press: ____________________ go into param menu;
    if (buttonState == 0) {
      menuRot = 0;
      inSideMenu = false;
      if (topMenuId < 3) { // [GLOB] doesn't have side menu
        inParamMenu = true;
        paramMenuId = 0;
      } // else go to param edit [GLOB], since all in-menus are false
    }
    //
    //esc: _______________________ go back to topMenu;
    if (eXs == 0) {
      inSideMenu = false;
      //sideMenuId = 30;
      inTopMenu = true;
      menuRot = topMenuId;// * 2;
      sideMenuId = -1;
    }
    //
  // =================================================
  } else if (inParamMenu) { //sideMenu doesnt have to stay lit. ???????????????
  // =================================================
    //rot: _____________________________ cycle params;
    max = 16;
    if (topMenuId == 2) max = 3;
    //max = (max - 1) * 2;
    if (menuRot < 0) menuRot = max;
    if (menuRot > max) menuRot = 0;
    paramMenuId = menuRot;// / 2;
    //
    //press: _______________________ go to param edit;
    if (buttonState == 0) {
      menuRot = readParamMenuIdValue(paramMenuId);
      inParamMenu = false;
    }
    //
    //esc: go back to sideMenu (topMenu in case of GLOB);
    if (eXs == 0) {
      inParamMenu = false;
      paramMenuId = -1;
      if (topMenuId == 3) {
        inTopMenu = true;
        menuRot = topMenuId; // * 2;
      } else {
        inSideMenu = true;
        paramMenuId = -1;
        menuRot = sideMenuId; // * 2;
      }
    }
  // =================================================
  } else {                            // in param edit
  // =================================================
    //rot:  edit param (write to array);
    //
    // ///////////////////////////////////////////////
    if (topMenuId == 3) { /* GLOB */
    // ///////////////////////////////////////////////
      char vmax = 127; char vmin = -127;
      if (paramMenuId == 2) {
        vmax = 6;
        vmin = 1;
      } else if (paramMenuId == 4 || paramMenuId == 12) {
        vmax = 1;
        vmin = 0;
      } else if (paramMenuId == 3 || paramMenuId == 5 || paramMenuId == 8 || paramMenuId == 10 || paramMenuId == 11) {
        vmax = 8;
        vmin = 1;
      } else if (paramMenuId == 6) {
        vmax = 8;
...

This file has been truncated, please download it to see its full contents.

Schematics

main board
Arduino pins are colored red
Schematics 5hhcppp1pf

Comments

Similar projects you might like

Arduino Bluetooth Basic Tutorial

by Mayoogh Girish

  • 454,771 views
  • 42 comments
  • 238 respects

Home Automation Using Raspberry Pi 2 And Windows 10 IoT

Project tutorial by Anurag S. Vasanwala

  • 285,509 views
  • 95 comments
  • 671 respects

Security Access Using RFID Reader

by Aritro Mukherjee

  • 229,430 views
  • 38 comments
  • 237 respects

OpenCat

Project in progress by Team Petoi

  • 195,958 views
  • 154 comments
  • 1,362 respects
Add projectSign up / Login