Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 6 | ||||
![]() |
| × | 2 | |||
| × | 2 | ||||
| × | 1 | ||||
![]() |
| × | 6 | |||
| × | 1 | ||||
| × | 1 |
Cthulhinho is an Arduino based MIDI sequencer 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):
working prototype
C/C++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.
Comments
Please log in or sign up to comment.