MIDI a la onda cuadrada polifónica en Arduino

3

He estado luchando con esto por unos días, y realmente me gustaría continuar con mi proyecto. Desafortunadamente esto me está sosteniendo Lo que estoy tratando de hacer es recuperar las señales MIDI enviadas a través de USB al Arduino (Uno), decodificarlas y luego usar una biblioteca de tonos no nativa ( enlace ) para reproducir múltiples tonos de onda cuadrada a la vez. Tengo 6 puertos de E / S en mi Arduino para trabajar, así que espero que eso me limite a 6 tonos. He intentado varios métodos diferentes para hacer que esto funcione, pero nunca pude terminar ninguno de ellos debido a que me encontré con un obstáculo de una manera u otra. Mi esperanza es reproducir múltiples tonos, ya sean del mismo canal (acordes) o de diferentes canales (instrumentos separados), pero todavía no puedo encontrar una buena manera de hacerlo.

La lectura en los datos MIDI no es la parte difícil. Tengo el siguiente código, escrito por Greg Kennedy y publicado en los foros de Arduino ( enlace ), para recuperar Los datos de un canal MIDI específico se reproducen con la función de tono nativo (), pero, por desgracia, solo se puede tocar una nota a la vez.

// A very simple MIDI synth.
// Greg Kennedy 2011

#include <avr/pgmspace.h>

#define statusLed 13
#define tonePin 7

// MIDI channel to answer to, 0x00 - 0x0F
#define myChannel 0x00
// set to TRUE and the device will respond to all channels
#define respondAllChannels false

// midi commands
#define MIDI_CMD_NOTE_OFF 0x80
#define MIDI_CMD_NOTE_ON 0x90
#define MIDI_CMD_KEY_PRESSURE 0xA0
#define MIDI_CMD_CONTROLLER_CHANGE 0xB0
#define MIDI_CMD_PROGRAM_CHANGE 0xC0
#define MIDI_CMD_CHANNEL_PRESSURE 0xD0
#define MIDI_CMD_PITCH_BEND 0xE0

// this is a placeholder: there are
//  in fact real midi commands from F0-FF which
//  are not channel specific.
// this simple synth will just ignore those though.
#define MIDI_CMD_SYSEX 0xF0

// a dummy "ignore" state for commands which
//  we wish to ignore.
#define MIDI_IGNORE 0x00

// midi "state" - which data byte we are receiving
#define MIDI_STATE_BYTE1 0x00
#define MIDI_STATE_BYTE2 0x01

// MIDI note to frequency
//  This isn't exact and may sound a bit detuned at lower notes, because
//  the floating point values have been rounded to uint16.
//  Based on A440 tuning.

// I would prefer to use the typedef for this (prog_uint16_t), but alas that triggers a gcc bug
// and does not put anything into the flash memory.

// Also note the limitations of tone() which at 16mhz specifies a minimum frequency of 31hz - in other words, notes below
// B0 will play at the wrong frequency since the timer can't run that slowly!
uint16_t frequency[128] PROGMEM = {8, 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 28, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978, 5274, 5588, 5920, 5920, 6645, 7040, 7459, 7902, 8372, 8870, 9397, 9956, 10548, 11175, 11840, 12544};

//setup: declaring iputs and outputs and begin serial
void setup() {
  pinMode(statusLed,OUTPUT);   // declare the LED's pin as output

  pinMode(tonePin,OUTPUT);           // setup tone output pin

  //start serial with midi baudrate 31250
  // or 38400 for debugging (eg MIDI over serial from PC)
  Serial.begin(31250);

  // indicate we are ready to receive data!
  digitalWrite(statusLed,HIGH);
}

//loop: wait for serial data
void loop () {
  static byte note;
  static byte lastCommand = MIDI_IGNORE;
  static byte state;
  static byte lastByte;

  while (Serial.available()) {

    // read the incoming byte:
    byte incomingByte = Serial.read();

    // Command byte?
    if (incomingByte & 0b10000000) {
      if (respondAllChannels ||
             (incomingByte & 0x0F) == myChannel) { // See if this is our channel
        lastCommand = incomingByte & 0xF0;
      } else { // Not our channel.  Ignore command.
        lastCommand = MIDI_IGNORE;
      }
      state = MIDI_STATE_BYTE1; // Reset our state to byte1.
    } else if (state == MIDI_STATE_BYTE1) { // process first data byte
      if ( lastCommand==MIDI_CMD_NOTE_OFF )
      { // if we received a "note off", make sure that is what is currently playing
        if (note == incomingByte) noTone(tonePin);
        state = MIDI_STATE_BYTE2; // expect to receive a velocity byte
      } else if ( lastCommand == MIDI_CMD_NOTE_ON ){ // if we received a "note on", we wait for the note (databyte)
        lastByte=incomingByte;    // save the current note
        state = MIDI_STATE_BYTE2; // expect to receive a velocity byte
      }
      // implement whatever further commands you want here
    } else { // process second data byte
      if (lastCommand == MIDI_CMD_NOTE_ON) {
        if (incomingByte != 0) {
          note = lastByte;
          tone(tonePin,(unsigned int)pgm_read_word(&frequency[note]));
        } else if (note == lastByte) {
          noTone(tonePin);
        }
      }
      state = MIDI_STATE_BYTE1; // message data complete
                                 // This should be changed for SysEx
    }
  }
}

Este código funciona muy bien para audio monofónico, pero realmente necesito producir tonos polifónicos. Siempre he sido más un tipo de PIC, por lo que Arduino todavía es bastante nuevo para mí. Gracias chicos!

EDITAR: para ser claros, quiero convertir una entrada MIDI multitono en una salida de onda cuadrada multitono, que utilizaré como señal de interrupción para otra parte del proyecto.

    
pregunta DerStrom8

2 respuestas

2

La reproducción de música polifónica mediante el uso de circuitos separados para cada "voz" funciona muy bien cuando la música se subdivide lógicamente en varios canales de voz única. En muchos casos, a uno no solo no le importará si las voces del hardware no se combinan perfectamente, sino que incluso puede querer que algunas de ellas sean un poco más altas que otras.

Sin embargo, cuando se utiliza un instrumento polifónico para reproducir datos MIDI, es importante asegurarse de que todas las voces se comporten de manera equivalente, y la forma más sencilla de hacerlo es utilizar los mismos circuitos para implementarlos, generalmente con algún tipo de onda. -tienda generadora.

Hay muchas maneras de interpretar música basada en tablas de ondas, que intercambian la sofisticación por el tiempo de CPU y los requisitos de memoria. Escribí un generador de tabla de ondas de cuatro voces para el 6502 que usaba doce instrucciones (46 ciclos) para cada muestra de salida [¡un promedio de solo tres por voz!] Pero requería casi 3K de tablas de datos. También he escrito un generador de tabla de ondas de 8 voces para un PIC de 10MHz y uno de 16 voces para el PSOC. No he hecho mucho con el Arduino, pero creo que un sintetizador de tabla de ondas de ocho voces sería viable.

Sin embargo, cuando se hace un sintetizador de tabla de ondas, es importante tener en cuenta que las ondas cuadradas a menudo suenan mal si la sincronización se cuantifica a algo que no es un múltiplo de su frecuencia. Si desea generar ondas cuadradas limpias para un teclado MIDI de cinco octavas, es posible que deba usar una frecuencia de muestreo bastante alta. Si desea generar ondas más suaves, en cambio, puede hacerlo con una tasa de muestreo más baja.

No he usado el Arduino, así que no sé qué tipo de construcciones en C producirían el mejor código, pero una implementación típica de tabla de ondas en el ARM sería:

uint32_t freq[8],phase[8];
int32_t volume[8];
int8_t *table[8]; // Pointer to 256-byte array
int32_t total;

total = 0;
phase[0] += freq[0]; total += table[0][phase[0] >> 8]*volume[0];
phase[1] += freq[1]; total += table[1][phase[1] >> 8]*volume[1];
...
phase[7] += freq[7]; total += table[7][phase[7] >> 8]*volume[7];

Después del código anterior, total mantendrá un valor que puede escalarse (si es necesario) y enviarlo a un DAC o usarlo para establecer un ciclo de trabajo de PWM.

El código anterior asume que la tabla de ondas tiene exactamente 256 bytes de longitud; Se pueden usar otros enfoques si ese no es el caso. Si el Arduino no puede realizar la multiplicación eficientemente por volume , es posible usar una tabla diferente para cada nivel de volumen (mi código 6502 usó dos conjuntos de tablas, una para "loud" y otra para "soft").

Una consideración adicional si está usando un DAC (menos aplicable si está usando un PWM) es que, en lugar de sumar todos los valores individuales generados para cada ola y generarlos como grupo, puede ser útil tener una interrupción que ocurre ocho veces más rápido, y genera el valor de una onda cada vez. Hacer eso efectivamente ganará tres bits más de precisión ADC. Además, si desea ondas sinusoidales y su hardware no admite la multiplicación rápida, generar dos ondas sinusoidales de amplitud completa cuyas frecuencias coinciden pero cuyas fases difieren, es equivalente a generar una onda sinusoidal cuya fase es el promedio de los dos -las de fuerza, y cuya amplitud es proporcional a la cantidad "uno más el coseno de la diferencia de fase". Mi generador de formas de onda basado en PIC utilizó ese truco.

    
respondido por el supercat
0

Estoy en una tableta, así que tendré que ser breve. Para tener múltiples tonos, querrá una estructura para cada voz en su instrumento polifónico que contiene el número de nota MIDI de tono, una constante que contiene el pin de salida, un "acumulador de fase" para el tono y un incremento de ángulo para la fase acumulador. Luego inicializa estas estructuras con los pines de salida que desea y otros campos vacíos y los almacena en algún tipo de estructura de datos (una pila sería buena). Esta es la "pila de notas sin usar".

Cuando llega un mensaje de MIDI Note On, usted llama a una función para que saque una nota no utilizada de la pila, y u Consulte el número de nota y una tabla de búsqueda para encontrar el incremento de ángulo apropiado para el acumulador de fase. Luego, la estructura rellenada se colocará en una ranura vacía en una matriz llamada "matriz de notas en reproducción". Cuando llega un mensaje de Note Off n, se llama una función que mira a través de esa matriz para encuentre la nota que se desactivó, y elimina la estructura de la matriz y la coloca de nuevo en la pila de notas no utilizada.

También habrá una interrupción del temporizador llamada a cierta velocidad dependiendo de la onda cuadrada de frecuencia de salida más alta que necesite. Los incrementos del acumulador de fase se calcularán en función de esta tasa. En la interrupción, se escaneará el conjunto de notas en reproducción y se actualizarán todos los acumuladores de fase. Para generar las ondas cuadradas, los pines de salida se establecerán en un nivel bajo cuando el byte superior del acumulador de fase respectivo sea menor que 128, alto cuando sea mayor.

Editar: para hacer las cosas más rápidas, podrías hacer un montón de estructuras para cada voz en tiempo de ejecución. Luego, use los punteros en lugar de moverse por las estructuras.

    
respondido por el Bitrex

Lea otras preguntas en las etiquetas