Adding Polyphony to Arduino MIDI-to-CV Converter

Hi there,

I'm trying to figure out how to add polyphony i.e. eight voices to the following code for a monophonic MIDI-to-CV Converter:

#include <MIDI.h>
#include <SPI.h>

// Note priority is set by pins A0 and A2
// Highest note priority: A0 and A2 high (open)
// Lowest note priority:  A0 low (ground), A2 high (open)
// Last note priority:    A2 low (ground)
 
#define NP_SEL1 A0  // Note priority is set by pins A0 and A2
#define NP_SEL2 A2  

#define GATE  2
#define TRIG  3
#define CLOCK 4
#define DAC1  8 
#define DAC2  9

MIDI_CREATE_DEFAULT_INSTANCE();

void setup()
{
 pinMode(NP_SEL1, INPUT_PULLUP);
 pinMode(NP_SEL2, INPUT_PULLUP);
 
 pinMode(GATE, OUTPUT);
 pinMode(TRIG, OUTPUT);
 pinMode(CLOCK, OUTPUT);
 pinMode(DAC1, OUTPUT);
 pinMode(DAC2, OUTPUT);
 digitalWrite(GATE,LOW);
 digitalWrite(TRIG,LOW);
 digitalWrite(CLOCK,LOW);
 digitalWrite(DAC1,HIGH);
 digitalWrite(DAC2,HIGH);

 SPI.begin();

 MIDI.begin(MIDI_CHANNEL_OMNI);

 // Set initial pitch bend voltage to 0.5V (mid point).  With Gain = 1X, this is 1023
 // Other DAC outputs will come up as 0V, so don't need to be initialized
 setVoltage(DAC2, 0, 0, 1023);  
}

bool notes[88] = {0}; 
int8_t noteOrder[20] = {0}, orderIndx = {0};
unsigned long trigTimer = 0;

void loop()
{
  int type, noteMsg, velocity, channel, d1, d2;
  static unsigned long clock_timer=0, clock_timeout=0;
  static unsigned int clock_count=0;
  bool S1, S2;

  if ((trigTimer > 0) && (millis() - trigTimer > 20)) { 
    digitalWrite(TRIG,LOW); // Set trigger low after 20 msec 
    trigTimer = 0;  
  }

  if ((clock_timer > 0) && (millis() - clock_timer > 20)) { 
    digitalWrite(CLOCK,LOW); // Set clock pulse low after 20 msec 
    clock_timer = 0;  
  }
  
  if (MIDI.read()) {                    
    byte type = MIDI.getType();
    switch (type) {
      case midi::NoteOn: 
      case midi::NoteOff:
        noteMsg = MIDI.getData1() - 21; // A0 = 21, Top Note = 108
        channel = MIDI.getChannel();
        
        if ((noteMsg < 0) || (noteMsg > 87)) break; // Only 88 notes of keyboard are supported

        if (type == midi::NoteOn) velocity = MIDI.getData2();
        else velocity  = 0;  

        if (velocity == 0)  {
          notes[noteMsg] = false;
        }
        else {
          notes[noteMsg] = true;
          // velocity range from 0 to 4095 mV  Left shift d2 by 5 to scale from 0 to 4095, 
          // and choose gain = 2X
          setVoltage(DAC1, 1, 1, velocity<<5);  // DAC1, channel 1, gain = 2X
        }

        // Pins NP_SEL1 and NP_SEL2 indictate note priority
        S1 = digitalRead(NP_SEL1);
        S2 = digitalRead(NP_SEL2);

        if (S1 && S2) { // Highest note priority
          commandTopNote();
        }
        else if (!S1 && S2) { // Lowest note priority
          commandBottomNote();
        }
        else { // Last note priority
           if (notes[noteMsg]) {  // If note is on and using last note priority, add to ordered list
            orderIndx = (orderIndx+1) % 20;
            noteOrder[orderIndx] = noteMsg;                 
          }
          commandLastNote();         
        }
        break;
        
      case midi::PitchBend:
        d1 = MIDI.getData1();
        d2 = MIDI.getData2(); // d2 from 0 to 127, mid point = 64

        // Pitch bend output from 0 to 1023 mV.  Left shift d2 by 4 to scale from 0 to 2047.
        // With DAC gain = 1X, this will yield a range from 0 to 1023 mV.  
        setVoltage(DAC2, 0, 0, d2<<4);  // DAC2, channel 0, gain = 1X
        
        break;

      case midi::ControlChange: 
        d1 = MIDI.getData1();
        d2 = MIDI.getData2(); // From 0 to 127

        // CC range from 0 to 4095 mV  Left shift d2 by 5 to scale from 0 to 4095, 
        // and choose gain = 2X
        setVoltage(DAC2, 1, 1, d2<<5);  // DAC2, channel 1, gain = 2X
        break;
        
      case midi::Clock:
        if (millis() > clock_timeout + 300) clock_count = 0; // Prevents Clock from starting in between quarter notes after clock is restarted!
        clock_timeout = millis();
        
        if (clock_count == 0) {
          digitalWrite(CLOCK,HIGH); // Start clock pulse
          clock_timer=millis();    
        }
        clock_count++;
        if (clock_count == 24) {  // MIDI timing clock sends 24 pulses per quarter note.  Sent pulse only once every 24 pulses
          clock_count = 0;  
        }
        break;
        
      case midi::ActiveSensing: 
        break;
        
      default:
        d1 = MIDI.getData1();
        d2 = MIDI.getData2();
    }
  }
}

void commandTopNote()
{
  int topNote = 0;
  bool noteActive = false;
  
  for (int i=0; i<88; i++)
  {
    if (notes[i]) {
      topNote = i;
      noteActive = true;
    }
  }

  if (noteActive) 
    commandNote(topNote);
  else // All notes are off, turn off gate
    digitalWrite(GATE,LOW);  
}

void commandBottomNote()
{
  int bottomNote = 0;
  bool noteActive = false;
 
  for (int i=87; i>=0; i--)
  {
    if (notes[i]) {
      bottomNote = i;
      noteActive = true;
    }
  }

  if (noteActive) 
    commandNote(bottomNote);
  else // All notes are off, turn off gate
    digitalWrite(GATE,LOW);
}

void commandLastNote()
{

  int8_t noteIndx;
  
  for (int i=0; i<20; i++) {
    noteIndx = noteOrder[ mod(orderIndx-i, 20) ];
    if (notes[noteIndx]) {
      commandNote(noteIndx);
      return;
    }
  }
  digitalWrite(GATE,LOW);  // All notes are off
}

// Rescale 88 notes to 4096 mV:
//    noteMsg = 0 -> 0 mV 
//    noteMsg = 87 -> 4096 mV
// DAC output will be (4095/87) = 47.069 mV per note, and 564.9655 mV per octive
// Note that DAC output will need to be amplified by 1.77X for the standard 1V/octave 

#define NOTE_SF 47.069f // This value can be tuned if CV output isn't exactly 1V/octave

void commandNote(int noteMsg) {
  digitalWrite(GATE,HIGH);
  digitalWrite(TRIG,HIGH);
  trigTimer = millis();
  
  unsigned int mV = (unsigned int) ((float) noteMsg * NOTE_SF + 0.5); 
  setVoltage(DAC1, 0, 1, mV);  // DAC1, channel 0, gain = 2X
}

// setVoltage -- Set DAC voltage output
// dacpin: chip select pin for DAC.  Note and velocity on DAC1, pitch bend and CC on DAC2
// channel: 0 (A) or 1 (B).  Note and pitch bend on 0, velocity and CC on 2.
// gain: 0 = 1X, 1 = 2X.  
// mV: integer 0 to 4095.  If gain is 1X, mV is in units of half mV (i.e., 0 to 2048 mV).
// If gain is 2X, mV is in units of mV

void setVoltage(int dacpin, bool channel, bool gain, unsigned int mV)
{
  unsigned int command = channel ? 0x9000 : 0x1000;

  command |= gain ? 0x0000 : 0x2000;
  command |= (mV & 0x0FFF);
  
  SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
  digitalWrite(dacpin,LOW);
  SPI.transfer(command>>8);
  SPI.transfer(command&0xFF);
  digitalWrite(dacpin,HIGH);
  SPI.endTransaction();
}

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}

Would anyone be able to kindly advise on how to go about doing so in the simplest way possible?

Many thanks,
Chris

Sry, not fully awake yet.

Do you mean you want to receive one MIDI data stream and produce 8 CV outputs?

Which makes me ask what is the current schematic and hardware you are using now for the monotony software?

Where did you find that software?

Also do you have google going on your internets?

1:50 googling:

midi polyphonic cv arduino

Seems like a path well trod. It may be easier to find another project as a starting point…

a7