tttapa / MIDI_controller

This is a library for creating a MIDI controller using an Arduino or Teensy board.
GNU General Public License v3.0
404 stars 70 forks source link

Create 4 midi banks controlled by 1 potentiometer #111

Open brosekerg opened 3 years ago

brosekerg commented 3 years ago

I have a working code and midi device for 16 buttons and 5 potentiometers. I want to use one of these potentiometers to choose between 4 banks notes. The notes need to be programmed into each bank, probably an array. The first bank is the one i got working already obviously, the only one, and it holds note numbers 45-60(c3-c4). i want bank2 to be 61-76, 3 to be 77-92, and 4 to be 92-107. This first bank is in an array already. I have this accomplished on an arduino uno using 2 8x1 MUX and a 2x1 MUX to act as the 4th selection bit to cover all 16 buttons. MUX1 is arduino pin 6, MUX2 is arduino pin 7, and MUX1 select pins go to pins 2,3,4 on arduino. A goes to 4, B goes to 3, C goes to 2. 2x1 MUX select is pin 5 and output is pin 8.

Arduino Uno (0-13)(A0-A5)

Using ddiakopoulos/hiduino flashed firmware. arduino is now native usb. I can use arduino as ISP(seperate arduino as programmer) to still upload sketches.)

using Midi.h, controller.h, and (can use control_surface if forced to.) Arduino 1.8.13 Windows 10 20h2 64 bit

(The variables defined after the pots are defined are my attempts to try to make these banks work, they werent in the original working code. Its to give you guys an idea of what im trying to do. Take particular note of the getKey() function. that function is supposed to select the right note to play based on which button i have pressed(the variable pressed), and which bank im in which is determined by the position of A5. I want to use a click rotary encoder but dont have one so this pot is for testing purposes for now. The function is also supposed to map pot values 1-255 to 0, 256-512 to 1, 513-768 to bank 2, and 769-1023 to bank 3. This is supposed to simulate the encoder, although im not sure its needed. Next, note where i actually send the NoteOn command in the updateMuxButtons() function. (case 0) i Have added the "+ getKey(i)" there in an attempt to play the correct note based on which bank im in determined by all the above. Finally, you can take note of the bankSelect() function at the bottom, which may be a better solution overall. Right now i have the program variable set to 0 but im not sure this is correct. Ideally it needs to store all 64 notes in an array and know which one to pick based on which bank im in and which button i press. Can anyone help me?

#include <MIDI.h>
#include "Controller.h"
#include <MIDI_Controller.h>

/*************************************************************
  MIDI CONTROLLER

  by Notes and Volts
  www.notesandvolts.com

  Version 1.2 **Arduino UNO ONLY!**
 *************************************************************/

MIDI_CREATE_DEFAULT_INSTANCE();

//************************************************************
//***SET THE NUMBER OF CONTROLS USED**************************
//************************************************************
//---How many buttons are connected directly to pins?---------
byte NUMBER_BUTTONS = 0;
//---How many potentiometers are connected directly to pins?--
byte NUMBER_POTS = 5;
//---How many buttons are connected to a multiplexer?---------
byte NUMBER_MUX_BUTTONS = 16;
//---How many potentiometers are connected to a multiplexer?--
byte NUMBER_MUX_POTS = 0;
//************************************************************

//***ANY MULTIPLEXERS? (74HC4067)************************************
//MUX address pins must be connected to Arduino UNO pins 2,3,4,5
//A0 = PIN2, A1 = PIN3, A2 = PIN4, A3 = PIN5
//*******************************************************************
//Mux NAME (OUTPUT PIN, , How Many Mux Pins?(8 or 16) , Is It Analog?);

Mux M1(6, 8, false); //Digital multiplexer on Arduino pin 10
Mux M2(7, 8, false); //Analog multiplexer on Arduino analog pin A0
//*******************************************************************

//***DEFINE DIRECTLY CONNECTED POTENTIOMETERS************************
//Pot (Pin Number, Command, CC Control, Channel Number)
//**Command parameter is for future use**

Pot PO1(A0, 0, 1, 1);
Pot PO2(A1, 0, 1, 1);
Pot PO3(A2, 0, 1, 1);
Pot PO4(A3, 0, 1, 1);
Pot PO5(A5, 0, 1, 1);

const int potMax = 1023;
int channel = 1
int pressed;   // one of 16 buttons (1 - 16)
int program = 0;   // one of 64 keys (0-63) on scale starting from C2
int bank = 1234;  // one of 4 banks (1-4)

//*******************************************************************
//Add pots used to array below like this->  Pot *POTS[] {&PO1, &PO2, &PO3, &PO4, &PO5, &PO6};
Pot *POTS[] {&PO1, &PO2, &PO3, &PO4, &PO5};
//*******************************************************************

//***DEFINE DIRECTLY CONNECTED BUTTONS*******************************
//Button (Pin Number, Command, Note Number, Channel, Debounce Time)
//** Command parameter 0=NOTE  1=CC  2=Toggle CC **

//Button BU1(2, 0, 60, 1, 5 );
//Button BU2(3, 0, 61, 1, 5 );
//Button BU3(4, 0, 62, 1, 5 );
//Button BU4(5, 0, 63, 1, 5 );
//Button BU5(6, 0, 64, 1, 5 );
//Button BU6(7, 0, 65, 1, 5 );
//Button BU7(8, 1, 64, 1, 5 );
//Button BU8(9, 2, 64, 1, 5 );
//*******************************************************************
//Add buttons used to array below like this->  Button *BUTTONS[] {&BU1, &BU2, &BU3, &BU4, &BU5, &BU6, &BU7, &BU8};
Button *BUTTONS[] {};
//*******************************************************************

//***DEFINE BUTTONS CONNECTED TO MULTIPLEXER*************************
//Button::Button(Mux mux, byte muxpin, byte command, byte value, byte channel, byte debounce)
//** Command parameter 0=NOTE  1=CC  2=Toggle CC **

Button MBU1(M1, 13, 0, 45, 1, 5);
Button MBU2(M1, 14, 0, 46, 1, 5);
Button MBU3(M1, 15, 0, 47, 1, 5);
Button MBU4(M1, 12, 0, 48, 1, 5);
Button MBU5(M1, 1, 0, 49, 1, 5);
Button MBU6(M1, 5, 0, 50, 1, 5);
Button MBU7(M1, 2, 0, 51, 1, 5);
Button MBU8(M1, 4, 0, 52, 1, 5);
Button MBU9(M2, 13, 0, 53, 1, 5);
Button MBU10(M2, 14, 0, 54, 1, 5);
Button MBU11(M2, 15, 0, 55, 1, 5);
Button MBU12(M2, 12, 0, 56, 1, 5);
Button MBU13(M2, 1, 0, 57, 1, 5);
Button MBU14(M2, 5, 0, 58, 1, 5);
Button MBU15(M2, 2, 0, 59, 1, 5);
Button MBU16(M2, 4, 0, 60, 1, 5);
//*******************************************************************
////Add multiplexed buttons used to array below like this->  Button *MUXBUTTONS[] {&MBU1, &MBU2, &MBU3, &MBU4, &MBU5, &MBU6.....};
Button *MUXBUTTONS[] {&MBU1, &MBU2, &MBU3, &MBU4, &MBU5, &MBU6, &MBU7, &MBU8, &MBU9, &MBU10, &MBU11, &MBU12, &MBU13, &MBU14, &MBU15, &MBU16};

//*******************************************************************

//***DEFINE POTENTIOMETERS CONNECTED TO MULTIPLEXER*******************
//Pot::Pot(Mux mux, byte muxpin, byte command, byte control, byte channel)
//**Command parameter is for future use**

//Pot MPO1(M2, 0, 0, 1, 1);
//Pot MPO2(M2, 1, 0, 7, 1);
//Pot MPO3(M2, 2, 0, 50, 1);
//Pot MPO4(M2, 3, 0, 55, 2);
//Pot MPO5(M2, 4, 0, 50, 1);
//Pot MPO6(M2, 5, 0, 55, 2);
//Pot MPO7(M2, 6, 0, 50, 1);
//Pot MPO8(M2, 7, 0, 55, 2);
//Pot MPO9(M2, 8, 0, 50, 1);
//Pot MPO10(M2, 9, 0, 55, 2);
//Pot MPO11(M2, 10, 0, 50, 1);
//Pot MPO12(M2, 11, 0, 55, 2);
//Pot MPO13(M2, 12, 0, 50, 1);
//Pot MPO14(M2, 13, 0, 55, 2);
//Pot MPO15(M2, 14, 0, 50, 1);
//Pot MPO16(M2, 15, 0, 55, 2);
//*******************************************************************
//Add multiplexed pots used to array below like this->  Pot *MUXPOTS[] {&MPO1, &MPO2, &MPO3, &MPO4, &MPO5, &MPO6.....};
Pot *MUXPOTS[] {};
//*******************************************************************

void setup() {
  MIDI.begin ();
}

int getKey(int pressed){
  bank = map(analogRead(A4), 0, potMax, 0, 3);  // this is the MONEY!!!
  return bank * 16 + pressed;
}

void loop() {
  if (NUMBER_BUTTONS != 0) updateButtons();
  if (NUMBER_POTS != 0) updatePots();
  if (NUMBER_MUX_BUTTONS != 0) updateMuxButtons();
  if (NUMBER_MUX_POTS != 0) updateMuxPots();
}

//*****************************************************************
void updateButtons() {

  // Cycle through Button array
  for (int i = 0; i < NUMBER_BUTTONS; i = i + 1) {
    byte message = BUTTONS[i]->getValue();

    //  Button is pressed
    if (message == 0) {
      switch (BUTTONS[i]->Bcommand) {
        case 0: //Note
          MIDI.sendNoteOn(BUTTONS[i]->Bvalue, 127, BUTTONS[i]->Bchannel);
          break;
        case 1: //CC
          MIDI.sendControlChange(BUTTONS[i]->Bvalue, 127, BUTTONS[i]->Bchannel);
          break;
        case 2: //Toggle
          if (BUTTONS[i]->Btoggle == 0) {
            MIDI.sendControlChange(BUTTONS[i]->Bvalue, 127, BUTTONS[i]->Bchannel);
            BUTTONS[i]->Btoggle = 1;
          }
          else if (BUTTONS[i]->Btoggle == 1) {
            MIDI.sendControlChange(BUTTONS[i]->Bvalue, 0, BUTTONS[i]->Bchannel);
            BUTTONS[i]->Btoggle = 0;
          }
          break;
      }
    }

    //  Button is not pressed
    if (message == 1) {
      switch (BUTTONS[i]->Bcommand) {
        case 0:
          MIDI.sendNoteOff(BUTTONS[i]->Bvalue, 0, BUTTONS[i]->Bchannel);
          break;
        case 1:
          MIDI.sendControlChange(BUTTONS[i]->Bvalue, 0, BUTTONS[i]->Bchannel);
          break;
      }
    }
  }
}
//*******************************************************************
void updateMuxButtons() {

  // Cycle through Mux Button array
  for (int i = 0; i < NUMBER_MUX_BUTTONS; i = i + 1) {

    MUXBUTTONS[i]->muxUpdate();
    byte message = MUXBUTTONS[i]->getValue();

    //  Button is pressed
    if (message == 0) {
      switch (MUXBUTTONS[i]->Bcommand) {
        case 0: //Note
          MIDI.sendNoteOn(MUXBUTTONS[i]->Bvalue + getKey(i), 127, MUXBUTTONS[i]->Bchannel);
          break;
        case 1: //CC
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 127, MUXBUTTONS[i]->Bchannel);
          break;
        case 2: //Toggle
          if (MUXBUTTONS[i]->Btoggle == 0) {
            MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 127, MUXBUTTONS[i]->Bchannel);
            MUXBUTTONS[i]->Btoggle = 1;
          }
          else if (MUXBUTTONS[i]->Btoggle == 1) {
            MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 0, MUXBUTTONS[i]->Bchannel);
            MUXBUTTONS[i]->Btoggle = 0;
          }
          break;
      }
    }
    //  Button is not pressed
    if (message == 1) {
      switch (MUXBUTTONS[i]->Bcommand) {
        case 0:
          MIDI.sendNoteOff(MUXBUTTONS[i]->Bvalue + getKey(i), 0, MUXBUTTONS[i]->Bchannel);
          break;
        case 1:
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 0, MUXBUTTONS[i]->Bchannel);
          break;
      }
    }
  }
}
//***********************************************************************
void updatePots() {
  for (int i = 0; i < NUMBER_POTS; i = i + 1) {
    byte potmessage = POTS[i]->getValue();
    if (potmessage != 255) MIDI.sendControlChange(POTS[i]->Pcontrol, potmessage, POTS[i]->Pchannel);
  }
}
//***********************************************************************
void updateMuxPots() {
  for (int i = 0; i < NUMBER_MUX_POTS; i = i + 1) {
    MUXPOTS[i]->muxUpdate();
    byte potmessage = MUXPOTS[i]->getValue();
    if (potmessage != 255) MIDI.sendControlChange(MUXPOTS[i]->Pcontrol, potmessage, MUXPOTS[i]->Pchannel);
  }
}
void bankSelect() {
  if (potMax <= 255) {
MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel, MIDI_CC::Bank_Select,     (bank >> 4) & 0x7F);
MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel, MIDI_CC::Bank_Select_LSB, (bank >> 0) & 0x7F); 
MIDI_Controller.MIDI()->send(PROGRAM_CHANGE, channel, program);
  }...
}

MidiOx as monitor

This is my capstone project for college and i appreciate all the help i can get! I hope this issue is understandable and solvable, im not seeing anywhere else of people using a pot as a bank selector. Short Summary of goal: 16 buttons, 4 banks (covers 64 of 88 keys) and 4 regular FX pots. Thanks guys!

tttapa commented 3 years ago

This is not something MIDI_controller supports. Using Control Surface, you could do something like this:

#include <Control_Surface.h>

USBMIDI_Interface midi;

// Transpose settings [0, +1, +2, +3], with offsets of 16 semitones between them
Transposer<0, +3> transposer {16};
// Select the transposition using a potentiometer on pin A0
PotSelector<4> selector {transposer, A0};

Bankable::NoteButton buttons[] {
  {transposer, 2, 45}, // transposer, pin, note number
  {transposer, 3, 46}, // transposer, pin, note number
  {transposer, 4, 47}, // transposer, pin, note number
  // ...
};

void setup() {
  Control_Surface.begin();
}

void loop() {
  Control_Surface.loop();
}

The PotSelector class is not included with Control Surface, but you could use something like this:

#include <Control_Surface.h>

template <setting_t N>
class PotSelector : public GenericSelector<N> {
  public:
    PotSelector(Selectable<N> &selectable, pin_t analogPin)
      : GenericSelector<N>(selectable, {}), analogInput(analogPin) {}

    void begin() override {
      analogInput.resetToCurrentValue();
      this->set(getPosition());
    }

    void update() override {
      if (analogInput.update()) {
        setting_t pos = getPosition();
        if (pos != this->get())
          this->set(pos);
      }
    }

  private:
    setting_t getPosition() const {
      return N * analogInput.getValue() / (1 << Precision);
    }

  private:
    constexpr static const uint8_t Precision = 7;
    FilteredAnalog<Precision> analogInput;
};

If the buttons are connected through multiplexers, you could do something like this:


CD74HC4051 mux1 {6, {4, 3, 2}}; // input pin, {address pins}
CD74HC4051 mux2 {7, {4, 3, 2}}; // input pin, {address pins}

Bankable::NoteButton buttons[] {
  {transposer, mux1.pin(0), 45}, // transposer, pin, note number
  {transposer, mux1.pin(1), 46},
  // ...
  {transposer, mux1.pin(7), 52},
  {transposer, mux2.pin(0), 53},
  // ...
  {transposer, mux2.pin(7), 61},
};

To use an encoder later, you can use the EncoderSelector class instead of the PotSelector. Also see this example: https://tttapa.github.io/Control-Surface-doc/Doxygen/d1/da6/Encoder-Selector-Bank_8ino-example.html

Use the CCPotentiometer class for the regular FX pots. See this example: https://tttapa.github.io/Control-Surface-doc/Doxygen/d3/d8d/Multiple-Control-Change-Potentiometers_8ino-example.html

The MIDI bank select function selects banks of patches and instrument voices for synthesizers etc., it's not related to banks in the context of MIDI controllers.

brosekerg commented 3 years ago

does this look like correct control-surface code for having 16 buttons with my 4 octave semitone banks, and 4 CC potientiometers? im waiting on my 16x1 mux to test. but does this look like it will work?

include

USBMIDI_Interface midi;

include

using namespace MIDI_Notes;

include <AH/Hardware/ExtendedInputOutput/AnalogMultiplex.hpp>

CD74HC4067 mux1 = { 6, {2, 3, 4, 5} };

Transposer<-12, +12> transposer; IncrementDecrementSelector<transposer.getNumberOfBanks()> selector = { transposer, {7, 8}, Wrap::Clamp, };

Bankable::NoteButton buttons[] { {transposer, mux1.pin(8), {note(C, 3), CHANNEL_1}}, {transposer, mux1.pin(7), {note(Db, 3), CHANNEL_1}}, {transposer, mux1.pin(6), {note(D, 3), CHANNEL_1}}, {transposer, mux1.pin(5), {note(Eb, 3), CHANNEL_1}}, {transposer, mux1.pin(4), {note(E, 3), CHANNEL_1}}, {transposer, mux1.pin(3), {note(F, 3), CHANNEL_1}}, {transposer, mux1.pin(2), {note(Gb, 3), CHANNEL_1}}, {transposer, mux1.pin(1), {note(G, 3), CHANNEL_1}}, {transposer, mux1.pin(22), {note(Ab, 3), CHANNEL_1}}, {transposer, mux1.pin(21), {note(A, 3), CHANNEL_1}}, {transposer, mux1.pin(20), {note(Bb, 3), CHANNEL_1}}, {transposer, mux1.pin(19), {note(B, 3), CHANNEL_1}}, {transposer, mux1.pin(18), {note(C, 4), CHANNEL_1}}, {transposer, mux1.pin(17), {note(Db, 4), CHANNEL_1}}, {transposer, mux1.pin(16), {note(D, 4), CHANNEL_1}}, {transposer, mux1.pin(15), {note(Eb, 4), CHANNEL_1}}, };

CCPotentiometer potentiometers[] = { {A0, 0x10}, {A1, 0x11}, {A2, 0x12}, {A3, 0x13}, };

void setup() { // put your setup code here, to run once: Control_Surface.begin(); mux1.begin(); mux1.pinMode(0, INPUT_PULLUP); }

void loop() { Control_Surface.loop(); }

tttapa commented 3 years ago

See https://github.com/tttapa/Control-Surface/discussions/412.