MIDI synth to send different channels to different output pins

Hello I am fairly new to midi, so please correct me if I make any mistakes. So I am currently using this tone midi synth to send out tones from my laptop to an external speaker. I want to change the channels for each track and only send out specific tracks on each channel to one device. For example, on channel 1 I want to send all the tracks to pin 12 on the arduino and on channel 2 I want to send all the tracks to pin 13 on the arduino. I can’t seem to do this using the basic midi synth. Can someone help me with this? Thanks, appreciate it. :slight_smile:

// 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
   }
 }
}

What have you tried so far? Post your best attempt.

Obviously you need two differently named tonePins and two different myChannels and then the code will need to use them.

Steve

So here is what I have done so far. I created a second digital output pin named tonePin2 and I am trying to output the midi on 2 output pins, but to no avail. Seems like it just skips my second line of code I added.

// A very simple MIDI DRSSTC interrupter
// Matt Lewis, 2016
// Based on "A very simple MIDI synth" by Greg Kennedy, 2011

#include <avr/pgmspace.h>

#define tonePin 7
#define outputPin 12
#define tonePin2 10

// MIDI channel to answer to, 0x00 - 0x0f
#define myChannel 0x00
#define myChannel2 0x01
// 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

// DRSSTC pulse width limit, in uS. Will not be precise.
// A value of '20' gives me approximately 26uS
#define PULSE_WIDTH 50

// 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!
const 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(tonePin,OUTPUT);     // setup tone output pin
  pinMode(outputPin,OUTPUT);   // output pin
  pinMode(tonePin2,OUTPUT);
  
  // Interrupt used for pulse-width limiting, attached to INT0 (digital pin 2)
  // set to trigger only on the rising edge
  attachInterrupt(0, __ISR_Trigger, RISING);   

  // start serial with midi baudrate 38400
  // (receiving MIDI over serial from PC)
  Serial.begin(38400);
}

//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(tonePin2);
          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
      }
    } else { 
      // process second data byte
      if (lastCommand == MIDI_CMD_NOTE_ON) {
        if (incomingByte != 0) {
          note = lastByte;
          tone(tonePin2,(unsigned int)pgm_read_word(&frequency[note]));

        } else if (note == lastByte) {
          noTone(tonePin2);
          noTone(tonePin);
        }
      }
      state = MIDI_STATE_BYTE1; // message data complete
    }
  }
}

// interrupt service routine for limiting pulse width. Output on tonePin
// is a ~50% duty cycle, which means the pulse width is much too long for
// most small Tesla coils. For this reason, tonePin is connected to digital 
// pin 2 of the Arduino (INT0) using an external jumper. Since the interrupt
// only triggers on the rising edge of the tonePin waveform, this will limit
// the pulse width to the value set in the PULSE_WIDTH constant, and will
// not re-trigger until the next rising edge of the tonePin output. This
// gives the same frequency, but with a limited pulse width on the outputPin.
void __ISR_Trigger() {
  digitalWrite(outputPin,HIGH);
  delayMicroseconds(PULSE_WIDTH); 
  digitalWrite(outputPin,LOW);
}

Good start but you need to check to see if the incoming message is on 'myChannel' OR 'myChannel2' and save the channel number.

Then in the NoteOn and NoteOff handlers you need to check which of those it was so you know which tonePin to set according to the On or Off message.

Steve

Thanks for your reply. So heres what I have done. During the while loop, I check if it is on channel one or channel 2. This still doesn’t seem to work and I just get a high pitched tone on pin 10 and nothing on pin 12 when I change the channels in virtual midi piano keyboard.

// A very simple MIDI DRSSTC interrupter
// Matt Lewis, 2016
// Based on "A very simple MIDI synth" by Greg Kennedy, 2011

#include <avr/pgmspace.h>

#define tonePin 7
#define outputPin 12
#define tonePin2 10

// MIDI channel to answer to, 0x00 - 0x0f
#define myChannel 0x00
#define myChannel2 0x01
// 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

// DRSSTC pulse width limit, in uS. Will not be precise.
// A value of '20' gives me approximately 26uS
#define PULSE_WIDTH 50

// 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!
const 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(tonePin,OUTPUT);     // setup tone output pin
  pinMode(outputPin,OUTPUT);   // output pin
  pinMode(tonePin2,OUTPUT);
  
  // Interrupt used for pulse-width limiting, attached to INT0 (digital pin 2)
  // set to trigger only on the rising edge
  attachInterrupt(0, __ISR_Trigger, RISING);   

  // start serial with midi baudrate 38400
  // (receiving MIDI over serial from PC)
  Serial.begin(38400);
}

//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
      }
    } 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
    }
    if (incomingByte & 0b10000000) {
      if ((respondAllChannels) ||
             ((incomingByte & 0x0F) == myChannel2)) { // 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(tonePin2);
     
        }
        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
      }
    } else { 
      // process second data byte
      if (lastCommand == MIDI_CMD_NOTE_ON) {
        if (incomingByte != 0) {
          note = lastByte;
          tone(tonePin2,(unsigned int)pgm_read_word(&frequency[note]));

        } else if (note == lastByte) {
          noTone(tonePin2);

        }
      }
      state = MIDI_STATE_BYTE1; // message data complete
    }
  }
}

// interrupt service routine for limiting pulse width. Output on tonePin
// is a ~50% duty cycle, which means the pulse width is much too long for
// most small Tesla coils. For this reason, tonePin is connected to digital 
// pin 2 of the Arduino (INT0) using an external jumper. Since the interrupt
// only triggers on the rising edge of the tonePin waveform, this will limit
// the pulse width to the value set in the PULSE_WIDTH constant, and will
// not re-trigger until the next rising edge of the tonePin output. This
// gives the same frequency, but with a limited pulse width on the outputPin.
void __ISR_Trigger() {
  digitalWrite(outputPin,HIGH);
  delayMicroseconds(PULSE_WIDTH); 
  digitalWrite(outputPin,LOW);
}

Do you know that tone can only play on one pin at a time? https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/

Oh, thats why nothing was working. Well not sure what to do at this point. Do you have any suggestions?

There are several libraries and code that support multiple tone outputs.
I wrote one for my book:-

You can get the code from here but you have to buy the book for the words.
In fact here is the code:-

// Poly Tone Woops - Mike Cook
  volatile byte tone1period = 1, tone2period = 1;
 volatile byte tone1restore = 41, tone2restore = 41;
 volatile unsigned int tcnt2;
 
ISR(TIMER2_OVF_vect){  // Interrupt service routine to generate the tones
  TCNT2 = tcnt2 + TCNT2; // reload counter
  tone1period--; 
  if(tone1period < 1) { // time to toggle the first pin
    tone1period = tone1restore;
    PORTB ^= _BV(5); // toggle pin 13, PB5 direct port addressing
  }  
  tone2period--;
  if(tone2period < 1) { // time to toggle the second pin
    tone2period = tone2restore;
    PORTB ^= _BV(4); // toggle pin 12, PB4 direct port addressing
  }
}

void setUpTimer(){  // sets the timer going at the decrement rate
TIMSK2 &= ~_BV(TOIE2);  // Disable the timer overflow interrupt
TCCR2A &= ~(_BV(WGM21) | _BV(WGM20)); // Configure timer2 in normal mode
TCCR2B &= ~_BV(WGM22);
ASSR &= ~_BV(AS2); // Select clock source: internal I/O clock
TIMSK2 &= ~_BV(OCIE2A); //Disable Compare Match A interrupt (only overflow)
TCCR2B |= _BV(CS22)  | _BV(CS20); // Set bits
TCCR2B = (TCCR2B & 0b00111000) | 0x1; // select a prescale value of 1:1
tcnt2 = 96 + 5; // give 10uS interrupt rate + adjustment 
TCNT2 = tcnt2; // pre load the value into the timer
}

void setup(){
  setUpTimer();
  pinMode(13,OUTPUT); // enable the pins you want to use as tone outputs
  pinMode(12,OUTPUT);
  TIMSK2 |= _BV(TOIE2);  // tone on
}

void loop() { // just an example to show how to use the calls.

  // raise tone 2 and drop tone 1 at different rates
  tone1restore += 10; // lower tone 1
  if(tone1restore > 240) tone1restore = 20; // back to initial position
  tone2restore -= 28; // increase tone 2
  if(tone2restore < 20) tone2restore = 240; // back to initial position
  delay(300); // speed of note changes
}

I would love to post the words but when you write a book you sell the copyright to the publishers.

However there are others that will do this if you search online.

WayneZengDaITGuy: Oh, thats why nothing was working. Well not sure what to do at this point. Do you have any suggestions?

Sorry I forgot about that but it's not the only reason why it's not working. You're still ignoring anything coming in on myChannel2. You need to add it here:

      if ((respondAllChannels) || ((incomingByte & 0x0F) == myChannel)) { // See if this is ONE OF our channelS

Steve