fbpixel
Etiquetas: ,

En este proyecto vamos a construir una caja MIDI para poder probar los instrumentos del sintetizador CoolSoft y tocar algunas notas. Vamos a utilizar tres sensores para modificar los mensajes MIDI. Eres libre de añadir elementos para hacer un controlador MIDI más completo.

Requisitos previos: Creación de una interfaz MIDI con Arduino

Equipamiento

  • Arduino UNO
  • potenciómetro
  • codificador rotatorio
  • teclado analógico 4×4

Características

Para probar la comunicación MIDI y el sintetizador. Vamos a definir algunas funciones simples:

  • Usando el teclado, vamos a enviar notas musicales
  • Le potenciómetro permet d’ajuster la vitesse de la note
  • Y el codificador modificará el instrumento sintetizado

N.B.: Elegimos un teclado analógico para este proyecto porque es fácil de cablear y nos permite probar rápidamente el sintetizador. El inconveniente es que sólo se puede pulsar una tecla a la vez. Para obtener un controlador MIDI más eficaz, puedes optar por un teclado digital o botones arcade que te proporcionen información sobre las notas tocadas.

Diagrama

arduino-midi-box-shematics_bb-1280x835 Creación de un controlador MIDI con Arduino

N.B.: Para facilitar la lectura, hemos eliminado la pantalla de 7 segmentos. Puede encontrar la conexión en este tutorial.

Descripción del código

En primer lugar, definiremos funciones para gestionar cada uno de los sensores.

Para el teclado 4×4, asignamos a cada tecla un identificador que corresponde a una nota. Creamos una función que devuelve el identificador cuando se pulsa una tecla.

const int valThresh[nbABtn] = {1000, 900, 820, 750, 660, 620, 585, 540, 500, 475, 455, 425, 370, 300, 260, 200};
const int pitches[nbABtn] = {50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80 }
int getABtn() { /* function getABtn */
 //// Read button states from keypad
 int val = analogRead(abtnPin);
 if (val <= 200) {
   return 0;
 } else {
   for (int i = 0; i < 16; i++) {
     if (val > valThresh[i]) {
       delay(100);
       return i + 1;
     }
   }
 }
}

Definimos una función readPot que modificará el valor de la velocidad en función del valor del potenciómetro velVal.

void readPot(){
  velVal = map(int(analogRead(potPin) / 10) * 10, 0, 1023, 0, 127);
  if (oldVel != velVal) {
    //Serial.println(velVal);
    velocity = velVal;
  }
  oldVel = velVal;
}

La función que gestiona el codificador nos permite modificar el instrumento musical (identificador rotVal entre 0 y 127).

void readRotary( ) { /* function readRotary */
  ////Test routine for Rotary
  // gestion position
  clkState = digitalRead(clkPin);
  if ((clkLast == LOW) && (clkState == HIGH)) {//rotary moving
    if (digitalRead(dtPin) == HIGH) {
      rotVal = rotVal - 1;
      if ( rotVal < 0 ) {
        rotVal = 0;
      }
    }
    else {
      rotVal++;
      if ( rotVal > 127 ) {
        rotVal = 127;
      }
    }
    MIDImessage(prgmChg, rotVal, 0);
    number = rotVal;
    for (int i = 0; i < NUM_OF_DIGITS; i++)
    {
      digit_data[i] = number % 10;
      number /= 10;
    }
    delay(200);

  }
  clkLast = clkState;

  //gestion bouton
  swState = digitalRead(swPin);
  if (swState == LOW && swLast == HIGH) {
    delay(100);//debounce
  }
  swLast = swState;
}

Una vez tenida en cuenta la modificación del aparato, su valor se visualiza en el módulo 7 Segmento.

void Display(int id, unsigned char num)
{
  digitalWrite(latch, LOW);
  shiftOut(data, cs, MSBFIRST, table[num]);
  digitalWrite(latch, HIGH);
  for (int j = 0; j < NUM_OF_DIGITS; j++) digitalWrite(dPins[j], LOW);
  digitalWrite(dPins[id], HIGH);
}

void updateDigits() {
  for (int j = 0; j < NUM_OF_DIGITS; j++){
    Display(j, digit_data[j]);
    delay(2);
  }
}

Por último, volvemos a la función que nos permite enviar mensajes MIDI

void MIDImessage(byte command, byte MIDInote, byte MIDIvelocity) {
  Serial.write(command);//send note on or note off command
  Serial.write(MIDInote);//send pitch data
  if (command == noteON || command == noteOFF) {
    Serial.write(MIDIvelocity);//send velocity data
  }
}

Sólo queda enviar el mensaje cuando se pulse un botón

void readAbtn() { /* function readAbtn */
  //// Read button states from keypad
  btnId = getABtn();
  if (btnId) {
    if (lastBtnId != btnId) {
      MIDImessage(noteOFF, pitches[lastBtnId - 1], velocity);
    }
    MIDImessage(noteON, pitches[btnId - 1], velocity); //turn note on
    lastBtnId = btnId;
  }
}

Código MIDIbox completo

byte velocity = 50;//velocity of MIDI notes, must be between 0 and 127
byte noteON = 144;// = 10010000 , Note On
byte noteOFF = 128;// = 10000000 , Note Off
byte pitchBend = 224; // = 11100000, Pitch Bender Change
byte polyKey = 160; // = 10100000, Polyphonic Key Pressure
byte overallPr = 208; // = 11010000, Overall Pressure
byte prgmChg = 192; // = 11000000, Program Change
byte ctrlChg = 176; // = 10110000,  Control Change

//Keypad
#define nbABtn 16
const int abtnPin = A1;
const int valThresh[nbABtn] = {1000, 900, 820, 750, 660, 620, 585, 540, 500, 475, 455, 425, 370, 300, 260, 200};
const int pitches[nbABtn] = {50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80 };
bool btnPressed = false;

//Potentiometer
const int potPin = A0; // Pin connected to sensor
int velVal = 0, oldVel = 0; // Analog value from the sensor

//rotary var
const int clkPin  = 53;
const int dtPin  = 51;
const int swPin  = 49;
int rotVal  = 0, btnId = 0, lastBtnId = 0;
bool clkState  = LOW;
bool clkLast  = HIGH;
bool swState  = HIGH;
bool swLast  = HIGH;

//4x7Segment
#define NUM_OF_DIGITS 4
int latch = 4; //74HC595  pin 9 STCP
int cs = 5; //74HC595  pin 10 SHCP
int data = 3; //74HC595  pin 8 DS
int dPins[4] = {8, 9, 10, 11};
//  DP G F E D C B A
//0: 1 1 0 0 0 0 0 0 0xc0
//1: 1 1 1 1 1 0 0 1 0xf9
//2: 1 0 1 0 0 1 0 0 0xa4
//3: 1 0 1 1 0 0 0 0 0xb0
//4: 1 0 0 1 1 0 0 1 0x99
//5: 1 0 0 1 0 0 1 0 0x92
//6: 1 0 0 0 0 0 1 0 0x82
//7: 1 1 1 1 1 0 0 0 0xf8
//8: 1 0 0 0 0 0 0 0 0x80
//9: 1 0 0 1 0 0 0 0 0x90
unsigned char table[] =
{0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
int digit_data[NUM_OF_DIGITS] = {0};
unsigned int number = 0;
unsigned long previousUpdate = 0, updateTime = 200;

void setup() {
  Serial.begin(115200);
  pinMode(potPin, INPUT);

  pinMode(clkPin, INPUT);
  pinMode(dtPin, INPUT);
  pinMode(swPin, INPUT_PULLUP);

  pinMode(latch, OUTPUT);
  pinMode(cs, OUTPUT);
  pinMode(data, OUTPUT);
  for (int j = 0; j < NUM_OF_DIGITS; j++) pinMode(dPins[j], OUTPUT);
}

void loop() {
  readRotary();
  updateDigits();
  readAbtn();
  readPot();
}

void MIDImessage(byte command, byte MIDInote, byte MIDIvelocity) {
  Serial.write(command);//send note on or note off command
  Serial.write(MIDInote);//send pitch data
  if (command == noteON || command == noteOFF) {
    Serial.write(MIDIvelocity);//send velocity data
  }
}


void readAbtn() { /* function readAbtn */
  //// Read button states from keypad
  btnId = getABtn();
  if (btnId) {
    if (lastBtnId != btnId) {
      MIDImessage(noteOFF, pitches[lastBtnId - 1], velocity);
    }
    MIDImessage(noteON, pitches[btnId - 1], velocity); //turn note on
    lastBtnId = btnId;
  }
}

int getABtn() { /* function getABtn */
  //// Read button states from keypad
  int val = analogRead(abtnPin);
  if (val <= 200) {
    return 0;
  } else {
    for (int i = 0; i < 16; i++) {
      if (val > valThresh[i]) {
        delay(100);
        return i + 1;
      }
    }
  }
}

//Potentiometer
void readPot(){
  velVal = map(int(analogRead(potPin) / 10) * 10, 0, 1023, 0, 127);
  if (oldVel != velVal) {
    //Serial.println(velVal);
    velocity = velVal;
  }
  oldVel = velVal;
}

//7Segment
void Display(int id, unsigned char num)
{
  digitalWrite(latch, LOW);
  shiftOut(data, cs, MSBFIRST, table[num]);
  digitalWrite(latch, HIGH);
  for (int j = 0; j < NUM_OF_DIGITS; j++) digitalWrite(dPins[j], LOW);
  digitalWrite(dPins[id], HIGH);
}

void updateDigits() {
  for (int j = 0; j < NUM_OF_DIGITS; j++)
  {
    Display(j, digit_data[j]);
    delay(2);
  }
}

//rotary
void readRotary( ) { /* function readRotary */
  ////Test routine for Rotary
  // gestion position
  clkState = digitalRead(clkPin);
  if ((clkLast == LOW) && (clkState == HIGH)) {//rotary moving
    if (digitalRead(dtPin) == HIGH) {
      rotVal = rotVal - 1;
      if ( rotVal < 0 ) {
        rotVal = 0;
      }
    }
    else {
      rotVal++;
      if ( rotVal > 127 ) {
        rotVal = 127;
      }
    }
    MIDImessage(prgmChg, rotVal, 0);
    number = rotVal;
    for (int i = 0; i < NUM_OF_DIGITS; i++)
    {
      digit_data[i] = number % 10;
      number /= 10;
    }
    delay(200);

  }
  clkLast = clkState;

  //gestion bouton
  swState = digitalRead(swPin);
  if (swState == LOW && swLast == HIGH) {
    delay(100);//debounce
  }
  swLast = swState;
}

Resultados

Para probar el código, es necesario abrir los programas hairless y VirtualMIDISynth como se describe en este artículo.

Si todo está configurado correctamente, deberías oír sonidos procedentes de tu ordenador cuando pulses las teclas del teclado.

arduino-midi-box-keypad-notechange Creación de un controlador MIDI con Arduino

También puedes cambiar la velocidad de la nota tocando el potenciómetro.

arduino-midi-box-potentiometer-velocitychange Creación de un controlador MIDI con Arduino

Entre dos notas tocadas, puedes seleccionar otro instrumento sintetizador con el codificador giratorio.

arduino-midi-box-encoder-programchange Creación de un controlador MIDI con Arduino
arduino-midi-box-prgmchange-1280x960 Creación de un controlador MIDI con Arduino

Próximos pasos

  • Uso de LED o botones iluminados
  • Gestión MIDI In para encender los LEDs
  • Uso de una pantalla OLED para un menú más completo

Fuentes