Thank you, I woke up a few moments ago and I'm going to try and learn and take some tries at it with this. This was me trying to learn from an existing project that has several modules in it. I took the advice to change all of the 1-4s to 0-3s but this was the original code which ended up working but is hard for me to read and I feel like it could be a lot more simplified:
byte notecv0;
byte notecv1;
byte notecv2;
byte notecv3;
bool robinhasrun = false;
byte robin0;
byte robin1;
byte robin2;
byte robin3;
result handleMidi2CV(eventMask event, navNode& nav, prompt &item) {
if (event == enterEvent) {
startMidi2CV();
activeModule = &loopMidi2CV;
}
if (event == exitEvent) {
activeModule = {};
endMidi2CV();
}
return proceed;
}
// called once on module enter
void startMidi2CV() {
robin0 = 0;
}
// called once on module exit
void endMidi2CV() {
resetCVOut();
resetGateOut();
}
// called constantly from main loop
void loopMidi2CV() {
//note is the last note played
byte note = MIDI_NOTES_ACTIVE.get(MIDI_NOTES_ACTIVE.size() - 1);
//starts when any note is played midi 0 is no notes
if (note > 0) {
//round robin movement
if (robinhasrun == false) {
robin0 ++;
robin1 = (robin0 + 1);
robin2 = (robin0 + 2);
robin3 = (robin0 + 3);
robinhasrun = true;
}
//returns individual robin positions back to 0 if they exceed 3
if (robin0 > 3) {
robin0 = 0;
}
if (robin1 > 3) {
robin1 = (robin1 - 4);
}
if (robin2 > 3) {
robin2 = (robin2 - 4);
}
if (robin3 > 3) {
robin3 = (robin3 - 4);
}
///mono only 1 note can be played
if (midi2CVpoly == 0) {
if (roundrobin == 0) {
notecv0 = note;
setGateOutDuration(0, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = note;
}
if (robin0 == 1) {
notecv1 = note;
}
if (robin0 == 2) {
notecv2 = note;
}
if (robin0 == 3) {
notecv3 = note;
}
setGateOutDuration(robin0, 10);
}
}
///duo only 2 notes can be played
if (midi2CVpoly == 1) {
if (MIDI_NOTES_ACTIVE.size() == 1) {
if (roundrobin == 0) {
notecv0 = note;
setGateOutDuration(0, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = note;
}
if (robin0 == 1) {
notecv1 = note;
}
if (robin0 == 2) {
notecv2 = note;
}
if (robin0 == 3) {
notecv3 = note;
}
setGateOutDuration(robin0, 10);
}
}
if (MIDI_NOTES_ACTIVE.size() > 1) {
if (roundrobin == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = note;
}
if (robin0 == 1) {
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = note;
}
if (robin0 == 2) {
notecv2 = MIDI_NOTES_ACTIVE.get(0);
notecv3 = note;
}
if (robin0 == 3) {
notecv3 = MIDI_NOTES_ACTIVE.get(0);
notecv0 = note;
}
setGateOutDuration(robin0, 10);
setGateOutDuration(robin1, 10);
}
}
}
///poly all 4 notes can be played no polyphonic restrictions
if (midi2CVpoly == 2) {
if (MIDI_NOTES_ACTIVE.size() == 1) {
if (roundrobin == 0) {
notecv0 = note;
setGateOutDuration(0, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = note;
}
if (robin0 == 1) {
notecv1 = note;
}
if (robin0 == 2) {
notecv2 = note;
}
if (robin0 == 3) {
notecv3 = note;
}
setGateOutDuration(robin0, 10);
}
}
if (MIDI_NOTES_ACTIVE.size() == 2) {
if (roundrobin == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = note;
}
if (robin0 == 1) {
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = note;
}
if (robin0 == 2) {
notecv2 = MIDI_NOTES_ACTIVE.get(0);
notecv3 = note;
}
if (robin0 == 3) {
notecv3 = MIDI_NOTES_ACTIVE.get(0);
notecv0 = note;
}
setGateOutDuration(robin0, 10);
setGateOutDuration(robin1, 10);
}
}
if (MIDI_NOTES_ACTIVE.size() == 3) {
if (roundrobin == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = MIDI_NOTES_ACTIVE.get(1);
notecv2 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
setGateOutDuration(2, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = MIDI_NOTES_ACTIVE.get(1);
notecv2 = note;
}
if (robin0 == 1) {
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = MIDI_NOTES_ACTIVE.get(1);
notecv3 = note;
}
if (robin0 == 2) {
notecv2 = MIDI_NOTES_ACTIVE.get(0);
notecv3 = MIDI_NOTES_ACTIVE.get(1);
notecv0 = note;
}
if (robin0 == 3) {
notecv3 = MIDI_NOTES_ACTIVE.get(0);
notecv0 = MIDI_NOTES_ACTIVE.get(1);
notecv1 = note;
}
setGateOutDuration(robin0, 10);
setGateOutDuration(robin1, 10);
setGateOutDuration(robin2, 10);
}
}
if (MIDI_NOTES_ACTIVE.size() > 3) {
if (roundrobin == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = MIDI_NOTES_ACTIVE.get(1);
notecv2 = MIDI_NOTES_ACTIVE.get(2);
notecv3 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
setGateOutDuration(2, 10);
setGateOutDuration(3, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = MIDI_NOTES_ACTIVE.get(1);
notecv2 = MIDI_NOTES_ACTIVE.get(2);
notecv3 = note;
}
if (robin0 == 1) {
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = MIDI_NOTES_ACTIVE.get(1);
notecv3 = MIDI_NOTES_ACTIVE.get(2);
notecv0 = note;
}
if (robin0 == 2) {
notecv2 = MIDI_NOTES_ACTIVE.get(0);
notecv3 = MIDI_NOTES_ACTIVE.get(1);
notecv0 = MIDI_NOTES_ACTIVE.get(2);
notecv1 = note;
}
if (robin0 == 3) {
notecv3 = MIDI_NOTES_ACTIVE.get(0);
notecv0 = MIDI_NOTES_ACTIVE.get(1);
notecv1 = MIDI_NOTES_ACTIVE.get(2);
notecv2 = note;
}
setGateOutDuration(robin0, 10);
setGateOutDuration(robin1, 10);
setGateOutDuration(robin2, 10);
setGateOutDuration(robin3, 10);
}
}
}
///uni 2 This plays the same note in unison twice to two different synth voices
if (midi2CVpoly == 3) {
if (MIDI_NOTES_ACTIVE.size() == 1) {
if (roundrobin == 0) {
notecv0 = note;
notecv1 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = note;
notecv1 = note;
}
if (robin0 == 1) {
notecv1 = note;
notecv2 = note;
}
if (robin0 == 2) {
notecv2 = note;
notecv3 = note;
}
if (robin0 == 3) {
notecv3 = note;
notecv0 = note;
}
setGateOutDuration(robin0, 10);
setGateOutDuration(robin1, 10);
}
}
if (MIDI_NOTES_ACTIVE.size() > 1) {
if (roundrobin == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = note;
notecv3 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
setGateOutDuration(2, 10);
setGateOutDuration(3, 10);
}
if (roundrobin == 1) {
if (robin0 == 0) {
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = note;
notecv3 = note;
}
if (robin0 == 1) {
notecv1 = MIDI_NOTES_ACTIVE.get(0);
notecv2 = MIDI_NOTES_ACTIVE.get(0);
notecv3 = note;
notecv0 = note;
}
if (robin0 == 2) {
notecv2 = MIDI_NOTES_ACTIVE.get(0);
notecv3 = MIDI_NOTES_ACTIVE.get(0);
notecv0 = note;
notecv1 = note;
}
if (robin0 == 3) {
notecv3 = MIDI_NOTES_ACTIVE.get(0);
notecv0 = MIDI_NOTES_ACTIVE.get(0);
notecv1 = note;
notecv2 = note;
}
setGateOutDuration(robin0, 10);
setGateOutDuration(robin1, 10);
setGateOutDuration(robin2, 10);
setGateOutDuration(robin3, 10);
}
}
}
///uni 4 this plays all 4 voices on the same note. no round robin option neccesary here
if (midi2CVpoly == 4) {
notecv0 = note;
notecv1 = note;
notecv2 = note;
notecv3 = note;
setGateOutDuration(0, 10);
setGateOutDuration(1, 10);
setGateOutDuration(2, 10);
setGateOutDuration(3, 10);
}
//CV values this converts the midi data into voltage for the synths using floats
float cv0 = getCVFromNote(notecv0 + 12 * midi2CVOctave);
float cv1 = getCVFromNote(notecv1 + 12 * midi2CVOctave);
float cv2 = getCVFromNote(notecv2 + 12 * midi2CVOctave);
float cv3 = getCVFromNote(notecv3 + 12 * midi2CVOctave);
setCVOut(0, cv0, true);
setCVOut(1, cv1, true);
setCVOut(2, cv2, true);
setCVOut(3, cv3, true);
}
//this returns robinhasrun to false when no notes are pressed
else {
robinhasrun = false;
}
}
There is also separate lines of code for the menu and I added a few options to it to get where I am at now. Random is my next goal to select random voices for each note played but to not repeat them into existing voices played. (IE, press 1st key and it picks a random voice and sends out data to it. While that note is held if I press a 2nd note it chooses any of the 3 remaining notes to play. The note played has to line up with the gate out sent) This is the menu code:
int midi2CVOctave = 0;
byte midi2CVpoly = 0;
byte roundrobin = 0;
result handleMidi2CV(eventMask event, navNode& nav, prompt &item);
SELECT(midi2CVOctave,midi2CVOctaveMenu,"OCTAVE",doNothing,noEvent,noStyle
,VALUE("-3",-3,doNothing,noEvent)
,VALUE("-2",-2,doNothing,noEvent)
,VALUE("-1",-1,doNothing,noEvent)
,VALUE("0",0,doNothing,noEvent)
,VALUE("1",1,doNothing,noEvent)
,VALUE("2",2,doNothing,noEvent)
,VALUE("3",3,doNothing,noEvent)
);
SELECT(midi2CVpoly,midi2CVpolyMenu,"VOICES",doNothing,noEvent,noStyle
,VALUE("MONO",0,doNothing,noEvent)
,VALUE("DUO",1,doNothing,noEvent)
,VALUE("POLY",2,doNothing,noEvent)
,VALUE("UNI 2",3,doNothing,noEvent)
,VALUE("UNI 4",4,doNothing,noEvent)
);
SELECT(roundrobin,roundrobinMenu,"ROBIN",doNothing,noEvent,noStyle
,VALUE("OFF",0,doNothing,noEvent)
,VALUE("ON",1,doNothing,noEvent)
,VALUE("RAND",2,doNothing,noEvent)
);
MENU(subMenuMidi2CV,"CHURRO MIDI",handleMidi2CV,(Menu::eventMask)(enterEvent|exitEvent),wrapStyle
,SUBMENU(midi2CVOctaveMenu)
,SUBMENU(midi2CVpolyMenu)
,SUBMENU(roundrobinMenu)
,EXIT("<BACK")
);
There is another code for the whole program front page that calls on the various libraries and gives all the menus, submenus, midi instances meaning. The menu sub menu and everything is working good to my knowledge it's just a very long code and gets all the added libraries like midi.h Theres alot more code in the front page but all the menu values and things end up looking something like this edit*** i just dropped the entire menu code because I didn't realize arduino had a nice neat scrollbar function. Most of this isn't neccessary i'm certain but It's everything:
#include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <Adafruit_MCP4728.h>
#include <MIDI.h>
#include <menu.h>
#include <menuIO/U8x8Out.h>
#include <U8x8lib.h>
#include <RotaryEncoder.h>
#include <Bounce2.h>
#include <LinkedList.h>
//#include "example_module.h"
#include "adsr.h"
#include "arp.h"
#include "midi2cv.h"
#include "multiple.h"
#include "quantizer.h"
#define DEBUG 1
#define CALIBRATE_CV_IN 0
#define CALIBRATE_CV_OUT 0
float CV_IN_CORRECTION[4] = {1.0079f, 1.0086f, 1.0088f, 1.0085f};
unsigned int CV_OUT_LUT[4][21];
int LED_PINS[2] = {13, A3};
int GATE_IN_PINS[4] = {6, 7, 8, 9};
int GATE_OUT_PINS[4] = {2, 3, 4, 5};
unsigned long GATE_OUT_DURATION[4];
int SWITCH_PINS[4] = {A0, A1, A2, 12};
Bounce2::Button SWITCHES[4];
int MIDI_CHANNEL = 1;
int OCTAVE_OFFSET = 3;
LinkedList<byte> MIDI_NOTES_ACTIVE;
void (*activeModule)(void);
Adafruit_ADS1115 ads;
Adafruit_MCP4728 mcp;
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
RotaryEncoder encoder(10, 11, RotaryEncoder::LatchMode::TWO03);
// menu
MENU(mainMenu,"KOSMO MULTITOOL",doNothing,noEvent,wrapStyle
//,SUBMENU(subMenuExample)
,SUBMENU(subMenuADSR)
,SUBMENU(subMenuArp)
,SUBMENU(subMenuMidi2CV)
,SUBMENU(subMenuMultiple)
,SUBMENU(subMenuQuantizer)
);
#define MAX_DEPTH 3
noInput in;
MENU_OUTPUTS(out,MAX_DEPTH,U8X8_OUT(u8x8,{0,0,17,8}),NONE);
NAVROOT(nav,mainMenu,MAX_DEPTH,in,out);
void setup(void) {
if (DEBUG) {
Serial.begin(9600);
Serial.println("Polykit Multitool. Initializing...");
}
// initialize adc
ads.setGain(GAIN_TWOTHIRDS);
ads.begin();
// initialize dac
mcp.begin();
// init midi
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.begin(MIDI_CHANNEL);
// init display
u8x8.begin();
u8x8.setFont(u8x8_font_chroma48medium8_u);
for (int i=0; i<2; i++) {
pinMode(LED_PINS[i], OUTPUT);
}
for (int i=0; i<4; i++) {
pinMode(GATE_IN_PINS[i], INPUT);
pinMode(GATE_OUT_PINS[i], OUTPUT);
GATE_OUT_DURATION[i] = 0;
}
for (int i=0; i<4; i++) {
SWITCHES[i] = Bounce2::Button();
SWITCHES[i].attach(SWITCH_PINS[i], INPUT_PULLUP);
SWITCHES[i].interval(10);
SWITCHES[i].setPressedState(LOW);
}
if (CALIBRATE_CV_IN) {
u8x8.drawString(0,0,"CALIBRATING...");
calibrateCVIn();
u8x8.drawString(0,1,"DONE");
while (1);
}
if (CALIBRATE_CV_OUT) {
u8x8.drawString(0,0,"CALIBRATING...");
calibrateCVOut();
u8x8.drawString(0,1,"DONE");
while (1);
} else {
u8x8.drawString(0,0,"LOADING...");
int addr = 0;
for (int c=0; c<4; c++) {
CV_OUT_LUT[c][0] = 0;
for (int i=1; i<=20; i++) {
EEPROM.get(addr, CV_OUT_LUT[c][i]);
addr += sizeof(unsigned int);
}
}
}
for (int i=0; i<4; i++) {
setLEDOn(0);
setLEDOff(1);
delay(100);
setLEDOn(1);
setLEDOff(0);
delay(100);
}
setLEDOff(1);
}
void loop(void) {
MIDI.read();
encoder.tick();
nav.poll();
handleMenu();
handleGateOutDurations();
handleSwitches();
if (activeModule) {
activeModule();
}
}
void handleNoteOn(byte channel, byte note, byte velocity) {
if (channel != MIDI_CHANNEL) return;
MIDI_NOTES_ACTIVE.add(note);
}
void handleNoteOff(byte channel, byte note, byte velocity) {
if (channel != MIDI_CHANNEL) return;
for (int i=0; i<MIDI_NOTES_ACTIVE.size(); i++) {
if (MIDI_NOTES_ACTIVE.get(i) == note) {
MIDI_NOTES_ACTIVE.remove(i);
return;
}
}
}
float getCVFromNote(byte note) {
return (note-12*OCTAVE_OFFSET)/12.0f;
}
void handleMenu() {
int dir = getEncoderDir();
if (dir == (int)RotaryEncoder::Direction::COUNTERCLOCKWISE) {
nav.doNav(navCmd(downCmd));
} else if(dir == (int)RotaryEncoder::Direction::CLOCKWISE) {
nav.doNav(navCmd(upCmd));
}
if (isSwitchOn(3)) {
nav.doNav(navCmd(enterCmd));
}
}
int getEncoderDir() {
return (int)encoder.getDirection();
}
int getEncoderPos() {
return encoder.getPosition();
}
float getCVIn(int n, bool correct) {
if (n>3) return -1;
int16_t adc = ads.readADC_SingleEnded(n);
float cv = ads.computeVolts(adc)*2;
if (correct) {
cv = cv*CV_IN_CORRECTION[n];
}
return cv;
}
void setCVOut(int n, float v, bool correct) {
if (n>3) return;
if (v>10) return;
MCP4728_channel_t ch;
if (n==0) ch = MCP4728_CHANNEL_A;
if (n==1) ch = MCP4728_CHANNEL_B;
if (n==2) ch = MCP4728_CHANNEL_C;
if (n==3) ch = MCP4728_CHANNEL_D;
int nv = map(v*1000, 0, 10000, 0, 4095);
if (correct) {
int pos = round(2*v);
nv = nv*(CV_OUT_LUT[n][pos]/1000.0f);
}
mcp.setChannelValue(ch, nv, MCP4728_VREF_INTERNAL, MCP4728_GAIN_2X);
}
void calibrateCVIn() {
if (DEBUG) Serial.println("ADC calibration started.");
int numSamples = 20;
for (int n=0; n<4; n++) {
if (DEBUG) {
Serial.print("CV");
Serial.print(n);
Serial.print("CV");
}
float cv = 0;
for (int i=0; i<numSamples; i++) {
cv += getCVIn(n, false);
}
if (DEBUG) Serial.println((cv/numSamples), 4);
}
if (DEBUG) Serial.println("ADC calibration ended.");
}
void calibrateCVOut() {
if (DEBUG) Serial.println("DAC calibration started.");
int numSamples = 20;
float cv;
int addr = 0;
for (int c=0; c<4; c++) {
if (DEBUG) {
Serial.print("Channel: ");
Serial.println(c);
}
CV_OUT_LUT[c][0] = 0;
for (int i=1; i<=20; i++) {
setCVOut(c, i*0.5f, false);
delay(100);
cv = 0;
for (int j=0; j<numSamples; j++) {
cv += getCVIn(c, true);
}
cv = cv/numSamples;
CV_OUT_LUT[c][i] = round(1000*i*0.5f/cv);
if (DEBUG) {
Serial.print("Wanted: ");
Serial.println(i*0.5f, 4);
Serial.print("Real: ");
Serial.println(cv, 4);
Serial.print("LUT: ");
Serial.println(CV_OUT_LUT[c][i]/1000.0f, 4);
}
EEPROM.put(addr, CV_OUT_LUT[c][i]);
addr += sizeof(unsigned int);
}
}
if (DEBUG) Serial.println("DAC calibration ended.");
}
void setGateOutDuration(int n, unsigned long duration_ms) {
if (n>3) return;
GATE_OUT_DURATION[n] = millis()+duration_ms;
setGateOutHigh(n);
}
void handleGateOutDurations() {
for (int n=0; n<4; n++) {
if (GATE_OUT_DURATION[n] > 0) {
long d = GATE_OUT_DURATION[n]-millis();
if (d < 0) {
setGateOutLow(n);
GATE_OUT_DURATION[n] = 0;
}
}
}
}
void setGateOutHigh(int n) {
if (n>3) return;
digitalWrite(GATE_OUT_PINS[n], HIGH);
}
void setGateOutLow(int n) {
if (n>3) return;
digitalWrite(GATE_OUT_PINS[n], LOW);
}
void setGateOut(int n, bool b) {
if (n>3) return;
digitalWrite(GATE_OUT_PINS[n], b);
}
bool getGateIn(int n) {
if (n>3) return -1;
return (bool)digitalRead(GATE_IN_PINS[n]);
}
void handleSwitches() {
for (int n=0; n<4; n++) {
SWITCHES[n].update();
}
}
bool isSwitchOn(int n) {
if (n>3) return 0;
bool pressed = SWITCHES[n].pressed();
return pressed;
}
void setLEDOn(int n) {
if (n>1) return;
digitalWrite(LED_PINS[n], HIGH);
}
void setLEDOff(int n) {
if (n>1) return;
digitalWrite(LED_PINS[n], LOW);
}
void resetCVOut() {
for(int i=0; i<4; i++) {
setCVOut(i, 0, false);
}
}
void resetGateOut() {
for(int i=0; i<4; i++) {
setGateOutLow(i);
}
}
Thank you all for all of the help and advice and I'm going to look through this and continue on my studying