primvla
Published © GPL3+

Cthulhinho: A MIDI Controller

Live performance oriented sequencer / arpeggiator for multiple synths control.

IntermediateShowcase (no instructions)10 hours1,463
Cthulhinho: A MIDI Controller

Things used in this project

Hardware components

Arduino Nano R3
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
LED (generic)
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

Story

Read more

Schematics

main board

Arduino pins are colored red
Schematics 5hhcppp1pf

Code

working prototype

C/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;
        vmin = 0;
      } else if (paramMenuId == 7 || paramMenuId == 9) {
        vmax = 127;
        vmin = 1;
      } else if (paramMenuId == 13) {
        vmax = 64;
        vmin = 2;
      }
      if (menuRot > vmax) menuRot = vmax;
      else if (menuRot < vmin) menuRot = vmin;
      glob[paramMenuId] = menuRot;
      //
      if (paramMenuId == 0) processTempo();
    }
    // ///////////////////////////////////////////////
    else if (topMenuId == 2) { /* CHORDS */
    // ///////////////////////////////////////////////
      if (menuRot < 0) menuRot = 127;
      else if (menuRot > 127) menuRot = 0;
      chords[sideMenuId][paramMenuId] = menuRot;
    }
    // ///////////////////////////////////////////////
    else if (topMenuId == 1) { /* ARP */
    // ///////////////////////////////////////////////
      char vmin = 0; char vmax = 12;
      if (sideMenuId == 0) vmax = 10;
      else if (sideMenuId == 2) vmax = 13;
      else if (sideMenuId == 3) vmax = 7;
      if (menuRot > vmax) menuRot = vmax;
      else if (menuRot < vmin) menuRot = vmin;
      arpd[sideMenuId][paramMenuId] = menuRot;
    }
    // ///////////////////////////////////////////////
    else if (topMenuId == 0) { /* SEQ */
    // ///////////////////////////////////////////////
      char vmin = 0; char vmax = 6;
      if (sideMenuId == 3) vmax = 3;
      if (menuRot > vmax) menuRot = vmax;
      else if (menuRot < vmin) menuRot = vmin;
      seqd[sideMenuId][paramMenuId] = menuRot;
    }
    //
    //press = esc: _____________________________ back;
    if (buttonState == 0 || eXs == 0) {
      inParamMenu = true;
      menuRot = paramMenuId;
    }
  }
  //
  refreshGUI = true;
}

// /////////////////////////////////////////////////////////////////////////////
int readParamMenuIdValue(int pmid) {
// /////////////////////////////////////////////////////////////////////////////
  int val = 0;
  if (topMenuId == 3) {
    val = glob[pmid];
  } else if (topMenuId == 2) {
    val = chords[sideMenuId][pmid];
  } else if (topMenuId == 1) {
    val = arpd[sideMenuId][pmid];
  } else if (topMenuId == 0) {
    val = seqd[sideMenuId][pmid];
  }
  return val;
}

// /////////////////////////////////////////////////////////////////////////////
void noteOn(byte channel, byte note, byte velocity) {
// /////////////////////////////////////////////////////////////////////////////
  //digitalWrite(13, HIGH);
  michord[notepress] = note;
  if (notepress >= 3) {
    for (byte j = 0; j < 4; j++) {
      chords[curcor][j] = michord[j];
    }
    setCCord(curcor);
  }
  notepress++;
}

// /////////////////////////////////////////////////////////////////////////////
void noteOff(byte channel, byte note, byte velocity) {
// /////////////////////////////////////////////////////////////////////////////
  notepress--;
}

Credits

primvla

primvla

1 project • 2 followers
Contact

Comments